大名鼎鼎的做用域和閉包,面試常常會問到。閉包(closure)是Javascript語言的一個難點,也是它的特點。面試
理解閉包,先理解函數的執行過程。閉包
代碼在執行的過程當中會有一個預解析的過程,也就是在代碼的執行過程當中,會先將代碼讀取到內存中,檢查其是否有錯誤,而後將全部聲明在此進行標記,讓js解析器知道有這樣的一個名字,後面使用時便不會出現未定義的錯誤,這個標記的過程就是提高。dom
代碼演示:函數
var num = 10; function foo(){ console.log(num); } foo();
預解析的過程(變量提高,函數提高):性能
var num; function foo(){ console.log(num) } num = 10; foo();
代碼執行時,首先會執行 num = 10; 而後執行foo(),進行函數體,打印出num的值。此時的num訪問到的是全局中定義的num,因此num的值爲10.代理
以上代碼涉及到做用域的問題,所謂的域,表示的是範圍,因此做用域表示的是做用範圍,也就是一個名字在什麼地方可使用,在什麼地方不可以使用。code
在js中,採用的是詞法做用域,詞法做用域是指在編寫代碼的過程當中體現出來的做用範圍,一旦代碼寫好了,不用執行,做用範圍就肯定好了。對象
Javascript的做用域無非就是兩種:全局變量和局部變量。ip
只有函數能夠構成做用域結構。只要存在代碼,就至少有一個做用域,即全局做用域。凡是代碼有函數,那麼這個函數就構成一個做用域,若是函數中還有函數,那麼在這個做用域中就又誕生一個做用域,那麼將這樣的全部做用域列出來,就能夠有一個:函數內指向函數外的鏈式結構。內存
做用域鏈變量訪問規則:看變量在當前做用域中,是否有變量的定義與賦值,若是有,則直接使用;若是沒有,則到外面的做用域中查看,若是有,則中止查找,使用外面一層做用域中定義的變量或值,若是沒有,則繼續往外查找,直到最外層的全局,若是全局也沒有定義,則會報錯: xx is not defined。
閉包,是一個具備封閉功能與包裹功能的一個結構或空間。在js中,函數能夠構成閉包。由於函數在當前的做用域中是一個封閉的結構,具備封閉性;同時根據做用域規則,只容許函數內部訪問外部的數據,而外部沒法訪問函數內部的數據,即函數具備封閉的對外不公開的特性,就像把一個東西包裹起來同樣,所以函數能夠構成閉包。
有點難理解,簡單來講,就是可以讀取其餘函數內部變量的函數,再簡潔一點就是:定義在一個函數內部的函數。
由於閉包不容許外界直接訪問,因此只能間接訪問函數內部的數據,得到函數內部數據的使用權。
function foo(){ var num = 123; function func(){ return num; } return func; } var f = foo(); var res1 = f(); var res2 = f(); // 此時,foo只調用了一次,不會再內存中從新建立一個函數,而經過f,能夠訪問並獲取num的值,那麼調用f,便可得到同樣的num值 改良: function foo(){ var num = 123; return function(){ return num; } } var f = foo(); var res1 = f(); var res2 = f(); 再改良: var f = (function foo(){ var num = 123; return function (){ return num; } })(); var res1 = f(); var res2 = f();
function func(){ var num1 = Math.random(); var num2 = Math.random(); return { num1: function(){ return num1; }, num2: function(){ return num2; } } } var p = func(); console.log(p.num1()); console.log(p.num1());// 這兩個訪問到的是同一個隨機數
如上面代碼演示的那樣,閉包能夠經過返回函數來間接訪問到函數內的數據,這樣,閉包能夠實現具備私有訪問空間的函數,保護私有的數據。另外一方面,能夠幫助其餘對象讀取到函數內部的變量
函數定義的變量會在函數執行結束後自動回收,可是由於閉包結構引出的數據常常會被外界所引用,這些數據將不會被回收,所以過多的閉包會消耗內存資源,影響性能。因此要謹慎使用閉包,能夠在使用閉包時,若是再也不使用某些變量了,必定要賦值一個null。
在ES6中,提出來對象代理概念,在代理層操做數據而不是直接操做原數據。