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.prototypeSymbol.iterator

Array.prototypeSymbol.iterator 内置实现的迭代器默认指向 Array.prototype.values 方法。我们能直接用 for...of 遍历数组是因为数组默认实现了 iterator。当用 for...of 遍历数组其实是在遍历Array.prototype.values 的返回值。

我们可以修改 Array.prototypeSymbol.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 实例方法,解析它们的实现原理。

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

相关推荐
jarvisuni9 分钟前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
破z晓2 小时前
javascript 导出excel表
开发语言·javascript·excel
小罗水3 小时前
第17章 后台管理端与演示支持
javascript·vue.js·ecmascript
乐思智能科技有限公司3 小时前
PLECS软件学习使用(二)直流电机基本系统模型
人工智能·算法·机器学习·面试·职场和发展
GitLqr4 小时前
深度拆解 Dart 事件循环:从面试题看清 Microtask 与 Event Queue 的执行顺序
flutter·面试·dart
haerapi4 小时前
用 System V 共享内存实现本机高速 IPC:从原理到可运行环形队列
面试
万少5 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端
宁风NF5 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
吾儿良辰5 小时前
Flutter 迎来重大变革:Material 设计系统拆分,迈向更开放的 UI 框架
javascript·flutter·ui
猫猫不是喵喵.5 小时前
Vue3 中 computed 计算属性与 watch、watchEffect 监听
前端·javascript·vue.js