装饰器(decorator)

装饰器是一种特殊的声明, 可以附加在类、方法、访问器、属性、参数声明上

装饰器使用@expression的形式,其中expression必须能够演算为运行时调用的函数,其中包含装饰声明信息.

装饰器类型及其执行顺序为

  1. 类装饰器—优先级 4 (对象实例化, 静态)
  2. 方法装饰器—优先级 2 (对象实例化, 静态)
  3. 访问器或属性装饰器—优先级 3 (对象实例化, 静态)
  4. 参数装饰器—优先级1 (对象实例化, 静态)

注意,如果装饰器应用与类构造函数的参数,那么不同的装饰器的优先级为:

  1. 参数装饰器
  2. 方法装饰器
  3. 访问器或参数装饰器
  4. 构造器参数装饰器
  5. 类装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 这是一个装饰器工厂——有助于将用户参数传给装饰器声明
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() {}
}

// f(): evaluated
// g(): evaluated
// g(): called
// f(): called

fg返回了另一个函数(装饰器函数). fg称为装饰器工厂

装饰器工厂帮助用户传递可供装饰器利用的参数

我们可以看到装饰器的,演算规则由顶向上,执行顺序由底向上

装饰器的签名

1
2
3
4
5
6
7
8
9
10
11
12
13
interface TypedPropertyDescriptor<T> {
enumerable?: boolean;
configurable?: boolean;
writable?: boolean;
value?: T;
get?: () => T;
set?: (value: T) => void;
}

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

方法装饰器

可以从上面的签名中,看到它有三个参数

  1. target: 当前对象的原型,也就是说,假设Employee是对象,那么target就是Employee.prototype
  2. propertyKey: 方法的名称
  3. descriptor: 方法的属性描述符,即Object.getOwnPropertyDescriptor(Employ.prototype, propertyKey)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export function logMethod(
target: Object,
propertyName: string,
propertyDescriptor: PropertyDescriptor): PropertyDescriptor {
// target === Employee.prototype
// propertyName === "greet"
// propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet")
const method = propertyDesciptor.value;

propertyDesciptor.value = function (...args: any[]) {
// 将 greet 的参数列表转换为字符串
const params = args.map(a => JSON.stringify(a)).join();
// 调用 greet() 并获取其返回值
const result = method.apply(this, args);
// 转换结尾为字符串
const r = JSON.stringify(result);
// 在终端显示函数调用细节
console.log(`Call: ${propertyName}(${params}) => ${r}`);
// 返回调用函数的结果
return result;
}
return propertyDesciptor;
};

class Employee {
constructor(private firstName: string, private lastName: string
) {}

@logMethod
greet(message: string): string {
return `${this.firstName} ${this.lastName} says: ${message}`;
}
}

const emp = new Employee('Mohan Ram', 'Ratnakumar');
emp.greet('hello');

javascript 编译后是什么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
var __decorate =
(this && this.__decorate) ||
function (decorators, target, key, desc) {
// 函数参数长度
var c = arguments.length;

/**
* 处理结果
* 如果仅仅传入了装饰器数组和目标,那么应该是个类装饰器。
* 否则,如果描述符(第 4 个参数)为 null,就根据已知值准备属性描述符,
* 反之则使用同一描述符。
*/

var r =
c < 3
? target
: desc === null
? (desc = Object.getOwnPropertyDescriptor(target, key))
: desc;

// 声明存储装饰器的变量
var d;

// 如果原生反射可用,使用原生反射触发装饰器
if (
typeof Reflect === "object" &&
typeof Reflect.decorate === "function"
) {
r = Reflect.decorate(decorators, target, key, desc);
} else {
// 自右向左迭代装饰器
for (var i = decorators.length - 1; i >= 0; i--) {
// 如果装饰器合法,将其赋值给 d
if ((d = decorators[i])) {
/**
* 如果仅仅传入了装饰器数组和目标,那么应该是类装饰器,
* 传入目标调用装饰器。
* 否则,如果 4 个参数俱全,那么应该是方法装饰器,
* 据此进行调用。
* 反之则使用同一描述符。
* 如果传入了 3 个参数,那么应该是属性装饰器,可进行相应的调用。
* 如果以上条件皆不满足,返回处理的结果。
*/
r =
(c < 3
? d(r)
: c > 3
? d(target, key, r)
: d(target, key)) || r;
}
}
}

/**
* 由于只有方法装饰器需要根据应用装饰器的结果修正其属性,
* 所以最后返回处理好的 r
*/
return c > 3 && r && Object.defineProperty(target, key, r), r;
};

var Employee = /** @class */ (function () {
function Employee(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Employee.prototype.greet = function (message) {
return this.firstName + " " + this.lastName + " says: " + message;
};

// typescript 调用 `__decorate` 辅助函数,
// 以便在对象原型上应用装饰器
__decorate([logMethod], Employee.prototype, "greet");
return Employee;
})();
var emp = new Employee("Mohan Ram", "Ratnakumar");
emp.greet("hello");

让我们开始分析Employee函数—构造器初始化namegreet方法,将其传入原型.

1
__decorate([logMethod], Employee.prototype, "greet");

这是 TypeScript 自动生成的通用方法,它根据装饰器类型和相应参数处理装饰器函数调用。

该函数有助于内省方法调用,并为开发者铺平了处理类似日志记忆化应用配置等横切关注点的道路。

在这个例子中,我们仅仅打印了函数调用及其参数、响应。

注意,阅读 __decorate 方法中的详细注释可以理解其内部机制。

属性装饰器

属性装饰器有两个参数

  1. target: 当前对象原型, 也就说,假设Employee是对象,那么target就是Employee.prototype
  2. propertyKey: 属性名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function logParameter(target: Object, propertyName: string) {
// 属性值
let _val = this[propertyName];

// 属性读取访问器
const getter = () => {
console.log(`Get: ${propertyName} => ${_val}`);
return _val;
};

// 属性写入访问器
const setter = newVal => {
console.log(`Set: ${propertyName} => ${newVal}`);
_val = newVal;
};

// 删除属性
if (delete this[propertyName]) {
// 创建新属性及其读取访问器、写入访问器
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}

class Employee {
@logParameter
name: string;
}

const emp = new Employee();
emp.name = 'Mohan Ram';
console.log(emp.name);
// Set: name => Mohan Ram
// Get: name => Mohan Ram
// Mohan Ram

上面的代码中, 我们在装饰器中内省属性的可访问性, 下面是编译后的代码

1
2
3
4
5
6
7
8
9
10
11
var Employee = /** @class */ (function () {
function Employee() {
}
__decorate([
logParameter
], Employee.prototype, "name");
return Employee;
}());
var emp = new Employee();
emp.name = 'Mohan Ram'; // Set: name => Mohan Ram
console.log(emp.name); // Get: name => Mohan Ram

参数装饰器

参数装饰器函数有三个参数:

  1. target: 当前对象的原型,也就是说,假设Employee是对象, 那么 target就是Employee.prototype
  2. propertyKey: 参数名称
  3. index: 参数数组中的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function logParameter(target: Object, propertyName: string, index: number) {
// 为相应方法生成元数据键,以储存被装饰的参数的位置
const metadataKey = `log_${propertyName}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}

class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');

上面的代码中,我们收集了所有被装饰的方法的索引或位置, 作为元数据加入对象的原型.后面是编译后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 返回接受参数索引和装饰器的函数
var __param = (this && this.__param) || function (paramIndex, decorator) {
// 该函数返回装饰器
return function (target, key) { decorator(target, key, paramIndex); }
};

var Employee = /** @class */ (function () {
function Employee() {}
Employee.prototype.greet = function (message) {
return "hello " + message;
};
__decorate([
__param(0, logParameter)
], Employee.prototype, "greet");
return Employee;
}());
var emp = new Employee();
emp.greet('hello');

类似之前见过的__decorate函数,__param 函数返回一个封装参数装饰器的装饰器

如我们所见,调用参数装饰器时, 会忽略其返回值.这就意味着,调用__param函数时, 其返回值不会用来覆盖参数值

这就是参数装饰器不反回的原因

访问器装饰器

访问器不过是类声明中属性的读取访问器和写入访问器

访问器装饰器应用于访问器的属性描述符,可用于观测修改替换访问器的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function enumerable(value: boolean) {
return function (
target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('decorator - sets the enumeration part of the accessor');
descriptor.enumerable = value;
};
}

class Employee {
private _salary: number;
private _name: string;

@enumerable(false)
get salary() { return `Rs. ${this._salary}`; }

set salary(salary: any) { this._salary = +salary; }

@enumerable(true)
get name() {
return `Sir/Madam, ${this._name}`;
}

set name(name: string) {
this._name = name;
}

}

const emp = new Employee();
emp.salary = 1000;
for (let prop in emp) {
console.log(`enumerable property = ${prop}`);
}
// salary 属性不在清单上,因为我们将其设为假
// output:
// decorator - sets the enumeration part of the accessor
// decorator - sets the enumeration part of the accessor
// enumerable property = _salary
// enumerable property = name

上面的例子中,我们定义了两个访问器namesalary,并通过装饰器设置是否将其列入清单,据此决定对象的行为.name将入清单,而salary不会

下面是编译的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function enumerable(value) {
return function (target, propertyKey, descriptor) {
console.log('decorator - sets the enumeration part of the accessor');
descriptor.enumerable = value;
};
}

var Employee = /** @class */ (function () {
function Employee() {
}
Object.defineProperty(Employee.prototype, "salary", {
get: function () { return "Rs. " + this._salary; },
set: function (salary) { this._salary = +salary; },
enumerable: true,
configurable: true
});
Object.defineProperty(Employee.prototype, "name", {
get: function () {
return "Sir/Madam, " + this._name;
},
set: function (name) {
this._name = name;
},
enumerable: true,
configurable: true
});
__decorate([
enumerable(false)
], Employee.prototype, "salary", null);
__decorate([
enumerable(true)
], Employee.prototype, "name", null);
return Employee;
}());
var emp = new Employee();
emp.salary = 1000;
for (var prop in emp) {
console.log("enumerable property = " + prop);
}

类装饰器

类装饰器应用于类的构造器,可用于观测、修改、替换类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export function logClass(target: Function) {
// 保存一份原构造器的引用
const original = target;

// 生成类的实例的辅助函数
function construct(constructor, args) {
const c: any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
return new c();
}

// 新构造器行为
const f: any = function (...args) {
console.log(`New: ${original['name']} is created`);
return construct(original, args);
}

// 复制 prototype 属性,保持 intanceof 操作符可用
f.prototype = original.prototype;

// 返回新构造器(将覆盖原构造器)
return f;
}

@logClass
class Employee {}

let emp = new Employee();
console.log('emp instanceof Employee');
console.log(emp instanceof Employee); // true

上面的装饰器声明了一个名为original的变量,将其值设为装饰的类构造器.

接着声明了一个名为construct的辅助函数,该函数用于创建类的实例.

接下来创建了一个名为f的变量,该变量用于用作新的构造器,该函数调用原构造器,同时在控制台打印了实例话的类名

这正是我们给原构造器加入的额外的行为的地方
原来构造器的原型复制到了f,以确保创建一个Employee新实例的时候,instanceof操作符的效果符合预期

新构造器一旦就绪,我们便返回它, 以完成类构造器的实现

新构造器就绪后,每次创建实例时会在控制台打印类名

编译后的代码如下

1
2
3
4
5
6
7
8
9
10
11
var Employee = /** @class */ (function () {
function Employee() {
}
Employee = __decorate([
logClass
], Employee);
return Employee;
}());
var emp = new Employee();
console.log('emp instanceof Employee');
console.log(emp instanceof Employee);

在编译后的代码中,我们注意到两处不同:

  1. 传给__decorate的参数有两个, 装饰器数组和构造器函数
  2. TypeScript编译器使用__decorate的返回值以覆盖原构造器

这正是类装饰器必须返回一个构造函数的原因所在

装饰器工厂

由于每种装饰器都有它自身的调用签名,我们可以使用装饰器工厂来泛化装饰器调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { logClass } from './class-decorator';
import { logMethod } from './method-decorator';
import { logProperty } from './property-decorator';
import { logParameter } from './parameter-decorator';

// 装饰器工厂,根据传入的参数调用相应的装饰器
export function log(...args) {
switch (args.length) {
case 3: // 可能是方法装饰器或参数装饰器
// 如果第三个参数是数字,那么它是索引,所以这是参数装饰器
if typeof args[2] === "number") {
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
case 2: // 属性装饰器
return logProperty.apply(this, args);
case 1: // 类装饰器
return logClass.apply(this, args);
default: // 参数数目不合法
throw new Error('Not a valid decorator');
}
}

@log
class Employee {
@log
private name: string;

constructor(name: string) {
this.name = name;
}

@log
greet(@log message: string): string {
return `${this.name} says: ${message}`;
}
}

元信息反射API

元信息反射API(例如 Reflect)能够用来以标准的方式组织元信息.

[反射]的意思是代码可以侦测同一系统中的其他代码(或其自身).

反射在组合/依赖注入、运行时类型断言、测试等使用场景下很有用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import "reflect-metadata";

// 参数装饰器使用反射 api 存储被装饰参数的索引
export function logParameter(target: Object, propertyName: string, index: number) {
// 获取目标对象的元信息
const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName) || [];
indices.push(index);
// 定义目标对象的元信息
Reflect.defineMetadata(`log_${propertyName}_parameters`, indices, target, propertyName);
}

// 属性装饰器使用反射 api 获取属性的运行时类型
export function logProperty(target: Object, propertyName: string): void {
// 获取对象属性的设计类型
var t = Reflect.getMetadata("design:type", target, propertyName);
console.log(`${propertyName} type: ${t.name}`); // name type: String
}


class Employee {
@logProperty
private name: string;

constructor(name: string) {
this.name = name;
}

greet(@logParameter message: string): string {
return `${this.name} says: ${message}`;
}
}

上面的代码用到了reflect-metadata这个库.其中我们使用了反射元信息的设计键(例如design:type).目前只有三个:

  1. 类型元信息: design:type,
  2. 参数类型元信息: design:paramtypes
  3. 返回类型元信息: design:returntype,

有了反射.我们就能够在运行时得到一下信息:

  1. 实体名
  2. 实体类型
  3. 实体实现的接口
  4. 实体构造器参数的名称和类型

总结

  1. 装饰器 不过是在设计时帮助内省代码,注解及修改类和属性的函数
  2. Yehuda Katz 提议在ECMAScript2016标准中加入装饰器特征tc39/proposal-decorators
  3. 我们可以通过装饰器工厂将用户提供的参数传给装饰器
  4. 有4种装饰器: 类装饰器、方法装饰器、属性/访问器装饰器、参数装饰器
  5. 元信息反射API有助于以标准化的方式在对象中加入元信息,以及在运行时获取设计类型信息

备注

本文源于

https://juejin.im/post/6844903876605280269

代码
https://github.com/mohanramphp/typescript-decorators

编程语言的运行

众所周知,我们通过编程语言完成的程序是通过处理器运行的,但是处理器不能直接理解我们通过高级语言(如C++、Go、Javascript等)编写的代码, 只能理解机器码,所以在执行程序前,需要经过一系列的步骤,将我们编写的代码翻译成机器语言,这个过程一般是由编译器(compiler) 或者解释器(Interpreter)来完成

编译器工作流程

源代码 - 词法分析- 语法分析 - AST - 语义分析 - 中间代码 - 代码优化 - 机器码 - 执行

解释器

源代码 - 词法分析 - 语法分析 - AST - 语义分析 - 字节码 - 执行

那么既然编译器和解释器都可以完成代码的翻译工作, 为何还同时存在呢

这是因为编程语言有两个类别: 静态类型 和动态类型.

静态类型: 比如 C++、Go 等,都需要提前编译(AOT) 成机器码 然后执行, 这个过程主要使用编译器来运行;
动态类型: 比如 Javascript、 Python 等, 只在运行时进行编译执行(JIT), 这个过程主要使用解释器完成.

V8并没有采用某种单一的技术,而是混合编译执行和解释执行这两种手段,我们把这种混合使用编译器和解释器的技术称为JIT(JustIn Time)技术

这是一种权衡策略,因为这两种办法都各自有各自的优缺点,解释执行的启动速度快,但是执行时的速度慢,而编译执行的启动速度慢,但是执行的速度快

v8执行js代码流程

V8启动执行javascript之前,它还需要准备执行JavaScript时所需要的一些基础环境,这些基础环境包括了 队空间、栈空间、全局执行上下文、全局作用域、消息循环系统、内置函数等, 这些内容都是执行javaScirpt 过程中 需要使用到的,比如:

  1. javaScript全局执行上下文就包含了执行过程中的全局信息, 比如一些内置函数,全局变量等信息、
  2. 全局作用域包含了一些全局变量,在执行过程中的数据都需要存放在内存中
  3. 而v8采用了经典的堆和栈内存管理模式,所以V8还需要初始化内存中的堆和栈结构
  4. 另外,想要我们的V8系统活起来,还需要初始化消息循环系统,消息循环系统包含了消息驱动器和消息队列,它如同V8的心脏,不断接受消息并决策如何处理消息

基础环境准备好之后,接下来就可以向V8提交要执行的JavaScript代码了

  1. 首先V8会接收到要执行的Javascirpt源代码,不过这对V8来说只是一堆字符串,V8并不能直接理解这段字符串的含义, 他需要结构化这段字符串,生成了抽象语法树(AST),AST是便于V8理解的结构.在生成AST的同时,V8还会生成相关的作用域, 作用域中存放相关的变量
  2. 有了AST和作用域之后,接下来就可以生成字节码了,字节码是介于AST和机器代码中间代码. 但是与特定类型的机器代码无关, 解释器可以直接解释执行行字解码,或者通过编译器将其编译为二进制的机器码,并输出执行结果
  3. 解释器就登场了,他会按照顺序解释执行字节码,并输出执行结果
  4. 有一个监控解释器执行状态的模块, 在解释执行字节码过程中,如果发现了某一段代码会被重复执行,那么监控机器人就会将这段代码标记为热点代码,
  5. 当某段代码被标记化为热点代码后,V8就会将这段字节码丢给优化编译器, 优化编译器会在后台将字节码编译为二进制代码, 然后再对编译后的二进制代码执行优化操作,优化后的二进制机器代码的执行效率会得到大大提升,如果下面再执行到这段代码时, 那么V8会优先选择优化之后的二进制代码,这样代码的执行速度就会大幅度提升
  6. 不过,和静态语言不同的是, javascript是一种非常灵活的动态语言, 对象的结构和属性是可以在运行时任意修改的, 而经过优化编译器优化的代码只能针对某种固定的结构,一旦在执行过程中, 对象的结构被动态修改了,那么优化之后的代码势必会变成无效的代码,这时候优化编译器就需要执行去优化操作,经过去优化的代码, 下次执行时就会回退到解释器解释执行

总结 V8执行一段JS代码的大致流程

  1. 初始化基础环境
  2. 解释源码生成AST和作用域
  3. 依据AST和作用域生成字节码;
  4. 解释执行字节码
  5. 监听热点代码
  6. 优化热点代码为二进制的机器代码
  7. 去优化生成的二进制机器代码

V8

V8引擎是一个javascript 引擎实现, 最初由一些语言方面的专家设计,后被谷歌收购, 随后谷歌对其进行了开源

v8引擎使用C++ 开发,在运行JavaScript之前, 相比其他JavaScript的引擎转换成字节码或解析执行,V8将其编译成原生机器码(IA-32, X86-64, ARM, or MIPS CPUs)

并且使用如内联缓存(inline-cahcing) 等方法来提高性能.

有了这些功能, JavaScript程序在V8引擎的运行速度媲美二进制程序

V8支持众多操作系统, 如 windows, linux, android 等,也支持其他硬件架构. 如IA32 X64, ARM等,具有很好的可移植性和跨平台性

Isolate

英文原意: 隔离.在操作系统中,我们有一个概念和之类似: 进程,进程是完全相互隔离的,一个进程里有多个线程, 同时各个进程之间并不相互共享资源.

Isolate 也是一样, Isolate1和Isolate2哥两拥有各自堆栈的虚拟机实例,且完全隔离

V8的官方定义

Isolate 标示 V8 引擎的Isolate实例, v8 Isolate具有完全独立的状态,一个Isolate 中的对象不得在其他Isolate中使用, 初始化时v8隐式创建一个默认的Isolate.
embedder可以创建其他Isolate, 并在多个线程中并行使用他们. 在任何给定实践, Isolate 最多只能接受一个线程的输入.必须使用Locker/UnLocker API进行同步处理

对比Context进行理解

在V8里面, Context 主要是用于创建一个JavaScript的运行上下文环境, A Context 中的js变量完全独立于 B Context, 一般用Context 来做js的安全隔离. 这里需要区别Isolate,context 依附于Isolate

Isolate 于 context 的区别

  1. 一个Isolate 是一份独立的 V8 runtime, 包括但不限于 一个heap管理器, 垃圾回收器等. 在一个时间端内, 有且只有一个线程能使用此isolate. 不过多个线程可以同时使用多个Isolate
  2. 单独的Isolate是不足以运行脚本的,我们在此需要一个全局对象, context 就是提供此全局变量的工具, 它在所处的Isolate 管理heap中建立一个对象, 并以此为全局变量构建出一个完整的执行环境供我们的脚本使用.
  3. 因此, 对于一个给定的Isolate, 不仅其可以有多个Context, 并且这些Context之间可以共享某写对象.

NodeJs Event Loop

NodeJS Event Loop 执行阶段

  1. timers: 执行 setTimeoutsetImmediate 的回调
  2. pending callbacks: 执行延迟到下一个循环迭代的I/O回调
  3. idle,prepare: 仅系统内部使用
  4. poll: 检索新的I/O事件;执行与I/O相关的回调,事件上除了其他几个阶段处理的事件,其他几乎所有的异步都在这个阶段处理
  5. check: setImmediate 在这里执行
  6. close callbacks: 一些关闭的回调函数, 如: socket.on(‘close’,..)

每个阶段都有一个自己的先进先出的队列,只有当这个队列的事件执行完或者达到该阶段的上限时, 才会进入下一个阶段,在每次事件循环之间,Node.js都会检查它是否在等待任何一个I/O或者定时器,如果没有的话,程序就关闭推出了,我们直观感受就是,如果一个node程序只有同步代码,你在控制台运行完后,他就自己推出了

new Class vs new Function

编译前

1
2
3
4
5
6
7
8
9
10
11
12
13
function a () {
console.dir("Funciton a")
}

class A {
constructor() {
console.dir('class A')
}
}

new a();

new A();

编译后

es5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"use strict";

function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}

function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

function a() {
console.dir("Funciton a");
}

var A = function A() {
_classCallCheck(this, A);

console.dir("class A");
};

new a();
new A();

_classCallCheck

  1. 作用: 防止Class A被当成函数调用A();

Symbol.hasInstance

  1. 作用: 用户可以用来自定义instaceof, 判断对象是不是构造器的实例。

  2. 兼容性 IE不支持

  3. 实现instanceof

example:

1
2
3
4
5
6
7
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}

console.dir([] instanceof MyArray); // true;

es9

异步迭代器

Asyncchronous Interator, 异步执行语句 for..await… of
异步生成器 Async generator

特殊对象
next() => {value, done} done 布尔类型

创建一个迭代器

const createIterator = (items) => {
const keys = Object(items)
const len = keys.length;
let pointer = 0;
return {
next() {
const done = pointer>=len;
const value= !done? items[keys[pointer++]: undefined;
},
return { value, done}
}
}

const it1 = createIterator([1,2,3]);

Symbol.iterator for …of …

生成器

Generator 特殊函数 yield 表达式

function * fn() {
console.log(“正常函数我会执行”)
yield 1;
yield 2;
yield 3;
console.log(执行完了)
}

console.log(iteratorFn.next());

// 异步迭代器
区别

同步: {value:””, done:false}

异步: next() => promsie()

Promise.finally();

Rest / Spread

对象浅拷贝

正则表达式

const dateStr= “2030-08-01”;

const reg = /[0-9]{4}-[0-9]{4}-[0-9]{2}/

const res = reg.exec(dataStr)

console.dir(res)

ES9 ?

const reg1 = /(?[0-9]{4})-(?[0-9]{4})-(?[0-9]{2})/

const res1 = reg1.exec(dataStr)

console.dir(res1)

replace 08-01-2030

const newDate = dateStr.replace(reg1, ${month}-${day}-${year})

// 反向断言

// 先行断言

const   str =  “$123”;

const reg = /\D(?=\d+)/

const result = reg.exec(str);

console.log(result)

后行断言 反向断言 (?<=patterm>)

const reg2 = /(?<=\D>\d+)/;
console.log(reg2.exec(str))

dotAll方式

. 回车符以外的单字符

const str = ‘yi\ndeng’;

console.log(/yi.deng/,test(str));// false

允许行终止符的出现
console.log(/yi.deng/s,test(str));// false

// 汉字匹配

const oldReg=/[\u4e00-\u9fa5]/;

const str = “”

const newReg=/\p{Script=Han}/u;

// 非转译序列的模板字符串
\u unicode转译 \x 十六进制转译

String.row(‘\u{54}’)

ES8

// Async/Await

// next => Promise

// 异步代码

// 1. 嵌套回掉
// 2. Promise
// 3. Generators

1
2
3
4
5
6
7
8
9
10
11
12
13
async function fn() {
await Promise.resolve();
console.log(1);
}


async function add(num) {
const a = 1;
return num +a;
}

console.log(add(2))

promsie 错误处理

await 的异步执行

Object.values() vs Object.keys() // 继承来的没有办法

Object.entries() vs for…in

String Padding

  1. String.prototype.padStart(targetLength, [padString])
  2. String.prototype.padEnd()

结尾允许逗号

Object.getOwnPropertyDescriptors(); // 对象描述符

SharedArrayBuffer 与 Atomics // 多线程功能

共享内存,把多线程引入js

JS主线程, web-worker线程

// postMessage();

多线程 竞争 Atomics

Atomics.load(SharedArrayBuffer, position)
Atomics.store(SharedArrayBuffer, position, newValue) //写入值
exchange() 返回替换的值
Atomics.wait(arrBuffer, 11, 11,2000) 休眠
Atomics.notify(共享视图数组,位置, 进程数)
Atomics.add(intArrBuffer, index,value)
sub()
and,or,xor
compareExchange(intArrBuffer,12,13,33)

ES7

includes

判断数组中存在某元素
arr = [1, 2,3]

arr.indexOf(4)>=0
arr.includes(4);

** 指数运算

Math.pow(2,3)
2**3

ES10 bigInt

// Flat flatMap()

flat 拉平数组,去除空项
const arr = [1,2,3,[4,5]]
const arr1 = [1,2,3,[4,5,[6]]]
console.log(arr.flat())
console.log(arr1.flat(num))
console.log(arr1.flat(Infinity))

const arr1 = [1,2,3,4]

console.log(arr1.map(x=>[x2]))
console.log(arr1.flatMap(x=>[x
2]))

//Object.formEntries() => 对象自身可枚举属性 for…in

const map = new Map([“name”,”-灯”], [“address”,”bejing”]);
console.log(Object.formEntries(map))
console.log(Object.entries);Ide

String.prototype.matchAll 返回包含所有匹配正则表达式及分组捕获迭代器

const str = “yideng xuetang xuetang”;

const reg= /xue*/g

while(matches = reg.exec(str)!== null) {
console.log(${matches[0]}-${reg.lastIndex})
}

let matches2 = str.matchAll(reg)

const reg = /y(i)(deng(\d?))/g

// trimStart // trimEnd

// Symbol.prototype.description

const sym = Symbol(“描述”)
console.log(String(sym))
console.log(sym.description);

Catch 参数可以省略

// 之前

try {

} catch(e) {

}

try {

} catch{

}

行分割符和段分割符号

JSON.parse JSON是ECMAScript的一个子集

// 草案 解决一个问题

const json = ‘{“name”: “yideng”\nxuetang}’

console.log(json)

JSON.parse(json);

JSON.stringify()

字符U+D800 到 U+DFFF处理

U+2028 行分隔符 U+2029 段分割符

JSON.stringify(‘\UDEAD’);

Array.prototype.sort()

小于10 插入排序 快速排序 O<n^2>

新的v8 TimSort() n(nlogn)

const arr = [
{name: ‘w’, age:18}
{name: ‘d’, age:1}
{name: ‘yideng ‘, age:8}
{name: ‘x’, age:108}
{name: ‘j’, age:198}
]

arr.sort((a,b)=> a.age-b.age);

Function.string()

Object.prototype.toString();

function /1213/ foo /123/() {
}

console.log(foo.toString())

// BigInt  任意进度整数 第七种基本数据类型

2^53-1

let num = 1n;

// 标准化的globalThis 对象

const getGlobal = function () {
if(typeof self != “undefined) return self;
if(typeof window != “undefined) return window;
if(typeof gloabal != “unefined”) reutrn global;

throw new Error();
}

// 在任何属性中打印this
consolelog(globalThis)