ES2021特性全集(二)

594 阅读25分钟

本文是系列文章第二篇,也是最后一篇,更多信息请参考第一篇 ES2021特性全集(一)


22. 可索引集合

数组

数组创建方法

[element0, element1, ..., elementN]
new Array(element0, element1[, ...[, elementN]])
new Array(arrayLength)

静态属性

  • get Array[@@species]

静态方法

  • Array.from(arrayLike [, mapFn [, thisArg]]) 根据类数组迭代对象生成新的数组,相当于Array.from(obj).map(mapFn, thisArg)
  • Array.isArray(value) 判断参数是不是数组
  • Array.of(element0[, element1[, ...[, elementN]]]) 将参数添加到新的数组

实例属性

  • length 这个值是一个32位无符号整数,在0到2^32-1之间, 删除length会丢弃多余的元素

实例属性

迭代

  • arr.entries()
  • arr.keys(),返回下标,比如0,1,2
  • arr.values()
  • arr.every(callback(element[, index[, array]])[, thisArg]) 返回所有元素是不是都通过了回调函数的测试。回调函数一旦返回false就会停止,且不会对已删除或没赋值的元素测试
  • arr.filter(callback(element[, index, [array]])[, thisArg]) 创建一个新数组包含通过测试的元素
  • arr.forEach(callback(currentValue [, index [, array]])[, thisArg]) 对每个元素依次进行处理,无返回值
  • arr.map(function callback( currentValue[, index[, array]]) {// return element for new_array}[, thisArg]) 返回新的数组
  • arr.reduce(callback( accumulator, currentValue, [, index[, array]] )[, initialValue]) 对数组中的元素执行一个reducer函数,如果数组为空,没有初始值会报错;如果数组元素只有一个,没有初始值,回调不会执行。分别表示
    • initialValue 初始值
    • accumulator 累加器 如果没有初始值,累加器为第一个元素,当前值为第二个,否则累加器为初始值,当前值为第一个
    • currentValue 当前值
    • array
  • arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue]) 从右到左执行reducer 是否包含
  • arr.find(callback(element[, index[, array]])[, thisArg]) 返回第一个符合条件的元素,否则返回undefined
  • arr.findIndex(callback( element[, index[, array]] )[, thisArg]) 返回第一个符合条件的下标
  • arr.indexOf(searchElement[, fromIndex]) 从前往后查找返回第一个匹配的index
  • arr.lastIndexOf(searchElement[, fromIndex]) 从后往前查找返回第一个匹配的index
  • arr.includes(valueToFind[, fromIndex]) 是否包含,返回布尔值
  • arr.some(callback(element[, index[, array]])[, thisArg]) 只要一个通过测试就返回true 增删
  • arr.pop() 移除最后一个元素,返回那个元素,如果数组是空的则返回undefined
  • arr.push([element1[, ...[, elementN]]]) 从最后添加元素,返回数组长度
  • arr.unshift(element1[, ...[, elementN]]) 从前面添加元素,返回数组长度
  • arr.shift() 移除最前面添加元素,并返回移除得元素,数组为空返回undefined

排序

  • arr.reverse() 逆序
  • arr.sort(function(a, b) { return a - b; }); 从小到大排序 其他
  • const new_array = old_array.concat([value1[, value2[, ...[, valueN]]]]) 合并两个或多个数组,返回新的数组。参数可以是数组或值,如果为空,则返回数组的浅复制
  • arr.copyWithin(target[, start[, end]]) 浅复制数组的一部分到到相同数组的另外的位置,返回修改后的数组,分别表示
    • target 目标位置,如果是负的则从后数
    • start 源数据开始位置,负的从后数,默认为0
    • end 元数据结束为止,负的从后数,默认数组长度
  • arr.fill(value[, start[, end]]) 以一个固定值填充数组,返回修改后的数组,分别表示
    • value 用来填充的值
    • start 开始的位置,默认为0,负的从后数
    • end 结束位置,默认数组长度,负的从后数
  • arr.slice([start[, end]]) 浅复制数组指定范围(左闭右开)的元素到新数组
  • let arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, ...]]]]) 通过删除或替换元素来修改数组
  • const new_array = old_array.concat([value1[, value2[, ...[, valueN]]]]) 参数是数组或数值,如果是数组,两数组合并,如果是数值,会添加到数组中。 如果参数省略会返回一个已存数组的浅复制。
  • arr.join([separator]) 通过分隔符将数组元素拼接成字符串,分隔符默认逗号
  • arr.toString() 返回数组元素字符串

TypedArray

TypedArray是一种用来读写buffer中原始二进制数据的对象,主要是处理音视频和WebSockets等的原始数据。

js将读写buffer的过程分为两部分:一部分是ArrayBuffer实现的buufer,是一个固定长度的二进制数据块,不可以直接操作;第二部分是视图,可以按照不同格式对buffer进行操作,如下图。视图包括这里介绍的TypedArray和DataView,在各自部分详细介绍。

在js中有一个TypedArray构造函数,作为各种特定的类型化数组的[[prototype]],但是没有直接向用户暴露,根据特定数组每个元素的的类型,包括以下typedArray对象,一旦创建就会初始化为0

  • Int8Array 8位有符号整数的数组,范围是-128 to 127
  • Uint8Array 8位无符号整数的数组,范围是0 to 255,对元素按照ToUint8进行处理,即对于小数向下取整,对2**8取模求值
  • Uint8ClampedArray 8位无符号整数,范围是0 to 255,和Uint8Array的区别是溢出时按照ToUint8Clamp进行处理,即对小数四舍五入,溢出的值替换为就近的0或255。
  • Int16Array 16位二进制整数
  • Uint16Array 16位无符号整数
  • Int32Array 32位二进制整数
  • Uint32Array 32位无符号整数
  • Float32Array 32位浮点数
  • Float64Array 64位浮点数
  • BigInt64Array 64位有符号整数
  • BigUint64Array 64位无符号整数

因为TypedArray和数组类似,下面涉及到相关属性方法时就不再展开

构造函数

new TypedArray();
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

静态属性

  • TypedArray.BYTES_PER_ELEMENT 代表数组元素的字节长度,比如
Int8Array.BYTES_PER_ELEMENT;         // 1
  • TypedArray.name 代表构造函数名的字符串

静态方法

  • TypedArray.from(source[, mapFn[, thisArg]])
  • TypedArray.of(element0[, element1[, ...[, elementN]]])

实例属性

  • TypedArray.prototype.buffer 引用的ArrayBuffer
  • TypedArray.prototype.byteLength 字节长度
  • TypedArray.prototype.byteOffset 从ArrayBuffer开始的偏移量
  • TypedArray.prototype.length 元素个数

实例属性

mdn,和Array有些许区别,比如没有push方法。

23. 用键集合

Map

Map对象具有键值对,且会记住添加的顺序,其键和值可以是对象和原始值在内的所有类型、
键相等的判断使用sameValueZero算法,除了把NaN和NaN视为相等,其余和===一样。

Objects vs Maps

包括以下区别

  • 默认key:Map没有,除了通过Object.create(null)创建的对象,每个对象都会有原型,包含默认key
  • key类型,Map可以是任何值,Object只能是字符串或symbol
  • key顺序,Map按照添加顺序,对于Object虽然现在是有序的,但是以前版本是无序的,且排序复杂不建议依赖属性属性
  • 大小,Map包含的数量可以通过size属性获取,Object需要手动获取
  • 可迭代性,Map可以迭代,Object没有实现迭代因此不能使用for...of,
  • 性能,Map对于频繁添加删除有优化,Object没有优化

构造函数

new Map([iterable])

实例属性

  • size 返回键值对的数目

实例方法

  • myMap.clear(); 移除所有元素
  • myMap.delete(key) 移除特定元素
  • myMap.get(key) 获取特定元素
  • myMap.has(key) 判断是不是含有某元素
  • myMap.set(key, value) 设置元素 迭代
  • myMap.keys()
  • myMap.values()
  • myMap.entries()
  • myMap.forEach(callback([value][, key][, map])[, thisArg]),比如
function logMapElements(value, key, map) {
    console.log(`map.get('${key}') = ${value}`)
}
new Map([['foo', 3], ['bar', {}], ['baz', undefined]]).forEach(logMapElements)
// logs:
// "map.get('foo') = 3"
// "map.get('bar') = [object Object]"
// "map.get('baz') = undefined"

Set

可以用来保存任何独一无二的元素,两个元素相等性和Map判断方法一致。

构造函数

new Set([iterable])

实例属性和实例方法

除了添加(Map为set方法,set为add)和访问(Map为get,Set没有)外其他和Map用法相同

WeakMap

也是键值对的集合,但是键只能是对象,值不限,键对象的引用是弱引用,即如果没有其他引用指向这个对象,这个对象会被辣鸡回收(garbage collection),其api和Map相同。
和Map的另一个区别是其键是不可枚举的,不可以获取键的列表(没有clear和其他迭代方法)。键是否存在取决于有没有被回收。如果想往对象上添加数据又不想干扰垃圾回收就可以使用WeakMap
一个使用场景是保存为对象保存私有数据或者隐藏实现细节,参考Hiding Implementation Details with ECMAScript 6 WeakMaps

const privates = new WeakMap();

function Public() {
  const me = {
    // Private data goes here
  };
  privates.set(this, me);
}

Public.prototype.method = function () {
  const me = privates.get(this);
  // Do stuff with private data in `me`...
};

module.exports = Public;

这样函数的私有数据和方法都保存在私有的WeakMap中,模块外界无法访问,Symbol同样也可以实现此效果,当然也可以通过闭包实现,但是会影响垃圾回收

function Public() {
  const closedOverPrivate = "foo";
  this.method = function () {
    // Do stuff with `closedOverPrivate`...
  };
}

// Or

function makePublic() {
  const closedOverPrivate = "foo";
  return {
    method: function () {
      // Do stuff with `closedOverPrivate`...
    }
  };
}

WeakSet

用来存储弱引用的对象(比如dom对象),且不可迭代,有add,delete和has三个实例方法。

24. 结构化数据

ArrayBuffer

用来代表一个通用的、固定长度的二进制buffer,其中buffer是指缓冲区,比如视频播放的缓冲,网络获取的数据和本地文件也可以用buffer表示。
它就是一个二进制数组,存放二进制数字,我们不能直接操作其中的内容,而是需要创建一个TypedArray或DateView来以一定格式读写buffer的内容。

构造函数

new ArrayBuffer(length)

创建一个特定长度的arrayBuffer对象,内容初始化为1,最长不能超过最大安全数字

  • ArrayBuffer.isView(value) 检查参数是不是一个arrayBuffer视图

实例属性

  • arraybuffer.byteLength 获取长度

实例方法

  • arraybuffer.slice(begin[, end]) 复制指定arraybuffer的指定位置到一个新arrayBuffer

SharedArrayBuffer

和ArrayBuffer类似,但是能用来为共享内存创建视图,且不能像ArrayBuffer一样被分开处理。 同一个cluster中的不同agent为了使用SharedArrayBuffer进行共享内存,需要用到postMessagestructured cloning算法,后者接受SharedArrayBuffers和映射到SharedArrayBuffers的TypedArrays,这两种情况,SharedArrayBuffers被传递到接收方生成一个新的私有的SharedArrayBuffers对象,但是两个SharedArrayBuffers引用同一个数据块,其中一个改变另一个也可见

var sab = new SharedArrayBuffer(1024);
worker.postMessage(sab);

同步操作共享内存是需要使用原子操作。 包含一个实例方法slice(用来复制部分内容生成另一个SharedArrayBuffer)和一个实例属性byteLength(表示字节长度),具体使用可参考Atomics一节。

DataView

可以从ArrayBuffer对象中读写多种数值类型的底层接口,使用时可以不用考虑不同平台的字节序问题。

字节序

字节序(endianness或byte-order)指的计算机怎么组织字节组成数字。
内存的存储单位是字节(8位),如果要存储大于8位的数字需要不止一个字节,大部分场景使用的是小字节序,即高位的字节在后,大字节序相反,比如存储0x12345678

  • 低字节序 0x78 0x56 0x34 0x12
  • 高字节序 0x12 0x34 0x56 0x78

构造函数

new DataView(buffer [, byteOffset [, byteLength]])

其中buffer指用来操作的buffer,比如ArrayBuffer or SharedArrayBuffer,返回一个视图。

实例属性

  • dataview.buffer 引用的buffer对象
  • dataview.byteLength 字节长度
  • dataview.byteOffset 视图从buffer开始的偏移值

实例方法

DataView对象可以由很多访问和操作方法,其中访问方法包含一个参数,偏移值;写方法包含两个参数,偏移值和写入的值。
不同的读写方法表示读写不同类型的数值,比如

// create an ArrayBuffer with a size in bytes
const buffer = new ArrayBuffer(16);

const view = new DataView(buffer);
view.setInt8(1, 127); // (max signed 8-bit integer)

console.log(view.getInt8(1));
// expected output: 127

Atomics

原子对象用其静态方法对SharedArrayBuffer和 ArrayBuffer进行原子性操作,类似于Math,只能用其静态方法,不能对其实例化

原子操作

当内容被共享时,多个线程可以读写内存中的相同数据,原子操作可以保证值读写的可预测性,这些操作在完成之前不会被打断,且完成后才能进行下一次操作。

Wait and notify

wait() 和 notify() 方法采用的是 Linux 上的 futexes 模型(fast user-space mutex),可以让进程一直等待直到某个特定的条件为真,主要用于实现阻塞。 通过wait将线程睡眠。然后通过notify唤醒,主要不要试图睡眠主线程。操作的内存只能是Int32Array

  • Atomics.wait(typedArray, index, value[, timeout]) 指定位置值不变时会休眠,直到被notify唤醒或者超时,返回值为 "ok", "not-equal", 或 "timed-out" 三种之一。当指定位置值变化时会收到写入线程的通知
  • Atomics.notify(typedArray, index, count) 在修改休眠进程指定位置后,用于通知休眠进程

比如在worker线程休眠

//worker.js
self.addEventListener('message', function (e) {
  const int32 = e.data
  console.log('You said: ' + int32);
  Atomics.wait(int32, 0, 0);
  console.log('...........')
}, false);

在主线程修改然后通知

//index.js
var sab = new SharedArrayBuffer(1024);
var int32 = new Int32Array(sab);
console.log(int32[0]);

const worker = new Worker('scripts/worker.js')
console.log(worker)

worker.postMessage(int32);

setTimeout(() => {
  Atomics.store(int32, 0, 123);
  Atomics.notify(int32, 0, 1);
},1500)

静态方法

对typedArray指定位置上的值和给定值进行特定操作

  • Atomics.add(typedArray, index, value) 相加
  • Atomics.sub(typedArray, index, value) 相减
  • Atomics.and(typedArray, index, value) AND位操作
  • Atomics.or(typedArray, index, value) OR位操作
  • Atomics.xor(typedArray, index, value) XOR位操作
  • Atomics.compareExchange(typedArray, index, expectedValue, replacementValue) 如果expectedValue和当前值相等则替换为replacementValue,否则不变
  • Atomics.exchange(typedArray, index, value) 替换为新值,返回旧值
  • Atomics.isLockFree(size) 判断给定的size可不可以使用锁或者原子操作,即是不是以下值
Int8Array.BYTES_PER_ELEMENT;         // 1
Uint8Array.BYTES_PER_ELEMENT;        // 1
Uint8ClampedArray.BYTES_PER_ELEMENT; // 1
Int16Array.BYTES_PER_ELEMENT;        // 2
Uint16Array.BYTES_PER_ELEMENT;       // 2
Int32Array.BYTES_PER_ELEMENT;        // 4
Uint32Array.BYTES_PER_ELEMENT;       // 4
Float32Array.BYTES_PER_ELEMENT;      // 4
Float64Array.BYTES_PER_ELEMENT;      // 8
  • Atomics.load(typedArray, index) 获取指定位置上的值
  • Atomics.store(typedArray, index, value) 在指定位置保存值 返回新值

JSON

这部分主要讲三个问题,一个是对JSON的介绍,和JSON对象上的两个静态方法

JSON介绍

JSON (JavaScript Object Notation) 是一个轻量的数据交换格式,用来序列化对象、数组、数字、字符串、布尔值和null,基于js语法,但又不一样,包括以下

  • 对象和数组,属性名要用双引号
  • 数字,不能0开头,有小数点的话后面至少跟着一位数,NaN和infinity不支持
  • 任何json文本都是有效的js表达式

JSON.parse(text[, reviver])

将字符串转化为json对象
可选参数reviver函数用于返回前做转换,解析值和它所包含的所有属性会从里到外依次调用reviver函数,调用过程中this是指当前属性所属的对象,当前的属性名和属性值会作为第一、第二个参数传入,如果返回undefined,当前属性会被删除,如果返回其他值,则会成为该属性新的值。当到最顶层也就是最后一次调用时,属性名为空字符串,如

JSON.parse('{"p": 5}', function (k, v) {
    if(k === '') return v;     // 如果到了最顶层,则直接返回属性值,
    return v * 2;              // 否则将属性值变为原来的 2 倍。
});                            // { p: 10 }

JSON.parse('{"1": 1, "2": 2,"3": {"4": 4, "5": {"6": 6}}}', function (k, v) {
    console.log(k); // 输出当前的属性名,从而得知遍历顺序是从内向外的,
                    // 最后一个属性名会是个空字符串。
    return v;       // 返回原始属性值,相当于没有传递 reviver 参数。
});

// 1
// 2
// 4
// 6
// 5
// 3
// ""

JSON.stringify(value[, replacer[, space]])

转换一个对象或者值到json字符串,如果包含循环引用或者bigInt会报错
replacer参数可以是一个函数或数组。

当作为函数时有两个参数:被字符串化对象的key和value,其中的this是key所属的对象。首先该函数会被空字符串作为key的对象调用,然后再被每个实际对象调用,返回值按以下

  • 如果返回的是number,string,boolean,null则会直接返回
  • 如果是Function,Symbol,undefined,改属性会被忽略(对于数组返回undefined会被转化为null)
  • 如果返回其他的对象则会递归调用replacer函数
function replacer(key, value) {
  // Filtering out properties
  if (typeof value === 'string') {
    return undefined;
  }
  return value;
}

var foo = {foundation: 'Mozilla', model: 'box', week: 45, transport: 'car', month: 7};
JSON.stringify(foo, replacer);
// '{"week":45,"month":7}'

当作为数组时,数组中包含的值会包含在结果中

JSON.stringify(foo, ['week', 'month']);
// '{"week":45,"month":7}', only keep "week" and "month" properties

space参数用来控制返回值缩进的空格数或值,可以为字符串或者数字
当作为字符串时,缩进会被字符串替代

JSON.stringify({ a: 2 }, null, 'ee')
//结果
"{
ee"a": 2
}"

当作为数字,缩进是对应个数的空格,最多是10

字符串化过程

当对value进行字符串化时按以下步骤

  • 如果value有一个toJSON方法(包括嵌套对象中的方法),则按其定义返回.该方法的参数是
    • 如果这个对象是一个属性值,则参数是属性名
    • 如果是一个数组,数组序号当做字符串做参数
    • 如果直接在这个对象上调用,则是空字符串
var obj = {
    data: 'data',

    toJSON (key) {
        if (key)
            return `Now I am a nested object under key '${key}'`;
        else
            return this;
    }
};

JSON.stringify(obj);
// '{"data":"data"}',此时直接在对象上调用,key是空字符串

JSON.stringify({ obj }); // Shorthand property names (ES2015).
// '{"obj":"Now I am a nested object under key 'obj'"}'
// 对象作为属性
JSON.stringify([ obj ]);
// '["Now I am a nested object under key '0'"]'
//对象作为数组元素
  • 布尔、数字和字符串对象会转化为对应的原始值
  • undefined,function和symbol等无效值在对象中被忽略,在数组中转为null
  • symbol做属性名的属性会被忽略
  • Date实例由于实现了返回字符串的toJSON(等同于date.toISOString()),因此会被作为字符串处理
  • 其他对象的可枚举属性会被序列化

25. 内存管理

本文分三部分,概述和介绍两个相关对象WeakRef和FinalizationRegistry

概述

js中没有手动管理内存的方式,会在对象创建时分配内存,不再使用时释放内存(garbage collection)。

内存生命周期

每种语言内存的生命周期是一样的,分为分配内存,读写内存,释放内存。

  • js中的内存分配
    • 值的初始化 当值初始化声明时自动分配
    var n = 123; // 为一个数字分配内存
    var s = 'azerty'; // 为字符串分配内存
    
    var o = {
      a: 1,
      b: null
    }; //为对象和包含的值分配内存
    
    // 和一般对象一样为数组及其包含的值分配内存
    var a = [1, null, 'abra'];
    
    function f(a) {
      return a + 2;
    } //为函数对象分配内存
    
    
    • 通过函数调用分配内存
    var d = new Date(); //为Date对象分配内存
    var e = document.createElement('div'); //分配一个dom元素
    //还有其他一些方法,比如下面的substr
    var s = 'azerty';
    var s2 = s.substr(0, 3);
    
  • 使用值 就是在分配的内存读写
  • 当内存不再需要时释放内存。内存管理出现的问题主要出现在这个阶段,这个阶段最困难的部分就是判断分配的内存什么时候不再需要。低级语言(比如c)需要用户手动指出分配的内存什么时候不再需要并且释放它。像js一样的高级语言利用一种叫做garbage collection (GC)的内存管理方式,用来监测内存分配并确定何时释放。这种处理是一种近似行为,因为很难直接计算出来。

garbage collection

为了理解垃圾回收先理解几个概念

  • 引用 是垃圾回收算法主要的概念,如果一个对象可以访问(显式或隐式)另一个对象,则称是对另一个对象的引用,比如比如一个对象隐式引用它的[[prototype]]以及直接引用它的属性
  • 引用计数(Reference-counting) 用于最初的垃圾回收算法,这个算法将对象是不是被使用简化为是否有其他对象引用,如果一个对象没有其他对象引用则被称为垃圾,比如
//两个对象被创建,内层的对象作为外层的对象的属性被引用
//外层的对象被变量x引用,很明显都不可以被内存回收
var x = {
  a: {
    b: 2
  }
};


//y变量是对外层对象的第二个引用
var y = x;     

//现在x被赋值为1,外层对象的引用只剩一个y
x = 1;          

//内层对象赋值为z,此时内层对象有两个引用,外层对象和z
var z = y.a;   

//现在外层对象的引用变成了0,被垃圾回收,但是内层对象被z引用,不能被垃圾回收
y = 'mozilla';  
//对内层对象的引用也有变成了一个,被垃圾回收
z = null;       

这种方式有个局限性,即循环引用,在下面的例子中,两个对象循环引用,当所在的函数调用结束,这两个对象已经不需要了,但是由于引用数不能被回收,循环引用是内存泄漏的常见原因

function f() {
  var x = {};
  var y = {};
  x.a = y;        // x references y
  y.a = x;        // y references x

  return 'azerty';
}

f();
  • 标记清除算法(Mark-and-sweep algorithm),这种算法将对象不再需要简化为一个对象不能访问。这个算法假设有一个叫root的对象,在js中是全局对象。垃圾回收器定期从root对象开始找出所有引用(直接或间接)的对象,然后找出不能访问的对象被垃圾回收。0引用也是有效的无法访问,循环引用也能有效处理。在2012年所有浏览器都使用了标记清除算法。

WeakRef

一个WeakRef对象包含对另一个对象的弱引用,后者被称为target或referent。对一个对象的弱引用不会阻止对象被垃圾回收,对应的一个强引用或者普通引用会保存在内存中。
不同js引擎对垃圾回收实现不太一样,应谨慎使用

构造函数

new WeakRef(targetObject);

参数是被弱引用的对象

实例方法

  • obj = ref.deref() 返回引用的对象,如果被回收则返回undefined

FinalizationRegistry

当一个注册的对象被清理时执行回调

构造函数

new FinalizationRegistry([callback]);

利用回调参数创建一个对象

实例方法

包含register和unregister两个实例方法,分别用于注册和取消注册

  • registry.register(target, heldValue, [unregisterToken]) 其中target是注册的对象,heldvalue是传给回调的参数,unregisterToken是用来取消注册的令牌
const registry = new FinalizationRegistry(heldValue => {
  // ....
});
registry.register(theObject, "some value",unregisterToken);
registry.unregister(unregisterToken);

26. 控制抽象对象

可用来结构化代码,特别是异步代码,比如不需要深嵌套

Iteration

Iteration并不是一个内置对象也不是一个新语法,而是一种协议,可以被任何对象遵循某些约定来实现。包括以下两个协议

可迭代协议

可迭代协议(The iterable protocol)可以让js对象自定义迭代行为来被for...in等循环使用。Array等是内置可迭代的,Object不是。
为了可迭代,一个对象要实现@@iterator方法,这意味着这个对象一定要有@@iterator属性,这个属性用Symbol.iterator常量访问。当调用该方法时不需要参数并返回迭代器。

迭代器协议

迭代器协议(The iterator protocol)定义了一种产生一系列值的标准方法。
当一个对象按照下面的语义实现next()方法时才是一个迭代器 next()是一个无参数函数,返回一个应拥有以下两个属性的对象

  • done 布尔值,如果迭代器可以产生下一个值则为false,否则返回true,此时下一个value值可选
  • value 任何类型

比如

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]

Promise

一个promise是一个创建时不一定已知的值的代理,可以用来将异步操作的成功值和失败原因与相关的处理相关联。这可以让异步方法像同步方法一样返回一个值,不同的是不是返回一个终值而是返回一个将来某个时间 提供值的promise.
一个promise会处于以下三种状态

  • pending 初始状态,既没fullfilled也没rejected
  • fulfilled 意味着操作成功完成
  • rejected 意味着操作失败

一个pending的promise要么被fulfilled要么被rejected,当它们发生时,用then方法排队的相关处理程序就会被执行。因为then方法和catch方法返回promise,因此可以链式调用

构造函数

用来包装返回值不是promise的函数

new Promise(executor)

其中executor函数会在实例化过程被执行,其包含resolve和reject两个函数参数,可以在其中进行一些异步操作,如果操作成功则执行resolve,失败则执行reject,如果executor函数出错也会被reject。

const myFirstPromise = new Promise((resolve, reject) => {
  // do something asynchronous which eventually calls either:
  //
  //   resolve(someValue)        // fulfilled
  // or
  //   reject("failure reason")  // rejected
});

静态方法

  • Promise.all(iterable) 参数是一个迭代对象,用于集体处理多个promise的返回结果.返回值是
    • 如果参数为空,返回值是一个resolved的promise,成功值为空
    • 如果迭代器中不包含promise,则是返回一个resolved的promise,成功值为迭代器各个元素组成的数组
    • 其他情况就是一个pending的promise,只要迭代器中有一个promise rejected就会被reject(失败原因是rejected的这个promise的原因)或所有resolved就会被resolve(成功值同上一条)
  • Promise.allSettled(iterable) 也是用于处理多个promise的返回结果,不管迭代器中的promise成功还是失败,都需要等到结束一起返回,返回的结果是各个promise的结果
Promise.allSettled([
  Promise.resolve(33),
  new Promise(resolve => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error('an error'))
])
.then(values => console.log(values));

// [
//   {status: "fulfilled", value: 33},
//   {status: "fulfilled", value: 66},
//   {status: "fulfilled", value: 99},
//   {status: "rejected",  reason: Error: an error}
// ]
  • Promise.any(iterable) 是all方法的相反操作,一个成功即返回成功,所有失败才算失败
  • Promise.race(iterable) 以第一个返回结果的promise为准
  • Promise.reject(reason) 返回一个带有参数原因被reject的promise
  • Promise.resolve(value) 返回一个带有参数值的被resolve的promise

实例方法

  • p.catch(onRejected) 参数将在p被reject时调用,参数是原因。该方法会返回一个promise,当回调返回一个reject的promise或者throw error时,.catch返回的promise会是rejected,不然就是resolved
  • p.then(onFulfilled[, onRejected]) 包含成功失败两个回调
  • p.finally(onFinally) 无论结果如何回调函数都会被调用

Generator

Generator对象是通过调用generator函数返回的,遵守可迭代协议和迭代器协议

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = generator(); // "Generator { }"

console.log(gen.next().value); // 1
console.log(generator().next().value); // 1
console.log(generator().next().value); // 1

生成器函数

生成器函数(generator function)用来生成迭代器,使用function*语法编写,最初调用时返回一个迭代器,通过调用迭代器上的next方法来执行生成器函数,直到遇到yield关键字。每次调用都会返回一个新的迭代器,每个迭代器只能使用迭代一次。
其中用来使生成器函数执行暂停的yield语法为

[rv] = yield [expression];

yield后面的表达式的值返回给生成器的调用者,yield返回的是一个含有value和done属性的对象,value指的是前面提的表达式,done为表示生成器是否执行完毕的布尔值
其中rv指的是迭代器next方法调用时传递的参数,比如

function* counter(value) {
 let step;

 while (true) {
   step = yield ++value;

   if (step) {
     value += step;
   }
 }
}

const generatorFunc = counter(0);
console.log(generatorFunc.next().value);   // 1
console.log(generatorFunc.next().value);   // 2
console.log(generatorFunc.next().value);   // 3
console.log(generatorFunc.next(10).value); // 14
console.log(generatorFunc.next().value);   // 15
console.log(generatorFunc.next(10).value); // 26

实例方法

  • gen.next(value) 返回yield返回的对象,参数value会作为yield的真正返回值赋值给上面说的rv
  • gen.return(value) 返回给定的value,并结束迭代器,返回{ value: value, done: true }
  • gen.throw(exception) 抛错,用来debug

GeneratorFunction

生成器函数( generator function)的构造函数,具体使用参考生成器函数

new GeneratorFunction ([arg1[, arg2[, ...argN]],] functionBody)

AsyncFunction

异步函数(async function)的构造函数

new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)

这里对异步函数的用法展开做一下说明。
异步函数是使用async关键字声明的函数,在其中使用await关键字用来暂停异步函数,直到其等待的promise 产生结果,注意await只能接收resolve的结果,对于reject的情况需要使用try...catch捕获。

//异步函数
async function name([param[, param[, ...param]]]) {
   statements
}
//yield关键字
[rv] = await expression;

27. 反射

Reflect

反射(Reflect)对象提供了拦截js操作的方法,这些方法和后面要讲的proxy handlers相同,注意我们使用的是它的静态方法。
其中涉及到的方法在Object上有部分重合,对比详情参考Comparing Reflect and Object methods

静态方法

  • Reflect.apply(target, thisArgument, argumentsList) 类似Function.prototype.apply()
  • Reflect.construct(target, argumentsList[, newTarget]) 类似new target(...args)
  • Reflect.defineProperty(target, propertyKey, attributes) 类似Object.defineProperty()
  • Reflect.deleteProperty(target, propertyKey) 类似delete target[name]
  • Reflect.get(target, propertyKey[, receiver]) 类似target[name]
  • Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似Object.getOwnPropertyDescriptor()
  • Reflect.getPrototypeOf(target) 类似Object.getPrototypeOf()
  • Reflect.has(target, propertyKey) 类似in操作符
  • Reflect.isExtensible(target) 类似Object.isExtensible()
  • Reflect.ownKeys(target) 类似 Object.keys(),但不受可枚举属性影响
  • Reflect.preventExtensions(target) 类似Object.preventExtensions()
  • Reflect.set(target, propertyKey, value[, receiver]) 类似属性访问器语法
  • Reflect.setPrototypeOf(target, prototype) 类似Object.setPrototypeOf()

Proxy

用来创建一个对象的代理,从而实现对对象操作的拦截和自定义

const p = new Proxy(target, handler)

其中target是被代理的对象,handler是一个包含对各种操作的代理回调,其中handler的键值对是Reflect的各种静态方法,比如

const target = {
  notProxied: "original value",
  proxied: "original value"
};

const handler = {
  get: function(target, prop, receiver) {
    if (prop === "proxied") {
      return "replaced value";
    }
    return Reflect.get(...arguments);
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.notProxied); // "original value"
console.log(proxy.proxied);    // "replaced value"

28. 内存模型

内存一致性模型(The memory consistency model)或者内存模型(memory model)指定共享数据块事件的先后顺序,这些事件是通过 SharedArrayBuffer支持的TypedArray实例访问和Atomics对象产生的。当数据没有data race时对同一块内存的操作顺序(在不同环境下)保证一致,但是出现date race时,由于编译器转换和cpu设计等造成实际执行的和预想的不一样。内存模型就是对这种场景的解决方案。

关于js的内存一致性模型相关资料较少,这里暂时无法做过多解读,暂时参考Consistency modelMemory Consistency Models: A Tutorial了解一下和内存一致性模型有关的东西。

结语

到这里ECMAscript specs已经过了一遍了,接下来可以进行查漏补缺或者看接下来的内容,