- 定义在词法阶段的作用域
- 由你在写代码时将变量和 块作用域写在哪里来决定的,因此当词法作用域处理代码时会保持作用域不变(大部分情况)
如
function foo(a) { var b = a * 2 function bar(c) { console.log(a, b, c) } bar(b * 3) } foo(2)
这里有3个逐嵌套的作用域 (全局,foo(){...} , bar(){..}
全局作用域下 只有一个标识符 foo
包含foo所创建的作用域,有3个标识符:a,bar,b
包含bar...: 标识符c
- 作用域查找会在找到第一个匹配的标识符时停止
- 全局变量会自动成为全局对象(如window)的属性,因此可以不直接通过全局对象的词法名称,而间接通过对全局对象属性的引用对其进行访问 window.a
- 无论函数在哪里声明或被调用,它的词法作用域都只由函数被声明时所处的位置决定
欺骗词法
- eval
- with
1.eval(..)函数接受一个字符串作为参数,并将其中的内容视为好像书写时就存在于程序中这个位置的代码(简单说就是eval的第一个参数可以是一个声明变量的代码,并动态插入到eval函数所在的作用域内)
在执行eval函数只够的代码时,引擎并不在意/知道前面的代码是以动态形式插入进来, 并对此法作用域的环境进行了修改。引擎指挥如往常进行词法作用域查找
function foo(str,a){ eval(str) // 进行了词法欺骗,相当于插入了 var b = 3 代码段 console.log(a,b) } var b =2 foo ('var b =3' ,1) //1 ,3
实参'var b =3' ,实际上再foo函数内创建了一个变量b,并遮蔽了全局作用域中的同名变量
eval通常被用来执行动态创建的代码
eval可以在运行期修改书写器的词法作用域
2.with关键字通常被当做重复引用同一个对象的多个属性的快捷方式,可以不需要重复引用对象本身
var obj = { a: 1, b: 2, c: 3 } with (obj) { ;(a = 4), (b = 5), (c = 6) } console.log(obj.a)// 4
with可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个词法作用域中的词法标识符
而这个作用域内部的var声明并不会被限制在这个作用域内,而会被添加到with所处的函数作用域中
- eval函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而with声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域
性能
eval和with都不推荐使用
js引擎会在编译阶段进行数项的性能优化,其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的位置,才能在执行过程中快速找到标识符
(也就是说eval这种动态插入,with这种会将标识符声明到with所处函数作用域中的行为,让js引擎很难做优化,因为在它无法很好的坐静态分析,快速定位标识符位置)
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。
编译的词法分析阶段基本能够知道全部的标识符在哪个位置及如何声明的,从而能够预测在执行过程中如何对它进行查找
Comments | NOTHING