一、前言
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;
- 函數作用域:是針對局部變量來說的,在函數中定義的變量在函數外不能獲取;
變量提升:是相對於作用域來說的,在每個作用域中,無論全局還是函數作用域,聲明的變量和函數,都能夠變量提升,也就是可以在聲明之前進行使用(調用)。
作用域鏈:函數的嵌套,使得子函數的執行會引用父函數的作用域,像這種函數作用域的嵌套就組成了所謂的函數作用域鏈;
當在自身作用域內找不到該變量的時候,會沿着作用域鏈逐步向上查找,若在全局作用域內部仍找不到該變量,則會拋出異常。
