词法作用域 & 欺骗语法
程序员文章站
2022-07-15 10:40:11
...
词法作用域
- 词法作用域是定义在词法阶段的作用域,是在写代码时将变量和块作用域写在哪里来决定的。
- 无论函数在哪里/如何被调用,它的词法作用域都只由函数被声明时所处的位置来决定
欺骗词法
欺骗词法作用域会导致性能下降,不可使用,但是在此进行介绍其原理
1. eval:
- eval(…)可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
- 在执行eval(…)之后的代码时,引擎并不知道前面的代码是以动态形式插入进来的,并对词法作用域的环境进行修改的,引擎只会如往常的进行词法作用域查找。
观察以下代码:
function foo(str, a) {
eval(str); // 欺骗
console.log(a, b);// 1, 3
}
var b = 2; // 被覆盖
foo("var b = 3;", 1);
eval(…)调用中的“var b = 3;”这段代码会被当作本来就在那里一样来处理,由于这段代码声明了新的b,因此它对已经存在的foo(…)的此法作用域进行了修改,相当于在foo(…)内部创建了新变量b覆盖了外部(全局)作用域中的同名变量。
当console.log被执行时,会在foo(…)的内部同时找到a和b,但是永远也无法找到外部的b,因此输出1 3,而不是2 3。
但是,在严格模式的程序中,eval(…)在运行时有自己的此法作用域,其中的声明无法修改所在的作用域。如下代码:
function foo(str) {
"use strict";
eval(str); // 欺骗
console.log(a);// ReferenceError: a is not defined
}
foo("var a = 3;");
2. with
-with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
var obj = {
a: 1,
b: 2,
c: 3,
};
// 重复
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 简单的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}
- 性能
eval和with会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。如果引擎在代码中发现了二者,它只能简单地假设关于标识符位置的判断都是无效的,因为无法在词法分析阶段明确知道eval会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给with用来创建新词法作用域对象的内容到底是什么。而且,如果出现了eval和with,所有的优化可能都是无意义的,因此最简单的做法就是完全不做任何优化。如果代码中大量使用二者,运行起来会非常慢。