Array 实例方法 keys、values、entries 的实现

Array 实例方法 keys、values、entries 的实现

本文从 ECMAScript 规范的角度出发,深入研究了 JavaScript 中 Array 实例方法 keysvaluesentries 的内部实现机制。通过解析 ECMAScript 规范中对这些方法的详细要求,结合实际编程经验,提出了一种符合规范且高效的实现方案。读者将在本文中深入了解这些方法的工作原理,并掌握在实际项目中如何利用规范要求进行正确实现的技巧。

keys、values、entries 方法

ECMAScript® 2025 语言规范中对 keys、values、entries 三个方法的原文描述如下:

Array.prototype.keys()

This method performs the following steps when called:

  1. Let O be ? ToObject(this value).
  2. Return CreateArrayIterator(O, KEY).

Array.prototype.values()

This method performs the following steps when called:

  1. Let O be ? ToObject(this value).
  2. Return CreateArrayIterator(O, VALUE).

Array.prototype.entries()

This method performs the following steps when called:

  1. Let O be ? ToObject(this value).
  2. Return CreateArrayIterator(O, KEY+VALUE).

翻译总结

Array.prototype.entries() 举例

  1. Let O be ? ToObject(this value) : 首先使用 ToObject 抽象操作符将当前方法的 this 值转换为一个对象。ToObject 的作用是确保操作符右侧的值是一个对象,如果不是对象,则会尝试将其转换为对象。在这里,this value 是当前调用 entries() 方法的数组实例。
  2. 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 实现完成啦。推荐大家去看其他方法实现:

  1. forEach 方法实现:juejin.cn/post/735163...
  2. flat 方法实现: juejin.cn/post/735018...
  3. map 方法实现:juejin.cn/post/734910...
  4. filter 方法实现: juejin.cn/post/734908...
  5. reduce 和 reduceRight 方法实现:juejin.cn/post/732199...

Array 实例方法实现系列

JavaScript 中的 Array 类型提供了一系列强大的实例方法。在这个专栏中,我将深入探讨一些常见的 Array 实例方法,解析它们的实现原理。

如果有错误或者不严谨的地方,请请大家务必给予指正,十分感谢。欢迎大家在评论区中讨论。

相关推荐
真的很上进1 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
qq_278063711 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl1 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码1 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347542 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
喝旺仔la2 小时前
VSCode的使用
java·开发语言·javascript
辛-夷2 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
一个很帅的帅哥3 小时前
axios(基于Promise的HTTP客户端) 与 `async` 和 `await` 结合使用
javascript·网络·网络协议·http·async·promise·await
dream_ready4 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5
编写美好前程4 小时前
ruoyi-vue若依前端是如何防止接口重复请求
前端·javascript·vue.js