JavaScript变量作用域和变量提升解释(JavaScript Variable Scope and Hoisting Explained)


这篇文章,我们要学习的是javascript的变量作用域和变量的提升以及它们的特性。

理解javascript的变量作用域和变量提升对于学习javascript来说,是非常重要的。这些概念看起来似乎很直白,但是还是有些很重要的细微之处需要我们理解的。

变量的作用域

变量的作用域就是变量的生存环境。简单的说,变量的作用域就是你在什么地方可以访问到这个变量。

javascript中的变量作用域只用两种:局部作用域和全局作用域。

局部作用域(函数级作用域)

跟其他大多数编程语言不同的是,javascript没有块级作用域(比如java中的if语句块中声明的变量,作用域就是这个if语句块,但是javascript中,if语句块中的变量却是全局变量)。不过,javascript有函数级作用域。在一个函数内定义的变量,叫做局部变量。它只能在这个函数里或者这个函数的内部函数里被访问到。更多关于内部函数访问外部函数中定义的变量的内容,请参考Closures(闭包)这篇文章。

函数级作用域演示:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var name = "Richard";  
  2.   
  3. function showName () {  
  4.     var name = "Jack"// 局部变量; 只能在 showName 函数中被访问  
  5.     console.log (name); // Jack  
  6. }  
  7. console.log (name); // Richard: 全局变量  
javascript中没有块级作用域,这是跟其他大部分编程语言都不一样的地方,需要特别注意

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var name = "Richard";  
  2. // 这个if语句块没有生成一个新的环境  
  3. if (name) {  
  4.     name = "Jack"// 这个name是全局变量name,它的值被改成了"Jack"  
  5.     console.log (name); // Jack: 这个还是全局变量  
  6. }  
  7.   
  8. // 这里的name,还是全局变量,但是它在if语句块中被改变了值。  
  9. console.log (name); // Jack  

  • 如果你不声明局部变量的话,很容易出错
    在使用局部变量之前,一定要记得先声明。你可以用JSHint 来检查你的语法。下面这个例子是不声明本地变量导致的问题:
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. //即使是在函数里面,声明一个变量时,没有使用var这个关键字,那这个变量依然是全局变量。  
    2. var name = "Michael Jackson";  
    3.   
    4. function showCelebrityName () {  
    5.     console.log (name);  
    6. }  
    7.   
    8. function showOrdinaryPersonName () {      
    9.     name = "Johnny Evers";  
    10.     console.log (name);  
    11. }  
    12. showCelebrityName (); // Michael Jackson  
    13.   
    14. //因为在函数里面没有对name进行声明,所以它用到的name是全局变量里面的name,只是把它的值改变成了"Johnny Evers"  
    15. showOrdinaryPersonName (); // Johnny Evers  
    16.   
    17. // 全局变量name的值现在是Johnny Evers了,而不再是原来的"Michael Jackson"了。  
    18. showCelebrityName (); // Johnny Evers  
    19.   
    20. // 下面是正确的在函数里声明本地变量的方法  
    21. function showOrdinaryPersonName () {      
    22.     var name = "Johnny Evers"// 现在这个name是本地变量了,不会覆盖掉全局变量里面的name.  
    23.     console.log (name);  
    24. }  
  • 在函数里局部变量的优先权高于全局变量
    如果你同时声明了一个全局变量,一个局部变量,两者的名字一样的话,在函数里面使用这个变量的时候,将优先使用本地声明的这个变量。在一个函数里面使用一个变量的时候,它会首先在本地环境里面查找这个变量,找到了就直接使用,只有在本地找不到该变量时,它才会到全局环境里去找全局变量。
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. var name = "Paul";  
    2.   
    3. function users () {  
    4.     // 这里声明了一个局部变量,跟全局变量的变量名都是name,但是在函数里,它的优先级比全局变量高  
    5. var name = "Jack";  
    6.   
    7. // 首先在这个函数里面查找name变量,找不到的话,才会去查找函数外的全局变量。  
    8. console.log (name);   
    9. }  
    10.   
    11. users (); // Jack  

全局变量
所有在函数之外声明的变量都是全局变量。在浏览器里面,全局环境或者说全局作用域的作用范围是window对象(或者是整个html文档)

  • 所有在函数外面声明或者初始化的变量都是全局变量,全局变量在整个应用中任何地方都可以被使用。比如:
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. // 下面几种都是声明全局变量的方式  
    2. var myName = "Richard";  
    3.   
    4. // 或者  
    5. firstName = "Richard";  
    6.   
    7. // 或者  
    8. var name;  
    9. name;  
    10.   
    11.   
    12. //需要注意的是,所有全局变量都跟window对象是关联上的。所有我们声明的全局变量可以像这样访问:  
    13.   
    14. console.log(window.myName); // Richard;  
    15.   
    16.  // 或者  
    17. console.log("myName" in window); // true  
    18. console.log("firstName" in window); // true  
  • 如果变量初始化(赋值)之前,没有使用var 关键字进行声明,那这个变量自动被加到全局环境里面,变成全局变量了。
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. function showAge () {  
    2.     // 这个age是全局变量  
    3.     age = 90;  
    4.     console.log(age);//   
    5. }  
    6.   
    7. showAge (); // 90  
    8.   
    9. // 因为age是全局变量,所以在函数外也能被访问到。  
    10. console.log(age); // 90  

    另一个例子:

    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. // 尽管第二个firstName被包含在{}中,这两个都是全局变量。因为javascript中没有块级作用域这一说。  
    2. var firstName = "Richard";  
    3. {  
    4. var firstName = "Bob";  
    5. }  
    6.   
    7. // 第二个firstName只是又声明了一次并且覆盖了第一个firstName的值  
    8. console.log (firstName); // Bob  

    另一个例子:

    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. for (var i = 1; i <= 10; i++) {  
    2.     console.log (i); // 输出 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;  
    3. };  
    4.   
    5. // i是全局变量,所以下面的函数里面可以访问到。  
    6. function aNumber () {  
    7. console.log(i);  
    8. }  
    9.   
    10. // aNumber函数里面的i是全局变量,它的值在上面的for循环里面被改变了,最后的值是for循环结束前的11.  
    11. aNumber ();  // 11  

  • setTimeout 的变量会被放在全局域里执行
    注意所有在setTimeout里面的函数都会在全局域里面执行。 这是比较棘手的一点,看下面这个:
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. // 在setTimeout里面的 "this" 对象表示的是 window 对象,而不是 myObj  
    2.   
    3. var highValue = 200;  
    4. var constantVal = 2;  
    5. var myObj = {  
    6.     highValue: 20,  
    7.     constantVal: 5,  
    8.     calculateIt: function () {  
    9.  setTimeout (function  () {  
    10.     console.log(this.constantVal * this.highValue);  
    11. }, 2000);  
    12.     }  
    13. }  
    14.   
    15. //setTimeout 函数里面的 "this" 对象使用全局的变量 highValue 和 constantVal,因为setTimeout函数里面的 "this" 的引用时全局的window对象,不是myObj  
    16.   
    17. myObj.calculateIt(); // 400  
    18. // This is an important point to remember.  
  • 不要过度使用全局变量
    如果你想成为一个javascript高手的话,这个应该是毫无疑问的(否则你现在应该在看选美小姐什么的而不是在读这篇文章了),你一定要尽量避免在全局域中创建过多的变量,像下面这样:
    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. // 这两个变量现在在全局域里面,但是更好的方法是把它们放到函数里面  
    2. var firstName, lastName;  
    3.   
    4. function fullName () {  
    5.     console.log ("Full Name: " + firstName + " " + lastName );  
    6. }  

    下面这个是改良之后的版本

    [javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
    1. // 在函数里面声明变量,它们就成了局部变量了。  
    2.   
    3. function fullName () {  
    4.     var firstName = "Michael", lastName = "Jackson";  
    5.   
    6.     console.log ("Full Name: " + firstName + " " + lastName );  
    7. }  

    这上面这个例子中,函数fullName依然是在全局域中。


变量提升

在javascript中,有个变量声明提升的概念。在一个函数里声明的变量,它的声明会被提到函数的顶部。在函数外定义的变量,它的声明则会被提到全局环境的顶部。

需要强调的是,只有函数的声明会被提升,变量的初始化或者赋值都不会被提升。

变量提升的例子:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. function showName () {  
  2. console.log ("First Name: " + name);  
  3. var name = "Ford";  
  4. console.log ("Last Name: " + name);  
  5. }  
  6.   
  7. showName ();   
  8. // First Name: undefined  
  9. // Last Name: Ford  
  10.   
  11. // 第一行打印出来的是undefined的原因就是局部变量name被提升到了函数的顶部。  
  12. // 第一行的name调用的就是这个name  
  13. // 下面的代码是javascript引擎实际的执行过程:  
  14.   
  15. function showName () {  
  16.     var name; // name的声明被提升到函数的顶部,由于赋值不会被提升,所以此时name的值为undefined  
  17. console.log ("First Name: " + name); // First Name: undefined  
  18.   
  19. name = "Ford"// name在原来这个地方被赋值  
  20.   
  21. // 现在name的值变为了Ford  
  22. console.log ("Last Name: " + name); // Last Name: Ford  
  23. }  

在提升的时候函数声明覆盖变量声明
函数的声明和变量的声明都会被提升到所在作用域的顶部。如果函数的名字和变量的名字相同的话,函数的声明会覆盖掉变量的声明(但是不能覆盖变量的赋值)。像上面提到的,变量的赋值是不会被提升的,函数的赋值也不会被提升。附:函数的赋值的格式 var myFunction = function () {}。
下面是一个基本的演示例子:

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.  // 函数和变量的名字都是 myName  
  2. var myName;
  
  3. function myName () {  
  4. console.log ("Rich");  
  5. }  
  6.   
  7. //函数的声明覆盖了变量的名字  
  8. console.log(typeof myName); // function  

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.  // 但是在这个例子中,变量的赋值又覆盖了函数的声明  
  2. var myName = "Richard"// 变量赋值(初始化)覆盖函数的声明  
  3.   
  4. function myName () {  
  5. console.log ("Rich");  
  6. }  
  7.   
  8. console.log(typeof myName); // string   

值得注意的是,像下面这样的函数表达式,是不会被提升的。

[javascript]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. var myName = function () {  
  2. console.log ("Rich");  
  3. }   

在strict 模式下,如果你给一个没有被声明的变量赋值,会产生错误。所以一定在变量赋值之前要先声明变量。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值