JS中的函數聲明和函數表達式的區別,即function(){}和var function(){},以及變量提升、作用域和作用域鏈


一、前言

  Uncaught TypeError: ... is not a function

  function max(){}表示函數聲明,可以放在代碼的任何位置,也可以在任何地方成功調用;

  var max  = function(){};表示函數表達式,即將一個匿名函數賦值給一個變量,實現通過變量來調用這個匿名函數,但它需要在聲明過后才能進行調用,如果調用在聲明之前就會報如上紅色字體的錯誤。而這在函數聲明中不會出現這樣的錯誤。

 

二、正文

(一)、代碼示例

//函數表達式
myFunc();//此處調用會報錯,即 Uncaught TypeError: myFunc is not a function

var myFunc = function(){ alert("函數表達式") } myFunc();//此處調用正確
=======================================================================
//函數聲明
otherFunc();//對於函數聲明,此處調用可以使用

var otherFunc = function(){ alert("函數聲明") } otherFunc();//此處調用也可以

可見對於函數表達式的使用,不可以提前進行調用,而函數聲明卻可以,為什么呢?

 

(二)、原因分析===JS作用域和聲明提前,以及作用域和作用域鏈

1. JS的作用域:

  變量有全局變量和局部變量之分,兩者的區別在於作用域的不同;

  •   全局作用域:針對於全局變量來說,全局變量在整個上下文都有效,只是在沒有賦值之前調用,會輸出undefined;
  •   函數作用域:是針對局部變量來說的,在函數中定義的變量在函數外不能獲取;
  •   塊級作用域:概念“{}”中間的部分都是塊級作用域ex:for while if ,js中沒有塊級作用域,但是可以用閉包實現類似功能。
alert(c);//輸出undefind 

// alert(d);報錯 Uncaught ReferenceError: d is not defined

// alert(b);報錯 Uncaught ReferenceError: b is not defined

var c=3; function test(){ var a=1; b=2;  //沒有var直接賦值的變量都屬於全局變量
 alert(c)//輸出3,
} // alert(b);報錯 Uncaught ReferenceError: b is not defined
 alert(c);//輸出3 
 test(); // alert(a);報錯 Uncaught ReferenceError: a is not defined
 alert(b);//輸出2,在test執行過后才能把b變為全局變量

 

function test(){ alert(a);//聲明未賦值輸出undefine 

      var a=1; alert(a);//1 
} // alert(a);報錯,外部獲取不到 
 test(); //alert(a);報錯,Uncaught ReferenceError: a is not defined

注意 : 

  • 如果在函數中去掉var進行聲明(如代碼中的b),則變量就會從局部變量升級為全局變量。
  • 局部變量的優先級高於同名的全局變量 。如果在函數中聲明一個局部變量同名,則全局變量就會被局部變量覆蓋。

2. JS的變量提升:

  變量在聲明之前就已經可用,因為瀏覽器在進行“預解析”時,對每個函數作用域中的所有變量和函數都會先提取出來,提前進行解析。

  我們稱這種特性為聲明提前,也就是預解析。

//變量提前,會對var和function,即變量和函數進行提前解析
console.log(a)    //打印function a(){console.log("123")}

var a=1

function a(){console.log("123")} var a=10

console.log(a) //預解析的過程a=undefined, a=function a(){...}, a=undefined //預解析的結果是a=function a(){...} //最終執行的結果是a=10,后面的覆蓋前面的

幾點說明:

  • 將變量聲明提升,只提升變量,不提升所賦的值;
  • 將函數聲明及函數內容提升,既提升函數聲明,又提升函數內容,可以理解為將整個function內容提升;
  • 塊內的變量聲明和函數聲明也會被提升,例如if語句。

三個重名沖突:

  • 遇到重名,預解析后只留下一個;
  • 如有重名變量和函數,留下函數,因為函數有內容;
  • 如有兩個重名函數,后一個函數覆蓋前一個函數。

 3. JS的作用域鏈

  作用域:是一個函數在執行時期的執行環境

  作用域嵌套:嵌套內函數的執行會引用其“父函數”的作用域,例如B()和C()的執行會引用A()的作用域;

  作用域鏈:像這種函數作用域的嵌套就組成了所謂的函數作用域鏈;

       當在自身作用域內找不到該變量的時候,會沿着作用域鏈逐步向上查找,若在全局作用域內部仍找不到該變量,則會拋出異常。

//name="123"; 
function A(){ var name="456"; function B(){ var name="789"; console.log(name); //首先在函數內查找name,查找到所以結果是“789”
 } function C(){ console.log(name); //首先在函數內沒有找到,向作用域鏈的上一級查找,找到了,結果是“456”
 } s(); ss(); } t(); console.log(name); //此處再次進行查找,當前作用域內沒有,整個鏈上都沒有,結果就是未定義

 

三、總結

  函數聲明和函數表達式相比,函數聲明使用可以更加自由,可以放在隨意的位置,因為它能夠整體的變量提升;

  而函數表達式使用就相對沒有那么自由了,調用必須在聲明的后面,因為變量提前只是將表達式的變量提前,並沒有將表達式的內容提前。

  

  作用域:Js是以函數范圍作為基本的局部;

  • 全局作用域:針對於全局變量來說,全局變量在整個上下文都有效,只是在沒有賦值之前調用,會輸出undefined;
  • 函數作用域:是針對局部變量來說的,在函數中定義的變量在函數外不能獲取;

  

  變量提升:是相對於作用域來說的,在每個作用域中,無論全局還是函數作用域,聲明的變量和函數,都能夠變量提升,也就是可以在聲明之前進行使用(調用)。

 

  作用域鏈:函數的嵌套,使得子函數的執行會引用父函數的作用域,像這種函數作用域的嵌套就組成了所謂的函數作用域鏈;

  當在自身作用域內找不到該變量的時候,會沿着作用域鏈逐步向上查找,若在全局作用域內部仍找不到該變量,則會拋出異常。

 


免責聲明!

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



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