Javascript 这门语言与其余的大部分语言相比,有不少特殊性,这是不少人喜欢它或者讨厌它的缘由。其中变量的做用域问题,对不少初学者来讲就是一个又一个「坑」。javascript
变量的做用域在编程技能中算是一个基本概念,而在 Javascript 中,这一基本概念每每挑战者初学者的常识。java
先上例子:编程
javascriptvar scope = 'global'; function checkScope(){ var scope = 'local'; console.log(scope); // local } checkScope(); console.log(scope); // global
上面的例子中,声明了全局变量 scope
和函数体内的局部变量 scope
。在函数体内部,局部变量的优先级比通明的全局变量要高,若是一个局部变量的名字与一个全局变量相同,那么,在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。函数
下面再看一个例子:spa
javascriptscope = '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
变量?code
这两个问题都源于一个特性。在全局做用域中声明变量能够省略 var
关键字,可是若是在函数体内声明变量时不使用 var
关键字,就会发生上面的现象。首先,函数体内的第一行语句,把全局变量中的 scope
变量的值改变了。而在声明 myScope
变量时,因为没有使用 var
关键字,Javascript 就会在全局范围内声明这个变量。所以,在声明局部变量时使用 var
关键字是个很好的习惯。对象
在 C 或者 Java 等语言中,if
、for
等语句块内能够包含本身的局部变量,这些变量的做用域是这些语句的语句块,而在 Javascript 中,不存在「块级做用域」的说法。blog
看下面的例子:ip
javascriptfunction 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 中不存在块级做用域,所以,函数中声明的全部变量,不管是在哪里声明的,在整个函数中它们都是有定义的。作用域
若是要更增强调上文中 函数中声明的全部变量,不管是在哪里声明的,在整个函数中它们都是有定义的
这句话,那么还能够在后面跟一句话:函数中声明的全部变量,不管是在哪里声明的,在整个函数中它们都是有定义的,即便是在声明以前。对于这句话,有个经典的困扰初学者的「坑」。
javascriptvar a = 2; function test(){ console.log(a); var a = 10; } test();
上面的例子中,控制台输出变量 a
的值为 undefined
,既不是全局变量 a
的值 2
,也不是局部变量 a
的值 10
。首先,局部变量在整个函数体内都是有定义的,所以,局部变量 a
会在函数体内覆盖全局变量 a
,而在函数体内,在 var
语句以前,它是不会被初始化的。若是要读取一个未被初始化的变量,将会获得一个默认值 undefined
。
因此,上面示例中的代码与下面的代码时等价的:
javascriptvar 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 的函数是能够嵌套的,所以每一个函数执行环境的做用域链可能包含不一样数目个对象,一个非嵌套的函数的执行环境中,做用域链包含了这个函数的调用对象和全局对象,而在嵌套的函数的执行环境中,做用域链包含了嵌套的每一层函数的调用对象以及全局变量。
咱们能够用一个图来直观地解释做用域链和变量名解析的过程: