原文地址javascript
JS中有兩種做用域:全局做用域|局部做用域java
console.log(name); //undefined var name = '波妞'; var like = '宗介' console.log(name); //波妞 function fun(){ console.log(name); //波妞 console.log(eat) //ReferenceError: eat is not defined (function(){ console.log(like) //宗介 var eat = '肉' })() } fun();
那麼問題來了,爲何第一個打印是"undefined",而不是"ReferenceError: name is not defined"。原理簡單的說就是JS的變量提高數組
變量提高:JS在解析代碼時,會將全部的聲明提早到所在做用域的最前面閉包
console.log(name); //undefined var name = '波妞'; console.log(name); //波妞 function fun(){ console.log(name) //undefined console.log(like) //undefined var name = '大西瓜'; var like = '宗介' } fun();
至關於函數
var name; console.log(name); //undefined name = '波妞'; console.log(name); //波妞 function fun(){ var name; var like; console.log(name) //undefined console.log(like) //undefined name = '大西瓜'; like = '宗介' console.log(name) //大西瓜 console.log(like) //宗介 } fun();
注意:是提早到當前做用域的最前面code
printName(); //printName is not a function var printName = function(){ console.log('波妞') } printName(); //波妞
至關於對象
var printName; printName(); //printName is not a function printName = function(){ console.log('波妞') } printName(); //波妞
這樣一來就好理解了,函數表達式在聲明的時候還只是個變量ip
{ var name = '波妞'; } console.log(name) //波妞 (function(){ var name = '波妞'; })() console.log(name) //ReferenceError: name is not defined { let name = '波妞'; } console.log(name) //ReferenceError: name is not defined
從上面的栗子能夠看出,不能夠草率的認爲JS中var聲明的變量的做用範圍就是大括號的起止範圍,ES5並無塊級做用域,實質是函數做用域;ES6中有了let、const定義後,纔有了塊級做用域。內存
function p1() { console.log(1); } function p2() { console.log(2); } (function () { if (false) { function p1() { console.log(3); } }else{ function p2(){ console.log(4) } } p2(); p1() })(); //4 //TypeError: print is not a function
這是一個很是經典的栗子,聲明提早了,可是由於判斷條件爲否,因此沒有執行函數體。因此會出現"TypeError: print is not a function"。while,switch,for同理作用域
函數與對其狀態即詞法環境(lexical environment)的引用共同構成閉包(closure)。也就是說,閉包可讓你從內部函數訪問外部函數做用域。在JavaScript中,函數在每次建立時生成閉包。
上面的定義來自MDN,簡單講,閉包就是指有權訪問另外一個函數做用域中變量的函數。
//舉個例子 function makeFunc() { var name = "波妞"; function displayName() { console.log(name); } return displayName; } var myFunc = makeFunc(); myFunc();
JavaScript中的函數會造成閉包。 閉包是由函數以及建立該函數的詞法環境組合而成。這個環境包含了這個閉包建立時所能訪問的全部局部變量
在例子中,myFunc 是執行 makeFunc 時建立的 displayName 函數實例的引用,而 displayName 實例仍可訪問其詞法做用域中的變量,便可以訪問到 name 。由此,當 myFunc 被調用時,name 仍可被訪問,其值 '波妞' 就被傳遞到console.log中。建立閉包最多見方式,就是在一個函數內部建立另外一個函數
//例二 function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12 //釋放對閉包的引用 add5 = null; add10 = null;
從本質上講,makeAdder 是一個函數工廠 — 他建立了將指定的值和它的參數相加求和的函數。在上面的示例中,咱們使用函數工廠建立了兩個新函數 — 一個將其參數和 5 求和,另外一個和 10 求和。
add5 和 add10 都是閉包。它們共享相同的函數定義,可是保存了不一樣的詞法環境。在 add5 的環境中,x 爲 5。而在 add10 中,x 則爲 10。
閉包的做用域鏈包含着它本身的做用域,以及包含它的函數的做用域和全局做用域。
//栗子1 function arrFun1(){ var arr = []; for(var i = 0 ; i < 10 ; i++){ arr[i] = function(){ return i } } return arr } console.log(arrFun1()[9]()); //10 console.log(arrFun1()[1]()); //10 //栗子2 function arrFun2(){ var arr = []; for(var i = 0 ; i < 10 ; i++){ arr[i] = function(num){ return function(){ return num }; }(i) } return arr } console.log(arrFun2()[9]()); //9 console.log(arrFun2()[1]()); //1
栗子 1 中,arr數組中包含10個匿名函數,每一個函數均可以訪問外部的變量 i , arrFun1 執行後,其做用域被銷燬,但它的變量依然存在內存中,能被循環中的匿名函數訪問,這是的 i 爲 10;
栗子 2 中,arr數組中有是個匿名函數,其匿名函數內還有匿名函數,最內層匿名函數訪問的 num 被 上一級匿名函數保存在了內存中,因此能夠訪問到每次的 i 的值。
若有錯誤,請斧正
以上