微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

理解 Javascript 中变量的作用域

2015-07-07 19:59 前端大全

(点击上方蓝字,可快速关注我们)


Javascript 这门语言与其他的大部分语言相比,有很多特殊性,这是很多人喜欢它或者讨厌它的原因。其中变量的作用域问题,对很多初学者来说就是一个又一个「坑」。


变量的作用域在编程技能中算是一个基本概念,而在 Javascript 中,这一基本概念往往挑战者初学者的常识。


基本的变量作用域


先上例子:


var scope = 'global';

function checkScope(){

var scope = 'local';

console.log(scope);

// local

}

checkScope();

console.log(scope);

// global


上面的例子中,声明了全局变量 scope 和函数体内的局部变量 scope。在函数体内部,局部变量的优先级比通明的全局变量要高,如果一个局部变量的名字与一个全局变量相同,那么,在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。


下面再看一个例子:


scope = 'global';

function checkScope(){

scope = 'local';

console.log(scope);

// local

myScope = 'local';

console.log(myScope);

// local

}

checkScope();

console.log(scope);

// local

console.log(myScope);

// local


对于初学者来说,可能会有两个疑问:为什么在函数体外,scope 的值也变成了 local ?为什么在函数体外可以访问myScope 变量?


这两个问题都源于一个特性。在全局作用域中声明变量可以省略 var 关键字,但是如果在函数体内声明变量时不使用 var关键字,就会发生上面的现象。首先,函数体内的第一行语句,把全局变量中的 scope 变量的值改变了。而在声明myScope 变量时,由于没有使用 var 关键字,Javascript 就会在全局范围内声明这个变量。因此,在声明局部变量时使用var 关键字是个很好的习惯。


在 Javascript 中,没有「块级作用域」一说


在 C 或者 Java 等语言中,if、for 等语句块内可以包含自己的局部变量,这些变量的作用域是这些语句的语句块,而在 Javascript 中,不存在「块级作用域」的说法。


看下面的例子:


function checkScope(obj){

var i = 0;

if (typeof obj == 'object') {

var j = 0;

for (var k = 0; k < 10; k++) {

console.log(k);

}

console.log(k);

}

console.log(j);

}

checkScope(new Object());


在上面的例子中,每一条控制台输出语句都能输出正确的值,这是因为,由于 Javascript 中不存在块级作用域,因此,函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的。


如果要更加强调上文中 函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的 这句话,那么还可以在后面跟一句话:函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的,即使是在声明之前。对于这句话,有个经典的困扰初学者的「坑」。


var a = 2;

function test(){

console.log(a);

var a = 10;

}

test();


上面的例子中,控制台输出变量 a 的值为 undefined,既不是全局变量 a 的值 2,也不是局部变量 a 的值 10。首先,局部变量在整个函数体内都是有定义的,因此,局部变量 a 会在函数体内覆盖全局变量 a,而在函数体内,在 var 语句之前,它是不会被初始化的。如果要读取一个未被初始化的变量,将会得到一个默认值 undefined。


所以,上面示例中的代码与下面的代码时等价的:


var a = 2;

function test(){

var a;

console.log(a);

a = 10;

}

test();


可见,把所有的函数声明集合起来放在函数的开头是个良好的习惯。


变量的真相


可能很多人已经注意到,在 Javascript 当中,一个变量与一个对象的一个属性,有很多相似的地方,实际上,它们并没有什么本质区别。在 Javascript 中,任何变量都是某个特定对象的属性。


全局变量都是全局对象的属性。在 Javascript 解释器开始运行且没有执行 Javascript 代码之前,会有一个「全局对象」被创建,然后 Javascript 解释器会给它与定义一些属性,这些属性就是我们在 Javascript 代码中可以直接使用的内置的变量和方法。之后,每当我们定义一个全局变量,实际上是给全局对象定义了一个属性。


在客户端的 Javascript 当中,这个全局变量就是 Window 对象,它有一个指向自己的属性 window ,这就是我们常用的全局变量。


对于函数的局部变量,则是在函数开始执行时,会有一个对应的「调用对象」被创建,函数的局部变量都作为它的属性而存储。这样可以防止局部变量覆盖全局变量。


作用域链


如果要深入理解 Javascript 中变量的作用域,那就必须拿出「作用域链」这个终极武器。


首先要理解的一个名词就是「执行环境」,每当 Javascript 执行时,都会有一个对应的执行环境被创建,这个执行环境中很重要的一部分就是函数的调用对象(前面说过,调用对象是用来存储相应函数的局部变量的对象),每一个 Javascript 方法都是在自己独有的执行环境中运行的。简而言之,函数的执行环境包含了调用对象,调用对象的属性就是函数的局部变量,每个函数就是在这样的执行环境中执行,而在函数之外的代码,也在一个执行环境中,这个执行环境包含了全局变量。


在 Javascript 的执行环境中,还有一个与之对应的「作用域链」,它是一个由对象组成的列表或链。


当 Javascript 代码需要查询一个变量 x 的时候,会有一个被称为「变量名解析」的过程。它会首先检查作用域链的第一个对象,如果这个对象包含名为 x 的属性,那么就采用这个属性的值,否则,会继续检查第二个对象,依此类推。当检查到最后一个对象的时候仍然没有相应的属性,则这个变量会被认定为是「未定义」的。


在全局的 Javascript 执行环境中,作用域链中只包含一个对象,就是全局对象。而在函数的执行环境中,则同时包含函数的调用对象。由于 Javascript 的函数是可以嵌套的,因此每个函数执行环境的作用域链可能包含不同数目个对象,一个非嵌套的函数的执行环境中,作用域链包含了这个函数的调用对象和全局对象,而在嵌套的函数的执行环境中,作用域链包含了嵌套的每一层函数的调用对象以及全局变量。


我们可以用一个图来直观地解释作用域链和变量名解析的过程:




来自:segmentfault-范斌

链接:http://segmentfault.com/a/1190000002960647




前端大全』分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯,相关职位。欢迎关注。


微信号:FrontDev

(长按上图,弹出“识别二维码”后可快速关注)




 
前端大全 更多文章 5个典型的JavaScript面试题(上) Limu:JavaScript的那些书 Web开发:我希望得到的编程学习路线图 JavaScript基础工具清单 常用排序算法之JavaScript实现
猜您喜欢 大数据正在改变着传统营销 千万业务到一无所有,我只是忘记做一件事【岂安低调分享】 从LAMP到LNMP 容器与iPaaS平台是互补的 Android应用短期班(实践出真知,迅速提高"战斗力")