|
| 1 | +with 是一个不推荐使用的语法,因为它的作用是改变上下文,而上下文环境对开发者影响很大。 |
| 2 | + |
| 3 | +本周通过 [JavaScript's Forgotten Keyword (with)](https://dev.to/mistval/javascript-s-forgotten-keyword-with-48id) 这篇文章介绍一下 with 的功能。 |
| 4 | + |
| 5 | +## 概述 |
| 6 | + |
| 7 | +下面是一种使用 with 的例子: |
| 8 | + |
| 9 | +```javascript |
| 10 | +with (console) { |
| 11 | + log('I dont need the "console." part anymore!'); |
| 12 | +} |
| 13 | +``` |
| 14 | + |
| 15 | +我们往上下文注入了 `console` 对象,而 `console.log` 这个属性就被注册到了这个 Scope 里。 |
| 16 | + |
| 17 | +再比如: |
| 18 | + |
| 19 | +```javascript |
| 20 | +with (console) { |
| 21 | + with (['a', 'b', 'c']) { |
| 22 | + log(join('')); // writes "abc" to the console. |
| 23 | + } |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +通过嵌套,我们可以追加注入上下文。其中 `with (['a', 'b', 'c'])` 其实是把 `['a', 'b', 'c']` 的返回值对象注入到了上下文,而数组对象具有 `.join` 成员函数,所以可以直接调用 `join('')` 输出 `"abc"`。 |
| 28 | + |
| 29 | +为了不让结果这么 Magic,建议以枚举方式申明要注入的 key: |
| 30 | + |
| 31 | +```javascript |
| 32 | +with ({ myProperty: 'Hello world!' }) { |
| 33 | + console.log(myProperty); // Logs "Hello world!" |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +那为什么不推荐使用 with 呢?比如下面的情况: |
| 38 | + |
| 39 | +```javascript |
| 40 | +function getAverage(min, max) { |
| 41 | + with (Math) { |
| 42 | + return round((min + max) / 2); |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +getAverage(1, 5); |
| 47 | +``` |
| 48 | + |
| 49 | +注入的上下文可能与已有上下文产生冲突,导致输出结果为 `NaN`。 |
| 50 | + |
| 51 | +所以业务代码中不推荐使用 with,而且实际上在 **严格模式** 下 with 也是被禁用的。 |
| 52 | + |
| 53 | +## 精读 |
| 54 | + |
| 55 | +由于 with 定义的上下文会优先查找,因此在前端沙盒领域是一种解决方案,具体做法是: |
| 56 | + |
| 57 | +```javascript |
| 58 | +const sandboxCode = `with(scope) { ${code} }` |
| 59 | +new Function('scope', sandboxCode) |
| 60 | +``` |
| 61 | + |
| 62 | +这样就把所有 scope 定义的对象限定住了。但如果访问 scope 外的对象还是会向上冒泡查找,我们可以结合 Proxy 来限制查找范围,这样就能完成一个可用性尚可的沙盒。 |
| 63 | + |
| 64 | +第二种 with 的用法是前端模版引擎。 |
| 65 | + |
| 66 | +我们经常看到模版引擎里会有一些 `forEach`、`map` 等特殊用法,这些语法完全可以通过 with 注入。当然并不是所有模版引擎都是这么实现的,还有另一种方案是,现将模版引擎解析为 AST,再根据 AST 构造并执行,如果把这个过程放到编译时,那么 JSX 就是一个例子。 |
| 67 | + |
| 68 | +最后关于 with 注入上下文,还有一个误区,那就是认为下面的代码仅仅注入了 `run` 属性: |
| 69 | + |
| 70 | +```javascript |
| 71 | +with ({ run: () => {} }) { |
| 72 | + run() |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +其实不然,因为 with 会在整个原型链上查找,而 `{}` 的原型链是 `Object.prototype`,这就导致挂在了许多非预期的属性。 |
| 77 | + |
| 78 | +如果想要挂载一个纯净的对象,可以使用 `Object.create()` 创建对象挂载到 with 上。 |
| 79 | + |
| 80 | +## 总结 |
| 81 | + |
| 82 | +with 的使用场景很少,一般情况下不推荐使用。 |
| 83 | + |
| 84 | +如果你还有其他正经的 with 使用场景,可以告知我,或者给出评论。 |
| 85 | + |
| 86 | +> 讨论地址是:[精读《JS with 语法》· Issue #343 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/343) |
| 87 | +
|
| 88 | +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** |
| 89 | + |
| 90 | +> 关注 **前端精读微信公众号** |
| 91 | +
|
| 92 | +<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg"> |
| 93 | + |
| 94 | +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |
0 commit comments