《你不知道的JavaScript - 上》之词法作用域

109 阅读3分钟

文章内容全是来自《你不知道的JavaScript - 上》,记录学习呀呀呀!!!

作用域共有两种主要的工作模型。第一种叫词法作用域,是最为普遍的,被大多数编程语言所采用。另外一种叫作动态作用域,仍有一些编程语言在使用(比如Bash脚本、Perl中的一些模式等)。

词法作用域

定义: 词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况),也会存在一些特例称为欺骗词法作用域。

下面这段代码及相应的分析,都是来印证上面的观点的。

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

为帮助理解,可以将它们想象成几个逐级包含的气泡。 image.png

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

在上一个代码片段中,引擎执行console.log(..)声明,并查找a、b和c三个变量的引用。它首先从最内部的作用域,也就是bar(..)函数的作用域气泡开始查找。引擎无法在这里找到a,因此会去上一级到所嵌套的foo(..)的作用域中继续查找。在这里找到了a,因此引擎使用了这个引用。对b、c同理。

总结: 假如a、b、c都存在于bar(..)函数中,那么都不需要逐级查找了,直接使用这三个变量,这个例子就是说明词法作用域是由写代码时的位置就决定了。

欺骗词法

定义: 是指通过一些技巧来改变 JavaScript 中的词法作用域,从而实现一些特殊的功能。欺骗词法的方法包括 eval 函数、with 语句等

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

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

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

虽然欺骗词法可以实现一些特殊的功能,但是由于会改变词法作用域,因此会导致代码的可读性和可维护性下降,同时也会带来安全性问题。因此,在编写代码时应该尽量避免使用欺骗词法。