js 冷门知识 - 弊端过多的 with 语句

1,591 阅读5分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前言

本系列将分多篇文章介绍 js 相关的“冷门知识”,这里的“冷门知识”是指大部分开发者都不曾留意过的知识点,亦或者是意料不到的知识点。这些知识点对现实中的面试、开发也许没有很大的帮助,甚至压根没任何用处,但希望以此博得大家一乐,并额外拓展下知识面。

本系列文章的大部分知识参考于阮一峰写的“网道 - 互联网开发文档 (wangdoc.com)”,强烈建议 web 前端开发者都看下这份文档,既是入门的好帮手,也是拓展知识面的利器。

本系列文章的任何参考,均会在文末标出。

with 语句

我相信大部分人都没有认识过 js 的 with 语句,大概是因为很少有人或教材提起过它,因为它的使用弊端多且严重。

下面我们看看 with 有什么用。

MDN 是这样讲解的。

with语句 扩展一个语句的作用域链。

看完这个定义,大概没人会理解吧。举个栗子,就容易理解了。

比如:

var obj = { 
  a: 1, 
  print() {
    console.log('a 的值为:', this.a) 
  }
}
with(obj) {
  a = 2
  print()
}

上述代码运行后,会将 obja 属性的值改变为 2,并且运行 objprint 方法输出 a 的值。也就是说 with 语句里面的 a = 2 其实等同于 obj.a = 2with 语句里面的 print() 等同于 obj.print()

我们可以看到 with 语句的设计初衷大概是希望简化对象的调用,省略 obj. 这一前缀。

再举多一个栗子。

with ('abc') {
    console.log(toUpperCase()) // 'ABC'
}

上面的代码相当于 console.log('abc'.toUpperCase()),将 abc 大写化并输出。

有没有注意到传入的 'abc' 是基本类型中的字符串,并非对象,但是 with 语句是针对对象的,为什么这里可以用字符串呢?

这其实与 with 语句无关。我在这里额外提一个知识点:包装对象。

我相信很多人都有这样写过代码。

var str = 'abc'
if (str.length === 3) {
    ...
}

但是大概很少有人质疑过为什么作为基本类型的字符串 str 可以像对象那样调用属性 length

那是因为 js 引擎在某些场合下会自动将基本类型转为对应的包装对象,像上面的字符串 str 就转为一个临时的字符串对象(用完立刻销毁),相当于做了 new String(str) 的操作,因此实际上代码 str.length 在引擎里表现为 new String(str).length,所以作为基本类型的字符串 str 看起来带有属性 length,事实上 str 是不带任何属性和方法。具体可以看看“网道”的这一章节“包装对象 - JavaScript 教程 - 网道 (wangdoc.com)”。

回到正题,也就是说上述代码是等同于下面的代码。

with (new String('abc')) {
    console.log(toUpperCase()) // 'ABC'
}

优点

这里直接引用 MDN 里的解释。

with 语句可以在不造成性能损失的情況下,减少变量的长度。其造成的附加计算量很少。使用'with'可以减少不必要的指针路径解析运算。需要注意的是,很多情況下,也可以不使用with语句,而是使用一个临时变量来保存指针,来达到同样的效果。

缺点

with 语句看起来还有一点的使用价值,但事实上它存在各种弊端。

意外创建全局变量

我们先看下以下代码。

with(obj) {
   a = 2
}
console.log(obj.a) // 输出1
console.log(a) // 输出2

这里的“输出1”和“输出2”的结果要看 obj 是否带有 a 这个属性。

如果 obj 带有属性 a,那么会预期将 obja 属性赋予 2 的值,“输出1”的结果为 2,“输出2”会报错 a is not defined

如果 obj 不带有属性 a,我们可能会认为产生这样的效果 obj.a = 2obj 将赋予属性 aa 的值等于 2,但事实上并不会,而是会在全局作用域里创建一个全局变量 aa 的值等于 2,也就是说,“输出1”的结果为 undefined,“输出2”的结果为 2

这种特性会意外地创建全局变量,从而污染全局作用域。

语义不明

当存在像以下这样的代码,如果我们不知道 b 里面有什么属性和方法,我们怎么知道 ab 里的 a,还是 function 的入参 a 呢?

function f(a, b) {
  with (b) {
    console.log(a);
  }
}

造成性能损失

这里直接引用 MDN 的相关解释。

with 语句使得程序在查找变量值时,都是先在指定的对象中查找。所以那些本来不是这个对象的属性的变量,查找起来将会很慢。如果是在对性能要求较高的场合,'with'下面的statement语句中的变量,只应该包含这个指定对象的属性。

总结

with 语句就是一个没卵用的语句,w3school 和 MDN 都明确不推荐使用。在 js 的严格模式下,with 语句甚至是完全禁用的。

参考

“js 冷门知识”系列文章推荐