如何区分数组空项与undefined?链表实现需遵循ECMA规范
undefined的处理问题 问题场景与现象
我正在基于数组方法实现双向链表,在实现indexOf和includes方法时遇到了数组空项(empty slot)的特殊逻辑问题。
先看原生数组的测试代码与输出:
var arr = [1,2,,4,5]; console.log(arr.indexOf(undefined)); console.log(arr.includes(undefined)); console.log(arr[2] === undefined);
输出:
-1 true true
根据ECMA-262规范,indexOf使用严格相等(===)判断,includes使用SameValueZero算法。我已经实现了对应的判断函数:
isStrictlyEqual(x,y) { return x === y; } sameValue(x, y) { return Object.is(x,y); } sameValueZero(x, y) { return x === y || (Number.isNaN(x) && Number.isNaN(y)); }
但我的链表indexOf查询undefined时返回索引2,而原生数组返回-1——只有数组显式存入undefined时,原生indexOf才会返回对应索引。这说明原生数组的空项并非真正存储undefined,二者有本质区别。
当前实现的问题
我的链表通过_extend方法从数组初始化,代码如下:
_extend(iterable) { for (var i = 0; i < iterable.length; ++i) { this.push(iterable[i]); } }
这个方法会把数组空项当作undefined存入链表;如果改用forEach则会直接跳过空项。我希望链表行为完全遵循ECMA规范,需要解决两个核心问题:如何区分数组空项与显式undefined,链表该如何处理这种特殊情况?
我的双向链表基础结构:
class DoublyLinkedList { constructor(array=null) { this.head = null; this.tail = null; this.length = 0; if (array !== null) { this._extend(array); } } }
解决方案
1. 初始化时区分数组空项与undefined
原生数组的空项(holes)可以通过Object.prototype.hasOwnProperty判断:对于数组arr,索引i如果是空项,arr.hasOwnProperty(i)返回false;如果是显式存储的undefined,则返回true。
修改_extend方法,用唯一Symbol标记空项,避免和显式undefined混淆:
_extend(iterable) { const EMPTY_SLOT = Symbol.for('empty-slot'); for (var i = 0; i < iterable.length; ++i) { const isEmptySlot = !iterable.hasOwnProperty(i); this.push(isEmptySlot ? EMPTY_SLOT : iterable[i]); } }
2. 调整indexOf方法逻辑
indexOf需要跳过空项,只在显式存储的值中用严格相等判断:
indexOf(searchElement) { const EMPTY_SLOT = Symbol.for('empty-slot'); let current = this.head; let index = 0; while (current !== null) { if (current.value !== EMPTY_SLOT && this.isStrictlyEqual(current.value, searchElement)) { return index; } current = current.next; index++; } return -1; }
3. 调整includes方法逻辑
includes需要同时处理显式undefined和空项(按SameValueZero规则,空项会匹配undefined):
includes(searchElement) { const EMPTY_SLOT = Symbol.for('empty-slot'); let current = this.head; while (current !== null) { const value = current.value; // 空项匹配undefined if (value === EMPTY_SLOT) { if (searchElement === undefined) { return true; } } else if (this.sameValueZero(value, searchElement)) { return true; } current = current.next; } return false; }
4. 补充节点存储逻辑
确保push方法正确存储节点值:
push(value) { const newNode = { value: value, prev: this.tail, next: null }; if (this.tail === null) { this.head = newNode; } else { this.tail.next = newNode; } this.tail = newNode; this.length++; }
关键说明
- 用全局
Symbol标记空项,避免和用户输入的任何值冲突,保证区分准确性。 - 严格遵循ECMA规范:
indexOf忽略空项,仅匹配显式存储的值;includes将空项视为undefined进行匹配。 - 若要实现其他数组方法(如
forEach、map),需对应处理空项逻辑——比如forEach要跳过空项,和原生数组行为一致。
内容的提问来源于stack exchange,提问作者vesii

