ECMAScript Apis 内部实现伪代码

原文地址(原文会一直不断更新): https://blog.gcl666.com/2019/07/08/ecma_pseudo_code/

简介

本文将维护且不断更新 ECMAScript 标准下一些常见,或最新的 API 或功能实现伪码。

参考地址: https://tc39.es/ecma262/#sec-generator-objects

可直接在版本一章 7 找到本文中所有 Api 。

本文约定:

约定内容 描述 示例
^{n-year} 表示该 API 发布的版本及年份 比如:ES6 是 ECMASCript 2015 代表 {6-2015}
^{Runtime} 表示那些在运行时所作的一些抽象操作伪码 比如: ObjectDefineProperties3.3

Array

Array.prototype.indexOf(searchElement[, fromIndex])

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
function indexOf(searchElement, fromIndex) {
let O = ToObject(this);
let len = LengthOfArrayLike(O);
if (len === 0) return -1;

let n = ToInteger(fromIndex);
if (fromIndex === undefined) n = 0
if (n >= len) return -1;

let k = 0
if (n >= 0) {
if (n/0 === -Infinity) {
k = +0;
} else {
k = n;
}
} else {
k = len + n;
if (k < 0) k = 0;
}

while (k < len) {
let kPresent = HasProperty(O, ToString(k));
if (kPresent) {
let elementK = Get(O, ToString(k));
// 这里直接用的恒等式比较,简单粗暴
let same = searchElement === elementK;
if (same) return k;
}
k++;
}

return -1;
}

Array.prototype.includes(val[, startIndex])7-2016

检测元素存在性,返回值类型 boolean

实现里面的零值比较:SameValueZero(x, y)

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
function includes(searchElement, fromIndex) {
let O = ToObject(this);

let len = LengthOfArrayLike(O);

if (len === 0) {
return false;
}

let n = ToInteger(fromIndex);

if (fromIndex === undefined) n = 0

let k = 0
if (n >= 0) {
// 查找起始位置
k = n;
} else {
k = len + n;
if (k < 0) k = 0;
}

while (k < len) {
let elmentK = Get(O, ToString(k));
if (SameValueZero(searchElement, elementK)) return true
k++;
}

// 没找到
return false;
}

Array.prototype.copyWithin(target, start[, end])6-2015

作用 : 从数组中拷贝 [start, end) 的元素,使用这些元素去替换 [target, length)
的元素值,替换起始位置由 start<=>target 一一对应,替换结束位置由两者最小长度值
决定(Math.min(end - start, length - target))。

参数 : target, start, end 无论哪个为负值,都会被当做 length + target/start/end
得到正值之后处理。

伪码

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
function copyWithin(target, start[, end]) {
let O = ToObject(this);

let len = ToLength(Get(O, 'length'));
let relativeTarget = ToInteger(target);

// 替换区间的起始位置
let to = relativeTarget < 0
? max(len + relativeTarget, 0)
: min(relativeTarget, len);

let relativeStart = ToInteger(start);

// 拷贝区间的起始位置
let from = relativeStart < 0
? max(len + relativeStart, 0)
: min(relativeStart, len);

let relativeEnd = undefined(end)
? len
: ToInteger(end);

// 拷贝区间的结束位置
let nfinal = relativeEnd < 0
? max(len + relativeEnd, 0)
: min(relativeEnd, len);

// 替换区间的长度,取两个区间(拷贝区间和替换区间)的大小最小值
let count = min(nfinal - from, len - to)

let direction;
if (from < to && to < from + count) {
// 替换区间的起始位置在拷贝区间内
// 存在重叠区,防止值丢失,所以选择从后往前覆盖
direction = -1;
from = from + count - 1;
to = to + count - 1;
} else {
direction = 1
}

while (count > 0) {
let fromKey = ToString(from);
let toKey = ToString(to);
let fromPresent = HasProperty(O, fromKey);
let fromVal;
if (fromPresent) {
fromVal = Get(O, fromKey);
Set(O, toKey, fromVal, true);
} else {
DeletePropertyOrThrow(O, toKey);
}

from = from + direction;
to = to + direction;
count--;
}

return O;
}

关键代码

这里的作用是当替换区域的起始位置在拷贝区域的区间之内的时候,需要从后往前拷贝,因
为从前往后拷贝,会导致值丢失问题。

1
2
3
4
5
6
7
8
9
if (from < to && to < from + count) {
// 替换区间的起始位置在拷贝区间内
// 存在重叠区,防止值丢失,所以选择从后往前覆盖
direction = -1;
from = from + count - 1;
to = to + count - 1;
} else {
direction = 1
}

如图:

img

Array.prototype.fill(value[, start[, end]])6-2015

  1. 处理 start 和 end 参数,合法化,默认值,上下限
  2. 遍历赋值
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
function fill(value[, start[, end]]) {
// start, end 如果是负数会进行处理: length + start 得到最后位置值

let O = ToObject(this);
let len = ToLength(Get(O, 'length'));

let relativeStart = ToInterger(start);

let k = 0
if (relativeStart < 0) {
// 防下限
k = max(relativeStart + len, 0);
} else {
// 防溢出
k = min(relativeStart, len);
}

let relativeEnd = undefined(end) ? len : ToInterger(end);

let ifinal = 0
if (relativeEnd < 0) {
ifinal = max(len + relativeEnd, 0);
} else {
ifnal = min(relativeEnd, len);
}

// 起始位置 < 结束位置
while (k < ifinal) {
let Pk = ToString(k);

Set(O, Pk, value, true);

k++;
}

return O;
}

实例:

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
let nums = [1, 2, 3, 4];

nums.fill(1); // 1, 1, 1, 1
console.log(nums.toString());

nums.fill(2, 1); // 1,2,2,2
console.log(nums.toString());

nums.fill(3, 2, 4); // 1,2,3,3
console.log(nums.toString());

// 负数,len + (-1) = 3 => fill(1, 3);
nums.fill(1, -1); // 1,2,3,1
console.log(nums.toString());

// 负数,len + (-2) = 2 => fill(1, 2);
nums.fill(1, -2); // 1,2,1,1
console.log(nums.toString());

// 负数,start: len + -2 = 2 => fill(1, 2, 1)
// 2 < 1 => start < end => 无效
nums.fill(1, -2, 1); // 1,2,1,1
console.log(nums.toString());

// start: len + -2 = 2
// end: len + -1 = 3
// => fill(1, 2, 3)
nums.fill(4, -2, -1); // 1,2,4,1
console.log(nums.toString());

+RESULTS:

1,1,1,1
1,2,2,2
1,2,3,3
1,2,3,1
1,2,1,1
1,2,1,1
1,2,4,1

Array.prototype.find(predicate[, thisArg])6-2015

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function find(predicate[, thisArg]) {
let O = ToObject(this);

let len = ToLength(Get(O, 'length'));

if (! IsCallable(predicate)) throw new TypeError()

let T = thisArg || undefined;

let k = 0;

while (k < len) {
let Pk = ToString(k);
let kValue = Get(O, Pk);
let testResult = ToBoolean(Call(predicate, T, <kValue, k, O>));

if (testResult) return kValue;

k++;
}

return undefined;
}

Array.prototype.findIndex(predicate[, thisArg])6-2015

findIndexfind 实现一样,无法就是找到元素的时候返回的是 k 索引值,而不
是元素值 kValue ,以及如果没找到返回的会是 -1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function find(predicate[, thisArg]) {
let O = ToObject(this);

let len = ToLength(Get(O, 'length'));

if (! IsCallable(predicate)) throw new TypeError()

let T = thisArg || undefined;

let k = 0;

while (k < len) {
let Pk = ToString(k);
let kValue = Get(O, Pk);
let testResult = ToBoolean(Call(predicate, T, <kValue, k, O>));

if (testResult) return k;

k++;
}

return -1;
}

Array.prototype.slice([start[, end]])

start 或 end 为负数的情况, len + negVal 是最后的索引值

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
function slice() {
let O = ToObject(this);

let len = ToLength(Get(O, 'length'));

let relativeStart = ToInterger(start);
let k = 0;

if (relativeStart < 0) {
// 起始位置为负数情况
k = max((len + relativeStart), 0)
} else {
// 防止起始位置超出数组长度
k = min(relativeStart, len)
}

let relativeEnd = 0
if (end === undefined) {
relativeEnd = len;
} else {
relativeEnd = ToInterger(end);
}

let ifinal = 0
if (relativeEnd < 0) {
// 结束位置为负数,取 len - |relativeEnd| 的值为结束位置
ifinal = max((len + relativeEnd), 0);
} else {
ifinal = min(relativeEnd, len);
}

// 要取的个数
let count = max(ifinal - k, 0);

// 创建一个新的数组
let A = ArraySpeciesCreate(O, count);

let n = 0;

// 起始位置 k,结束位置 ifinal
let kValue, kPresent, Pk, n = 0;
while (k < ifinal) {
Pk = ToString(k);
kPresent = HasProperty(O, Pk);
if (kPresent) {
kValue = Get(O, Pk);
// 有效元素添加到新数组 A
CreateDataPropertyOrThrow(A, ToString(n), kValue);
}

k++;
n++; // 新数组 A 的长度
}

Set(A, 'length', n, true);

return A;
}

Array.of(…items)6-2015

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
Array.of = function(...items) {
let len = items.length;
let C = this
let A = null

// 构建长度 len 的空数组
if (IsConstructor(C)) {
A = Construct(C, len)
} else {
A = ArrayCreate(len)
}

let k = 0
while (k < len) {
let kValue = items[k];
let Pk = ToString(k);

// 定义数组数字那个
CreateDataPropertyOrThrow(A, Pk, kValue);

k++;
}

// 设置长度属性
Set(A, 'length', len, true).

return A;
}
  1. 构建长度为传递参数的个数的数组 A
  2. 定义数组下标和对应的值(注意这里 Pk 是个字符串,即数组下标内部为字符串存储)
  3. 设置长度属性

Array.from(items[, mapfn[, thisarg]])6-2015

参数列表:

  • items 类数组对象
  • mapFn optional , 对每个元素处理返回结果
  • thisArg mapFn 函数内部的 this 指向

实现:

  1. items 的迭代器,如果没有说明是普通类型对象,而非列表型或自带迭代器的类型。
  2. 如果有自己的迭代器,直接使用 iterator 遍历,取 key 取 value ,保存到 A
    数组。
  3. 如果没有自己的迭代器,就使用简单的循环取索引值为数字的值添加到数组 A 中。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
Array.from = function(items[, mapFn[, thisArg]]) {
let C = this;

let mapping = true, T, A;
if (mapFn === undefined) {
mapping = false;
} else {
if (! IsCallable(mapFn)) {
throw new TypeError('mapFn 必须是有效的函数类型。')
}

T = thisArg || undefined;
mapping = true;
}

// 取出类数组对象的迭代器,将用来取出有效的数组元素
let usingIterator = GetMethod(items, @@iterator);

// 列表类型,有自己的迭代器
if (usingIterator) {
// 构建空数组 A
if (IsConstructor(C)) {
A = Constructor(C)
} else {
A = ArrayCreate(0);
}

// 取出同步迭代器
let iteratorRecord = GetIterator(items, sync, usingIterator);

let k = 0, error;

while(1) {
if (k >= Math.pow(2, 53) - 1) {
error = ThrowCompletion(new TypeError('溢出'));
// 结束迭代过程
return IteratorClose(iteratorRecord, error);
}

let Pk = ToString(k);
// 调用 iterator.next() 启动迭代器,取下一个 yield 值
let next = IteratorStep(iteratorRecord);

if (! next) {
// 没有元素了,设置长度结束
Set(A, 'length', k, true)
return A;
}

// 取出当前迭代 { value: xxx, done: false } 中的 value 值
let nextValue = IteratorValue(next);

if (mapping) {
// 将值交给 mapFn 处理得到结果覆盖原来的 nextValue 值
let mappedValue = Call(mapFn, T, <nextValue, k>);
if (AbruptCompletion(mappedValue)) {
// 异常,或退出值,中断迭代过程
return IteratorClose(iteratorRecord, mappedValue);
}
// 取出处理之后的结果值
mappedValue = mappedValue.[[Value]];
} else {
// 没有回调,直接返回当前值
mappedValue = nextValue;
}

// 将迭代出的值,添加到数组 Pk 位置上。
let defineStatus = CreateDataPropertyOrThrow(A, Pk, mappedValue);

if (AbruptCompletion(defineStatus)) {
// 异常结束
return IteratorClose(iteratorRecord, defineStatus);
}

// 进入下一次循环。
k++;
}

}

// 非列表类型,没有自己的迭代器,可能是个类数组对象
let arrayLike = ToObject(items);
// 必须具备长度属性,才能转数组,这也是类数组对象必备条件之一
let len = ToLength(Get(arrayLike, 'length'))

// 创建新的数组
if (IsConstructor(C)) {
A = Constructor(C, <len>);
} else {
A = ArrayCreate(len);
}

let k = 0;

while (k < len) {
let Pk = ToString(k);
// 根据 k 取出对象中的值
let kValue = Get(arrayLike, Pk);
if (mapping) {
mappedValue = Call(mapFn, T, <kValue, k);
} else {
mappedValue = kValue
}

CreateDataPropertyOrThrow(A, Pk, mappedValue);

k++;
}

Set(A, 'length', len, true);

return A;
}

示例一 :有 length 无数值索引

1
2
3
4
5
6
7
8
9
10
var noElementObj = {
length: 2
}

var arr1 = Array.from(noElementObj);

// 有长度但是没有数值类型 key
console.log(arr1.length); // 2
console.log(arr1[0]); // undefined
console.log(arr1[1]); // undefined

+RESULTS:

2
undefined
undefined

示例二 : 无 length 有数值索引

1
2
3
4
5
6
7
8
var noLenObj = {
'0': 'xxx',
'1': 'yyy'
}

var arr2 = Array.from(noLenObj);
console.log(arr2.length)
console.log(arr2[0])

+RESULTS:

0
undefined
undefined

示例三 :有 length 有数值索引

1
2
3
4
5
6
7
8
9
10
11
var lenNumObj = {
length: 2,
'0': 'xxx',
'1': 'yyy'
}

var arr = Array.from(lenNumObj);

console.log(arr.length); // 2
console.log(arr[0]); // 'xxx'
console.log(arr[1]); // 'yyy'

+RESULTS:

2
xxx
yyy

示例四 :有 length 且数值索引的属性个数 > length

1
2
3
4
5
6
7
8
9
10
11
12
13
var lenNumObj = {
length: 2,
'0': 'xxx',
'1': 'yyy',
'2': 'zzz'
}

var arr = Array.from(lenNumObj);

console.log(arr.length); // 2
console.log(arr[0]); // 'xxx'
console.log(arr[1]); // 'yyy'
console.log(arr[2]); // 'undefined'

+RESULTS: 结果数组长度由 length 决定

2
xxx
yyy
undefined

示例五 :有长度有数值索引,但是这些索引值不连续

1
2
3
4
5
6
7
8
9
10
11
12
13
var lenNumObj = {
length: 3,
'0': 'xxx',
'3': 'yyy',
'10': 'zzz'
}

var arr = Array.from(lenNumObj);

console.log(arr.length); // 3
console.log(arr[0]); // 'xxx'
console.log(arr[1]); // 'undefined'
console.log(arr[2]); // 'undefined'

+RESULTS: 结果只有 0 索引的值被加到数组中了

3
xxx
undefined
undefined

这里结果可以从伪码中找出原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (k < len) {
let Pk = ToString(k);
// 根据 k 取出对象中的值
let kValue = Get(arrayLike, Pk);
if (mapping) {
mappedValue = Call(mapFn, T, <kValue, k);
} else {
mappedValue = kValue
}

CreateDataPropertyOrThrow(A, Pk, mappedValue);

k++;
}

这里是针对非迭代对象的处理,我们可以看到使用的 while 循环中,无论什么情况下
k++ 都会被执行,且有 k < len 的条件,也就是说对非迭代对象的处理是直接在索引
+1 基础上取元素值,只要超出长度值就结束。

因此 lenNumObj 中的 '3''10' 对应的值是不会被统计到数组中的。

试着将长度加大到 11 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var lenNumObj = {
length: 11,
'0': 'xxx',
'3': 'yyy',
'10': 'zzz'
}

var arr = Array.from(lenNumObj);

console.log(arr.length); // 3
console.log(arr[0]); // 'xxx'
console.log(arr[1]); // 'undefined'
console.log(arr[2]); // 'undefined'
console.log(arr[3]); // 'undefined'
console.log(arr[10]); // 'undefined'

+RESULTS:

11
xxx
undefined
undefined
yyy
zzz
undefined

Object

Object.freeze(O)6-2015

1
2
3
4
5
6
7
8
9
10
function freeze(O) {
// 只对对象生效
if (Type(O) !== 'object') return O;

let status = SetIntegrityLevel(O, 'frozen');

if (status === false) throw new TypeError('对象已被冻结。');

return O;
}

Object.defineProperty(O, P, Desc)

给对象增加新的属性。

描述符对象

  1. value 属性值
  2. get() 访问器属性 getter
  3. set(val) 访问器属性 setter
  4. configurable 属性是否可以被 Object.defineProperty() 定义
  5. enumerable 是否可枚举
  6. writable 是否可以通过 = 赋值,不能和访问器属性共存

可以通过 Object.getOwnPropertyDescriptor(obj, key) 来获取属性的描述符。

value 定义属性的值

简单的只要给一个值就行:

1
2
3
4
5
6
7
let target = {}

Object.defineProperty(target, 'value', {
value: 42
})

console.log(target.value)

+RESULTS:

42

get() 和 set()

有了访问器属性就不能同时存在 writable 否则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let target = {}
let _val = undefined
Object.defineProperty(target, 'value', {
get() {
return _val || 100
},
set(val) {
_val = val * 2
return _val
}
})

console.log(target, target.value)
target.value = 200
console.log(target, target.value)

configurable

置为 false 后表示不能重复使用 Object.defineProperty()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let target = {}

Object.defineProperty(target, 'value', {
value: 42,
configurable: false
})

console.log(target, target.value)

try {
Object.defineProperty(target, 'value', {
value: 100
})
} catch(e) {
console.log(e.message)
}

+RESULTS: 置为 false 之后,就不能再重复定义了

{} 42
Cannot redefine property: value
undefined

enumerable 可枚举

置为 false 之后,一些枚举操作将忽略该属性,比如 for...in

1
2
3
4
5
6
7
8
9
10
11
12
let target = {
name: 'xxx'
}

Object.defineProperty(target, 'value', {
value: 42,
enumerable: false
})

for (let prop in target) {
console.log(prop, target[prop])
}

+RESULTS: 结果中没有 value 属性

name xxx

writable 是否可以赋值

不能与访问器属性共存,置为 false 将阻止属性的赋值操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'use strict';

let target = {}

Object.defineProperty(target, 'value', {
value: 42,
writable: false
})

console.log(target.value)
try {

target.value = 100
} catch (e) {
console.log(e.message)
}
console.log(target.value)

+RESULTS: 严格模式下无效,且会抛异常

42
Cannot assign to read only property 'value' of object '#<Object>'
42
undefined

+RESULTS: 非严格模式下,赋值无效

42
42

ObjectDefineProperties(O, Properties)Runtime

将属性列表 Properties 扩展到对象 O 上。

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
function ObjectDefineProperties(O, Properties) {
const type = Type(O);

if (type !== 'object')
throw new TypeError('target not an object.');

let props = ToObject(Properties)

// 取出所有属性的 key
let keys = props.[[OwnPropertyKeys]]();

// 创建一个空的 key-descriptor 键值对列表
// 格式: [[key, desc], [key1, desc1]]
let descriptors = new List()

// 遍历所有的属性列表的 keys 集合
for (let nextKey in keys) {
// 根据 key 去 props 对象中取对应的值
let propDesc = props.[[GetOwnProperty]](nextKey)

// 想要被扩展到 O 上去,必须满足两个条件:
// 1. 必须有值
// 2. 必须是可枚举的属性
if (propDesc !== undefined && propDesc.[[Enumerable]]) {
// 取出当前被扩展的属性,原本的属性描述符
let descObj = Get(props, nextKey)
// 转换成属性描述符对象
let desc = ToPropertyDescriptor(descObj);

// 追加属性描述符列表上去,等待被扩展到新对象 O 上
descriptors.append([nextKey, desc]);
}
}

// 经过上面的 for...in 得到了将被扩展的属性的 key 和 对应的属性描述符
// 列表,这里真正进行扩展到新对象上
for (let pair in descriptors) {
let P = pair[0]
let desc = pair[1]
DefinePropertyOrThrow(O, P, desc);
}

return O;
}

这里对新对象的扩展,需要经过以下步骤:

  1. 解析出被添加的 Properties 属性列表的 keys
  2. 创建一个空列表 list,用来存储被添加的属性的描述符对象
  3. 根据 propskeys 取出所有可枚举且有值得属性,组成 [key, value] 添加到
    list 中,等待被处理
  4. 处理 list 中的有效 key-descriptor 对,扩展到新的对象上

因此,这里最关键的就是这个判断 (propDesc !== undefined && propDesc.[[Enumerable]])
它直接定义了什么样的属性才能被扩展到新对象上,“必须有值且是可枚举的”属性。

ObjectCreate(proto[, internalSlotsList])Runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ObjectCreate(proto[, internalSlotsList]) {
// 内部插槽属性列表,类似内部 [[Constructor]] 插槽这样
// 对用户代码是不可见的
if (!internalSlotsList || !internalSlotsList.length) {
internalSlotsList = new List();
}

let obj = {}

// 相当于将插槽属性扩展到新建的 obj 上
Object.assign(obj, ...internalSlotsList)

// 将新创建的对象原型设置成传入的对象 proto
obj.[[Prototype]] = proto;
obj.[[Extensible]] = true;

return obj
}

这个操作的目的就是给原型对象扩展一些应该有的内部属性。

Object.create(O, Properties)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Object.create = function(O, Properties) {

const type = Type(O);

if (type !== null && type !== 'object') {
throw new TypeError('被继承类型必须是个有效对象');
}

const obj = ObjectCreate(O)

if (Properties) {
return ObjectDefineProperties(obj, Properties)
}

return obj
}

上面用到两个抽象操作:

ObjectCreate : 首先创建一个空的对象(或 null),给这个对象定义一些内部属性。

ObjectDefineProperties : 将 Properties 中有值且可枚举的属性,追加到 obj 上。

也就是说 Object.create 重要操作分别在上面两个抽象操作当中。

Object.assign(target, …sources)6-2015

实现原理图:

img

伪码:

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

function assign(target, ...sources) {
let to = ToObjecct(target);

// 1. 只有目标参数,原样返回
if (arguments.length === 1) {
return to;
}

// 2. 获取后面的参数列表,要合并到 target 里面的对象列表
let sources = List(sources); // 参数列表

// 3. 取到当前循环中的源对象
for (let nextSource in sources) {
// 3.1 非法情况不做处理
if (nextSource !== undefined || nextSource !== null) {
let from = ToObject(nextSource);

// 3.2 取出当前对象的所有自身的 keys
let keys = from.[[OwnPropertyKeys]]();

// 3.3 遍历这些 keys 取出非 Undefined 且可枚举的属性
// 添加到 target 对象上去
for (let nextKey in keys) {
let desc = from.[[GetOwnProperty]][nextKey];

if (desc !== undefined && desc.[[Enumerable]] === true) {
let propValue = Get(from, nextKey);
Set(to, nextKey, propValue, true);
}
}
}
}

// 4. 返回扩展之后的目标对象
return to;
}

Object.prototype.toString(obj)

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
// toString(object)

function toString(obj) {
// 1. 判断 undefined 和 null
if (this === undefined) {
return '[object Undefined]';
}

if (this === null) {
return '[object Null]';
}

let O = ToObject(this); // 上下文变量对象化
let isArray = IsArray(O); // 先判断是不是数组类型
let builtinTag = ''

let has = builtinName => !!O.builtinName;

// 2. 根据内置属性,检测各对象的类型
if (isArray === true) { // 数组类型
builtinTag = 'Array';
} else if ( has([[ParameterMap]]) ) { // 参数列表,函数参数对象
// 函数的参数 arguments 对象
builtinTag = 'Arguments';
} else if ( has([[Call]]) ) { // 函数
builtinTag = 'Function';
} else if ( has([[ErrorData]]) ) { // Error对象
builtinTag = 'Error';
} else if ( has([[BooleanData]]) ) { // Boolean 布尔对象
builtinTag = 'Boolean';
} else if ( has([[StringData]]) ) { // String 对象
builtinTag = 'String';
} else if ( has([[DateValue]]) ) { // Date 对象
builtinTag = 'Date';
} else if ( has([[RegExpMatcher]]) ) { // RegExp 正则对象
builtinTag = 'RegExp';
} else {
builtinTag = 'Object' // 其他
}

// 3. 最后检测 @@toStringTag - Symbol.toStringTag 的值
let tag = Get(O, @@toStringTag);

if (Type(tag) !== 'string') {
tag = builtinTag;
}

return `[object ${tag}]`;
}

Symbol

Symbol.for(key)6-2015

在全局符号注册表(Global Symbol Registry)中创建或查找符号值,返回一个符号变量。

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
Symbol.for = function (key) {

// 1 key 转字符串
let stringKey = ToString(key);

// 2. 遍历 GlobalSymbolRegistryList 注册表
for (let e in GlobalSymbolRegistryList) {
// 符号值已经存在
if (SameValue(e.[[Key]], stringKey)) {
return e.[[Symbol]];
}
}

// 3. 注册表中不含 `stringKey` 的符号值,则创建新的符号值
// 3.1 新建符号值
let newSymbol = Symbol(stringKey);
// 3.1 给 [[Description]] 赋值
newSymbol.[[Description]] = stringKey;

// 4. 注册到符号注册表中去
GlobalSymbolRegistryList.push({
[[Key]]: stringKey,
[[Symbol]]: newSymbol
});

// 5. 返回新建的符号值
return newSymbol;

}

Generator 抽象操作(Abstract Operations)

同步 Generator

GeneratorStart(generator, generatorBody)6-2015

实现包含:

  • generator 状态初始化
  • 将当前的执行上下文交给 generator
  • 如果恢复执行(next())
    1. 执行 generator 函数体,得到执行结果
    2. 断言执行过程是否异常(assert.throw)或无内容(assert.return)直接返回
    3. 函数体执行完,释放栈帧,重置下一个栈帧为当前运行栈帧
    4. 将 Generator 状态置为 ‘completed’ ,因为到这里表示正常执行完成了
    5. 根据 result.[ [Type]] 类型(normal, return, throw) 决定 value 的值
    6. 最后得到 {value: xxx, done: true/false } 将结果返回
  • 否则,保存上下文到 generator, 然后改变状态为 suspendedStart
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
function GeneratorStart(generator, generatorBody) {

// Assert: 初始状态
generator.[[GeneratorState]] = undefined;

// 取出栈顶的运行时栈帧
let genContext = RunningExecutionContext;

// 当前执行上下文的执行组件,设置成当前逐个 generator
genContext.component = generator;

// 调用 next() 触发代码执行
if (evaluation.resumed) {
// 执行 generator 的内容,得到结果
let result = eval(generatorBody),
resultValue;

// Assert: 直接返回
if (assert.return) {
return;
}

// Assert: 出现异常
if (assert.throw) {
throw new Error()
}

// generator body 执行完成,删除该栈帧 genContext
ExecutionContextStack.delete(genContext);
// 将栈顶的栈帧置为当前运行的栈帧
RunningExecutionContext = ExecutionContextStack.unshift();

// 设置 generator 的状态:已完成,此时该 generator 已经执行完成
// 执行上下文被删除,永远不会再恢复,后面其他的代码都不会被执行而丢弃掉
generator.[[GeneratorState]] = 'completed';

// 处理结果值 { value: xxx, done: false/true }
// 根据结果类型,决定 value 的值
if (result.[[Type]] === 'normal') {
// 正常情况,返回 { value: undefined, done: true }
resultValue = undefined;
} else if (result.[[Type]] === 'return') {
resultValue = result.[[Value]];
} else {
if (result.[[Type]] === 'throw'){
throw new Error();
}

// 返回结果值 { value: xxx, done: true }
return Completion(result);
}
}

// 没有调用 next() 将状态置为挂起,保存 generator 的执行上下文
generator.[[GeneratorContext]] = genContext;
generator.[[GeneratorState]] = 'suspendedStart';
return NormalCompletion(undefined);
}

img

GeneratorValidate(generator)6-2015

验证 generator 的状态并返回它:

  1. 是否有 GeneratorState 内部属性
  2. 是否有 GeneratorContext 内部属性
  3. 如果状态处于正在执行,抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function GeneratorValidate(generator) {
// 是否有 GeneratorState 内部属性
assert:
RquireInternalSlot(generator, [[GeneratorState]])

assert:
generator.has([[GeneratorContext]])


let state = generator.[[GeneratorState]];

if (state === 'executing') throw new TypeError();

// 返回状态置
return state;
}

GeneratorResume(generator, value)6-2015

恢复 generator 迭代器执行。

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
function GeneratorResume(generator, value) {

// 状态验证,如果正在执行(executing)会抛异常,结束运行
let state = GeneratorValidate(generator);

if (state === 'completed') {
// 如果状态已经完成,返回 { value: undefinde, done: true }
return CreateIterResultObject(undefined, true);
}

// 状态刚开始或暂停了
assert: state === 'suspendedStart' || state === 'suspendedYield';

// 取执行上下文
let genContext = generator.[[GeneratorContext]];

// 取当前运行栈中的顶帧
let methodContext = RunningExecutionContext;

// 挂起当前帧,准备给 generator 腾出位置
suspend(methodContext);

generator.[[GeneratorState]] = 'executing';

ExecutionContextStack.push(genContext);
RunningExecutionContext = genContext;

// 恢复 generator 的执行
eval.resume(genContext);
// 执行后的到结果
let result = NormalCompletion(value);

assert: return;

ExecutionContextStack.delete(genContext);
// 恢复原来的栈顶帧
RunningExecutionContext = methodContext;

// 将执行结果返回
return Completion(result);
}

GeneratorResumeAbrupt(generator, abruptCompletion)

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
function GeneratorResumeAbrupt(generator, abruptCompletion) {
let state = GeneratorValidate(generator);

if (state === 'suspendedStart') {
state = generator.[[GeneratorState]] = 'completed';
} else if (state === 'completed') {
if (abruptCompletion.[[Type]] === 'return') {
return CreateIterResultObject(abruptCompletion.[[Value]], true);
} else {
return Completion(abruptCompletion);
}
}

assert: state === 'suspendedYield';

// 下面是恢复 generator 过程
// 1. 取上下文
let genContext = generator.[[GeneratorContext]];

// 2. 保存当前栈中运行的帧
let methodContext = RunningExectionContext;

// 3. 挂起栈顶帧,将控制权交给当前的 generator
suspend(methodContext);

// 4. 更新 generator 状态
generator.[[GeneratorState]] = 'executing';

// 5. 将 generator 压入栈顶,成为 Running Frame
ExecutionContextStack.unshift(genContext);
RunningExectionContext = genContext;

// 6. 执行 Generator 得到结果,并且将中断结果作为该操作的结果传递给恢复执行程序
let result = evaluation.resume(genContext, abruptCompletion);

// 7. 断言:在这里执行退出了,从运行栈中删除 generator,并将执行权交换给原来的帧
assert: return;
> ExecutionContextStack.delete(genContext);
> RunningExectionContext = methodContext;

// 8. 正常执行完成,返回结果。
return Completion(result);
}

GetGeneratorKind()

根据运行时栈帧的 component 属性来检测当前帧是否是 Generator

如果不是返回:非 geneartor。

如果是,检测 Generator 的内部属性 AsyncGeneratorState 如果该属性存在,说明是
个异步 Generator 返回 async 否则返回 sync 同步 Generator。

归结作用就是检测 RunningExecutionContext 中的帧是不是 Generator, 是异步还是同步
的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function GetGeneratorKind() {
let genContext = RunningExecutionStack;

if (! genContext.component) {
// 不是生成器
return 'non-generator';
}

let generator = genContext.component;

if (generator.[[AsyncGeneratorState]]) {
return 'async'
}

return 'sync'
}

GeneratorYield(iterNextObj)

暂停一个 iterable object.

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 GeneratorYield(iterNextObj) {

assert: iterNextObj implements IteratorResult;

let methodContext = RunningExecutionContext;
RunningExecutionContext = genContext

assert: is(genContext, Generator.ExecutionContext);

let generator = genContext.component;

assert: GetGeneratorKind(generator) === 'sync';

generator.[[GeneratorState]] = 'suspendedYield';

ExecutionContextStack.delete(genContext);
RunningExecutionContext = methodContext;

if (genContext.resume) {
// 如果 generator 恢复运行,这里会返回当前 yield 表达式执行的结果值。
return resumptionValue = YieldExpression();
}

return NormalCompletion(iterNextObj);
}

异步 Generator

抽象操作

测试和比较操作(Testing and Comparation Operations)

SameValueNonNumber(x, y)

非数值类型的比较。

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
function SameValueNonNumber(x, y) {

// 这里值处理数值
if (Type(x) !== 'number') return false;

// 不同类型
if (Type(x) !== Type(y)) return false;

if (Type(x) === 'undefined') return true;

if (Type(x) === 'null') return true;

if (Type(x) === 'string') {
if (x.length === y.length) {
for (let i = 0; i < x.length; i++) {
// 有任一个字符不一样
if (x[i] !== y[i]) return false
}
// 长度不一致,不相等
return true;
}
return false;
}

if (Type(x) === 'boolean') {
return x && y || !x && !y;
}

if (Type(x) === 'symbol') {
// 符号类型
return x === y;
}

if (Type(x) === 'object' && Type(y) === 'object') {
return x === y;
}
}

SameValueZero(x, y)

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
const isNegZero = v => 1/v === -Inifinity
const isPosZero = v => 1/v === Inifinity

function SameValueZero(x, y) {
// 数据类型都不一样
if (Type(x) !== Type(y)) return false;

if (Type(x) === 'number') {
// 数值类型
if (isNaN(x) && isNaN(y)) return true;

// +0 和 -0
if (isPosZero(x) && isNegZero(y)) return true;
// -0 和 +0
if (isPosZero(y) && isNegZero(x)) return true;

// 同一个数字
if (x === y) return true;

return false;
}

// 非数值类型的比较
return SameValueNonNumber(x, y);
}

对象操作

SetIntegrityLevel(O, level)

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
function SetIntegrityLevel(O, level) {
if (Type(O) !== 'object') return false;

if (level !== 'sealed' && level !== 'frozen') return false;

let status = O.[[PreventExtensions]]();

if (status === false) return false;

let keys = O.[[OwnPropertyKeys]]();

if (level === 'sealed') {
for (let k in keys) {
DefinePropertyOrThrow(O, k, {
configurable: false
});
}
} else if (level === 'frozen') {
for (let k in keys) {
let currentDesc = O.[[GetOwnProperty]](k);

let desc = null
if (currentDesc !== undefined) {
if (IsAccessorDescriptor(currentDesc)) {
desc = {
configurable: false
}
} else {
desc = {
configurable: false, writable: false
}
}

DefinePropertyOrThrow(O, k, desc);
}
}
}

return true;
}

版本链接

< ECMAScript 2015

Api 链接(Link)
Object.prototype.toString(obj) Link->3.7
Array.prototype.slice([start[, end]]) Link->Array.p.slice

ECMAScript 2015(ES6)

Api 链接(Link)
Array.of(...items) Link->Array.of
Array.from(items[, mapFn[, thisArg]]) Link->Array.from
Array.prototype.find(predicate[, thisArg]) Link->Array.p.find
Array.prototype.findIndex(predicate[, thisArg]) Link->Array.p.findIndex
Array.prototype.fill(value[, start[, end]]) Link->Array.p.fill
Object.assign(target, ...sources) Link->3.6
Object.create(O, Properties) Link->3.5
ObjectDefineProperties(O, Properties)^{Runtime} Link->3.3
ObjectCreate(proto[, internalSlotsList])^{Runtime} Link->3.4
Symbol.for(key) Link->4.1
GeneratorStart(generator, generatorBody) Link->5.1.1
GeneratorValidate(generator) Link->5.1.2
GeneratorResume(generator, value) Link->5.1.3
GeneratorResumeAbrupt(generator, abruptCompletion) Link->5.1.4
GetGeneratorKind() Link->5.1.5
GeneratorYield(iterNextObj) Link->5.1.6

本文标题:ECMAScript Apis 内部实现伪代码

文章作者:ZhiCheng Lee

发布时间:2019年07月08日 - 22:06:08

最后更新:2019年08月14日 - 11:35:48

原始链接:http://blog.gcl666.com/2019/07/08/ecma_pseudo_code/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%