2.1 词法阶段
词法作用域
- 是定义变量在词法阶段的作用域
- 即由变量在代码中的位置决定的作用域
- 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
变量查找
从代码当前位置所在的作用域逐层寻找变量,作用域查找会在找到第一个匹配的标识符时停止。
遮蔽效应
多层的嵌套作用域中可以定义同名的标识符,内部的标识符“遮蔽”了外部的标识符
全局变量
全局变量会自动成为全局对象,在浏览器中全局变量会被添加到window的属性上,比如全局变量a,可以通过window.a进行访问.
2.2 欺骗词法
在运行时来“修改”(欺骗)词法作用域。
Js中有2种实现机制
eval(code)函数
接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。
修改局部变量的声明(非严格模式)
function foo(str, a) {
eval(str);//使用eval来在此处声明一个内部的变量b
console.log(a, b);//str中声明的b会遮蔽外部的变量b
}
var b = 2;
foo("var b = 3;", 1);//1,3
类似机制(不推荐)
- setTimeout(...),setInterval(...)第一个参数可以是字符串代码
setInterval("console.log(111)",1000);
setTimeout("console.log(222)",1000);
- new Function(...) 接收字符串代码
const fun = new Function("a", "b", "console.log(a,b)");
fun(21, 22);
with机制
不推荐
with 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。
原始作用,快捷的访问对象属性
var obj = { a: 1, b: 2, c: 3 }
// 点语法
obj.a;
obj.b;
obj.c;
console.log(obj.a, obj.b, obj.c);
// with
with (obj) {
a;
b;
c;
console.log(a, b, c);
}
由于变量的LHS引用,当全局中找不到变量a时,就会帮忙声明一个a,所以a被声明到全局中了
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
}
var o2 = {
b: 4
}
foo(o1);
console.log(o1.a);//2
foo(o2);
console.log(o2.a);//undefined
console.log(a);//2 被泄露到全局作用域中了
console.log(window.a);//2
eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。
尽管 with 块可以将一个对象处理为词法作用域,但是这个块内部正常的 var声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作用域中。
严格模式中,with被禁用
对性能的影响
- js引擎的编译期优化不会进行,因为优化需要预先确定所有变量和函数的位置,而eavl和with可能会带来新的变量声明或作用域变化等,因此直接不做优化,所以导致性能下降。