關於JS變量提升的一些坑


1 function log(str) {
2    // 本篇文章所有的打印都將調用此方法
3    console.log(str);
4 }

 

函數聲明和變量聲明總是會被解釋器悄悄地被“提升”到方法體的最頂部

變量聲明、命名、提升


在JS中, 變量有4種基本方式進入作用域:

  • 語言內置: 所有的作用域里都有this和arguments;(需要注意的是arguments在全局作用域是不可見的)
  • 形式參數: 函數的形式參數會作為函數體作用域的一部分;
  • 函數聲明: 像這種形式: function foo() {};
  • 變量聲明: 像這樣: var foo;

 

變量提升

 

function test1() {
      a = 5;
      log(a); 
      log(window.a); 
      var a = 10;
      log(a); 
    }
    test1();

依次會輸出 5 、undefined 、10   因為在解析時候是等價於

1 var a;
2 a=5;
3 log(a);
4 log(window.a);
5 a=10;
6 log(a);

 接着看另外一個例子:

 1 function test2() {
 2    var a = 1;
 3    var b = 2;
 4    var c = 3;
 5 }
 6 /*test2中的語句,是這樣被執行的  這個時候就把變量提升了
 7     
8 function test2(){
9 var a,b,c; 10 var a = 1; 11 var b = 2; 12 var c = 3; 13 } 14 */

只有函數級作用域,if語句不會有:test3():

function test3(){
var a = 1;
log(a); // 1 
if (true) {
var a = 2;
log(a); //2 
}
log(a); // 2 
}

 

函數的提升


我們寫JS的時候,通常會有兩種寫法:

  • 函數表達式 var fn=function fn(){}
  • 函數聲明方式 function fn(){}

我們需要重點注意的是,只有函數聲明形式才能被提升。
變量賦值並沒有被提升,只是聲明被提升了。但是,函數的聲明有點不一樣,函數體也會一同被提升

 1 function test3() {
 2    fn();
 3    function fn() {
 4      log("我來自fn");
 5    }
 6  }
 7  test3();
 8  function test4() {
 9    fn(); // fn is not a function
10    var fn = function fn() {
11      alert("我來自 fn  test4");
12    }
13  }
14  test4();

 

 

函數表達式需要注意的

  • 在function內部,fn完全等於fn1
  • 在function外面,fn1則是 not defined
 1 function test5() {
 2       var fn = function fn1() {
 3         log(fn === fn1); // true
 4         log(fn == fn1); // true
 5       }
 6       fn();
 7       log(fn === fn1); // fn1 is not defined
 8       log(fn == fn1);  // fn1 is not defined
 9     }
10     test5();

!兼容
// b();
// var a = function b() {alert('this is b')};
// 則ie下是可以執行b的. 說明不同瀏覽器在處理函數表達式細節上是有差別的.

 

補充一點函數表達式

定義里面的指定的函數名是不是被提升的

 1 function text7() {
 2   a(); // TypeError "a is not a function" 
 3   b();
 4   c(); // TypeError "c is not a function" 
 5   d(); // ReferenceError "d is not defined"
 6 
 7   var a = function() {};    // a指向匿名函數 
 8   function b() {};          // 函數聲明 
 9   var c = function d() {};  // 命名函數,只有c被提升,d不會被提升。
10 
11   a();
13   b();
14   c();
15   d(); // ReferenceError "d is not defined"
16 }
17 text7();

 

大家先看下面一段代碼test6,思考一下會打印什么?

 1 function text6() {
 2    var a = 1;
 3    function b() {
 4      a = 10;
 5       return;
 6       function a() {}
 7     }
 8     b();
 9     log(a);         //
10 }
11 text6();

||

||

||

|| 輸出在下面

||

||

||

||

||

||

what? 什么鬼?為什么是1?
這里需要注意的是,在function b()中,
var = a // function 類型的
a=10; // 重新把10復制給a,  此時的a是function b()中的內部變量
return;
function a() {} // 不會被執行

所以,外面輸出的a 依舊是最開始定義的全局變量

 

函數的聲明比變量的聲明的優先級要高

 1 function text6() {
 2   function a() {}
 3   var a;
 4   log(a);                //打印出a的函數體
 5 
 6   var b;
 7   function b() {}
 8   log(b);                 //打印出b的函數體 
 9 
10   // !注意看,一旦變量被賦值后,將會輸出變量
11   var c = 12
12   function c() {}
13   log(c);                 //12
14   
15   function d() {}
16   var d = 12
17   log(d);                //12
18 }
19 text6();

 

變量解析的順序


一般情況下,會按照最開始說的四種方式依次解析

  • 語言內置:
  • 形式參數: 
  • 函數聲明: 
  • 變量聲明: 

 也有例外:

  • 內置的名稱arguments表現得很奇怪,看起來應該是聲明在形參之后,但是卻在聲明之前。這是說,如果形參里面有arguments,它會比內置的那個優先級高。所以盡可能不要在形參里面使用arguments;
  • 在任何地方定義this變量都會出語法錯誤
  • 如果多個形式參數擁有相同的名稱,最后的那個優先級高,即便是實際運行的時候它的值是undefined;

 

CAO!這么多坑,以后腫么寫代碼?


用var定義變量。對於一個名稱,在一個作用域里面永遠只有一次var聲明。這樣就不會遇到作用域和變量提升問題。

ECMAScript參考文檔關於作用域和變量提升的部分:
    如果變量在函數體類聲明,則它是函數作用域。否則,它是全局作用域(作為global的屬性)。變量將會在執行進入作用域的時候被創建。塊(比如if(){})不會定義新的作用域,只有函數聲明和全局性質的代碼(單個JS文件)才會創造新的作用域。變量在創建的時候會被初始化為undefined。如果變量聲明語句里面帶有賦值操作,則賦值操作只有被執行到的時候才會發生,而不是創建的時候。

 

最后,

由於時間倉促,demo有很多不足之處,多諒解。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM