eval与with(已完结)

eval与with

除了eval与with的基本用法外,本文着重强调的是通过eval与with欺骗作用域

eval(属于window)
  • eval的基本用法

JavaScript 中的eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码

例如:

    eval('var a = 100;');
    function test(){
        eval('var b = 200;');
        console.log(a + ',' + b);//100,200
    }
    test();
  • 通过eval欺骗词法作用域

思考以下代码:

    function foo(str, a) {
        eval( str ); // 欺骗!
        console.log( a, b );
    }
    var b = 2;
    foo( "var b = 3;", 1 ); // 1, 3

在执行eval(..) 之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的。引擎只会如往常地进行词法作用域查找。

eval(..) 调用中的”var b = 3;” 这段代码会被当作本来就在那里一样来处理。由于那段代码声明了一个新的变量b,因此它对已经存在的foo(..) 的词法作用域进行了修改。事实上,和前面提到的原理一样,这段代码实际上在foo(..) 内部创建了一个变量b,并遮蔽了外部(全局)作用域中的同名变量。当console.log(..) 被执行时,会在foo(..) 的内部同时找到a 和b,但是永远也无法找到外部的b。因此会输出“1, 3”而不是正常情况下会输出的“1, 2”

但是在严格模式下,eval无法修改作用域

如:

    var a = 10;
    function test(){
        "use strict";
        eval('var a = 20;');
        console.log(a);
    }
    test();//10
with关键字

当然,with本身就是用来改变作用域的

with会根据你传递给它的对象凭空创建一个全新的词法作用域。而这个对象的属性也会被处理为定义在这个作用域中的词法标识符(就相当于对象中的属性似乎变为了with生成的作用域中的变量一样)

考虑下列代码:

    function foo(obj) {
        with (obj) {
            a = 2;
        }
    }
    var o1 = {
        a: 3
    };
    var o2 = {
        b: 3
    };
    foo( o1 );
    console.log( o1.a ); // 2
    foo( o2 );
    console.log( o2.a ); // undefined
    console.log( a ); // 2——a 被泄漏到全局作用域上了

上述代码分析如下:

foo(..) 函数接受一个obj参数,该参数是一个对象引用,并对这个对象引用执行了with(obj){..}。
然后with生成一个词法作用域,o1中的属性a就相当于定义在with作用域中的变量

执行a=2,此时对a进行LHS查询,注意,这儿是首先到with形成的作用域中查询(至于为什么,请看后面的词法作用域包含分析)。在with中查询到a,并给a赋值2,此时a原来的值3被覆盖,现在值为2。所以
o1.a输出2

foo(..) 函数再次接受o2参数,该参数是一个对象引用,并对这个对象引用执行了with(obj){..}。
然后with生成一个词法作用域,o2中的属性b就相当于定义在with作用域中的变量

执行a=2,此时对a进行LHS查询,注意,这儿是首先到with形成的作用域中查询(至于为什么,请看后面的词法作用域包含分析)。在with中只有b,查询a失败(注意这而并不会在o2中创建一个a属性。因为这儿a=2不同于o2.a=2)

根据LHS查询规则,继续到上一层词法作用域(这儿是foo函数作用域)中查找,依然查询失败

在上一层(即全局)中查找,依然失败,根据LHS查询规则,会自动在全局作用域中创建一个变量a(非严格模式下)并赋值为2

固o2.a为undefined,全局a为2

对上述代码的词法作用域包含分析:

上述代码的词法作用域是这样嵌套的(由内向外):with词法作用域–>函数foo词法作用域–>全局作用域

尽管with 块可以将一个对象处理为词法作用域,但是这个块内部正常的var声明(只是变量声明,不包含赋值)并不会被限制在这个块的作用域中,而是被添加到with 所处的函数作用域中。(with中的函数声明也会被添加到with所处的函数作用域中)

小例题:

    function test(){
        with(obj){
            var b = 100;//b被提到test中声明,但在with中赋值
            console.log(b);//100
        }
        console.log(b);//undefined
    }
    var obj = {
        b : 222
    }
    test();
    console.log(obj.b);//100
    console.log(b);//报错
eval与with对性能的影响

JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但如果引擎在代码中发现了eval(..) 或with,它只能简单地假设关于标识符位置的判断都是无效的,因为无法在词法分析阶段明确知道eval(..) 会接收到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给with用来创建新词法作用域的对象的内容到底是什么。

ps:本文参考并引用下列书籍
《你不知道的JavaScript》(上卷)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值