更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看前端
------ 如下是正文 ------webpack
本期的主題是做用域閉包,本計劃一共28期,每期重點攻克一個面試重難點,若是你還不瞭解本進階計劃,文末點擊查看所有文章。git
若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。github
紅寶書(p178)上對於閉包的定義:閉包是指有權訪問另一個函數做用域中的變量的函數
關鍵在於下面兩點:web
對於閉包有下面三個特性:面試
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //訪問外部的date
}
return getDate('今天是:'); //"今天是:815"
}
getOuter();
複製代碼
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //訪問外部的date
}
return getDate; //外部函數返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"
複製代碼
function updateCount(){
var count = 0;
function getCount(val){
count = val;
console.log(count);
}
return getCount; //外部函數返回
}
var count = updateCount();
count(815); //815
count(816); //816
複製代碼
Javascript中有一個執行上下文(execution context)的概念,它定義了變量或函數有權訪問的其它數據,決定了他們各自的行爲。每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。算法
詳情查看 【進階1-2期】JavaScript深刻之執行上下文棧和變量對象跨域
做用域鏈:當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者不在父做用域中,這就是做用域鏈。瀏覽器
做用域鏈和原型繼承查找時的區別:若是去查找一個普通對象的屬性,可是在當前對象和其原型中都找不到時,會返回undefined;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError。安全
做用域鏈的頂端是全局對象,在全局環境中定義的變量就會綁定到全局對象中。
// my_script.js
"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
var a = 1;
var b = 2;
var foo = 3;
console.log("inside myFunc");
}
console.log("outside");
myFunc();
複製代碼
定義時:當myFunc被定義的時候,myFunc的標識符(identifier)就被加到了全局對象中,這個標識符所引用的是一個函數對象(myFunc function object)。
內部屬性[[scope]]指向當前的做用域對象,也就是函數的標識符被建立的時候,咱們所可以直接訪問的那個做用域對象(即全局對象)。
myFunc所引用的函數對象,其自己不只僅含有函數的代碼,而且還含有指向其被建立的時候的做用域對象。
調用時:當myFunc函數被調用的時候,一個新的做用域對象被建立了。新的做用域對象中包含myFunc函數所定義的本地變量,以及其參數(arguments)。這個新的做用域對象的父做用域對象就是在運行myFunc時能直接訪問的那個做用域對象(即全局對象)。
當函數返回沒有被引用的時候,就會被垃圾回收器回收。可是對於閉包,即便外部函數返回了,函數對象仍會引用它被建立時的做用域對象。
"use strict";
function createCounter(initial) {
var counter = initial;
function increment(value) {
counter += value;
}
function get() {
return counter;
}
return {
increment: increment,
get: get
};
}
var myCounter = createCounter(100);
console.log(myCounter.get()); // 返回 100
myCounter.increment(5);
console.log(myCounter.get()); // 返回 105
複製代碼
當調用 createCounter(100) 時,內嵌函數increment和get都有指向createCounter(100) scope的引用。假設createCounter(100)沒有任何返回值,那麼createCounter(100) scope再也不被引用,因而就能夠被垃圾回收。
可是createCounter(100)其實是有返回值的,而且返回值被存儲在了myCounter中,因此對象之間的引用關係以下圖:
即便createCounter(100)已經返回,可是其做用域仍在,而且只能被內聯函數訪問。能夠經過調用myCounter.increment() 或 myCounter.get()來直接訪問createCounter(100)的做用域。
當myCounter.increment() 或 myCounter.get()被調用時,新的做用域對象會被建立,而且該做用域對象的父做用域對象會是當前能夠直接訪問的做用域對象。
調用get()
時,當執行到return counter
時,在get()所在的做用域並無找到對應的標示符,就會沿着做用域鏈往上找,直到找到變量counter
,而後返回該變量。
單獨調用increment(5)時,參數value保存在當前的做用域對象。當函數要訪問counter時,沒有找到,因而沿着做用域鏈向上查找,在createCounter(100)的做用域找到了對應的標示符,increment()就會修改counter的值。除此以外,沒有其餘方式來修改這個變量。閉包的強大也在於此,可以存貯私有數據。
建立兩個函數:myCounter1
和myCounter2
//my_script.js
"use strict";
function createCounter(initial) {
/* ... see the code from previous example ... */
}
//-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);
複製代碼
關係圖以下
myCounter1.increment和myCounter2.increment的函數對象擁有着同樣的代碼以及同樣的屬性值(name,length等等),可是它們的[[scope]]指向的是不同的做用域對象。
進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,歡迎領取,以爲不錯點個star。
我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!