我们可以使用 with 关键字来打印消息到 console:
with (console) {
log('I dont need the "console." part anymore!');
}
with 还可以用来把数组合并为字符串:
with (console) {
with (['a', 'b', 'c']) {
log(join('')); // 在 console 中输出 'abc'
}
}
没错兄弟,你没看错,我们写的就是 JavaScript。
「with」做了什么?
以下是 来自 MDN 的解释:
JavaScript 查找某个未使用命名空间的变量时,会通过作用域链来查找,作用域链是跟执行代码的 context 或者包含这个变量的函数有关。“with” 语句将某个对象添加到作用域链的顶部,如果在 statement 中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则将拋出 ReferenceError 异常。
简而言之:当代码中存在标识符时(就像前文中代码段里的 log 或者 join ),JavaScript 会查找 作用域链 上的对象,如果其中存在一个对象的属性名和代码中的标识符 一致,JavaScript 就会使用该对象的属性值。
with 关键字允许你将任何对象注入到 作用域链 顶部。我举个例子,你应该更好理解:
with ({ myProperty: 'Hello world!' }) {
console.log(myProperty); // 打印 "Hello world!"
}
不要使用
with 看起来能帮我们省下很多代码,对吧?嗯,也许没有你想的那么好用。
首先,在大多数情况下,其实我们用临时变量就能实现同样的效果了,而且 解构赋值 语法能帮我们更方便的使用临时变量。
除此之外,with 还有很多致命的问题,MDN 列出了其中一些:
严格模式下被禁用
我们不能在严格模式下使用 with。由于 ES module 和 class 会自动启用严格模式,所以这基本上打消了 with 语法在现代开发中使用的可能性。
变量遮蔽(Variable shadowing)
思考下面的代码,我们将两个数求平均,然后将结果四舍五入:
function getAverage(min, max) {
with (Math) {
return round((min + max) / 2);
}
}
getAverage(1, 5);
最终返回是 NaN 。为什么?因为 Math.min() 和 Math.max() 遮蔽 了函数接收的参数,所以我们其实是在将两个函数相加,其结果当然就是 NaN 了。
如果你用了 with , 那你就不得不小心翼翼的选择标识符的命名了。你必须检查你往 with 里传了什么东西,确认其中的属性不会导致 高层作用域的 变量遮蔽 。
使用 with 还可能引发安全漏洞。如果 传入 with 的对象 被 攻击者添加一些属性,那么就可能会遮蔽你的标识符,并会通过各种你意想不到的方式影响你程序的行为。
例如,从一个未经过验证的 JSON HTTP 请求体 中解析得到 JS对象 后,直接传给 with,这么做是极其危险的行为。
性能
把东西添加到 作用域链,会降低代码的运行速度,因为这增加了解析标识符到实际值时所需要搜索的对象数量。
你会被其他人排斥
如果你用了 with 关键字,所有人可能都会认为你疯了,然后在吃午饭的时候远离你。也许还会无情的对你发起嘲笑。
无论如何,使用这种没多少人知道的神奇语言特性,不但不会给你带来多少好处,还会让你的代码变得更难维护。
结论
with 关键字为 JavaScript 增添了一些有趣的能力,但是到头来 弊大于利,我个人是不推荐使用的。
当然,不要只听我的一家之言。MDN 也对 with 恨之入骨,在严格模式中禁用它,那是有理由的。
我写 JavaScript 超过 5 年了,令我惊讶的是,直到今天我还在学习语言的关键字,甚至这些关键字都不是最近才出现的。天呐,到底还有多少知识点在潜藏着呢?
with 源于什么?谁设计的?为什么这么设计?是想实现类似于 C++ 命名空间的东西吗?还是通过占卜得到的呢?
无论如何,被长期遗忘的 with 要被永远的扔进历史的垃圾箱了。
就像黑魔法一样,有趣又混乱!