美文网首页
你不知道的JavaScript-词法作用域

你不知道的JavaScript-词法作用域

作者: 前端小白的摸爬滚打 | 来源:发表于2022-01-20 11:22 被阅读0次

    词法阶段

    词法作用域:由你写代码时将变量和块作用域写在哪里所决定,因此当词法解析器在处理代码时会保持作用域不变

    无论函数在哪里被调用,也无论它如何被调用,它的词法作用域只由函数被声明时所处的位置决定

    函数中的 this 是在函数调用的时候才能决定,和函数定义的位置无关

    欺骗词法

    如果词法作用域完全由函数声明的位置来定义,那么怎样才能在运行时修改(欺骗)词法作用域呢?

    JS 中有两种机制可以实现欺骗词法作用域,但是他们都有一个很大的问题:欺骗词法作用域会导致性能下降

    eval

    eval 可以接受一个字符串作为参数,并将字符串的内容视为 在书写时就存在于程序这个位置的代码。也就是相当于你的代码就定义在 eval 函数调用的位置

    function foo(str, a) {
      eval(str); // 欺骗
      console.log(a, b);
    }
    var b = 2;
    foo("var b = 3", 1); // 1 3
    

    var b = 3相当于本来就在 foo 的那行来处理。由于那段代码声明了一个新的变量 b,相当于对已经存在的 foo 的词法作用域进行了修改。在 foo 中定义了一个变量 b,并且遮蔽了全局作用域中的变量 b。

    eval 可以在运行期间修改书写时期的词法作用域

    在严格模式下,eval 函数在运行时有自己的词法作用域,这意味着其中的声明无法修改所在的作用域

    function foo(str) {
      eval(str);
      console.log(a); // ReferenceError: a is not defined
    }
    foo("var a = 2");
    

    with

    function foo(obj) {
      with (obj) {
        a = 2;
      }
    }
    const o1 = {
      a: 3
    };
    const o2 = {
      b: 3
    };
    
    foo(o1);
    console.log(o1.a); // 2
    
    foo(o2);
    console.log(o2.a); // undefined
    console.log(a); // 2 a 被泄露到了全局作用域
    

    这个例子中创建了 o1 和 o2 两个对象。其中一个具有 a 属性,另外一个没有。foo(..) 函 数接受一个 obj 参数,该参数是一个对象引用,并对这个对象引用执行了 with(obj) {..}。 在 with 块内部,我们写的代码看起来只是对变量 a 进行简单的词法引用,实际上就是一个 LHS 引用(查看第 1 章),并将 2 赋值给它。

    当我们将 o1 传递进去,a = 2 赋值操作找到了 o1.a 并将 2 赋值给它,这在后面的 console. log(o1.a) 中可以体现。而当 o2 传递进去,o2 并没有 a 属性,因此不会创建这个属性, o2.a 保持 undefined。

    但是可以注意到一个奇怪的副作用,实际上 a = 2 赋值操作创建了一个全局的变量 a。

    尽管 with 块可以将一个对象处理为词法作用域,但是这个块内部正常的 var 声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作 用域中。

    eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而 with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。

    严格模式下,with 会被完全禁止

    性能

    使用 eval 和 with 会导致性能变差

    JavaScript 引擎会在编译阶段进行性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。

    最悲观的情况是如果出现了 eval(..) 或 with,所有的优化可能都是无意义的,因此最简单的做法就是完全不做任何优化。

    小结

    词法作用域,意味着作用域是由书写代码时函数声明的位置来决定。

    JavaScript 中有两个机制可以“欺骗”词法作用域:eval(..) 和 with。也就是在代码运行时修改词法作用域

    这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。不要使用它们。

    相关文章

      网友评论

          本文标题:你不知道的JavaScript-词法作用域

          本文链接:https://www.haomeiwen.com/subject/rorjhrtx.html