欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

javascript從作用域鏈談閉包

 更新時間:2020年07月29日 15:29:37   作者:dwqs  
這篇文章主要從作用域鏈談閉包,閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn),本文針對閉包進(jìn)行學(xué)習(xí),需要的朋友可以參考下

神馬是閉包

關(guān)于閉包的概念,是婆說婆有理。

閉包是指有權(quán)訪問另外一個函數(shù)作用域中的變量的函數(shù)
這概念有點繞,拆分一下。從概念上說,閉包有兩個特點:

  • 1、函數(shù)
  • 2、能訪問另外一個函數(shù)作用域中的變量

在ES 6之前,Javascript只有函數(shù)作用域的概念,沒有塊級作用域(但catch捕獲的異常 只能在catch塊中訪問)的概念(IIFE可以創(chuàng)建局部作用域)。每個函數(shù)作用域都是封閉的,即外部是訪問不到函數(shù)作用域中的變量。

function getName() {
 var name = "美女的名字";
 console.log(name); //"美女的名字"
}
function displayName() {
 console.log(name); //報錯
}

但是為了得到美女的名字,不死心的單身汪把代碼改成了這樣:

function getName() {
 var name = "美女的名字";
 function displayName() {
 console.log(name); 
 }
 return displayName;
}
var 美女 = getName(); 
美女() //"美女的名字"

這下,美女是一個閉包了,單身汪想怎么玩就怎么玩了。(但并不推薦單身汪用中文做變量名的寫法,大家不要學(xué))。

關(guān)于閉包呢,還想再說三點:

1、閉包可以訪問當(dāng)前函數(shù)以外的變量

function getOuter(){
 var date = '815';
 function getDate(str){
 console.log(str + date); //訪問外部的date
 }
 return getDate('今天是:'); //"今天是:815"
}
getOuter();

getDate是一個閉包,該函數(shù)執(zhí)行時,會形成一個作用域A,A中并沒有定義變量date,但它能在父一級作用域中找到該變量的定義。

2、即使外部函數(shù)已經(jīng)返回,閉包仍能訪問外部函數(shù)定義的變量

function getOuter(){
 var date = '815';
 function getDate(str){
 console.log(str + date); //訪問外部的date
 }
 return getDate; //外部函數(shù)返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"

3、閉包可以更新外部變量的值

function updateCount(){
 var count = 0;
 function getCount(val){
 count = val;
 console.log(count);
 }
 return getCount; //外部函數(shù)返回
}
var count = updateCount();
count(815); //815
count(816); //816

作用域鏈
為毛閉包就能訪問外部函數(shù)的變量呢?這就要說說Javascript中的作用域鏈了。
Javascript中有一個執(zhí)行環(huán)境(execution context)的概念,它定義了變量或函數(shù)有權(quán)訪問的其它數(shù)據(jù),決定了他們各自的行為。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象,環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。你可以把它當(dāng)做Javascript的一個普通對象,但是你只能修改它的屬性,卻不能引用它。

變量對象也是有父作用域的。當(dāng)訪問一個變量時,解釋器會首先在當(dāng)前作用域查找標(biāo)示符,如果沒有找到,就去父作用域找,直到找到該變量的標(biāo)示符或者不再存在父作用域了,這就是作用域鏈。

作用域鏈和原型繼承有點類似,但又有點小區(qū)別:如果去查找一個普通對象的屬性時,在當(dāng)前對象和其原型中都找不到時,會返回undefined;但查找的屬性在作用域鏈中不存在的話就會拋出ReferenceError。

作用域鏈的頂端是全局對象。對于全局環(huán)境中的代碼,作用域鏈只包含一個元素:全局對象。所以,在全局環(huán)境中定義變量的時候,它們就會被定義到全局對象中。當(dāng)函數(shù)被調(diào)用的時候,作用域鏈就會包含多個作用域?qū)ο蟆?/p>

  • 全局環(huán)境

關(guān)于作用域鏈講得略多(紅皮書上有關(guān)于作用域及執(zhí)行環(huán)境的詳細(xì)解釋),看一個簡單地例子:

// my_script.js
"use strict";
var foo = 1;
var bar = 2;

在全局環(huán)境中,創(chuàng)建了兩個簡單地變量。如前面所說,此時變量對象是全局對象。

  • Non-nested functions

改動一下代碼,創(chuàng)建一個沒有函數(shù)嵌套的函數(shù):

"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
 //-- define local-to-function variables
 var a = 1;
 var b = 2;
 var foo = 3;
 console.log("inside myFunc");
}
console.log("outside");
//-- and then, call it:
myFunc();

當(dāng)myFunc被定義的時候,myFunc的標(biāo)識符(identifier)就被加到了當(dāng)前的作用域?qū)ο笾校ㄔ谶@里就是全局對象),并且這個標(biāo)識符所引用的是一個函數(shù)對象(function object)。函數(shù)對象中所包含的是函數(shù)的源代碼以及其他的屬性。其中一個我們所關(guān)心的屬性就是內(nèi)部屬性[[scope]]。[[scope]]所指向的就是當(dāng)前的作用域?qū)ο?。也就是指的就是函?shù)的標(biāo)識符被創(chuàng)建的時候,我們所能夠直接訪問的那個作用域?qū)ο螅ㄔ谶@里就是全局對象)。

比較重要的一點是:myFunc所引用的函數(shù)對象,其本身不僅僅含有函數(shù)的代碼,并且還含有指向其被創(chuàng)建的時候的作用域?qū)ο蟆?/p>

當(dāng)myFunc函數(shù)被調(diào)用的時候,一個新的作用域?qū)ο蟊粍?chuàng)建了。新的作用域?qū)ο笾邪琺yFunc函數(shù)所定義的本地變量,以及其參數(shù)(arguments)。這個新的作用域?qū)ο蟮母缸饔糜驅(qū)ο缶褪窃谶\(yùn)行myFunc時我們所能直接訪問的那個作用域?qū)ο蟆?/p>

  • Nested functions

如前面所說,當(dāng)函數(shù)返回沒有被引用的時候,就會被垃圾回收器回收。但是對于閉包(函數(shù)嵌套是形成閉包的一種簡單方式)呢,即使外部函數(shù)返回了,函數(shù)對象仍會引用它被創(chuàng)建時的作用域?qū)ο蟆?/p>

"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

當(dāng)調(diào)用createCounter(100)時,內(nèi)嵌函數(shù)increment和get都有指向createCounter(100) scope的引用。如果createCounter(100)沒有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因為createCounter(100)實際上是有返回值的,并且返回值被存儲在了myCounter中,所以對象之間的引用關(guān)系發(fā)生變化。

需要用點時間思考的是:即使createCounter(100)已經(jīng)返回,但是其作用域仍在,并能且只能被內(nèi)聯(lián)函數(shù)訪問。可以通過調(diào)用myCounter.increment() 或 myCounter.get()來直接訪問createCounter(100)的作用域。

當(dāng)myCounter.increment() 或 myCounter.get()被調(diào)用時,新的作用域?qū)ο髸粍?chuàng)建,并且該作用域?qū)ο蟮母缸饔糜驅(qū)ο髸钱?dāng)前可以直接訪問的作用域?qū)ο蟆?/p>

當(dāng)執(zhí)行到return counter;時,在get()所在的作用域并沒有找到對應(yīng)的標(biāo)示符,就會沿著作用域鏈往上找,直到找到變量counter,然后返回該變量,調(diào)用increment(5)則會更有意思。當(dāng)單獨調(diào)用increment(5)時,參數(shù)value會存貯在當(dāng)前的作用域?qū)ο?。函?shù)要訪問value,能馬上在當(dāng)前作用域找到該變量。但是當(dāng)函數(shù)要訪問counter時,并沒有找到,于是沿著作用域鏈向上查找,在createCounter(100)的作用域找到了對應(yīng)的標(biāo)示符,increment()就會修改counter的值。除此之外,沒有其他方式來修改這個變量。閉包的強(qiáng)大也在于此,能夠存貯私有數(shù)據(jù)。

Similar function objects, different scope objects
對于上面的counter示例,再說點擴(kuò)展的事??创a:

//myScript.js
"use strict";
function createCounter(initial) {
 /* ... see the code from previous example ... */
}
//-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);

myCounter1 和 myCounter2創(chuàng)建之后,關(guān)系圖是醬紫的:

在上面的例子中,myCounter1.increment和myCounter2.increment的函數(shù)對象擁有著一樣的代碼以及一樣的屬性值(name,length等等),但是它們的[[scope]]指向的是不一樣的作用域?qū)ο蟆?/p>

這才有了下面的結(jié)果:

var a, b;
a = myCounter1.get(); // a 等于 100
b = myCounter2.get(); // b 等于 200
myCounter1.increment(1);
myCounter1.increment(2);
myCounter2.increment(5);
a = myCounter1.get(); // a 等于 103
b = myCounter2.get(); // b 等于 205

作用域和this

作用域會存儲變量,但this并不是作用域的一部分,它取決于函數(shù)調(diào)用時的方式。

相關(guān)文章

最新評論