blog icon indicating copy to clipboard operation
blog copied to clipboard

突破JS的作用域规则

Open renaesop opened this issue 8 years ago • 2 comments

会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的作用域规则。对外部变量的访问受到我们控制了,只能访问到我们所设置的“白名单”里的对象,想怎么玩就怎么玩。

renaesop avatar Oct 10 '16 09:10 renaesop

说到底还是with

slashhuang avatar Oct 10 '16 16:10 slashhuang

@slashhuang Proxy更重要啦

renaesop avatar Oct 11 '16 02:10 renaesop