with欺骗作用域

with关键字可以划分出一个属于某个对象的作用域,然后在该作用域内对该对象的属性进行更新。
例子:

var obj = {
    a:1,
    b:2,
    c:3
 }
 
 obj.a = 11;
 obj.b = 22;
 obj.c = 33;
 //等价于
 with(obj){
    a = 11;
    b = 22;
    c = 33;
}

但是如果在with划分出来的作用于里面对一些不属于与之绑定的对象的属性进行赋值时,会有特殊情况发生:

function f(obj){
    with(obj){
        a=34;
    }
    console.log(obj);
}
var o1={a:1},o2={b:2};
f(o1);
f(o2);
console.log(o2.a);
console.log("window",a);
//输出
{a:34}
{b:2}
undefined
window 34

因为o1中有属性a,所以它的a被改变了,而o2中没有,这里并不会给它添加一个属性a,所以o2.a依然是undefined。而事实上,在传入参数为o2,执行a=34时,JavaScript引擎对a进行了LHS操作,引擎会沿着作用域链去查找a,如果直到全局作用域还没找到,就会创建一个a,所以最后可以看到全局作用域下有了一个a,并且被赋值为34.

如果试着在函数f内创建一个变量a,在传入参数为o2时,改变的是f函数里的a,全局作用域下并不会创建a.

function f(obj){
  var a = 9;
    with(obj){
        a=34;
    }
    console.log(obj);
  console.log("f",a)
}
var o1={a:1},o2={b:2};
f(o1)
f(o2)

console.log("window",a)
//输出
{a:34}
f 9
{b:2}
f 34
Uncaught ReferenceError: a is not defined

如果在with块内用var声明变量,该变量会被创建到with所在的作用域里:
不加var

function f(obj){
    with(obj){
       a=34;
    }
    console.log(obj);
    console.log("f",a)
}
var o1={a:1},o2={b:2};
f(o1)
f(o2)
//f(o1)和f(o2)分别会输出
{a:34}
Uncaught ReferenceError: a is not defined
{b:2}
f 34

f(o1)在打印a时会出错,因为f函数作用域和window作用域都没有创建过a。
f(o2)输出的a其实是window.a,因为函数可以访问外部作用域的变量。
看看加了var的:

function f(obj){
    with(obj){
       var a=34;
    }
    console.log(obj);
  console.log("f",a)
}
var o1={a:1},o2={b:2};
f(o1)
f(o2)
console.log("window",a);
//输出
{a:34}
f undefined
{b:2}
f 34
Uncaught ReferenceError: a is not defined

这次调用f(o1)时没报错了,因为var a的声明被提升到了函数作用域上,而赋值操作依然是对obj.a进行的(如果有该属性的话)。
在with内使用const和let,既不能提升到外层作用域,也不能完成赋值。

参考:《你所不知道的JavaScript》(上卷)

上一篇:New运算符工作原理


下一篇:线程剩余内容