js里的with,除了刚接触前端的时候知道不推荐用以外,就没有再有任何的了解和使用。但最近在看阿里巴巴的飞冰项目中的微前端的实现时,在其中沙箱用了with去阻断沙箱内对 window 全局变量的访问和修改。在好奇沙箱原理前,必须温习下with
首先with只能在非严格模式中使用,通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
var obj = {
a: 1,
b: 2,
c: void 0
}
with (obj) {
a = a + b;
c = a;
}
console.log(obj.c) // 3
上面看起来写法确实简洁,但有一个问题,可能会导致数据泄露,比如:如果上面的obj初始化时没有加c这个变量,还会打印出3吗?结果是undefined,但c的赋值去哪了?其实都离不开js的基础知识,这时涉及的就是作用域链了,with里找c属性,实际是找obj.c,下一步发现没有这个属性,就往上一个作用域去找,这时就是window了,而非严格模式下,即使没有声明c这个变量,但也会隐式去声明并赋值。
或者可以这样想with里面的运行状况
with (obj) {
a = a + b;
c = a;
}
等于:
function (obj) {
var a = obj.a;
var b = obj.b;
a = a + b;
c = a;
obj.a = a;
obj.b = b;
}()
另外with还有性能问题,原因是 浏览器JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但with的话,无法确认with里的作用域值。
可以这样对比下,性能差距还是挺大的:
function normal() {
console.time("normal");
var obj = {
a: [1, 2, 3]
};
for(var i = 0; i < 100000; i++)
{
var v = obj.a[0];
}
console.timeEnd("normal");
}
normal();
function funcWith() {
console.time("funcWith");
var obj = {
a: [1, 2, 3]
};
with(obj) {
for(var i = 0; i < 100000; i++) {
var v = a[0];
}
}
console.timeEnd("funcWith");
}
funcWith();