2
0
mirror of https://github.com/gnu4cn/ts-learnings.git synced 2025-03-27 04:20:52 +08:00

Content added.

This commit is contained in:
gnu4cn 2019-03-27 11:40:36 +08:00
parent 8f7b0a4bd1
commit 6cefe55819
22 changed files with 5357 additions and 369 deletions

284
19_decorators.md Normal file
View File

@ -0,0 +1,284 @@
# 装饰器
**Decorators**
## 简介
随着TypeScript及ES6中类的引入就有了一些需要对类与类的成员进行批注与修改等额外特性的场景With the introduction of Classes in TypeScript and ES6, there now exist certain scennarios that require additional features to support annotating or modifying classes and class members。装饰器这一特性就提供了一种将类声明与成员的批注与元编程语法加入进来的方式。装饰器特性是一项JavaScript的[第二阶段提议](https://github.com/tc39/proposal-decorators)且是TypeScript的一项实验特性。
> 注意:装饰器是一项实验特性,在以后的版本发布中可能改变。
要开启装饰器的实验性支持,就必须在命令行或`tsconfig.json`中开启编译器的`experimentalDecorators`选项:
**命令行**
```bash
tsc --target ES5 --experimentalDecorators
```
**tsconfig.json**
```json
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
```
## 关于装饰器Decorators
*装饰器* 是一类特殊的声明,可被附加到[类的声明](#class-decorators)、[方法](#method-decorators)、[访问器](#accessor-decorators)、[属性](#property-decorators)或者[参数](#parameter-decorators)。装饰器使用的形式是`@expression`,其中的`expression`必须评估为一个将在运行时以有关被装饰声明的信息被调用的函数Decorators use the form `@expression`, where `expression` must evaluate to a function that will be called at runtime with information about the decorated declaration
比如,对于给定的装饰器`@sealed`,那么就可能向下面这样写该`sealed`函数:
```typescript
function sealed(target) {
// ... 对`target`进行一些操作 ...
}
```
> 注意:在下面的[类装饰器](#class-decorators)中,可以看到更详细的示例
### 装饰器工厂Decorator Factories
<a href="decorator-factories"></a>
可通过编写一个装饰器工厂,来对装饰器作用于声明的方式进行定制。 *装饰器工厂* 就是一个返回由装饰器在运行时调用的表达式的函数If you want to customize how a decorator is applied to a declaration, we can write a decorator factory. A *Decorator Factory* is simply a function that returns the expression that will be called by the decorator at runtime
可以下面的形式,来编写一个装饰器工厂:
```typescript
function color (value: string) { // 这是装饰器工厂
return function (target) { // 这是装饰器
// 以`target`与`value`来完成一些操作
}
}
```
> 注意,在下面的[方法装饰器](#method-decorators)部分,可见到装饰工厂的更详细示例。
### 装饰器的复合Decorator Composition
对某个声明,可应用多个装饰器,如下面的示例中那样:
+ 在同一行:
```typescript
@f @g x
```
+ 在多行上:
```typescript
@f
@g
x
```
当有多个装饰器应用到单个声明时,它们的执行与[数学中的复合函数](http://en.wikipedia.org/wiki/Function_composition)类似。在这个模型中,当将`f`与`g`进行复合时,`(f∘ g)(x)`复合结果与`f(g(x))`等价。
因此TypeScript中在对单一声明上的多个装饰器进行执行时将完成以下步骤
1. 各个装饰器的表达式将自顶向下执行。
2. 随后的结果作为函数被自底向上进行调用。
当使用了[装饰器工厂](#decorator-factories),就可以在下面的示例中观察到这种执行顺序:
```typescript
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
```
其将把下面的输出打印到控制台:
```bash
f(): evaluated
g(): evaluated
g(): called
f(): called
```
### 装饰器求值Decorator Evaluation
对于将装饰器如何应用到类内部的各种声明,有着以下可遵循的定义良好的顺序:
1. 对于各个实例成员, *参数装饰器*,接着分别是 *方法*、*访问器* 或者 *属性装饰器* 将被应用( *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each instance member
2. 对于各个静态成员, *参数装饰器*,接着分别是 *方法*、*访问器* 或者 *属性装饰器* 将被应用( *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each static member
3. 对于构造器,将应用参数装饰器( *Parameter Decorators* are applied for the constructor
4. 对于类,将应用 *类装饰器* *Class Decorators* are applied for the class )。
### 类装饰器
*类装饰器* 是在类声明之前、紧接着类声明处使用的。类声明作用与类的构造器,而可用于对类的定义进行观察、修改或替换。类装饰器不能在声明文件,或任何其它外围上下文中使用(比如在某个`declare`类上。The class decorator is applied to the constructor of the class and can be used to observe, modify or replace a class definition. A class decorator cannot be used in a declaration file, or in any other ambient context(such as on a `declare` class))。
> 什么是TypeScript的外围上下文ambient context, 有的翻译为“已有环境”)?
>
>
类装饰器的表达式,将被作为一个函数,在运行时以被装饰的类的构造器函数,作为唯一参数而被调用。
> **注意** 应注意返回一个新的构造器函数因为必须注意维护好原来的原型。运行时对装饰器的应用这一逻辑并不会做这件事Should you chose to return a new constructor function, you must take care to maintain the original prototype. The logic that applies decorators at runtime will not do this for you
下面是一个应用到`Greeter`类的类装饰器(`@sealed`)的示例:
```typescript
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greeter () {
return `Hello, { this.greeting }`;
}
}
```
可将`@sealed`装饰器定义为使用下面的函数声明:
```typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
```
在`@sealed`装饰器(于运行时)被执行后,它将同时封闭构造器及其原型。
接着的是一个如何覆写构造器的示例:
```typescript
function classDecorator<T extends {new( ...args: any[] ): {}}>(constructor: T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
```
### 方法装饰器Method Decorators
*方法装饰器* 是在某个方法声明之前、紧接着该方法处使用的。此种装饰器被应用到方法的 *属性描述符* ,而可用于对方法定义进行观察、修改或替换。不能在定义文件、过载或任何其它已有上下文中(比如某个`declare`类中使用方法装饰器The decorator is applied to the *Property Descriptor* for the method, and can be used to observe, modify, or replace a method definition. A method decorator cannot be used in a declaration file, on an overload, or in any other ambient context(such as in a `declare` class))。
方法装饰器的表达式,将在运行时作为函数,以下面的三个参数进行调用:
1. 静态成员的类构造函数或实例成员的类的原型Either the constructor function of the class for a static member, or the prototype of the class for an instance member, )。
> 关于静态成员与实例成员的区别:
> 前面所说的都是对于类的实例成员在实例化后的对象才会起作用。可以使用static定义类中的静态成员所有实例可以使用this中的名称来访问静态成员。
> [TypeScript笔记](http://yrq110.me/2018/01/06/20180106-typescript-note/)
2. 成员的名称。
3. 成员的 *属性描述符*
> **注意** 在低于ES5的目标脚本中 *成员描述符* 将为 `undefined`
在方法装饰器有返回值时,其将作为该方法的 *属性描述符*
> **注意** 在目标脚本低于ES5版本中该返回值将被忽略。
下面是一个应用到`Greeter`类的方法装饰器(`@enumerable`)的示例:
```typescript
function enumerable (value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
}
}
class Greeter {
greeting: string;
constructor (m: string) {
this.greeting = m;
}
@enumerable(false)
greet () {
return `Hello, ${ this.greeting }`;
}
}
let g = new Greeter("world");
console.log(g.greet());
```
这里的`@enumerable(false)`装饰器是一个装饰器工厂。在`@enumerable(false)`装饰器被调用时,其就对属性描述符的`enumberable`属性,进行修改。
### 访问器装饰器Accessor Decorators
*访问器装饰器* 是在紧接着某个访问器声明之前进行声明的。访问器装饰器是应用到该访问器的 *属性描述符the Property Descriptor* 上的,且可用于对某个访问器的定义进行观察、修改或替换。在定义文件、或其他任何外围上下文(比如某个`declare`的类)中,都不能使用访问器的装饰器。
> **注意** 对与单个成员TypeScript是不允许对其`get`或`set`访问器进行装饰的。而是该成员的所有装饰器都必须应用到按文档顺序所指定的第一个访问器TypeScript disallows decorating both the `get` and `set` accessor for a single member. Instead, all decorators for the member must be applied to the first accessor specified in document order。这是因为应用到 *熟悉描述符* 的那些结合了`get`与`set`访问器的装饰器,并不是各自单独声明的。
访问器装饰器的表达式,在运行时将作为函数得以调用,有着以下三个参数:
1. 对于静态成员,类的构造函数;或对于实例成员,那就就是类的原型
2. 该成员的名称
3. 该成员的 *属性描述符*
在访问器装饰器返回一个值时,该值将作为成员的 *属性描述符* 得以使用。
> **注意** 在低于`ES5`的目标脚本下,该返回值将被忽略。
下面是一个应用到`Point`类的某个成员上的访问器修饰器`@configurable`的示例:
```typescript
Class Point {
private _x: number;
private _y: number;
constructor (x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable (false)
get x() { return this._x; }
@configurable (false)
get y() { return this._y; }
}
```

View File

@ -1,35 +1,25 @@
const gulp = require('gulp'),
browserify = require('browserify'),
source = require('vinyl-source-stream'),
watchify = require('watchify'),
tsify = require('tsify'),
gutil = require('gulp-util');
var gulp = require('gulp');
var ts = require('gulp-typescript');
var tsProject = ts.createProject('tsconfig.json');
let paths = {
pages: ["src/*.html"]
};
let watchedBrowserify = watchify(browserify({
basedir: '.',
debug: true,
entries: ["src/main.ts"],
cache: {},
packageCache: {}
// tsify 是browserify的插件用于编译 TypeScript, 选项写在后面
}).plugin(tsify, { noImplicitAny: true }));
gulp.task("copy-html", ()=>{
return gulp.src(paths.pages)
.pipe(gulp.dest("dist"))
});
function bundle () {
return watchedBrowserify
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest("dist"))
}
gulp.task('tsc', () => {
return gulp.src('src/*.ts')
.pipe(tsProject())
.pipe(gulp.dest('dist'));
});
gulp.task('default', ['copy-html'], bundle);
watchedBrowserify.on("update", bundle);
watchedBrowserify.on("log", gutil.log);
gulp.task('watch', () => {
gulp.watch('src/*ts', ['tsc']);
});
gulp.task('default', gulp.series('copy-html', 'tsc', 'tsc'));

5038
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@
"gulp-sourcemaps": "^2.6.1",
"gulp-typescript": "^3.2.3",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.8",
"live-server": "^1.2.0",
"typescript": "^2.6.2"
}

View File

@ -1,74 +0,0 @@
export class Calculator {
private current = 0;
private memory = 0;
private operator: string;
protected processDigit (digit: string, currentValue: number) {
if (digit >= "0" && digit <= "9") {
return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));
}
}
protected processOperator (operator: string) {
if (["+", "-", "*", "/"].indexOf(operator) >= 0) {
return operator;
}
}
protected evaluateOperator (operator: string, left: number, right: number): number {
switch (this.operator) {
case "+": return left + right;
case "-": return left - right;
case "*": return left * right;
case "/": return left / right;
}
}
private evaluate () {
if (this.operator) {
this.memory = this.evaluateOperator(this.operator, this.memory, this.current);
}
else {
this.memory = this.current;
}
this.current = 0;
}
public handelChar (char: string) {
if (char === "=") {
this.evaluate();
return;
}
else {
let value = this.processDigit(char, this.current);
if (value !== undefined) {
this.current = value;
return;
}
else {
let value = this.processOperator(char);
if (value !== undefined) {
this.evaluate();
this.operator = value;
return;
}
}
}
throw new Error(`Unsupported input: '${char}'`);
}
public getResult() {
return this.memory;
}
}
export function test (c: Calculator, input: string) {
for (let i = 0; i < input.length; i++){
c.handelChar(input[i]);
}
console.log(`result of '${input}' is '${c.getResult()}'`);
}

View File

@ -1,12 +0,0 @@
'use strict';
/// <reference path="Validation.ts">
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable (s: string) {
return lettersRegexp.test(s);
}
}
}

View File

@ -1,25 +0,0 @@
import { Calculator } from "./Calculator";
class ProgrammerCalculator extends Calculator {
static digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
constructor (public base: number) {
super();
if (base <= 0 || base > ProgrammerCalculator.digits.length) {
throw new Error("基数必须要在0到16的范围");
}
}
protected processDigit(digit: string, currentValue: number) {
if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {
return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit);
}
}
}
// 将新的已扩展的计算器作为`Calculator`进行导出
export { ProgrammerCalculator as Calculator };
// 还要导出辅助函数
export { test } from "./Calculator";

View File

@ -1,21 +0,0 @@
/// <reference path="Validation.ts">
/// <reference path="LettersOnlyValidator.ts">
/// <reference path="ZipCodeValidator.ts">
// 一些尝试样本
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 展示各个字符串是否通过各个验证器验证
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`"${ s }" ${ isMatch ? "matches" : "does not match" } "${ name }".`);
}
}

View File

@ -1,4 +0,0 @@
import { Calculator, test } from "./Calculator";
let c = new Calculator;
test(c, "1+2*33/11-3=");

View File

@ -1,4 +0,0 @@
import { Calculator, test } from "./ProgrammerCalculator";
let c = new Calculator(16);
test(c, "001+0A0=");

View File

@ -1,7 +0,0 @@
'use strict';
namespace Validation {
export interface StringValidator {
isAcceptable (s: string): boolean;
}
}

View File

@ -1,12 +0,0 @@
'use strict';
/// <reference path="Validation.ts">
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}

View File

@ -1,8 +0,0 @@
function buildLabel(name) {
return buildLabel.prefix + name + buildLabel.suffix;
}
(function (buildLabel) {
buildLabel.suffix = "";
buildLabel.prefix = "Hello, ";
})(buildLabel || (buildLabel = {}));
console.log(buildLabel("Sam Smith"));

View File

@ -1,10 +0,0 @@
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
console.log(buildLabel("Sam Smith"));

View File

@ -1,6 +0,0 @@
"use strict";
exports.__esModule = true;
function sayHello(name) {
return "Hello from " + name;
}
exports.sayHello = sayHello;

View File

@ -1,5 +0,0 @@
export function sayHello(name: string) {
return `Hello from ${name}`;
}

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>测试...</title>
</head>
<body>
<h3 id="greeting">Loading...</h3>
</body>
<script src="./bundle.js"></script>
</html>

View File

@ -1,21 +1,20 @@
'use strict';
import { StringValidator } from "./Validation";
import { ZipCodeValidator } from "./ZipCodeValidator";
import { LettersOnlyValidator } from "./LettersOnlyValidator";
// 一些测试样本
let strings = ["Hello", "98052", "101"];
// 要用到的验证器
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// 演示各个字符串是否通过各个验证器验证
strings.forEach(s => {
for (let name in validators) {
console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches": "does not match" } ${ name }`);
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
});
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}

View File

@ -1,39 +0,0 @@
namespace Validation {
export interface StringValidator {
isAcceptable (s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable (s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// 一些尝试样本
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 展示各个字符串是否通过各个验证器验证
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`"${ s }" ${ isMatch ? "matches" : "does not match" } "${ name }".`);
}
}

View File

@ -1,46 +0,0 @@
'use strict';
/// <reference path="Validation.ts">
var Validation;
(function (Validation) {
var lettersRegexp = /^[A-Za-z]+$/;
var LettersOnlyValidator = /** @class */ (function () {
function LettersOnlyValidator() {
}
LettersOnlyValidator.prototype.isAcceptable = function (s) {
return lettersRegexp.test(s);
};
return LettersOnlyValidator;
}());
Validation.LettersOnlyValidator = LettersOnlyValidator;
})(Validation || (Validation = {}));
/// <reference path="Validation.ts">
var Validation;
(function (Validation) {
var numberRegexp = /^[0-9]+$/;
var ZipCodeValidator = /** @class */ (function () {
function ZipCodeValidator() {
}
ZipCodeValidator.prototype.isAcceptable = function (s) {
return s.length === 5 && numberRegexp.test(s);
};
return ZipCodeValidator;
}());
Validation.ZipCodeValidator = ZipCodeValidator;
})(Validation || (Validation = {}));
/// <reference path="Validation.ts">
/// <reference path="LettersOnlyValidator.ts">
/// <reference path="ZipCodeValidator.ts">
// 一些尝试样本
var strings = ["Hello", "98052", "101"];
// 要使用的验证器
var validators = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 展示各个字符串是否通过各个验证器验证
for (var _i = 0, strings_1 = strings; _i < strings_1.length; _i++) {
var s = strings_1[_i];
for (var name_1 in validators) {
var isMatch = validators[name_1].isAcceptable(s);
console.log("\"" + s + "\" " + (isMatch ? "matches" : "does not match") + " \"" + name_1 + "\".");
}
}

View File

@ -1,37 +0,0 @@
interface StringValidator {
isAcceptable (s: string): boolean;
}
let lettersRegexp = /^[A-Za-z]+$/;
let numberRegexp = /^[0-9]+$/;
class LettersOnlyValidator implements StringValidator {
isAcceptable (s: string) {
return lettersRegexp.test(s);
}
}
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// 一些尝试样本
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// 展示各个字符串是否通过各个验证器验证
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`"${ s }" ${ isMatch ? "matches" : "does not match" } "${ name }".`);
}
}

View File

@ -5,6 +5,7 @@
"compilerOptions": {
"noImplicitAny": true,
"target": "es5",
"outDir": "dist/"
"outDir": "dist/",
"experimentalDecorators": true
}
}