You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何区分数组空项与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:55:09