媒介:最近在细读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引擎在优化标识符查询方面做得不错,因此这个差异在未来生怕能够疏忽不记。
然则,我们照样要养成优越的编程习气。虽然说,这个差异能够疏忽不记。