《你不知道的javascript》--读书笔记(2)

词法作用域

将“作用域"定义为一套规则,用来管理引擎如何在当前作用于以及嵌套作用域中根据标识符名称进行变量查找。

作用域有两种主要的工作模型,词法作用域和动态作用域。

2.1 词法阶段

词法作用域就是定义在词法阶段的作用域,是由你在写代码时将变量和块作用域写在哪决定的。

function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar( b * 3 );
}
foo( 2 ); // 2, 4, 12
引擎执行console.log(...),从内部作用域bar(...)开始查找。
引擎无法再其中找到a,就回去上一级嵌套的foo(...)作用域继续查找,在其中找到了a。
  • 遮蔽效应:作用域查找会在找到第一个匹配的标识符时停止

作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见
第一个匹配的标识符为止。

全局变量会自动变为全局对象的属性,可以间接通过全局对象属性的引用来访问全局变量,这种方法可以访问被同名变量所遮蔽的全局变量,非全局变量被遮蔽,无法被访问到

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

2.2 欺骗词法

2.2.1 eval

  • 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;"这段代码会被当做本来就是在那里一样来处理,
所以它对已经存在的foo(...)作用域进行了修改。

因此当console.log(...)被执行时,咋foo(...)作用域中就已经找到a和b了,
而外部作用域的b被遮蔽,所以这里输出的是"1,3".
  • eval(…) 通常被用来执行动态创建的代码,因为像例子中这样动态地执行一段固定字符所组成的代码,并没有比直接将代码写在那里更有好处。

  • 在严格模式的程序中, eval(…) 在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域

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

2.2.2 with

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

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 = 2 赋值操作创建了一个全局的变量 a

原因:with可以将对象处理为完全隔离的词法作用域,所以该对象的属性会被处理为定义在此作用域的词法标识符。with块内部正常的var声明不会被限制在with块的作用域中,而会被添加到with所在的函数作用域中

不推荐使用 eval(…) 和 with 的原因是会被严格模式所影响(限制)。with 被完全禁止,而在保留核心功能的前提下,间接或非安全地使用eval(…) 也被禁止了。

2.2.3 性能

eval(…) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词
法作用域。

如果代码中出现了大量的eval(…)或with,引擎无法准确找到标识符的位置,只能简单地假设关于标识符位置的判断都是无效的,以为引擎无法再词法分析阶段明确知道eval(…)会接收到什么代码,会对作用域进行什么样的修改,也无法知道with创建新此法作用域对象的内容到底是什么。词法阶段引擎所做的优化可能就毫无意义。最简单的做法就是完全不做任何优化,那么如果程序中有大量的eval(…)和with,那么运行起来一定会变得非常慢。

2.3 小结

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

  • 词法分析阶段基本可以知道全部的标识符的位置以及如何声明,从而能够预测在执行过程中如何对其进行查找。

  • JavaScript两个机制“欺骗”词法作用域:eval(…)和with。

  • 非严格模式下,在运行时,eval(…)对一段包含声明的“代码”字符串进行演算,借此修改已经存在的作用域。同样with通过讲一个对象引用当作作用域来处理,把对象的属性当作作用域标识符,从而创建一个新的词法作用域。

  • 两个机制副作用是引擎在编译时对作用域查找进行优化。

猜你喜欢

转载自blog.csdn.net/NEW_cai/article/details/84951312