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

Posted kjcy8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你不知道的JavaScript-2.词法作用域相关的知识,希望对你有一定的参考价值。

考虑以下代码:

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log( a, b, c );
    }
    bar( b * 3 );
}
foo( 2 ); // 2, 4, 12

在这个例子中有三个逐级嵌套的作用域。

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


全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问。
window.a
通过方式可以访问那些被同名局部变量所遮蔽的全局变量。但非全局的变量如果被遮蔽了,无论如何都无法被访问到。


eval

javascript 中的 eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。

考虑以下代码:

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

eval(..) 调用中的 "var b = 3;" 这段代码会被当作本来就在那里一样来处理。
由于那段代码声明了一个新的变量 b ,因此它对已经存在的 foo(..) 的词法作用域进行了修改。事实上,和前面提到的原理一样,这段代码实际上在 foo(..) 内部创建了一个变量 b ,并遮蔽了外部(全局)作用域中的同名变量。


with

with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

// 单调乏味的重复 "obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 简单的快捷方式
with (obj) {
    a = 3;
    b = 4;
    c = 5;
}

但实际上这不仅仅是为了方便地访问对象属性。考虑如下代码:

function foo(obj) {
    with (obj) {
      a = 2;
    }
}
var o1 = {
    a: 3
};
var o2 = {
    b: 3
};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!    

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

可以这样理解,当我们传递 o1 给 with 时, with 所声明的作用域是 o1 ,而这个作用域中含有一个同 o1.a 属性相符的标识符。但当我们将 o2 作为作用域时,其中并没有 a 标识符,因此进行了正常的 LHS 标识符查找.

o2 的作用域、 foo(..) 的作用域和全局作用域中都没有找到标识符 a ,因此当 a=2 执行时,自动创建了一个全局变量(因为是非严格模式)。


eval、with 会导致引擎无法在编译时对作用域查找进行优化,从而影响性能。

以上是关于你不知道的JavaScript-2.词法作用域的主要内容,如果未能解决你的问题,请参考以下文章

你不知道的JS系列 ( 7 ) - 欺骗词法作用域

你不知道的JavaScript上卷 - 读书笔记 - 第2章词法作用域-2.2 欺骗词法

你不知道的JavaScript上卷 - 读书笔记 - 第2章词法作用域-2.2 欺骗词法

《你不知道的javascript》——词法/函数/块作用域

你不知道的Javascript(上卷)读书笔记之二 ---- 词法作用域

《你不知道的JS(上卷)》作用域闭包