谈谈eval另一面

之前在祼看ECMA262-5,在说到eval的处所,死活看不邃晓为何会有一节特地扯到Direct Call to Eval

A direct call to the eval function is one that is expressed as a CallExpression that meets the following two conditions:

The Reference that is the result of evaluating the MemberExpression in the CallExpression has an environment record as its base value and its reference name is “eval”.

The result of calling the abstract operation GetValue with that Reference as the argument is the standard built-in function defined in 15.1.2.1.

当时以为写范例的那群家伙肯定是有毛病,尽人皆知,eval无非是吸收一个字符串,把这个字符串当作代码诠释实行。

这个题目我一向不邃晓,然后就放着。

直到有一天…..

关于闭包

某一天,小伙伴们讨论到说关于闭包变量的题目时,Y君指出,假如一个函数没有援用到其所处闭包的a变量,那这个a变量所指向的空间将被开释。

function getClosure() {
    var a = 1;
    var b = 2;

    return function inner() {
      debugger;
      console.log(b);
    }
}
var func = getClosure();
func();

正如上面代码所示,当翻开Chrome调试东西时抵达debugger语句时,我们只能接见到b变量。试图接见a是会抛出ReferenceError的。

然则这真的能证实a援用已被开释了吗?假如没被开释的话,还不让在调试的时刻接见,这个设想就只能说是(哔~)坑爹了,Y君补充道。

因而我写了以下代码做了测试:

function getClosure() {
    var a = 1;
    var b = 2;

    return function inner(val) {
      debugger;
      console.log(eval(val));
    }
}

var func = getClosure();
func('a');
func('b');

在闭包中运用eval,如许引擎就不晓得我在闭包中的会援用到什么变量了。这一次抵达debugger语句时,惊异地发明,ab都能够直接援用到了。岂非是由于eval的缘由?引擎在发明闭包中有eval以后,就不会接纳闭包中的渣滓了?(由于它无从得知哪些变量会在未来被援用到)。

但假如引擎不晓得未来会不会实行到eval呢?

function getClosure(){
    var a = 1;
    var b = 2;

    return function inner(func, val) {
        debugger;
        console.log(func(val));
    }
}

var f = getClosure();

f(eval, a);

eval当作函数传进去,然后让这个eval函数诠释实行传入的val指向的变量。遗憾的是,到在debugger处,无法接见到a和b,实行到console.log(func(val))抛了非常,示意找不到援用。

因而我才回过神,好像之前阅读过蛇精病般的什么direct call to eval的东西,跟这个相干?

回到eval

在范例中指出,进挪用eval函数时:

  • 假如是直接挪用eval函数的话,将当前的this指向词法环境以及变量环境当作新的实行高低文的this指向,词法环境以及变量环境
  • 假如不是直接挪用的话,则新的实行高低文就相当于全局实行高低文。
    >更正确地说,则是以global对象全局词法环境以及全局变量环境当作新的实行高低文的this指向,词法环境以及变量环境

那什么是直接挪用呢?参照篇首。简言之,有以下限定:

a. BaseValue必需是一个Environment Record
b. Reference Name必需是”eval”

以下例子:

var a = eval;
a('hello'); //非直接挪用,由于Reference Name是'a'而不是'eval'

var f = {eval: eval};
f.eval("hello"); //非直接挪用,由于BaseValue是f变量,而不是Environment Record

eval('hello'); //直接挪用

function test() {
    var eval = window.eval,
        x    = window.eval;
    eval('hello'); //直接挪用
    x('hello'); //非直接挪用
}

看到规律了吧?只如果祼的eval挪用就是direct call,前面不要有宿主对象,且函数名肯定如果eval字符串。

再回到闭包

了解了什么是直接eval挪用后,假如我弄出如许的代码呢?

function getClosure() {
    var a = 1;
    var b = 2;
    var eval = function(x){
      return x;
    }

    return function inner(val) {
      debugger;
      console.log(eval(val));
    }
}

var func = getClosure();
func('a');
func('b');

getClosure中给一eval赋成一个完整不相干的函数,进入到debugger时,能不能接见到ab这两个变量呢?思索一下。

末了

从eval能够管窥出来,ES5的范例几乎是从引擎实际者的角度来斟酌题目。假如高低文中没有eval的话,那末闭包中的变量将被很好的开释掉(并不完整正确,拜见A surprising JavaScript memory leak found at Meteor),由于引擎能够检测出哪些变量会被援用到,而哪些不会。

关于ES3的引擎而言,如IE6,IE7, IE8,不管闭包中是不是包括eval,它们都不会开释掉那些再也援用不到的变量。缘由在于,ES3范例中没有对非直接eval挪用举行范例,以下代码在IE6,7,8能经由过程,而在当代浏览器中会报错:

function getClosure(){
    var a = 1;
    var b = 2;

    return function (func, val) {
        alert(func(val));
    }
}

var f = getClosure();
f(eval, 'a');
f(eval, 'b');

结论

  1. 不要在闭包顶用eval,很多渣滓将得不到接纳。
  2. 不要采纳eval当函数名,引擎会误认为那是一个direct call to eval的,然后内存得不到开释。
  3. 从理论上而言,ES5比ES3要有更好的机能,ES5更多地斟酌到了内存治理的题目。
    原文作者:ssnau
    原文地址: https://segmentfault.com/a/1190000000534251
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞