js中的eval和with

js中有两个方法可以改变词法作用域,eval(...)和with(...)。前者可以接受一段字符串代码来进行演算,并借此来修改已存在的词法作用域,后者本质上是通过将一个对象的引用当作词法作用域,将对象的属性当作作用域中的标识符来处理,从而创建一个新的词法作用域。

首先我们来理解词法作用域的概念:

作用域

图中的1和2分别表示两个作用域,这里的b变量只能在作用域1中找到,一般来说,变量所在的作用域都与变量声明的位置有关,但是在JS中的eval和with函数可以改变作用域,让变量的作用域与声明时所在的位置无关,我们再来看下面这个例子:

function test(str) {
    eval(str);
    console.log(a + b); //输出3
}
test("var a = 1; var b = 2");

这里按照我们的理解,console.log(a + b)应该报出ReferenceError,但是因为eval的存在并且接受一个声明a和b两个变量的字符串导致程序不是我们预期的结果。而a和b的作用域也变成了test函数内。

我们再来看一下with函数:

//我们给一个对象赋值的方式
var obj = {
    a: 1,
    b: 2,
    c: 3
}
//或者
obj.a = 1;
obj.b = 2;
obj.c = 3;
//使用with函数
with(obj) {
    a = 1;
    b = 2;
    c = 3;
}

这种方式不仅仅可以用来方便的访问属性对象,也可以用来改变词法作用域:

function test (obj) {
    with(obj) {
        a = 2;
    }
}
var o1 = {
    a: 3
}
var o2 = {
    b: 3         
}
test(o1);
console.log(o1.a); //2
test(o2);
console.log(o2.a); //undefined
//接下来我们输出一下全局a的值,我们会发现a泄漏到了全局变量上
console.log(a);//2

首先,当将o1传递给test时,with函数创建了一个o1对象的作用域,并且在这个作用域中找到了a,然后将2赋给a。当o2传递给test时,with函数创建一个o2对象的作用域,然后在这个作用域中没有找到a,接下来根据LHS查找规则,会向上层作用域查找a,这里with创建的o2作用域的上层作用域是test函数,在test中也没有找到,接下来再向上找,也就是全局作用域,在全局作用域中也没找到,然后就会在最上层作用域中创建一个var a = 2。

上面就是eval和with函数的作用,现在我们一般都尽量不会去使用者两个函数,因为在JS的严格模式下会对这两个函数有限制,并且,因为编译器无法知道eval函数和with函数接受到的参数会是什么,所以编译器在编译时很难对代码进行优化,在程序中出现很多eval和with时会使程序变得非常慢。

总之,eval和with可以在运行时修改或创建新的词法作用域,但是我们要尽量避免使用它们。

猜你喜欢

转载自blog.csdn.net/caishi13202/article/details/82699744