为什么不建议使用eval和with?

假装被面试

面试官:为什么不建议使用eval和with?

因为影响性能、减低代码的安全性、代码更加难于阅读。

那为什么会影响性能?、为何会减低安全性?、怎么就让代码更加难于阅读?

这个些问题主要是考察Js开发人对词法作用域的理解,如果你和我一样一脸蒙蔽那就跟着我一起来看看这其中的"奥秘"吧!

先了解一下什么是词法作用域

简单地来说,词法作用域就是定义在词法阶段的作用域。词法作用域是由你在写代码时将变量和块作用域写在哪了来决定的,因此当词法分析器处理代码时会保存作用域不变(大部分情况下是这样)

下面用一张图帮助理解作用域:


来自《你不知道的JavaScript上卷》

(1)、包含着整个全局作用域,其中只有一个标识符:foo
(2)、包含着foo所创建的作用域,其中有三个标识符: a、b、 bar
(3)、包含着bar所创建的作用域,其中只有一个标识符:c

从上面那张图片可以看到,作用域是逐级嵌套的。在执行console.log(a, b, c)时,引擎会从最内部的作用域(3)开始查找,如果没有找到,就逐层往外找。⚠️作用域查找会在找到第一个匹配的标识符时停止


下面回归正题,为什么会影响性能?

这里就要提到“欺骗词法(代码在运行的时候"修改"词法作用域)”这个词, JavaScript引擎在编译阶段有一些性能上的优化是依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速的找到标识符。

但如果引擎在代码中发现了eval或with,它只能简单地假设关于标识符位置但判断都是无效的,因为无法在词法分析阶段明确的知道eval会接收到什么代码,这些代码是如何对作用域进行修改,因此最简单的做法就是完全不做任何优化。如果代码中大量使用eval和with,导致引擎没有对这些进行优化。那么代码运行起来一定会变得更慢。

代码安全问题

我们都知道eval接收一个字符串,然后解释为一段可执行的代码,在传递进来的字符为不可预知的情况,这是多么危险的行为。在JavaScript中有类似功能,还有: setTimeoutsetIntervalnew Function,我们应该尽量避免这样使用它们。

说到with,它会在你不知的情况下把一些内部作用域声明的函数或者变量泄露到其他作用域,例如泄露到全局:

function a() {
 console.log('hello')
}

function foo(obj) {
 with(obj) {
   a = function() {
     console.log("hi")
   }
 }
}

var obj = {
 b: 3
}
a();   // hello
foo(obj)
console.log(obj.a)   // undefined
a();                        // hi

在obj创建了作用域没有找到a,然后在foo()作用域找,也没有找到、最后在全局作用域中找到了,然后就把它给改了。

代码更加难于阅读

这个就不难说明,如果传递给它们的参数都是不可预知的变量。那肯定难于阅读,因为你根本不知道代码下一步运行会不会改变这个变量。

总结

with和eval的本意是好,但它们阻断了变量名的词法作用域绑定,导致的副作用远远大于它的使用价值,所以我们应该在开发中避免使用它们。


参考资料

《你不知道的Javascript》上卷

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 9,689评论 0 13
  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 6,948评论 0 11
  • 特别说明,为便于查阅,文章转自https://github.com/getify/You-Dont-Know-JS...
    杀破狼real阅读 3,567评论 0 3
  • 在之前我们讲过,将作用域定义成“一套规则”,这套规则用于管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称...
    我就是z阅读 4,676评论 4 9
  • 晨读感悟 过程与结果 对于目前的我来说结果比过程重要,并不是说忽略过程,而是比较之下更在乎结果。我想是目前所处的社...
    梅梅_c7d4阅读 1,631评论 0 1