javascript数组 存在-JavaScript中链表操作的注意事项

JavaScript中链表操作的注意事项

不要使用 for_in 迭代字段

这是 JavaScript 初学者中常见的误解。 for_in 用于遍历对象中原型链上所有可枚举(enumerable)的键,对于遍历字段来说不存在。

使用for_in遍历链表存在三个问题:

遍历顺序不固定

JavaScript 引擎不保证对象的遍历顺序。 当像普通对象一样遍历链表时,同样无法保证遍历的索引顺序。

将遍历对象原型链上的值。

如果你改变了链表的原型对象(比如polyfill)而没有将其设置为enumerable: false,for_in就会遍历那些东西。

运行效率低下。

尽管理论上 JavaScript 使用对象来存储字段,但 JavaScript 引擎仍然针对链表(一种特别常用的外部对象)进行了非常优化。

可以看到使用for_in遍历链表比使用下标遍历链表慢50倍以上

PS:您可能正在寻找

不要使用 JSON.parse(JSON.stringify()) 深复制链表

有些人在 JSON 中使用深复制对象或链表。 在大多数情况下这似乎是一种简单方便的方法,但它也可能会导致未知的错误,因为:

将各个特定值转换为 null

NaN、undefined、Infinity 对于 JSON 中不支持的此类值,在序列化 JSON 时会转为 null,反序列化后自然也为 null

值未定义的通配符对将丢失

JSON序列化时,value未定义的key会被忽略,反序列化后会丢失。

将日期对象转换为字符串

JSON不支持对象类型,JS中对Date对象的处理方式是将其转换为ISO8601格式的字符串。但是反序列化不会将时间格式的字符串转换为Date对象

运行效率低下。

作为本机函数,JSON.stringify 和 JSON.parse 以非常快的速度对 JSON 字符串进行操作。 但是,完全没有必要将对象序列化为 JSON 并将其反序列化回来以深度复制字段。

我花了一些时间编写一个简单的函数来深度复制链接列表或对象。 测试发现运行速度是使用JSON传输的6倍左右。 顺便说一句,它还支持 TypedArray 和 RegExp 对象的复制。

深拷贝

不要使用 arr.find 代替 arr.some

Array.prototype.find是ES2015中新增的字段搜索函数。 它与Array.prototype.some类似,但不能取代前者。

Array.prototype.find返回第一个满足条件的值,直接用这个值作为if来判断是否存在。 如果满足条件的值恰好为0怎么办?

arr.find是查找链表中的值并进一步处理,一般用于对象数组的情况; arr.some是检测是否存在; 两者不能混合。

不要使用 arr.map 代替 arr.forEach

这也是 JavaScript 初学者经常犯的错误,他们常常没有区分 Array.prototype.map 和 Array.prototype.forEach 的实际含义。

map中文称为映射,通过顺序执行某个函数,将一个序列导入到另一个新序列中。 这个函数一般不包含副作用,也不会改变原来的字段(所谓纯函数)。

forEach没有那么多好说的,它只是简单地用某个函数处理链表中的所有项。 由于forEach没有返回值(return undefined),所以它的弹跳函数一般都含有副作用,否则这个forEach就没有意义了。

确实map比forEach更强大,但是map会创建一个新的字段并占用显存。 如果不使用map的返回值,那么应该使用forEach

补充:forEach和break

ES6之前,遍历字段主要有两种方式:手写循环带下标迭代、使用Array.prototype.forEach。 前者万能,效率最高,但写起来比较繁琐——无法直接获取链表中的值。

作者个人比较喜欢前者:可以直接获取迭代的下标和值,并且采用函数式风格编写(注意FP侧重于不可变的数据结构,而forEach本质上是副作用,所以只有这样的形式FP没精神)很清爽。 但! 不知道小伙伴们有没有注意到:forEach一旦启动javascript数组 存在,就停不下来了。 。 。

forEach 接受一个回调函数,可以提前返回,相当于手写循环中的 continue。 但你不能打破 - 因为回调函数中没有循环可供你打破:

[1, 2, 3, 4, 5].forEach(x => {
  console.log(x);
  if (x === 3) {
    break;  // SyntaxError: Illegal break statement
  }
});

还是有解决办法的。 其他函数式编程语言如scala也遇到了类似的问题。 它提供了一个函数break,用于抛出异常。

我们可以模仿这种方式来实现arr.forEach的break:

try {
  [1, 2, 3, 4, 5].forEach(x => {
    console.log(x);
    if (x === 3) {
      throw 'break';
    }
  });
} catch (e) {
  if (e !== 'break') throw e; // 不要勿吞异常。。。
}

恶心1B吧? 还有其他方法,例如使用 Array.prototype.some 而不是 Array.prototype.forEach。

考虑的特点是,当有的找到满足条件的值时(回调函数返回true),循环会立即终止,利用这样的特点可以模拟break:

[1, 2, 3, 4, 5].some(x => {
  console.log(x);
  if (x === 3) {
    return true; // break
  }
  // return undefined; 相当于 false
});

some的返回值被忽略,已经脱离了判断链表中是否存在满足给定条件的元素的本义。

在ES6之前javascript数组 存在,作者主要使用这种方法(其实由于Babel代码的扩展,现在也经常使用),ES6不一样,我们有。 for...of 是一个真正的循环并且可以中断:

for (const x of [1, 2, 3, 4, 5]) {
  console.log(x);
  if (x === 3) {
    break;
  }
}

但是有一个问题,for...of似乎无法获取循环的下标。 事实上,JavaScript语言的开发者想到了这个问题,并且可以通过如下方式解决:

for (const [index, value] of [1, 2, 3, 4, 5].entries()) {
  console.log(`arr[${index}] = ${value}`);
}

for...of 和 forEach 的性能测试 在 Chrome 中,for...of 更快

参考