“eval” is not evil

一般我们代码几乎不会使用到eval, 但凡需要使用到eval的地方,都会代而使用构造器Function生成函数实例的方式,因为eval is evileval可以在全局作域下执行代码,也可以在局部作用域(间接调用eval)下执行代码。而使用构造器Function生成函数实例的方式,可以确保我们的代码是在全局作用域下执行。

依赖Function构造函数,我们可以实现自己的“eval”。这里我将实现的“eval”命名为$eval,以示区分。当然,$eval实现的功能和原生eval功能不尽相同。$eval可以让我们在指定作用域下执行代码。同时,这里还额外将$eval方法定义到Object.prototype中以适用不同场合。

下面是$evalObject.prototype.$eval的代码实现。

void function (global) {
  /**
   * Execute javascript code within specific scope
   * @param  {Function|String} fn Scoped function or expression
   * @param  {Object} imports     An object which defines required local variables
   * @return {Function}           Function that can be invoked to run code in specific scope
   */
  function scopedRunner(fn, imports) {
    var rfunc = /^function\s+(?:[^(]*)\(([^)]*)\)\s*{([^]*)}$/;
    var found = String(fn).match(rfunc) || [,,'return (' + fn + ');'];
    var args = found[1] || '';
    var stmt = found[2] || '';

    var body = Object.keys(imports || {}).reduce(function (ret, key, idx) {
      return ret + (idx ? ', ' : 'var ') + key + ' = $scope["' + key + '"]';
    }, '') + '; return function (' + args + ') {' + stmt + '};';

    return Function('$scope', body).call(null, imports);
  }

  // define `global.$eval`
  Object.defineProperty(global, '$eval', {
    value: function () {
      return scopedRunner.apply(null, arguments)();
    }
  });

  // define `Object.prototype.$eval`
  Object.defineProperty(Object.prototype, '$eval', {
    value: function (expr) {
      return global.$eval(expr, this);
    }
  });
}(function () { return this; }());

以下是一些关于$evalObject.prototype.$eval的使用例子。

  • e.g 1
// define some global variables
x = 10;
y = 30;

void function () {
  // define some local variables
  var a = 6, b = 7;

  $eval(function () {
    // Uncaught ReferenceError: a is not defined
    // console.log(a + b);
  });

  // Uncaught ReferenceError: a is not defined
  // console.log($eval('a + b'));

  console.log($eval('y / x')); //=> 3
}();
  • e.g 2
void function () {
  var obj = { a: 6, b: 7 };
  var result;

  result = $eval(function () {
    return a * b;
  }, obj);
  console.log(result); //=> 42

  result = $eval('a * b', obj);
  console.log(result); //=> 42

  result = obj.$eval('a * b');
  console.log(result); //=> 42
}();
  • e.g 3
void function () {
  var raws = ['42', '"42"', 'a * b', '[a, b]', '{ x: a, y: b }'];
  var out = raws.map(eval.$eval.bind({ a: 6, b: 7 }));

  //=> [42, '42', 42, [6, 7], { x: 6, y: 7 }]
  console.log(out);
}();
  • e.g 4
x = 10;
y = 30;

void function () {
  var obj = {
    x: 6,
    y: 7,
    times: function () {
      return this.x * this.y;
    },
    sum: function (x, y) {
      return x + y;
    }
  };

  console.log(
    $eval('$scope', obj) === obj,                        //=> true
    $eval(function () { return $scope; }, obj) === obj,  //=> true
    obj.$eval('$scope') === obj,                         //=> true
    obj.$eval(function () { return $scope; }) === obj    //=> true
  );

  console.log(
    obj.$eval('times()'),             //=> 300
    obj.$eval('$scope.times()'),      //=> 42
    obj.$eval('times.call($scope)')   //=> 42
  );

  obj.$eval(function () {
    console.log(times());             //=> 300
    console.log($scope.times());      //=> 42
    console.log(times.call($scope));  //=> 42
  });

  console.log(
    obj.$eval('sum(x,y)'),                          //=> 13
    obj.$eval(function () { return sum(x, y); }),   //=> 13
    $eval('sum(x,y)', obj),                         //=> 13
    $eval(function () { return sum(x, y); }, obj)   //=> 13
  );
}();

最后,$eval is not eval, $eval is not evil