JS代码作用域解析

作用域链

作用域可以理解为一套规则,用于确定在何处以及如何查找变量(标识符)。这里所指的作用域,指词法作用域,即在书写代码的时候由函数或变量声明的位置来决定。javascript引擎在编译阶段会对代码进行词法分析,预先确定所有变量和函数的定义位置。要理解javascript中的作用域首先要搞清楚两个概念:执行环境(execution
context),变量对象(variable object)。

        执行环境只有两种,即全局执行环境和局部执行环境,在web浏览器中,全局执行环境指的是window对象,因此任何全局变量、全局函数都被当作是window对象的属性和方法。而每个javascript函数都有自己的执行环境,即局部环境。当执行流进入一个函数时,当前函数的局部环境便会被压入环境栈中,在函数执行完毕之后,栈将该函数局部环境弹出,该环境被销毁,任何定义在该环境中的变量或者函数也都被销毁。全局执行环境中的变量或者函数直到应用程序退出时(关闭网页或者浏览器)才会被销毁。
        每个执行环境都有一个与之相关联的变量对象,它由当前执行环境中所定义的函数和变量组成。当代码在环境中执行的时候,会创建一个由变量对象组成的作用域链。作用域链的用途就是明确当前代码执行环境中能够访问以及以何种顺序访问的所有变量以及函数。
如下面代码:

var globalVar = 20;
function fun(a)
{
    var b = a*2;
    function bar(c)
    {
        console.log(a,b,c);
    }
    bar(b*3);
}
fun(2);//output 2,4,12

上述代码中:
全局执行环境的变量对象中包含变量globalVar和函数fun;
有两个局部执行环境fun和bar,fun的变量对象中包含变量a,b和函数bar,bar的变量对象中包含c。
javascript引擎利用这些变量对象组成的作用域链来确定所执行代码中标识符的位置,这种查找的过程总是从内向外的。以console.log(a,b,c)为例,当引擎执行到这里时,首先会查找console.log这个标识符,由于在当前活动对象(bar的变量对象)中找不到,所以引擎一直向上查找,当进行到全局变量对象的时候,找到window.console.log,然后同样查找a,b,c。这里要注意的是:

作用域链的查找会在找到第一个匹配的标识符后停止。所以,当在多层的嵌套作用域中定义同名的标识符时,处于内部的标识符会遮蔽外部的同名标识符,这在其它语言中,称为变量的隐藏。

无论函数在哪被调用,也无论他如何被调用,其词法作用域都只由函数声明时候的位置所决定。

欺骗词法

eval

eval函数可以接收一个字符串作为参数,并可以将其内容视为好像在书写时就存在于程序中的该位置一样(考虑c语言的宏)。eval通过这样来修改词法作用域环境,考虑如下代码:

function fun(str,a)
{
    eval(str);//欺骗词法作用域
    console.log(a,b);//output 1,2
}
var b = 3;
fun("var b=2;",1);

在执行eval之后,javascript引擎并不知道或者并不在意这里的代码是后面动态插入的还是在书写的时候就存在的,其会按照正常的查找逻辑来进行标识符的解析,由于执行了 var b = 2;这段代码会被当作本来就存在于那里一样,所以输出2.
注意,这里传入eval的字符串是固定的,当然可以态生成字符串来创建代码

with

with语句在执行statement时会将指定的对象添加到当前作用域的前端。

with (expression)
         statement;

一般是为了简写,当重复引用同一个对象的多个属性的时候,考虑如下两种写法:

var human ={
    name:"ck",
    age:20,
    height:20,
    addr:"BeiJ"
};
function setAttr(){
    human.name = "pk";
    human.age= 17;
    human.height= 18;
    human.addr= "ChongQ";
}

var human ={
    name:"ck",
    age:20,
    height:20,
    addr:"BeiJ"
};
function setAttr(){
    with(human)
    {o
        name = "pk";
        age= 17;
        height= 18;
        addr= "ChongQ";
    }
}

显然,with语句能够使得在重复书写对象的时候得到简化,但是在一些情况下会带来问题,考虑如下代码:

function fun(obj)
{
    with(obj)
    {
        a = 20;
    }
}
var obj1 = {
    a = 10;
};
var obj2 = {
    b = 30;
};
fun(obj);
console.log(obj1.a);//20
console.log(obj2.a);//undefined
console.log(window.a);//20

当obj2对象中没有a属性时,对a沿着作用域链向上查找,当找到全局执行环境的时候还没有找到a,便会创建一个全局变量a(非严格模式下)。

性能影响

如前所述,javascript引擎会在编译阶段进行性能优化,其中就包括对代码进行词法分析,预先确定所有变量和函数的定义位置,这样在执行的过程中便可快速定位到标识符,但是引擎如果在代码中发现eval(…)或者with(…)的时候,由于无法确定eval会接受到什么代码,也无法确定with接收到的对象有什么内容,这时候javascript引擎对此不做任何优化,对性能造成很大影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值