装饰器(decorator)
装饰器是一种特殊的声明, 可以附加在类、方法、访问器、属性、参数声明上
装饰器使用@expression
的形式,其中expression必须能够演算为运行时调用的函数,其中包含装饰声明信息.
装饰器类型及其执行顺序为
- 类装饰器—优先级 4 (对象实例化, 静态)
- 方法装饰器—优先级 2 (对象实例化, 静态)
- 访问器或属性装饰器—优先级 3 (对象实例化, 静态)
- 参数装饰器—优先级1 (对象实例化, 静态)
注意,如果装饰器应用与类构造函数的参数,那么不同的装饰器的优先级为:
- 参数装饰器
- 方法装饰器
- 访问器或参数装饰器
- 构造器参数装饰器
- 类装饰器
1 | // 这是一个装饰器工厂——有助于将用户参数传给装饰器声明 |
f
和g
返回了另一个函数(装饰器函数). f
和g
称为装饰器工厂
装饰器工厂
帮助用户传递可供装饰器利用的参数
我们可以看到装饰器的,演算规则
为由顶向上,执行顺序由底向上
装饰器的签名
1 | interface TypedPropertyDescriptor<T> { |
方法装饰器
可以从上面的签名中,看到它有三个参数
- target: 当前对象的原型,也就是说,假设Employee是对象,那么target就是Employee.prototype
- propertyKey: 方法的名称
- descriptor: 方法的属性描述符,即Object.getOwnPropertyDescriptor(Employ.prototype, propertyKey)
1 | export function logMethod( |
javascript 编译后是什么样
1 | var __decorate = |
让我们开始分析Employee
函数—构造器初始化name
和greet
方法,将其传入原型.
1 | __decorate([logMethod], Employee.prototype, "greet"); |
这是 TypeScript 自动生成的通用方法,它根据装饰器类型和相应参数处理装饰器函数调用。
该函数有助于内省方法调用,并为开发者铺平了处理类似日志
、记忆化
、应用配置
等横切关注点的道路。
在这个例子中,我们仅仅打印了函数调用及其参数、响应。
注意,阅读 __decorate
方法中的详细注释可以理解其内部机制。
属性装饰器
属性装饰器有两个参数
- target: 当前对象原型, 也就说,假设Employee是对象,那么target就是Employee.prototype
- propertyKey: 属性名称
1 | function logParameter(target: Object, propertyName: string) { |
上面的代码中, 我们在装饰器中内省属性的可访问性, 下面是编译后的代码
1 | var Employee = /** @class */ (function () { |
参数装饰器
参数装饰器函数有三个参数:
- target: 当前对象的原型,也就是说,假设Employee是对象, 那么 target就是Employee.prototype
- propertyKey: 参数名称
- index: 参数数组中的位置
1 | function logParameter(target: Object, propertyName: string, index: number) { |
上面的代码中,我们收集了所有被装饰的方法的索引或位置, 作为元数据加入对象的原型.后面是编译后的代码
1 | // 返回接受参数索引和装饰器的函数 |
类似之前见过的__decorate函数,__param 函数返回一个封装参数装饰器的装饰器
如我们所见,调用参数装饰器时, 会忽略其返回值.这就意味着,调用__param
函数时, 其返回值不会用来覆盖参数值
这就是参数装饰器
不反回的原因
访问器装饰器
访问器不过是类声明中属性的读取访问器和写入访问器
访问器装饰器应用于访问器的属性描述符
,可用于观测
、修改
、替换
访问器的定义
1 | function enumerable(value: boolean) { |
上面的例子中,我们定义了两个访问器name
和salary
,并通过装饰器设置是否将其列入清单,据此决定对象的行为.name
将入清单,而salary
不会
下面是编译的代码
1 | function enumerable(value) { |
类装饰器
类装饰器应用于类的构造器,可用于观测、修改、替换类的定义
1 | export function logClass(target: Function) { |
上面的装饰器声明了一个名为original的变量,将其值设为装饰的类构造器.
接着声明了一个名为construct的辅助函数,该函数用于创建类的实例.
接下来创建了一个名为f的变量,该变量用于用作新的构造器,该函数调用原构造器,同时在控制台打印了实例话的类名
这正是我们给原构造器加入的额外的行为的地方
原来构造器的原型复制到了f,以确保创建一个Employee新实例的时候,instanceof操作符的效果符合预期
新构造器一旦就绪,我们便返回它, 以完成类构造器的实现
新构造器就绪后,每次创建实例时会在控制台打印类名
编译后的代码如下
1 | var Employee = /** @class */ (function () { |
在编译后的代码中,我们注意到两处不同:
- 传给__decorate的参数有两个, 装饰器数组和构造器函数
- TypeScript编译器使用__decorate的返回值以覆盖原构造器
这正是类装饰器必须返回一个构造函数的原因所在
装饰器工厂
由于每种装饰器都有它自身的调用签名,我们可以使用装饰器工厂来泛化装饰器调用
1 | import { logClass } from './class-decorator'; |
元信息反射API
元信息反射API(例如 Reflect)能够用来以标准的方式组织元信息.
[反射]的意思是代码可以侦测同一系统中的其他代码(或其自身).
反射在组合/依赖注入、运行时类型断言、测试等使用场景下很有用.
1 | import "reflect-metadata"; |
上面的代码用到了reflect-metadata
这个库.其中我们使用了反射元信息的设计键(例如design:type
).目前只有三个:
- 类型元信息:
design:type
, - 参数类型元信息:
design:paramtypes
- 返回类型元信息:
design:returntype
,
有了反射.我们就能够在运行时得到一下信息:
- 实体名
- 实体类型
- 实体实现的接口
- 实体构造器参数的名称和类型
总结
- 装饰器 不过是在设计时帮助内省代码,注解及修改类和属性的函数
- Yehuda Katz 提议在ECMAScript2016标准中加入装饰器特征tc39/proposal-decorators
- 我们可以通过装饰器工厂将用户提供的参数传给装饰器
- 有4种装饰器: 类装饰器、方法装饰器、属性/访问器装饰器、参数装饰器
- 元信息反射API有助于以标准化的方式在对象中加入元信息,以及在运行时获取设计类型信息
备注
本文源于
编程语言的运行
众所周知,我们通过编程语言完成的程序是通过处理器运行的,但是处理器不能直接理解我们通过高级语言(如C++、Go、Javascript等)编写的代码, 只能理解机器码,所以在执行程序前,需要经过一系列的步骤,将我们编写的代码翻译成机器语言,这个过程一般是由编译器(compiler) 或者解释器(Interpreter)来完成
编译器工作流程
源代码 - 词法分析- 语法分析 - AST - 语义分析 - 中间代码 - 代码优化 - 机器码 - 执行
解释器
源代码 - 词法分析 - 语法分析 - AST - 语义分析 - 字节码 - 执行
那么既然编译器和解释器都可以完成代码的翻译工作, 为何还同时存在呢
这是因为编程语言有两个类别: 静态类型 和动态类型.
静态类型: 比如 C++、Go 等,都需要提前编译(AOT) 成机器码 然后执行, 这个过程主要使用编译器来运行;
动态类型: 比如 Javascript、 Python 等, 只在运行时进行编译执行(JIT), 这个过程主要使用解释器完成.
V8并没有采用某种单一的技术,而是混合编译执行和解释执行这两种手段,我们把这种混合使用编译器和解释器的技术称为JIT(JustIn Time)技术
这是一种权衡策略,因为这两种办法都各自有各自的优缺点,解释执行的启动速度快,但是执行时的速度慢,而编译执行的启动速度慢,但是执行的速度快
v8执行js代码流程
V8启动执行javascript之前,它还需要准备执行JavaScript时所需要的一些基础环境,这些基础环境包括了 队空间、栈空间、全局执行上下文、全局作用域、消息循环系统、内置函数等, 这些内容都是执行javaScirpt 过程中 需要使用到的,比如:
- javaScript全局执行上下文就包含了执行过程中的全局信息, 比如一些内置函数,全局变量等信息、
- 全局作用域包含了一些全局变量,在执行过程中的数据都需要存放在内存中
- 而v8采用了经典的堆和栈内存管理模式,所以V8还需要初始化内存中的堆和栈结构
- 另外,想要我们的V8系统活起来,还需要初始化消息循环系统,消息循环系统包含了消息驱动器和消息队列,它如同V8的心脏,不断接受消息并决策如何处理消息
基础环境准备好之后,接下来就可以向V8提交要执行的JavaScript代码了
- 首先V8会接收到要执行的Javascirpt源代码,不过这对V8来说只是一堆字符串,V8并不能直接理解这段字符串的含义, 他需要结构化这段字符串,生成了抽象语法树(AST),AST是便于V8理解的结构.在生成AST的同时,V8还会生成相关的作用域, 作用域中存放相关的变量
- 有了AST和作用域之后,接下来就可以生成字节码了,字节码是介于AST和机器代码中间代码. 但是与特定类型的机器代码无关, 解释器可以直接解释执行行字解码,或者通过编译器将其编译为二进制的机器码,并输出执行结果
- 解释器就登场了,他会按照顺序解释执行字节码,并输出执行结果
- 有一个监控解释器执行状态的模块, 在解释执行字节码过程中,如果发现了某一段代码会被重复执行,那么监控机器人就会将这段代码标记为热点代码,
- 当某段代码被标记化为热点代码后,V8就会将这段字节码丢给优化编译器, 优化编译器会在后台将字节码编译为二进制代码, 然后再对编译后的二进制代码执行优化操作,优化后的二进制机器代码的执行效率会得到大大提升,如果下面再执行到这段代码时, 那么V8会优先选择优化之后的二进制代码,这样代码的执行速度就会大幅度提升
- 不过,和静态语言不同的是, javascript是一种非常灵活的动态语言, 对象的结构和属性是可以在运行时任意修改的, 而经过优化编译器优化的代码只能针对某种固定的结构,一旦在执行过程中, 对象的结构被动态修改了,那么优化之后的代码势必会变成无效的代码,这时候优化编译器就需要执行去优化操作,经过去优化的代码, 下次执行时就会回退到解释器解释执行
总结 V8执行一段JS代码的大致流程
- 初始化基础环境
- 解释源码生成AST和作用域
- 依据AST和作用域生成字节码;
- 解释执行字节码
- 监听热点代码
- 优化热点代码为二进制的机器代码
- 去优化生成的二进制机器代码
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 的区别
- 一个Isolate 是一份独立的 V8 runtime, 包括但不限于 一个heap管理器, 垃圾回收器等. 在一个时间端内, 有且只有一个线程能使用此isolate. 不过多个线程可以同时使用多个Isolate
- 单独的Isolate是不足以运行脚本的,我们在此需要一个全局对象, context 就是提供此全局变量的工具, 它在所处的Isolate 管理heap中建立一个对象, 并以此为全局变量构建出一个完整的执行环境供我们的脚本使用.
- 因此, 对于一个给定的Isolate, 不仅其可以有多个Context, 并且这些Context之间可以共享某写对象.
NodeJs Event Loop
NodeJS Event Loop 执行阶段
- timers: 执行
setTimeout
和setImmediate
的回调 - pending callbacks: 执行延迟到下一个循环迭代的I/O回调
- idle,prepare: 仅系统内部使用
- poll: 检索新的I/O事件;执行与I/O相关的回调,事件上除了其他几个阶段处理的事件,其他几乎所有的异步都在这个阶段处理
- check: setImmediate 在这里执行
- close callbacks: 一些关闭的回调函数, 如: socket.on(‘close’,..)
每个阶段都有一个自己的先进先出的队列,只有当这个队列的事件执行完或者达到该阶段的上限时, 才会进入下一个阶段,在每次事件循环之间,Node.js都会检查它是否在等待任何一个I/O或者定时器,如果没有的话,程序就关闭推出了,我们直观感受就是,如果一个node程序只有同步代码,你在控制台运行完后,他就自己推出了
new Class vs new Function
编译前
1 | function a () { |
编译后
1 | ; |
_classCallCheck
- 作用: 防止
Class A
被当成函数调用A()
;
Symbol.hasInstance
作用: 用户可以用来自定义
instaceof
, 判断对象是不是构造器的实例。兼容性 IE不支持
实现
instanceof
example:
1 | class MyArray { |
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 = /(?
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 | async function fn() { |
promsie 错误处理
await 的异步执行
Object.values() vs Object.keys() // 继承来的没有办法
Object.entries() vs for…in
String Padding
- String.prototype.padStart(targetLength, [padString])
- 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)
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=>[x2]))
//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)