不为人知的JavaScript陷阱

不为人知的JavaScript陷阱


image.png

做者|Casper Beyer译者|王强编辑|王文婧自告别 Harmony 的时代以来,JavaScript 推出了许多新的、带语法糖的功能。虽然说更多新功能可让咱们编写可读性和质量更高的代码,但咱们也很容易被这些新奇、亮眼的特性迷惑,反而陷入一些潜在的陷阱。本文做者回顾他在使用 JS 时常常遇到的困惑,新旧问题都有。但愿你能够经过阅读本文,避免这些问题在你的编码中发生。箭头函数和对象字面量箭头函数提供了更简短的语法,其中一个特性是你能够将函数编写为具备隐式返回值的 lambda 表达式。编写函数样式的代码时这就很顺手,好比说有时你必须使用一个函数映射一些数组的状况。使用常规函数可能会多出不少空行。例如:javascript

const numbers = [1234];
numbers.map(function(n) {
return n * n;
});
用 lambda 样式的箭头函数来写的话,就会写成两行优雅、易读的代码:
const numbers = [1234];
numbers.map(n => n * n);

在这种用例中,箭头函数的表现符合预期,它将值自己相乘并返回到包含 [1, 4, 9, 16] 的新数组。html

但若是你尝试映射到对象,那么语法可能就不是你直觉指望的那样了。例如,假设咱们试图将数字映射到包含以下值的对象数组中:
const numbers = [1234];
numbers.map(n => { value: n });
这里的结果其实是一个包含未定义值的数组。虽然看起来咱们在这里返回一个对象,可是解释器看到的东西是彻底不同的。花括号被解释为箭头函数的块做用域,而值语句最后实际上成为了标签。若是将上述箭头函数外推到解释器最终实际执行的内容中,它将看起来像这样:
const numbers = [1234];
numbers.map(function(n) {
value:
n
return;
});
解决方法很是微妙。咱们只须要将对象包装在括号中,就能够将它变成一个表达式而不是一个块语句,以下所示:
const numbers = [1234];
numbers.map(n => ({ value: n }));

这会计算出一个包含对象数组的数组,该对象数组具备预期的值。前端

箭头函数和绑定

箭头函数另外一个须要注意的点是,它们没有本身的 this 绑定,意味着它们的 this 值和封闭词法做用域的 this 值是同样的。java

所以,尽管箭头函数的语法更时尚一些,但它并不能替代一些很好的旧函数。你可能会很容易遇到 this 绑定与你本来所想不同的状况。例如:
let calculator = {
  value: 0,
  add: (values) => {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
calculator.add([123]);
console.log(calculator.value);

尽管人们可能但愿这里的 this 绑定为此处的 calculator 对象,但实际上 this 绑定最后要么是未定义,要么是全局对象,具体取决于代码是否在严格模式下运行。这是由于这里最接近的词汇做用域是全局做用域。在严格模式下这是未定义的。不然,它会是浏览器中的窗口对象(或 Node.js 兼容环境中的过程对象)。数组

常规函数确实具备 this 绑定。在对象上调用时,this 将指向该对象,所以常规函数仍然是得到成员函数的正确途径。
let calculator = {
  value: 0,
  add: (values) => {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
calculator.add([123]);
console.log(calculator.value);

另外,因为箭头函数没有 this 绑定,所以 Function.prototype.call、Function.proto-type.bind 和 Function.prototype.apply 均没法使用。声明箭头函数后,this 绑定设置为固定,没法更改。浏览器

所以,在下面的示例中,咱们将遇到与以前相同的问题:当调用 adder 的 add 函数时,this 绑定又成了全局对象,尽管咱们尝试使用 Function.prototype.call 覆盖它:
const adder = {
  add: (values) => {
    this.value = values.reduce((a, v) => a + v, this.value);
  },
};
let calculator = {
  value0
};
adder.add.call(calculator, [123]);

箭头函数很简洁,但不能替换须要 this 绑定的常规成员函数。微信

自动分号插入

虽然这不是一项新功能,但自动分号插入(ASI)是 JavaScript 中比较怪异的功能之一,所以值得一提。从理论上讲,你能够在大多数时候省略分号(许多项目都这样作)。若是项目有先例,则应遵循此先例。但你必定须要记得 ASI 是一项功能,不然最后你会写出容易迷惑人的代码。架构

请看如下示例:
return
{
  value42
}
有人可能会认为它会返回对象字面量,但实际上它会返回未定义的值,由于发生了分号插入,使其成为空的 return 语句,后跟一个 block 语句和一个 label 语句。换句话说,最终被解释的代码看起来更像是下面这种写法:
return;
{
  value42
};

根据经验,即便使用分号时也切勿以大括号、方括号或模板字符串字面量开头,由于 ASI 老是会起做用。app

浅集合集合较浅,意味着重复的数组和具备相同值的对象,这将致使集合中有多个条目。例如:
let set = new Set();
set.add([123]);
set.add([123]);
console.log(set.length);
该集合的大小将为 2,若是你考虑引用它的话就要注意了,由于它们是不一样的对象。但字符串是不可变的。以集合中的多个字符串为例:
let set = new Set();
set.add([123].join(','));
set.add([123].join(','));
console.log(set.size);

因为字符串是不可变的,且驻留在 JavaScript 中,所以最终的集合大小为 1。若是你须要存储一组对象,那么这就能够是一种解决方法,也能够将它们序列化和反序列化。less

类与暂时死区在 JavaScript 中,常规函数被提高到词法做用域的顶部,这意味着下面的示例将按预期工做:
let segment = new Segment();
function Segment() {
  this.x = 0;
  this.y = 0;
}
可是对于类来讲并不是如此。类实际上没有被提高,而且在尝试使用它们以前须要在词法做用域内对其进行彻底定义。例如:
let segment = new Segment();
class Segment {
  constructor() {
    this.x = 0;
    this.y = 0;
  }
}

尝试构造类的新实例时会致使 ReferenceError,由于它们没有像函数那样被提高。

FinallyFinally 是一个特殊状况。看一下如下代码片断:
try {
  return true;
finally {
  return false;
}

你认为它会返回什么值?答案既符合直觉,同时又能够是反直觉的。有人可能会认为第一个 return 语句使函数实际返回并弹出调用栈。但这里是该规则的例外,由于 Finally 语句始终都会运行,所以会返回 Finally 块中的 return 语句。

小  结

JavaScript 很容易入门,但很难精通。换句话说,开发人员须要搞清楚本身正在作什么,明白为何要这样作,不然就很容易出错。

ECMAScript 6 及其含糖功能尤为如此。特别是箭头函数哪里都会冒出来。让我来猜的话,那是由于开发人员认为它们比常规函数更漂亮。但它们不是常规函数,所以没法替代后者。

时不时地浏览一下规范并无什么坏处。它不是世界上最激动人心的文档,但就规范自己而言写得还算不错。

规范:https://www.ecma-international.org/ecma-262/9.0/index.html

AST Explorer 之类的工具还能够帮助你了解某些极端场景下的情况。人类和计算机每每会以不一样的方式来解析事物。

文章最后,我把最后一个示例留做练习供你们思考。image.png

原文连接:https://medium.com/better-programming/lesser-known-javascript-hazards-8d688a463b1f

 活动推荐

偶发 bug 大大增长了排查的成本,复现也变成了全部研发心中的痛。在即将召开的 ArchSummit 全球架构师峰会(北京站)上,贝壳找房基础架构中心前端架构委员会专家陈辰将为你们带来贝壳自研监控平台灯塔以外的另外一项目——时光机,揭秘如何利用时光机让偶现 bug 无所遁形。

点击【阅读原文】查看详情。目前 9 折限时直降 880 元!了解详情请联系票务经理灰灰:15600537884 (同微信)。
相关文章
相关标签/搜索