Array 实例方法 keys、values、entries 的实现
本文从 ECMAScript 规范的角度出发,深入研究了 JavaScript 中 Array 实例方法
keys
、values
和entries
的内部实现机制。通过解析 ECMAScript 规范中对这些方法的详细要求,结合实际编程经验,提出了一种符合规范且高效的实现方案。读者将在本文中深入了解这些方法的工作原理,并掌握在实际项目中如何利用规范要求进行正确实现的技巧。
keys、values、entries 方法
ECMAScript® 2025 语言规范中对 keys、values、entries 三个方法的原文描述如下:
Array.prototype.keys()
This method performs the following steps when called:
- Let
O
be ? ToObject(this value). - Return CreateArrayIterator(
O
, KEY).
Array.prototype.values()
This method performs the following steps when called:
- Let
O
be ? ToObject(this value). - Return CreateArrayIterator(
O
, VALUE).
Array.prototype.entries()
This method performs the following steps when called:
- Let
O
be ? ToObject(this value). - Return CreateArrayIterator(
O
, KEY+VALUE).
翻译总结
Array.prototype.entries() 举例
- Let O be ? ToObject(this value) : 首先使用
ToObject
抽象操作符将当前方法的this
值转换为一个对象。ToObject
的作用是确保操作符右侧的值是一个对象,如果不是对象,则会尝试将其转换为对象。在这里,this value
是当前调用entries()
方法的数组实例。 - Return CreateArrayIterator(O, KEY+VALUE) : 然后调用了
CreateArrayIterator
抽象操作符,该操作符接受两个参数:一个对象和一个枚举值。在这里,它传入了转换后的对象O
以及一个枚举值KEY+VALUE
。这个枚举值表示迭代器应该返回键值对。最后,返回的是由CreateArrayIterator
操作符返回的迭代器对象。
从上面规范描述中就能看出它们的区别,就是 CreateArrayIterator 抽象操作的第二个参数区别。 所以只要实现 CreateArrayIterator 抽象操作,然后对第二个参数做出不同响应即可。
实现 keys、values、entries 方法
返回值
keys、values、entries 方法的返回值都是迭代器对象。
- keys() 方法返回索引迭代器
- values() 方法返回值迭代器
- entries() 方法返回索引和值的迭代器
所以 CreateArrayIterator 就是迭代器函数。要生成迭代器对象我们需要 Generator 函数。
对 Generator 和 Iterator 不太懂的同学可以先补充一下这方面知识点。
CreateArrayIterator 实现
根据规范实现一下 CreateArrayIterator 抽象操作。这里只实现 CreateArrayIterator 抽象操作的 array 部分,完整的 CreateArrayIterator 抽象操作功能要复杂很多。
js
function* CreateArrayIterator (array, kind) { // * 号创建 Generator 函数
let index = 0
const len = array.length
while (index < len) {
let result
const elementValue = array[index] // 直接使用索引访问数组元素
if (kind === 'KEY') {
result = index // 返回索引值
} else {
if (kind === 'VALUE') {
result = elementValue // 返回数组元素值
} else {
// kind 是 KEY+VALUE
result = [index, elementValue] // 返回索引和数组元素值的数组
}
}
yield result // yield 修饰返回值,生成迭代器
index++
}
}
keys、values、entries 实现代码
js
function ToObject (argument) {
if (Object.is(argument, undefined) || Object.is(argument, null)) {
throw TypeError('Array.prototype.properties called on null or undefined')
} // 排除 undefined 和 null
return Object(argument)
}
function* CreateArrayIterator (array, kind) {
let index = 0
const len = array.length
while (index < len) {
let result
const elementValue = array[index] // 直接使用索引访问数组元素
if (kind === 'KEY') {
result = index // 返回索引值
} else {
if (kind === 'VALUE') {
result = elementValue // 返回数组元素值
} else {
// kind 是 KEY+VALUE
result = [index, elementValue] // 返回索引和数组元素值的数组
}
}
yield result
index++
}
}
Array.prototype.myKeys = function () { // keys() 方法
const O = ToObject(this)
return CreateArrayIterator(O, 'KEY')
}
Array.prototype.myValues = function () { // values() 方法
const O = ToObject(this)
return CreateArrayIterator(O, 'VALUE')
}
Array.prototype.myEntries = function () { // entries() 方法
const O = ToObject(this)
return CreateArrayIterator(O, 'KEY+VALUE')
}
Array.prototype[Symbol.iterator]
Array.prototype[Symbol.iterator] 内置实现的迭代器默认指向 Array.prototype.values 方法。我们能直接用 for...of 遍历数组是因为数组默认实现了 iterator。当用 for...of 遍历数组其实是在遍历Array.prototype.values 的返回值。
我们可以修改 Array.prototype[Symbol.iterator] 的指向,让它指向我们实现的 Array.prototype.myEntries 看看什么效果。
js
Array.prototype[Symbol.iterator] = Array.prototype.myEntries
const arr = [1, 2, 3]
for (const item of arr) {
console.log(item)
}
// [0, 1]
// [1, 2]
// [2, 3]
从打印结果看出 for...of 遍历出来的不是值,而是 Array.prototype.myEntries() 方法返回的 [key, value] 值。
测试用例
js
const array = [1, 2, ,]
console.log('测试 keys() 方法 --------------------------------')
const iteratorMyKeys = array.myKeys()
const iteratorKeys = array.keys()
console.log(iteratorMyKeys.next().value) // 输出:0
console.log(iteratorMyKeys.next().value) // 输出:1
console.log(iteratorMyKeys.next().value) // 输出:2
console.log(iteratorMyKeys.next().value) // 输出:undefined
console.log(iteratorKeys.next().value) // 输出:0
console.log(iteratorKeys.next().value) // 输出:1
console.log(iteratorKeys.next().value) // 输出:2
console.log(iteratorKeys.next().value) // 输出:undefined
console.log('测试 values() 方法 --------------------------------')
const iteratorMyValues = array.myValues()
const iteratorValues = array.values()
console.log([...iteratorMyValues]) // 输出:[1, 2, undefined]
console.log([...iteratorValues]) // 输出:[1, 2, undefined]
console.log('测试 entries() 方法 --------------------------------')
const iteratorMyEntries = array.myEntries()
const iteratorEntries = array.entries()
console.log([...iteratorMyEntries]) // 输出:[[0, 1], [1, 2], [2, undefined]]
console.log([...iteratorEntries]) // 输出:[[0, 1], [1, 2], [2, undefined]]
console.log('for of 中测试 ----------------------------')
const iteratorForMyEntries = array.myEntries()
const iteratorForEntries = array.entries()
for (const myItem of iteratorForMyEntries) {
console.log(myItem)
}
for (const item of iteratorForEntries) {
console.log(item)
}
// [0, 1]
// [1, 2]
// [2, undefined]
console.log('在非数组对象上调用 --------------------------')
const arrayLike = {
length: 3,
0: "a",
1: "b",
2: "c",
}
for (const myEntry of Array.prototype.myEntries.call(arrayLike)) {
console.log(myEntry)
}
for (const entry of Array.prototype.entries.call(arrayLike)) {
console.log(entry)
}
// [0, 'a']
// [1, 'b']
// [2, 'c']
结语
到这里 Array 实例方法 keys、values、entries 实现完成啦。推荐大家去看其他方法实现:
- forEach 方法实现:juejin.cn/post/735163...
- flat 方法实现: juejin.cn/post/735018...
- map 方法实现:juejin.cn/post/734910...
- filter 方法实现: juejin.cn/post/734908...
- reduce 和 reduceRight 方法实现:juejin.cn/post/732199...
Array 实例方法实现系列
JavaScript 中的 Array 类型提供了一系列强大的实例方法。在这个专栏中,我将深入探讨一些常见的 Array 实例方法,解析它们的实现原理。
如果有错误或者不严谨的地方,请请大家务必给予指正,十分感谢。欢迎大家在评论区中讨论。