资讯专栏INFORMATION COLUMN

谈谈eval另一面

ybak / 2928人阅读

摘要:直到有一天关于闭包某一天,小伙伴们讨论到说关于闭包变量的问题时,君指出,如果一个函数没有引用到其所处闭包的变量,那这个变量所指向的空间将被释放。对于的引擎而言,如,,,无论闭包中是否包含,它们都不会释放掉那些再也引用不到的变量。

之前在祼看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");
结论

不要在闭包中用eval,许多垃圾将得不到回收。

不要采用eval当函数名,引擎会误认为那是一个direct call to eval的,然后内存得不到释放。

从理论上而言,ES5比ES3要有更好的性能,ES5更多地考虑到了内存管理的问题。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78156.html

相关文章

  • 谈谈JavaScript中严格模式你应该遵守的那些事

    严格模式 首先来了解一下严格模式是什么?严格模式是JavaScript中的一种限制性更强的变种方式,不是一个子集:它在语义上与正常代码有明显的差异,不支持严格模式的浏览器与支持严格模式的浏览器行为上也不一样,所以不要在未经严格模式特性测试情况下使用严格模式,严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式 严格模式的目的 首先,严格模式会将JavaScript陷阱直接变成明显的错...

    MingjunYang 评论0 收藏0
  • 2018年蚂蚁金服前端一面总结(面向2019届学生)

    摘要:在这次蚂蚁金服的电话面试里面认识到了自己很多不足的地方吧。把字符串分割为字符串数组。从起始索引号提取字符串中指定数目的字符。通常消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。 先简短的介绍一下我自己吧,我是一个前端学习者,虽然我基础知识也学了比较好,但是许久不用的知识就像流失的水,很容易就忘。在这次蚂蚁金服的电话面试里面认识到了自己很多不足的地方吧。虽然在阿里内推后的人才...

    VEIGHTZ 评论0 收藏0
  • 2018年蚂蚁金服前端一面总结(面向2019届学生)

    摘要:在这次蚂蚁金服的电话面试里面认识到了自己很多不足的地方吧。把字符串分割为字符串数组。从起始索引号提取字符串中指定数目的字符。通常消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。 先简短的介绍一下我自己吧,我是一个前端学习者,虽然我基础知识也学了比较好,但是许久不用的知识就像流失的水,很容易就忘。在这次蚂蚁金服的电话面试里面认识到了自己很多不足的地方吧。虽然在阿里内推后的人才...

    RobinQu 评论0 收藏0
  • Java开发 大厂面试整理

    摘要:用户态不能干扰内核态所以指令就有两种特权指令和非特权指令不同的状态对应不同的指令。非特权指令所有程序均可直接使用。用户态常态目态执行非特权指令。 这是我今年从三月份开始,主要的大厂面试经过,有些企业面试的还没来得及整理,可能有些没有带答案就发出来了,还请各位先思考如果是你怎么回答面试官?这篇文章会持续更新,请各位持续关注,希望对你有所帮助! 面试清单 平安产险 飞猪 上汽大通 浩鲸科...

    Scorpion 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<