javascript系列--javascript深刻淺出圖解做用域鏈和閉包

1、概要

對於閉包的定義(紅寶書P178):閉包就是指有權訪問另一個函數的做用域中的變量的函數。javascript

關鍵點:java

一、閉包是一個函數git

二、可以訪問另一個函數做用域中的變量github

2、閉包特性

對於閉包有下面三個特性:閉包

一、閉包能夠訪問當前函數之外的變量ide

function getOuter(){
var date = '815';
function getDate(str){函數

console.log(str + date);  //訪問外部的date

}
return getDate('今天是:'); //"今天是:815"
}
getOuter();spa

二、即便外部函數已經返回,閉包仍能訪問外部函數定義的變量code

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

3、做用域鏈

javascript中有一個執行上下文(execution context)的概念,它定義了變量或函數有權訪問的其餘數據,決定它們各自的行爲。每個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。你能夠把它當作Javascript的一個普通對象,可是你只能修改它的屬性,卻不能引用它。

變量對象也是有父做用域的。

做用域鏈定義:當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者再也不存在父做用域了,這就是做用域鏈。

做用域鏈和原型繼承有點相似,但又有點小區別:若是去查找一個普通對象的屬性時,在當前對象和其原型中都找不到時,會返回undefined;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError。

做用域鏈的頂端是全局對象。對於全局環境中的代碼,做用域鏈只包含一個元素:全局對象。因此,在全局環境中定義變量的時候,它們就會被定義到全局對象中。當函數被調用的時候,做用域鏈就會包含多個做用域對象。

4、全局環境

關於做用域鏈講得略多(紅皮書上有關於做用域及執行環境的詳細解釋),看一個簡單地例子:

// my_script.js
"use strict";
var foo = 1;
var bar = 2;
在全局環境中,建立了兩個簡單地變量。如前面所說,此時變量對象是全局對象:

image

執行上述代碼,my_script.js自己會造成一個執行環境,以及它所引用的變量對象。

4.1無嵌套函數

// 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]]指向當前的做用域對象,也就是函數的標識符被建立的時候,咱們所可以直接訪問的那個做用域對象(即全局對象)。

image

myFunc所引用的函數對象,其自己不只僅含有函數的代碼,而且還含有指向其被建立的時候的做用域對象。

調用時:當myFunc函數被調用的時候,一個新的做用域對象被建立了。新的做用域對象中包含myFunc函數所定義的本地變量,以及其參數(arguments)。這個新的做用域對象的父做用域對象就是在運行myFunc時能直接訪問的那個做用域對象(即全局對象)。

image

4.2嵌套函數

當函數返回沒有被引用的時候,就會被垃圾回收器回收。可是對於閉包,即便外部函數返回了,函數對象仍會引用它被建立時的做用域對象。

"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再也不被引用,因而就能夠被垃圾回收。

image

可是createCounter(100)其實是有返回值的,而且返回值被存儲在了myCounter中,因此對象之間的引用關係以下圖:

image

即便createCounter(100)已經返回,可是其做用域仍在,而且只能被內聯函數訪問。能夠經過調用myCounter.increment() 或 myCounter.get()來直接訪問createCounter(100)的做用域。

當myCounter.increment() 或 myCounter.get()被調用時,新的做用域對象會被建立,而且該做用域對象的父做用域對象會是當前能夠直接訪問的做用域對象。

調用get()時,當執行到return counter時,在get()所在的做用域並無找到對應的標示符,就會沿着做用域鏈往上找,直到找到變量counter,而後返回該變量。

image

單獨調用increment(5)時,參數value保存在當前的做用域對象。當函數要訪問counter時,沒有找到,因而沿着做用域鏈向上查找,在createCounter(100)的做用域找到了對應的標示符,increment()就會修改counter的值。除此以外,沒有其餘方式來修改這個變量。閉包的強大也在於此,可以存貯私有數據。

image

建立兩個函數: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);
關係圖以下

image

myCounter1.increment和myCounter2.increment的函數對象擁有着同樣的代碼以及同樣的屬性值(name,length等等),可是它們的[[scope]]指向的是不同的做用域對象。

5、參考

https://github.com/dwqs/blog/...

相關文章
相關標籤/搜索