咱們都知道js是一個基於對象的語言,系統內置各類對象。
瀏覽器
而window做爲一個自然存在的全局對象,它承擔了全部全局資源的存儲。閉包
咱們使用的任何全局變量,都是window下的。也就是說,在js中,實際上沒有任何對象、方法是能夠獨立的,它們必須依賴於某個對象才能夠被訪問或執行。函數
就像alert(),它的完整寫法是window.alert()this
parseInt(), 完整寫法是window.parseInt()orm
全部放在window對象下的資源,訪問時能夠默認省略windowcdn
但有一種狀況很是特殊,例如函數中的局部變量:對象
function Person(){blog
var name = 「abc」;內存
}原型鏈
當咱們試圖訪問這個name屬性時
console.log(newPerson().name);
結果是undefined
咱們只能在函數內部訪問:
function Person(){
var name = 「abc」;
console.log(name);
}
這種屬性,在構造函數中,也被稱爲私有屬性,咱們必須提供一種對外公開的方法才能夠被外界訪問。例如:
function Person(){
var name = 「abc」;
this.getName = function(){
returnname;
}
this.setName= function(_name){
name = _name;
}
}
這是一個典型的做用域問題, 彷佛咱們每一個人都知道。
但這彷佛也違反了咱們的一個常識:那就是在js中,全部資源都必須依賴對象才能存在,不可獨立使用。好比說:
function aaa(){
function bbb(){ }
bbb();
}
這段代碼看上去並無錯,可是請問bbb這個函數爲何能夠獨立存在呢?若是咱們把它換成這樣:
function aaa(){
function bbb(){ }
window.bbb();
}
結果是運行錯誤!
那若是換成這樣呢?
function aaa(){
function bbb(){ }
this.bbb();
}
結果仍是運行錯誤!
那麼咱們不由要發問了,bbb這個函數究竟是屬於哪一個對象的?
當咱們在調用一個函數的時候,瀏覽器會爲這個函數的執行開闢一塊內存區域用來存儲這個方法在執行的臨時數據。而對象做爲js的基本存儲單位,所以,臨時數據實際上都被保存到了一個對象當中,這個對象,就是咱們平時所說的執行上下文環境
當咱們調用aaa函數時,例如window.aaa()
瀏覽器會爲這一次函數的執行,建立一個執行上下文環境對象,咱們暫時給它起個名字,叫作contextAAA吧,固然它是臨時的,由於函數執行完它也就消失了。
那咱們的代碼實際上會變成這樣:
function aaa(){
function bbb(){ }
contextAAA.bbb();
}
儘管contextAAA對象是看不見的,但它確實存在。
而當咱們執行bbb函數時,瀏覽器會再次爲這一次函數調用建立一個臨時的執行上下文環境,咱們暫且叫它contextBBB
那麼contextAAA 和contextBBB以及window之間會造成鏈條關係,如圖
#FormatImgID_0#
舉個例子來講明吧
var num = 888;
function aaa(){
var num = 100;
function bbb(){
var num= 200;
console.log(num);
}
bbb();
}
aaa();
那麼contextAAA 以下:
contextAAA = {
num :100,
bbb : function(){ … },
parentContext: window//父級上下文對象
}
那麼contextBBB以下:
contextBBB = {
num : 200,
parentContext: contextAAA //父級上下文對象
}
所以咱們發現,在父級上下文對象中,咱們沒有辦法訪問到子級上下對象,這是一個單向鏈表,這就是全局不能訪問局部的緣由。
而bbb函數中打印出的num應該是多少呢?這取決在上下文對象中的查找順序,順序大概是這樣的:
首先在當前上下文對象contextBBB中,找一下有沒有num變量,找到就直接打印。以咱們目前的代碼看,結果應該是200
咱們把代碼改造一下:
var num= 888;
function aaa(){
var num = 100;
function bbb(){
console.log(num);
}
bbb();
}
aaa();
因爲此次在contextBBB對象中找不到num變量了,所以它會從父級上下文對象中查找,也就是contextAAA裏面的num,所以打印的結果是100;
咱們再把代碼改造一下
var num= 888;
function aaa(){
function bbb(){
console.log(num);
}
bbb();
}
aaa();
因爲此次連contextAAA對象裏也找不到了,會再次向它的父級上下文對象,也就是window查找,所以打印結果是888
contextAAA和contextBBB的父子關係,在你寫代碼的一刻就決定了,這就是做用域。
function aaa(){
var num = 10;
function bbb(){ console.log(num); }
}
而代碼執行時,產生的上下文對象,是鏈表的關係。這就是咱們所說的做用域鏈,它的原理跟原型鏈是同樣的。
理解了這一點,也能弄明白閉包的原理。
function aaa(){
var num = 10;
return functionbbb(){ console.log(num); }
}
aaa( )( )
儘管bbb函數經過return在全局範圍被執行了,但做用域的鏈表關係並無發生改變,所以,bbb函數依然能夠訪問num這個局部變量。