ES5之实行环境和作用域(变量的生命周期)

媒介:最近在细读Javascript高等顺序设计,关于我而言,中文版,书中许多处所一笔带过,所以用本身所明白的,尝试仔细解读下。若有马虎或毛病,会异常感谢您的指出。文中绝大部分内容援用自《JavaScript高等顺序设计第三版》。

实行环境(execution context)

实行环境(execution context,为了简朴起见,偶然也成为环境)是JavaScript中最为主要的一个观点。

实行环境,定义了变量或函数有权接见其他数据,且决议了它们各自的行动。

每一个实行环境都有一个与之关联的变量对象(variable object),环境中定义的一切变量和函数都保留在这个对象中。

虽然我们编写的代码无法接见这个对象,但剖析器在处置惩罚数据时会在背景运用它。

全局实行环境时最外围的一个实行环境。

依据ECMAScript完成地点的宿主环境差异,示意实行环境的对象也不一样。

在Web浏览器中,全局实行环境被认为是window对象,因此一切全局变量和函数都是作为window对象的属性和要领建立的。

(变量的生命周期),某个实行环境中的一切代码实行终了后,该环境被烧毁,保留在个中的一切变量和函数定义也随之烧毁)

全局实行环境直到应用顺序退出——比方关闭网页或浏览器时才会被烧毁。

每一个函数都有本身的实行环境。当实行流进入一个函数时,函数的环境就会被推入一个环境栈中。在函数实行以后,栈将其环境弹出,把掌握权返回给之前的实行环境。ECMAScript顺序中的实行流恰是由这个轻易的机制掌握着。

作用域链(scope chain)

当代码在一个环境中实行时,会建立变量对象的一个作用域链(scope chain)。
作用域链的用处,是保证对实行环境,有权接见的一切变量和函数的有序接见。

作用域链的前端,一向都是当前实行的代码地点环境的变量对象。(也能够明白为“就近准绳”)。

假如这个环境是函数,则将其运动对象(activation object)作为变量对象。

函数实行环境中的运动对象在最最先时,只包括一个变量,即arguments对象(这个对象在全局环境中是不存在的)作为变量对象。

作用域链中的下一个变量对象来自包括(外部)环境,而再下一个变量对象则来自下一个包括环境,如许,一向延续到全局实行环境。

全局实行环境的变量对象,一向是作用域链中的末了一个对象。

标识符剖析是沿着作用域链一级一级地搜刮标识标识符的历程。
搜刮历程一向从作用域链的前端最先,然后逐级向后回溯,直至找到标识符为止(假如找不到标识符,经由过程会致使毛病发作)。


var color = "blue";

function changeColor() {
    if(color === "blue") {
        color = "red";
    } else {
        color = "blue";
    }
}

changeColor();

console.log("Color is now " + color); // "color is now red"

在这个简朴的例子中,函数changeColor()的作用域链包括两个对象:

它本身的变量对象(个中定义着arguments对象)和全局环境的变量对象。

能够在函数内部接见变量color,就是由于能够在这个作用域链中找到它。

另外,在部分作用中定义的变量能够在部分环境中与全局变量交换运用。

var color = "blue";

function changeColor() {
    var anotherColor = "red";

    function swapColors(){

        //这里能够接见color、anotherColor和tempColor
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    //这里能够接见color和anotherColor,但不能接见tempColor
    swapColors();
}

//这里只能接见color
changeColor();

以上代码,触及3个实行环境:

  • 全局环境(在web浏览器中就是window)
  • 函数changeColor()的部分环境
  • 函数swapColors()的部分部分

全局环境中有一个变量color和一个函数changeColor()。changeColor()的部分环境中有一个名为anotherColor的变量和一个名为swapColors()的函数,但它也能够接见全局环境中的变量color。swapColors()的部分环境中有一个变量tempColor,该变量只能在这个环境中接见到。

不管全局环境照样changeColor()的部分环境都无权接见tempColor。

然则,在swapColors()内部,则能够接见其他两个环境中的变量,由于那两个环境是它的父实行环境。

      
 window, color, changeColor()
            |
    anotherColor, swapColors()
                    |
                tempColor

内部环境能够经由过程作用域链接见一切的外部环境,但外部环境不能接见内部环境中的任何变量和函数。

这些环境之间的联络是线性、有序次的。

每一个环境都能够向上搜刮作用域链,以查询变量和函数名。然则,任何环境都不能经由过程向下搜刮作用域链而进入另一个实行环境。

函数参数也被当作变量来看待,因此其接见划定规矩与实行环境中的其他变量雷同。

没有块级作用域(ES5中没有)

JavaScript没有块级作用域经常会致使明白上的疑心。
在其他类C的言语中,由花括号关闭的代码块都有本身的作用域(假如用ECMAScript的话来讲,就是它们本身的实行环境),因此支撑依据前提来定义变量。


if(true) {
    var color = "blue";
}

console.log(color); //"blue"

这里是在有一个if语句中定义了变量color。
假如是在C、C++或Java中,color会在if语句实行终了后被烧毁。
但在JavaScript中,if语句中的变量声明会将变量增加当前的实行环境(在这里是全局环境window)中。

在运用for语句时尤其要切记这一差异。


for(var i = 0; i < 10; i++) {
    console.log(i); // 0,1,2,3,4,5,6,7,8,9
}

/*
//等价于
var i;

for(i = 0; i < 10; i++) {
    console.log(i);
}

*/

console.log(i); //10

关于有块级作用域的言语来讲,for语句初始化变量的表达式所定义的变量,只会存在于循坏的环境当中。而关于JavaScript来讲,由for语句建立的变量i纵然在for轮回完毕以后,也照旧会存在于循坏外部的实行环境中。

声明变量

运用var声明的变量会自动被增加到最接近的环境中,在函数内部,最接近的环境就是函数的部分环境。

假如初始化变量时没有运用var声明,该变量会自动被增加到全局作用域。


function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}

var result = add(10,20); //30
console.log(sum); //sum is not defined

以上代码中的函数add()定义了一个名为sum的部分变量,该变量包括加法操纵的效果。
虽然效果值从函数中返回了,但变量sum在函数外部是接见不到的。
假如省略这个例子中的var关键字,那末当add()实行终了后,sum也将能够接见到。


function add(num1, num2) {
    sum = num1 + num2;
    return sum;
}

var result = add(10,20); // 30
console.log(sum); 30

在这个例子中的变量sum在被初始化赋值时没有运用var关键字。
因而,当挪用完add()以后,增加到全局环境中的变量sum将继承存在。
纵然函数已实行终了,背面的代码照旧能够接见它。

在编写JavaScript代码的历程当中,不声明而直接初始化变量时一个罕见的毛病,如许会致使一些不可预估的不测。养成优越的习气,在初始化变量之前,一定要先声明,如许就能够防止类似问题。在严厉形式下,初始化未经声明的变量会致使毛病。

2.查询标识符

当在某个环境中为了读取或写入而援用一个标识符时,必需经由过程搜刮来肯定该标识符现实代表什么。搜刮历程从作用域链的前端最先,向上逐级查询与给定名字婚配的标识符。

假如在部分环境中找到了该标识符,搜刮历程住手,变量停当。

假如在部分环境中没有找到该变量,则继承沿作用域向上搜刮。

搜刮历程将一向追溯到全局环境的变量对象。

假如在全局环境中也没有找到这个标识符,则意味着该变量还没有声明。

var color = "blue";

function getColor() {
    return color;
}

console.log(getColor()); // "blue"

/*
window = {
    color,
    getColor = function() {
        return color;
    }
}
*/

挪用本例中的函数getColor()时会援用变量color。

为了肯定变量color的值,将最先一个两步的搜刮历程。

  • 起首,在getColor()的部分环境中搜刮变量对象,查找个中是不是包括一个名为color的标识符。
  • 然后,没有找到,对不?那就到外面的环境中找,在全局作用域中找到名为color的标识符。

搜刮到了定义这个变量的变量对象,搜刮历程宣布完毕。

在这个搜刮历程当中,假如存在一个部分的变量的定义,则搜刮会自动住手(找到了,我就不找了),不再进入另一个变量对象。换句话说,假如部分环境中存在着同名标识符,就不会运用位于父环境中的标识符。

var color = "blue";

function getColor() {
    var color = "red";
    return color;
}

console.log(getColor()); //"red"

修改后的代码在getColor()函数中声清楚明了一个名为color的部分变量。
挪用函数时,该变量就会被声明。而当函数中的第二行代码实行时,意味着必需找到并返回变量color的值。
搜刮历程,起首从部分环境中最先,而且在这里发现了一个名为color的变量,其值为“red”。
变量已在函数的部分环境中找到了,所以搜刮住手,return语句就运用这个部分变量,并为函数返回“red”。

假如不运用window.color都无法接见全局color变量。

变量查询也不是没有价值的。很明显,接见部分变量要比接见全局变量更快,由于不必向上搜刮作用域链。JavaScript引擎在优化标识符查询方面做得不错,因此这个差异在未来生怕能够疏忽不记。

然则,我们照样要养成优越的编程习气。虽然说,这个差异能够疏忽不记。

    原文作者:来自卡冈图雅
    原文地址: https://segmentfault.com/a/1190000017081830
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞