ES2015、ES2016 和一些未纳入 ECMAScript 的特性
ECMAScript 由 TC39 (Technical Committee 39, 推进 JavaScript 发展的委员会) 设计 , 每个 ECMAScript 特性的建议将会从阶段 0 开始, 然后经过 Strawman 初稿、Proposal 建议、Draft 草案、Candidate 候选、Finished 完成, 最后加入 ECMAScript 规范之中。 当规范通过年度审核成为标准, 该建议也正式成为标准的一部分。 这篇文章主要聊一下新被纳入和部分未纳入 ECMAScript 的特性。
已被纳入 ES2015 的特性
let 和 const 命令
let 声明一个变量。 const 声明一个只读的常量,并立即初始化,不能留到以后赋值。
let / const 会创建块级作用域,不存在变量提升,不允许重复声明。
如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
在没有 let 之前,typeof 运算符是百分之百安全的,永远不会报错。现在这一点不成立了。
ES5的规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ES6引入了块级作用域,明确允许在块级作用域之中声明函数。
变量的解构赋值
解构赋值一般用于 交换变量的值、从函数返回多个值、函数参数的定义、提取JSON数据、函数参数的默认值、遍历Map结构、输入模块的指定方法 等。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
let { id, status, data: number } = jsonData;
const { SourceMapConsumer, SourceNode } = require("source-map");
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。 这种写法属于“模式匹配”
// 数组的解构赋值 次序
var [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
let [ , , third] = ["foo", "bar", "baz"];
let [x, y, ...z] = ['a']; // "a", undefined, []
let [x, y, z] = new Set(["a", "b", "c"]);
// 默认值
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
// 对象的解构赋值 变量必须与属性同名,才能取到正确的值。
var { bar, foo } = { foo: "aaa", bar: "bbb" };
// 字符串的解构赋值
const [a, b, c, d, e] = 'hello'; // a:"h", b:"e", c:"l", d:"l", e:"o"
let {length : len} = 'hello'; // len:5
字符串的扩展
-
字符的Unicode表示法 : “\u{41}\u{42}\u{43}”
-
codePointAt() : 正确处理4个字节储存的字符,返回一个字符的码点。(charAt 不能)
-
String.fromCodePoint() : 识别大于 0xFFFF 的码点。(fromCharCode 不能)
-
字符串的遍历器接口 : let codePoint of ‘foo’ 可以识别大于0xFFFF的码点。(for 不能)
-
at() : 可以识别Unicode编号大于0xFFFF的字符,返回正确的字符。(charAt 不能)
-
normalize(‘NFC’/‘NFD’/‘NFKC’/‘NFKD’) : 将字符的不同表示方法统一为同样的形式
-
includes(‘str’, [startIndex]), startsWith(‘str’, [startIndex]), endsWith(‘str’, [endIndex]) : indexOf 扩展。
-
repeat() : 将原字符串重复n次。
-
模板字符串 : 反引号 引入变量还能调用函数
`${x} + ${y} = ${fn()}`
var template = `
<ul>
<% for(var i=0; i < data.supplies.length; i++) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
var message =
SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
var s = templateData[0];
for (var i = 1; i < arguments.length; i++) {
var arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;
}
- String.raw() : 返回一个斜杠都被转义的字符串,对应于替换变量后的模板字符串。
数值的扩展
-
二进制和八进制表示法 : 二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
-
Number.isFinite(), Number.isNaN(), Number.isInteger()
-
Number.parseInt(), Number.parseFloat() : 移植到Number对象上面
-
Number.EPSILON : 极小的常量,为浮点数计算,设置一个误差范围。
-
安全整数和Number.isSafeInteger() : 引入了
Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。该方法用来判断一个整数是否落在这个范围之内。 -
Math.trunc() : 去除一个数的小数部分,返回整数部分。
-
Math.sign() : 用来判断一个数到底是正数、负数、还是零。
-
Math.cbrt() : 计算一个数的立方根。内部也是先使用Number方法将其转为数值。
-
Math.clz32() (count leading zero bits in 32-bit binary ) : 返回一个数的32位无符号整数形式有多少个前导0。对于小数,Math.clz32方法只考虑整数部分。
-
Math.imul() : 返回两个数以32位带符号整数形式相乘的结果,返回的也是一个32位的带符号整数。
-
Math.fround() : 返回一个数的单精度浮点数形式。
-
Math.hypot() : 返回所有参数的平方和的平方根。
-
Math.expm1() : Math.exp(x) - 1。
-
Math.log1p() : Math.log(1 + x)
-
Math.log10() / Math.log2() / Math.sinh(x) / Math.cosh(x) / Math.tanh(x) / Math.asinh(x) / Math.acosh(x) / Math.atanh(x)
函数的扩展
-
函数参数的默认值 : 指定了默认值后,length 属性将失真。
- 基本用法 function Point(x = 0, y = 0) {this.x = x;this.y = y;}
- 与解构赋值默认值结合使用 function fetch(url, { method = ‘GET’ } = {}) { … }
-
rest 参数 (…变量名) : rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
-
扩展运算符 (…) : 将一个数组转为用逗号分隔的参数序列。
- 替代数组的apply方法
- 合并数组 [1, 2, …more] / ([1, 2].concat(more))
- 与解构赋值结合 [a, …rest] = list / a = list[0], rest = list.slice(1)
- 扩展运算符提供了解决函数返回多个值的一种变通方法。
- 扩展运算符还可以将字符串转为真正的数组。任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。Map和Set 结构,Generator 函数
-
name 属性 : 返回该函数的函数名。
- 如果将一个匿名函数赋值给一个变量会返回实际的函数名。
- 如果将一个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字。
- Function构造函数返回的函数实例,name属性的值为“anonymous”。
- bind返回的函数,name属性值会加上“bound ”前缀。
-
箭头函数 (=>) :
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
-
尾调用(尾递归)优化 : 只保留内层函数的调用帧 (尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧)。
ES6的尾调用优化只在严格模式下开启,正常模式是无效的。
递归函数的改写 : 把所有用到的内部变量改写成函数的参数。
function Fibonacci (n , ac1 = 1 , ac2 = 1) {
if (n <= 1) { return ac2; };
return Fibonacci (n - 1, ac2, ac1 + ac2);
}
对象的扩展
- 属性的简洁表示法 : ES6 允许在对象之中直接写入变量和函数,作为对象的属性和方法。只写属性名,不写属性值,属性值等于属性名所代表的变量。
var birth = '2000/01/01';
var Person = {
name: '张三',
// 等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
// 如果某个方法的值是一个 Generator 函数,前面需要加上星号。
var obj = {
* m(){
yield 'hello world';
}
};
-
属性名表达式 : ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
{ ['h'+'ello'] () {return 'hi';} } -
方法的 name 属性 : 对象方法也是函数,因此也有 name 属性。
- bind 方法创造的函数,name属性返回“bound”加上原函数的名字;
- Function 构造函数创造的函数,name属性返回“anonymous”。
- 如果对象的方法是一个Symbol值,那么 name 属性返回的是这个 Symbol 值的描述。
-
Object.is() : ES6提出“Same-value equality”(同值相等)算法
-
Object.assign(target, source1, source2) : 用于对象的合并,将源对象(source)的所有可枚举属性(不拷贝继承和不可枚举的属性),浅拷贝到目标对象(target)。
- 为对象添加属性 / 方法
- 克隆对象
- 合并多个对象
-
属性的可枚举性 : Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。描述对象的 enumerable 属性为 false 时 for…in循环、Object.keys()、JSON.stringify() 会忽略。
-
属性的遍历 : 首先遍历所有属性名为数值的属性,按照数字排序。其次遍历所有属性名为字符串的属性,按照生成时间排序。最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
- for…in 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
- Object.keys(obj) 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。
- Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。
- Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的所有Symbol属性。
- Reflect.ownKeys(obj) 回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。
-
__proto__属性(读取或设置当前对象的prototype对象),Object.setPrototypeOf(),Object.getPrototypeOf() : 只有浏览器必须部署__proto__属性,其他运行环境不一定需要部署。 -
Object.keys() : 返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
Symbol
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:Undefined、Null、Boolean、String、Number、Object。 凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol() 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。相同参数的 Symbol 函数的返回值是不相等的。
-
作为属性名的 Symbol : Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。Symbol值作为对象属性名时,不能用点运算符。常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证 switch 语句会按设计的方式工作。
-
实例:消除魔术字符串 : 魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
常用的消除魔术字符串的方法,就是把它写成一个变量。这里就很适合改用Symbol值。
- 属性名的遍历 : Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。另一个新的API,Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和Symbol键名。
可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
-
Symbol.for() : 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。 Symbol.for() 与 Symbol() 这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。 Symbol.for() 不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的 key 是否已经存在,如果不存在才会新建一个值。 Symbol.for 为 Symbol 值登记的名字,是全局环境的,可以在不同的iframe或service worker中取到同一个值。
-
Symbol.keyFor() : 返回一个已登记的 Symbol 类型值的 key。
-
实例:模块的 Singleton 模式 : Singleton模式指的是调用一个类,任何时候返回的都是同一个实例。使用 Symbol 保证引用不会被其他脚本改写。
-
内置的Symbol值 :
- Symbol.hasInstance 属性,指向一个内部方法。当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo 在语言内部,实际调用的是 FooSymbol.hasInstance。
- 对象的 Symbol.isConcatSpreadable 属性等于一个布尔值,表示该对象使用 Array.prototype.concat() 时,是否可以展开。属性等于 true 或 undefined 是可以展开,
- 对象的 Symbol.species 属性,指向一个方法。该对象作为构造函数创造实例时,会调用这个方法。即如果 this.constructor[Symbol.species] 存在,就会使用这个属性作为构造函数,来创造新的实例对象。
- 对象的 Symbol.match 属性,指向一个函数。当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
- 对象的 Symbol.replace 属性,指向一个方法,当该对象被 String.prototype.replace 方法调用时,会返回该方法的返回值。
- 对象的 Symbol.search 属性,指向一个方法,当该对象被 String.prototype.search 方法调用时,会返回该方法的返回值。
- 对象的 Symbol.split 属性,指向一个方法,当该对象被 String.prototype.split 方法调用时,会返回该方法的返回值。
- 对象的 Symbol.iterator 属性,指向该对象的默认遍历器方法。
- 对象的 Symbol.toPrimitive 属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。被调用时,会接受一个字符串参数,表示当前运算的模式:Number/String/Default
- 对象的 Symbol.toStringTag 属性,指向一个方法。在该对象上面调用 Object.prototype.toString 方法时,如果这个属性存在,它的返回值会出现在 toString 方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制 [object Object] 或 [object Array] 中 object 后面的那个字符串。
- 对象的 Symbol.unscopables 属性,指向一个对象。该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
-
生成Proxy实例 : var proxy = new Proxy(target, handler);
-
get(target, propKey, receiver) : 拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。
-
set(target, propKey, value, receiver) : 拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
-
has(target, propKey) : 拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。
-
deleteProperty(target, propKey) : 拦截delete proxy[propKey]的操作,返回一个布尔值。
-
ownKeys(target) : 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。
-
getOwnPropertyDescriptor(target, propKey) : 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
-
defineProperty(target, propKey, propDesc) : 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
-
preventExtensions(target) : 拦截Object.preventExtensions(proxy),返回一个布尔值。
-
getPrototypeOf(target) : 拦截Object.getPrototypeOf(proxy),返回一个对象。
-
isExtensible(target) : 拦截Object.isExtensible(proxy),返回一个布尔值。
-
setPrototypeOf(target, proto) : 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
-
apply(target, object, args) : 拦截Proxy实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
-
construct(target, args) : 拦截Proxy实例作为构造函数调用的操作,比如new proxy(…args)。
Reflect
设计目的 :
-
将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。
-
修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
-
让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
-
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Reflect对象的方法清单如下,共13个。
- Reflect.apply(target,thisArg,args)
- Reflect.construct(target,args)
- Reflect.get(target,name,receiver)
- Reflect.set(target,name,value,receiver)
- Reflect.defineProperty(target,name,desc)
- Reflect.deleteProperty(target,name)
- Reflect.has(target,name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
二进制数组
它很像 C 语言的数组,允许开发者以数组下标的形式,直接操作内存,大大增强了 JavaScript 处理二进制数据的能力,使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二进制通信。
二进制数组由三类对象组成 :
-
ArrayBuffer 对象 : 代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
-
TypedArray 视图 : 共包括 9 种类型的视图。
-
DataView 视图 : 可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号8位整数)、第二、三个字节是 Int16(16位整数)、第四个字节开始是 Float32(32位浮点数)等等,此外还可以自定义字节序。
ArrayBuffer 对象
ArrayBuffer 对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray 视图和 DataView 视图)来读写,视图的作用是以指定格式解读二进制数据。 ArrayBuffer 也是一个构造函数,可以分配一段可以存放数据的连续内存区域。
-
ArrayBuffer.prototype.byteLength : ArrayBuffer 实例的 byteLength 属性,返回所分配的内存区域的字节长度。
-
ArrayBuffer.prototype.slice() : ArrayBuffer 实例有一个 slice 方法,允许将内存区域的一部分,拷贝生成一个新的 ArrayBuffer 对象。
-
ArrayBuffer.isView() : ArrayBuffer 有一个静态方法 isView,返回一个布尔值,表示参数是否为 ArrayBuffer 的视图实例。这个方法大致相当于判断参数,是否为 TypedArray 实例或 DataView 实例。
TypedArray 视图
TypedArray 视图一共包括 9 种类型,每一种视图都是一种构造函数。
- Int8Array:8位有符号整数,长度1个字节。
- Uint8Array:8位无符号整数,长度1个字节。
- Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
- Int16Array:16位有符号整数,长度2个字节。
- Uint16Array:16位无符号整数,长度2个字节。
- Int32Array:32位有符号整数,长度4个字节。
- Uint32Array:32位无符号整数,长度4个字节。
- Float32Array:32位浮点数,长度4个字节。
- Float64Array:64位浮点数,长度8个字节。
DataView视图
二进制数组的应用
- AJAX : XMLHttpRequest 第二版 XHR2 允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(responseType)设为 arraybuffer;如果不知道,就设为 blob。
var xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
let arrayBuffer = xhr.response;
// ···
};
xhr.send();
xhr.onreadystatechange = function () {
if (req.readyState === 4 ) {
var arrayResponse = xhr.response;
var dataView = new DataView(arrayResponse);
var ints = new Uint32Array(dataView.byteLength / 4);
xhrDiv.style.backgroundColor = "#00FF00";
xhrDiv.innerText = "Array is " + ints.length + "uints long";
}
}
- Canvas :
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var uint8ClampedArray = imageData.data;
- WebSocket : WebSocket 可以通过 ArrayBuffer,发送或接收二进制数据。
var socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
// Wait until socket is open
socket.addEventListener('open', function (event) {
// Send binary data
var typedArray = new Uint8Array(4);
socket.send(typedArray.buffer);
});
// Receive binary data
socket.addEventListener('message', function (event) {
var arrayBuffer = event.data;
// ···
});
- Fetch API : Fetch API 取回的数据,就是 ArrayBuffer 对象。
fetch(url)
.then(function(request){
return request.arrayBuffer()
})
.then(function(arrayBuffer){
// ...
});
- File API : 如果知道一个文件的二进制数据类型,也可以将这个文件读取为 ArrayBuffer 对象。
Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。向Set加入值的时候,不会发生类型转换。
-
Set 实例的属性和方法 : Set.prototype.constructor / Set.prototype.size / add(value) / delete(value) / has(value) /clear()
-
Set() : 构造函数,接受一个数组(或类似数组的对象)作为参数
-
遍历操作 : keys() / values() / entries() / forEach()
-
去除数组的重复成员 :
[...new Set(array)]
WeakSet
WeakSet 结构与 Set 类似,但是,只能存储对象。WeakSet 中的对象都是弱引用,无法引用 WeakSet 的成员,因此 WeakSet 是不可遍历的。
-
WeakSet() : 构造函数,接受一个具有iterable接口的对象作为参数
-
WeakSet 实例的属性和方法 : WeakSet.prototype.add(value) / WeakSet.prototype.delete(value) / WeakSet.prototype.has(value)
Map
ES6 提供了 Map 数据结构。类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
-
Map() : 构造函数,接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
-
实例的属性和操作方法 : size / set(key, value) / get(key) / has(key) / delete(key) / clear()
-
遍历方法 : keys() / values() / entries() / forEach() Map的遍历顺序就是插入顺序。
-
Map 转为数组 : 使用扩展运算符(…)。
WeakMap
WeakMap 结构与 Map 结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。
Iterator 和 for…of 循环
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。
Iterator的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for…of 循环遍历它的成员
Generator 函数
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 Generator 函数有多种理解角度。从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function 关键字与函数名之间有一个星号;二是,函数体内部使用 yield 语句,定义不同的内部状态(yield 语句在英语里的意思就是“产出”)。 调用 Generator 函数后,返回一个指向内部状态的遍历器对象(Iterator Object)。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。 总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
-
yield 语句 : 由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。 yield语句与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句。正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说Generator生成了一系列的值,这也就是它的名称的来历(在英语中,generator这个词是“生成器”的意思)。
-
next 方法的参数 : yield 句本身没有返回值,或者说总是返回 undefined。next 方法可以带一个参数,该参数就会被当作上一个 yield 语句的返回值。
function* f() {
for(var i=0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
-
for…of循环 : for…of循环可以自动遍历调用Generator函数时生成的Iterator对象,且此时不再需要调用next方法。
-
Generator.prototype.throw() : Generator 函数返回的遍历器对象,都有一个 throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
-
Generator.prototype.return() : Generator 函数返回的遍历器对象,还有一个 return方 法,可以返回给定的值,并且终结遍历 Generator 函数。如果Generator函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行。
-
yield* 语句 : 如果在 Generater 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。这个就需要用到 yield* 语句,用来在一个 Generator 函数里面执行另一个 Generator 函数。 从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*语句。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
-
作为对象属性的 Generator 函数 : 如果一个对象的属性是 Generator 函数,可以在属性前面加一个星号,表示这个属性是一个 Generator 函数。
-
Generator 函数的 this : 不能获得 this 。首先,生成一个空对象,使用 bind 方法绑定 Generator 函数内部的 this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。
-
Generator 是实现状态机的最佳结构。
-
Generator 与协程 :
传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。 协程与其不同,多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。 也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。 这种可以并行执行、交换执行权的线程(或函数),就称为协程。
从实现上看,在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。
协程适合用于多任务运行的环境。在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量。 它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。 此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。
由于 ECMAScript 是单线程语言,只能保持一个调用栈。 引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。
Generator 函数是 ECMAScript 6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。
如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用 yield 语句交换控制权。
- 应用 : Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。
- 异步操作的同步化表达 : Generator 函数的暂停执行的效果,意味着可以把异步操作写在 yield 语句里面,等到调用 next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
- 控制流管理 : Generator 函数可以进一步改善代码运行流程。实际操作中,一般让yield语句返回Promise对象。多个任务按顺序一个接一个执行时,yield语句可以按顺序排列。多个任务需要并列执行时(比如只有A任务和B任务都执行完,才能执行C任务),可以采用数组的写法。
- 部署iterator接口 : 利用Generator函数,可以在任意对象上部署iterator接口。
- 作为数据结构 : Generator 可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。
Promise 对象
- Promise 的含义 : Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 Promise 对象有以下两个特点。
(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用 stream 模式是比部署 Promise 更好的选择。
- 基本用法 : ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject 。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved ),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 Pending 变为 Rejected ),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise 实例生成以后,可以用 then 方法分别指定 Resolved 状态和 Reject 状态的回调函数。then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 Resolved 时调用,第二个回调函数是 Promise 对象的状态变为 Reject 时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数。 Promise新建后就会立即执行。
-
Promise.prototype.then() : 为 Promise 实例添加状态改变时的回调函数。第一个参数是 Resolved 状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。返回的是一个新的 Promise 实例(注意,不是原来那个Promise实例)。因此可以采用链式写法。采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
-
Promise.prototype.catch() : Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。
-
Promise.all() : Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。接受一个具有 Iterator 接口的对象的参数,都是 Promise 对象的实例,如果不是,就会先调用 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。只有这6个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。
-
Promise.race() : Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
-
Promise.resolve() : 将现有对象转为 Promise 对象。
- 参数是一个 Promise 实例将不做任何修改、原封不动地返回这个实例。
- 具有then方法的对象,将这个对象转为 Promise 对象,然后就立即执行 thenable 对象的 then 方法。
- 参数不是具有 then 方法的对象,或根本就不是对象返回一个新的Promise对象,状态为Resolved。
- 不带参数,直接返回一个Resolved状态的Promise对象。立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
-
Promise.reject() : 返回一个新的 Promise 实例,该实例的状态为 rejected。它的参数用法与 Promise.resolve 方法完全一致。
-
两个有用的附加方法 : 自己部署 done() 总是处于回调链的尾端,保证抛出任何可能出现的错误。finally() 不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
-
应用 :
- 加载图片 我们可以将图片的加载写成一个 Promise,一旦加载完成,Promise 的状态就发生变化。
- Generator 函数与 Promise 的结合 使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个 Promise 对象。
Class
- Class基本语法 : 里面有一个 constructor 方法,这就是构造方法,而 this 关键字则代表实例对象。 定义“类”的方法的时候,前面不需要加上 function 这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 构造函数的 prototype 属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的 prototype 属性上面。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
-
constructor 方法 : 构造函数。如果没有显式定义,默认添加一个空的 constructor 方法。默认返回实例对象(即this)。
-
类的实例对象 : 使用 new 命令。如果忘记加上 new,像函数那样调用 Class,将会报错。实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
-
不存在变量提升 : Class不存在变量提升(hoist),这一点与ES5完全不同。
-
严格模式 : 类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
-
name属性 : name属性总是返回紧跟在class关键字后面的类名。
-
Class的继承 : Class 之间可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
-
super关键字 : 作为函数调用时(即super(…args)),super 代表父类的构造函数。作为对象调用时(即super.prop或super.method()),super 代表父类。
-
Class 的取值函数(getter)和存值函数(setter) : 在 Class 内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
-
Class 的静态方法 : 如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
-
new.target 属性 : (在构造函数中)返回 new 命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
-
Mixin模式的实现 : Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。
Module
在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。 ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。
import { stat, exists, readFile } from ‘fs’; 从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”,即ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。当然,这也导致了没法引用ES6模块本身,因为它不是对象。
-
严格模式 : ES6的模块自动采用严格模式,不管你有没有在模块头部加上“use strict”;。
-
export命令 : 模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。使用大括号指定所要输出的一组变量。export命令除了输出变量,还可以输出函数或类(class)。export {firstName, lastName, year};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion};
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
-
import命令 : 使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。 import命令具有提升效果,会提升到整个模块的头部,首先执行。 如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起。export { es6 as default } from ‘./someModule’;
-
模块的整体加载 : 用星号(*)指定一个对象,所有输出值都加载在这个对象上面。import * as circle from ‘./circle’;
-
export default命令 : 为模块指定默认输出。其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。export default命令用在非匿名函数前,也是可以的。
-
ES6模块加载的实质 : ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。
已被纳入 ES2016 的特性
只有 Array.prototype.includes 被纳入 ES2016。
Array.prototype.includes()
Array.prototype.includes(value:任意值): boolean
该方法接收任意值, 根据数组是否包含返回 boolean 变量。数组和类型数组都有 includes() 方法。
includes 方法与 indexOf 方法很相似,唯一的区别是 includes() 方法能找到 NaN , 而 indexOf() 不行。
处于阶段 3 的特性
指数运算符
****=
x ** y 与 Math.pow(x, y) 运算结果相同。
SIMD.JS – SIMD APIs + polyfill
SIMD (single instruction, multiple data 单指令流多数据流), CPU 可以通过单条指令操作一组数据(而不是仅操作单一值), SIMD 指令集最有名的一个例子就是由 Intel 处理器的 SSE(Streaming SIMD Extensions)。
var a = SIMD.float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.float32x4(5.0, 6.0, 7.0, 8.0);
var c = SIMD.float32x4.add(a,b);
异步函数
async/await
函数声明 : async function foo() { }
函数表达式 : const foo = async function () {};
方法定义 : let obj = { async foo() {} }
箭头函数 : const foo = async () => {}
- 用 Promises 和 generators 编写异步代码
通过 Promises 和 generators 来使用看起来同步的代码去执行异步操作。
对于需要异步地计算一次性结果的函数来说,Promises(ES6 的一部分)现在越来越流行。一个例子就是 客户端 fetch API ,它是一种替代 XMLHttpRequest 检索文件的方案。代码如下:
function fetchJson(url) {
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR: ${error.stack}`);
});
}
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
co 是一个使用了 Promises 和 generators 让编程风格看起来更像异步的库,但也需要使用类似于前一个例子的风格去编码:
const fetchJson = co(function* () {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
});
每次回调函数(一个 generator 函数!)产生一个 Promise 给 co,回调函数就会被挂起。一旦这个 Promise 完成,co 就会恢复该回调函数:如果 Promise 被实现, yield 会返回这个实现的值,如果被拒绝,则 yield 会抛出异常。另外,co 能处理回调函数返回的结果(与 then() 相似)。
- 异步函数
异步函数的语法基本上可以说是实现了 co 所做的:
async function fetchJson(url) {
try {
let request = await fetch(url);
let text = await request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
}
在内部,异步函数的工作非常类似于 generators,但它并不会被翻译为 generator 函数。 async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,async 函数返回一个 Promise 对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
Object.values / Object.entries
Object.entries(value : any) : Array<[string,any]> 返回键和值 Object.values(value : any) : Array
只返回值
Object.entries({ one: 1, two: 2 }) // [ [ 'one', 1 ], [ 'two', 2 ] ]
Object.entries({ [Symbol()]: 123, foo: 'abc' }); // [ [ 'foo', 'abc' ] ]
let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}
let map = new Map(Object.entries({
one: 1,
two: 2,
}));
console.log(JSON.stringify([...map]));
字符串填充
String.prototype.padStart(maxLength, fillString=’ ‘) String.prototype.padEnd(maxLength, fillString=’ ’)
'x'.padStart(5, 'ab') //'ababx'
'x'.padStart(4, 'ab') //'abax'
'abcd'.padStart(2, '#')//'abcd'
'abc'.padStart(10, '0123456789') //'0123456abc'
函数参数列表与调用中的尾逗号
允许函数的最后一个参数有尾逗号
函数绑定
::
函数绑定运算符左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即 this 对象),绑定到右边的函数上面。用来取代 call、apply、bind 调用。 如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。返回的还是原对象,因此可以采用链式写法。
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
对象的扩展
- Rest解构赋值 : 对象的 Rest 解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
- 扩展运算符 : 扩展运算符(
...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。let z = { a: 3, b: 4 };let n = { ...z }; - Object.getOwnPropertyDescriptors() : 返回指定对象所有自身属性(非继承属性)的描述对象。主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
Class 的静态属性和实例属性
- 类的实例属性可以用等式,写入类的定义之中。以前,我们定义实例属性,只能写在类的 constructor 方法里面。
- 类的静态属性只要在上面的实例属性写法前面,加上 static 关键字就可以了。
修饰器(Decorator)
修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。
修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
Additional export-from statements
export v from ‘mod’;