blog
blog copied to clipboard
突破JS的作用域规则
会JS的同学都知道JS的作用域规则,也就是在函数和块的内部,可以访问外部的变量,比如全局变量。看起来这是一条语言铁律,但是实际上我们可以突破这一点,可以让函数无法访问到外部的变量甚至全局变量。
首先给出一段典型的代码:
var a = 1;
function factory() {
console.log(a);
}
factory();
毫无疑问,这段代码会输出1,因为factory可以访问到外部的变量。那我们可不可以通过一定的手段让factory无法访问到a呢,答案是肯定的,只是需要一定的技巧。
首先,我们需要明确一点,JS函数中变量的引用是在函数定义点决定的,也就是说,只要在定义函数的地方访问不到外部的变量,函数也就访问不到了(废话︿( ̄︶ ̄)︿)。不过这听起来还是没啥用。
其次,我们知道,在JS中有个被批的很惨的关键字——with,而且还被严格模式给驱逐出境了。with被批的原因,一在于他可以改变作用域规则,让局部变量的优先级甚至更低;二则是因为浏览器厂商痛恨他,因为有他不利于性能提升。由此可以看出,我们可以利用with来改变一下作用域,比如说想要屏蔽到外部的变量a,就可以给个对象里面含有同名变量名。结合第一点,我们可以写出下面的代码:
var a = 1;
var shadow = {
a: 2,
};
function factory() {
console.log(a);
}
function sandbox(factory) {
with (shadow) {
return eval('((' + factory.toString() + ')())');
}
}
sandbox(factory);
上述代码会输出2,不过你肯定想说,这有什么卵用,我只能指定某个变量,然后拿个假的值来保护原值。嘿,这是没什么卵用,但是如果配合上ES6的一个新特性——Proxy就很有用了。
ES6的Proxy应该是大家很少接触的一个特性,这个特性使得JS可以元编程了。嘿,结合Proxy可以把我们的sanbox
函数改变得很有用处:
var a = 1;
function sandbox(factory) {
var shadow = new Proxy({
window: {},
factory,
eval,
console,
}, {
get(target, key, reveiver) {
return Reflect.get(target, key, reveiver);
},
has() {
return true;
}
});
with (shadow) {
return eval('((' + factory.toString() + ')())');
}
}
function factory() {
console.log(a);
console.log(this.a);
!function () {
var b = 3;
console.log(b);
}();
console.log(b);
}
sandbox(factory);
执行这个函数可以发现,外部变量甚至全局变量a无法被访问到了。而且由于函数的缘故,内部的作用域并没有被打乱,也就是说我们实际上获得了一个真·沙盒!!!
通过使用sandbox,我们完完全全突破了JS的作用域规则。对外部变量的访问受到我们控制了,只能访问到我们所设置的“白名单”里的对象,想怎么玩就怎么玩。
说到底还是with
@slashhuang Proxy更重要啦