首先給js的做用域這個話題打標籤:2,var, 全局變量,局部變量,函數,undefined, 做用域提高,賦值不會提高,ReferenceError, 同名覆蓋。
打完標籤以後,咱們來講跟做用域有關的幾條鐵打的規則:
1: JS的做用域有2
種:全局做用域,函數做用域。瀏覽器
把做用域想象成一個房間,而{}是房間的門。門上裝了一個貓眼,因此房間裏面能夠看清楚外面,可是外面卻看不見裏面。 在JAVA或者C裏面,大括號可能會出現的狀況有兩種: 1: 一個function定義的時候 2: 一個塊定義(好比if,for, while)的時候. 因此此時的做用域有三種類型: 1: 全局做用域, 2: 函數做用域 3: 塊級做用域。
可是在JS裏面,雖然{}出現的狀況也有兩種,可是隻有function的{}才起到柵欄的做用。閉包
2: 聲明在全局做用域裏的變量是全局變量
,聲明在函數裏面的變量是局部變量
。函數
3: 怎麼創造一個全局變量和局部變量?學習
創造全局變量的方法有兩種:spa
1: 在全局做用域內用var定義: var a; 2: 聲明一個變量,不帶var(不管是在全局,在函數裏面仍是在一個塊裏面):b
創造局部變量的方法只有一種:3d
在函數體裏面,帶var聲明一個變量: function func(){var b;}
3: 使用一個沒有聲明過的變量,會獲得一個ReferenceError
。不管什麼狀況下。
4: 不一樣做用域內,同名的變量,越小的做用域的變量會覆蓋
越大的做用域的。 調試
4: 做用域提高:變量在聲明以前就能夠引用了!
這個不是和第三點矛盾了嗎?其實並無。它背後的真正原理是:並非做用域被提高(咱們前面說了,一個變量的做用域會被框在一對柵欄{}裏面,一旦這個柵欄肯定了,那這個做用域是不可能變化的),實際上是變量的‘聲明’在其做用域裏面被放到任何代碼以前(固然包括引用它的代碼以前)。看一段代碼:code
var scope = 'global'; function func(){ console.log(scope); //輸出‘undefined’,而不是‘global’ var scope = 'local'; console.log(scope); //輸出‘local’ }
看到第一個console,可能覺得會輸出‘global’, 由於經過貓眼能夠看見外面的變量。可是,一旦咱們進到一個函數體裏面,遇到任何的變量的引用,首先要先在當前的房間裏面找,只有在當前的房間裏面找不到時,纔到父層去找。那爲何是‘undefined
呢?其實以上的代碼等價於:圖片
var scope = 'global'; function func(){ var scope; //變量的聲明會提高到最前面,可是賦值並不會,因此此刻scope的值還只是undefined. console.log(scope); //輸出‘undefined’,而不是‘global’ scope = 'local'; //賦值在這裏完成 console.log(scope); //輸出‘local’ }
============閉包的分割線===========
1: 什麼是閉包?
我看過看多不一樣的書,對必包的定義都不同,並且就算是我知道了閉包的定義,對我真正理解它的工做原理仍是沒有什麼用。因此,我就不去糾結它究竟是什麼,我接下來只關注它是怎麼工做的。
2: 何時會造成閉包?
如下內容非原創,這裏只是我本身的一個學習筆記。我看了http://www.jianshu.com/p/7312...(在這裏感謝做者),跟着文章裏面的例子(代碼根據本身的喜愛改了一些)走一遍:
1: When? 閉包出現的時刻?ip
function foo() { var a = 2; function baz() { console.log( a ); } return baz; } var fn = foo() fn();//2
在斷點的過程當中,運行完第9行代碼的時候,調試窗口裏並無出現任何閉包;直到我運行了第10行代碼,跳到第5行的時候(也就是baz這個方法被調用的時候),調試窗口出現了閉包(Closure)。而且能夠看到說foo是closure, 它包含一個變量a,值爲2。
結論1:雖然不少書上說閉包跟函數定義的時候的做用域有關,跟它執行時候的做用域無關,可是它在瀏覽器裏面出現的時機倒是在執行的時候。
2 How? 閉包出現的條件?
function foo() { var a = 2; function baz(m) { console.log(m); } return baz; } var fn = foo() fn(20); //20
不少地方都說在方法裏面定義方法就會造成閉包,可是在這裏例子裏面,我執行完第10行的代碼,調試窗口並無出現任何閉包。和例1的差異在於,baz沒有引用變量a.
結論2:一個方法,必定要引用其父層方法(非本身方法內部的變量)的變量纔會造成閉包。
可是閉包的造成是否是必定要執行到引用了父層做用域變量的方法纔出現呢?看下面一個例子:
3: 當父層方法裏有不僅一個方法
function foo() { var a = 10; var b = 30; function fn1() { return a; } function fn2() { return 20; } return fn2; } var fn = foo(); fn();
當我執行到17行而後跳進其方法體(第10行)的時候,調試窗口出現了Closure, 而且它有一個變量a,
值爲10。其實這個例子說明了兩個事情:
結論3: 在執行一個定義在方法裏面的方法時,即便它的方法體本身沒有引用父層變量,可是隻要有任何兄弟方法引用了,那就會造成閉包。閉包裏面保存的變量只有被方法引用了的變量(這個例子裏,閉包裏只有a,並無b)。
4: 當不是兄弟方法,而是子方法引用了父級變量,會發生什麼狀況呢?
function foo() { var a = 10; var b = 30; function fn1() { var c = 20; function fn2(){ return a; } return fn2; } return fn1; } var fn1 = foo(); var fn2 = fn1(); fn2();
fn1方法體內沒有引用任何的父層變量,可是它的子方法fn2引用了變量a。在調試的過程當中,當執行到f1的時候(執行到第6行,這時候能夠看到變量c都仍是'undefined'),Closure就已經出現了,並不像以前那樣必定要等到執行fn2.最後看一個例子:
5: 當不是直系子方法,而是侄子方法引用了父級變量
function foo() { var a = 10; var b = 30; function fn1() { var c = 20; function fn2(){ return a; } return fn2; } function fn3(){ return 40; } return fn3; } var fn3 = foo(); fn3();
當執行到調用fn3的時候,Chosure出現了。因此閉包究竟是在何時造成呢?在咱們前面的結論裏面有提到說方法執行的時候,可是通過後面的這幾個例子說明並不必定非得是執行的時候,並且JavaScript是基於詞法做用域的語言(變量的做用域在其定義的時候就已經肯定了,不依賴於執行時的環境),因此我傾向於閉包(Closure)是在方法定義的時候就造成了的。那最後來講說What的問題:閉包究竟是什麼?
1: 閉包是一個做用域。(鑑於在Chrome的調試窗口,Closure是放在Scope下面的)
2: 那閉包這個做用域是個什麼範圍:被後代方法(子方法,孫子方法。。。)所引用的變量所在的做用域(或者說這個變量所在的方法)。
3: 閉包裏面有什麼:只包含被後代方法所引用了的變量,並不包含這個變量的兄弟姐妹。
4: 上面的全部例子都只引用了變量,換成方法,也都是一樣的結果。
真的是最後一個例子了: