摘要:自由變量指的是不在函數(shù)內(nèi)部聲明的變量。作用域鏈就是在所有內(nèi)部環(huán)境中查找變量的鏈?zhǔn)奖?。閉包的形式閉包的過(guò)程寫的不是很嚴(yán)謹(jǐn)。
要弄懂這個(gè)問(wèn)題首先要搞清楚一個(gè)概念, 執(zhí)行上下文。
執(zhí)行上下文執(zhí)行上下文是什么?
可以簡(jiǎn)單理解執(zhí)行上下文是js代碼執(zhí)行的環(huán)境,當(dāng)js執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對(duì)應(yīng)的執(zhí)行上下文。他的組成如下:
executionContextObj = { this: 對(duì)的就是你關(guān)注的那個(gè)this, VO:變量對(duì)象, scopeChain: 作用域鏈,跟閉包相關(guān) }
由于JS是單線程的,一次只能發(fā)生一件事情,其他事情會(huì)放在指定上下文棧中排隊(duì)。
js解釋器在初始化執(zhí)行代碼時(shí),會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文到棧中,接著隨著每次函數(shù)的調(diào)用都會(huì)創(chuàng)建并壓入一個(gè)新的執(zhí)行上下文棧。函數(shù)執(zhí)行后,該執(zhí)行上下文被彈出。
五個(gè)關(guān)鍵點(diǎn):
單線程
同步執(zhí)行
一個(gè)全局上下文
無(wú)限制函數(shù)上下文
每次函數(shù)調(diào)用創(chuàng)建新的上下文,包括調(diào)用自己
執(zhí)行上下文建立的步奏## 創(chuàng)建階段
初始化作用域鏈
創(chuàng)建變量對(duì)象
創(chuàng)建arguments
掃描函數(shù)聲明
掃描變量聲明
求this
初始化變量和函數(shù)的引用
執(zhí)行代碼
this
在函數(shù)執(zhí)行時(shí),this 總是指向調(diào)用該函數(shù)的對(duì)象。要判斷 this 的指向,其實(shí)就是判斷 this 所在的函數(shù)屬于誰(shuí)。
指向調(diào)用對(duì)象:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 // 指向全局對(duì)象 function foo() { console.log( this.a ); } var a = 2; foo(); // 2 // 注意 //接上 var bar = foo a = 3 bar() // 3不是2
// 通過(guò)這個(gè)例子可以更加了解this是函數(shù)調(diào)用時(shí)才確定的.
//再繞一點(diǎn)
function foo() { console.log( this.a ); } function doFoo(fn) { this.a = 4 fn(); } var obj = { a: 2, foo: foo }; var a =3 doFoo( obj.foo ); // 4 // 而 function foo() { this.a = 1 console.log( this.a ); } function doFoo(fn) { this.a = 4 fn(); } var obj = { a: 2, foo: foo }; var a =3 doFoo( obj.foo ); // 1
這是為什么呢?是因?yàn)閮?yōu)先讀取foo中設(shè)置的a,類似作用域的原理嗎?
通過(guò)打印foo和doFoo的this,可以知道,他們的this都是指向window的,他們的操作會(huì)修改window中的a的值。并不是優(yōu)先讀取foo中設(shè)置的a
因此如果把代碼改成
function foo() { setTimeout(() => this.a = 1,0) console.log( this.a ); } function doFoo(fn) { this.a = 4 fn(); } var obj = { a: 2, foo: foo }; var a =3 doFoo( obj.foo ); // 4 setTimeout(obj.foo,0) // 1
上面的代碼結(jié)果可以證實(shí)我們的猜測(cè)。
用new構(gòu)造就指向新對(duì)象:a = 4 function A() { this.a = 3 this.callA = function() { console.log(this.a) } } A() // 返回undefined, A().callA會(huì)報(bào)錯(cuò)。callA被保存在window上 var a = new A() a.callA() // 3,callA在new A返回的對(duì)象里apply/call/bind
大家應(yīng)該都很熟悉,令this指向傳遞的第一個(gè)參數(shù),如果第一個(gè)參數(shù)為null,undefined或是不傳,則指向全局變量
a = 3 function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj ); // 2 foo.call( null ); // 3 foo.call( undefined ); // 3 foo.call( ); // 3 var obj2 = { a: 5, foo } obj2.foo.call() // 3,不是5! //bind返回一個(gè)新的函數(shù) function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = a: 2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5箭頭函數(shù)
箭頭函數(shù)比較特殊,沒(méi)有自己的this,它使用封閉執(zhí)行上下文(函數(shù)或是global)的 this 值。
var x=11; var obj={ x:22, say:()=>{ console.log(this.x); //this指向window } } obj.say();// 11 obj.say.call({x:13}) // 11 x = 14 obj.say() // 14 //對(duì)比一下 var obj2={ x:22, say() { console.log(this.x); //this指向obj2 } } obj2.say();// 22 obj2.say.call({x:13}) // 13事件監(jiān)聽函數(shù)
指向被綁定的dom元素
document.body.addEventListener("click",function(){ console.log(this) } ) // 點(diǎn)擊網(wǎng)頁(yè) // ...
HTML
HTML標(biāo)簽的屬性中是可能寫JS的,這種情況下this指代該HTML元素。
變量對(duì)象
變量對(duì)象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲(chǔ)了上下文中定義的變量和函數(shù)聲明。
變量對(duì)象式一個(gè)抽象的概念,在不同的上下文中,表示不同的對(duì)象
全局執(zhí)行上下文的變量對(duì)象
全局執(zhí)行上下文中,變量對(duì)象就是全局對(duì)象。
在頂層js代碼中,this指向全局對(duì)象,全局變量會(huì)作為該對(duì)象的屬性來(lái)被查詢。在瀏覽器中,window就是全局對(duì)象。
var a = 1 console.log(window.a) // 1 console.log(this.a) // 1函數(shù)執(zhí)行上下文的變量對(duì)象
函數(shù)上下文中,變量對(duì)象VO就是活動(dòng)對(duì)象AO。
初始化時(shí),帶有arguments屬性。
函數(shù)代碼分成兩個(gè)階段執(zhí)行
進(jìn)入執(zhí)行上下文時(shí)
此時(shí)變量對(duì)象包括
形參
函數(shù)聲明,會(huì)替換已有變量對(duì)象
變量聲明,不會(huì)替換形參和函數(shù)
函數(shù)執(zhí)行
根據(jù)代碼修改變量對(duì)象的值
舉個(gè)例子
function test (a,c) { console.log(a, b, c, d) // 5 undefined [Function: c] undefined var b = 3; a = 4 function c () { } var d = function () { } console.log(a, b, c, d) // 4 3 [Function: c] [Function: d] var c = 5 console.log(a, b, c, d) // 4 3 5 [Function: d] } test(5,6)
來(lái)分析一下過(guò)程
1.創(chuàng)建執(zhí)行上下文時(shí)
VO = {
arguments: {0:5},
a: 5,
b: undefined,
c: [Function], //函數(shù)C覆蓋了參數(shù)c,但是變量聲明c無(wú)法覆蓋函數(shù)c的聲明
d: undefined, // 函數(shù)表達(dá)式?jīng)]有提升,在執(zhí)行到對(duì)應(yīng)語(yǔ)句之前為undefined
}
執(zhí)行代碼時(shí)
通過(guò)最后的console可以發(fā)現(xiàn),函數(shù)聲明可以被覆蓋
作用域鏈
先了解一下作用域
變量與函數(shù)的可訪問(wèn)范圍,控制著變量及函數(shù)的可見性與生命周期。分為全局作用域和局部作用域。
全局作用域:
在代碼中任何地方都能訪問(wèn)到的對(duì)象擁有全局作用域,有以下幾種:
在最外層定義的變量;
全局對(duì)象的屬性
任何地方隱式定義的變量(未定義直接賦值的變量),在任何地方隱式定義的變量都會(huì)定義在全局作用域中,即不通過(guò) var 聲明直接賦值的變量。
局部作用域:
JavaScript的作用域是通過(guò)函數(shù)來(lái)定義的,在一個(gè)函數(shù)中定義的變量只對(duì)這個(gè)函數(shù)內(nèi)部可見,稱為函數(shù)(局部)作用域
作用域鏈作用域鏈?zhǔn)且粋€(gè)對(duì)象列表,用以檢索上下文代碼中出現(xiàn)的標(biāo)識(shí)符。
標(biāo)識(shí)符可以理解為變量名稱,參數(shù),函數(shù)聲明。
函數(shù)在定義的時(shí)候會(huì)把父級(jí)的變量對(duì)象AO/VO的集合保存在內(nèi)部屬性[[scope]]中,該集合稱為作用域鏈。
自由變量指的是不在函數(shù)內(nèi)部聲明的變量。
當(dāng)函數(shù)需要訪問(wèn)自由變量時(shí),會(huì)順著作用域鏈來(lái)查找數(shù)據(jù)。子對(duì)象會(huì)一級(jí)一級(jí)的向上查找父對(duì)象的變量,父對(duì)象的變量對(duì)子對(duì)象是可見的,反之不成立。
作用域鏈就是在所有內(nèi)部環(huán)境中查找變量的鏈?zhǔn)奖怼?/p>
可以直接的說(shuō),JS采用了詞法作用域(靜態(tài)作用域),JS的函數(shù)運(yùn)行在他們被定義的作用域中,而不是他們被執(zhí)行的作用域。可以舉一個(gè)例子說(shuō)明:
var s = 3 function a () { console.log(s) } function b () { var s = 6 a() } b() // 3,不是6
如果js采用動(dòng)態(tài)作用域,打印出來(lái)的應(yīng)該是6而不是3,這個(gè)例子說(shuō)明了js是靜態(tài)作用域。
函數(shù)作用域鏈的偽代碼:
function foo() { function bar() { ... } } foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
函數(shù)在運(yùn)行激活的時(shí)候,會(huì)先復(fù)制[[scope]]屬性創(chuàng)建作用域鏈,然后創(chuàng)建變量對(duì)象VO,然后將其加入到作用域鏈。
executionContextObj: { VO:{}, scopeChain: [VO, [[scope]]] }閉包
閉包是什么
閉包按照mdn的定義是可以訪問(wèn)自由變量的函數(shù)。
自由變量前面提到過(guò),指的是不在函數(shù)內(nèi)部聲明的變量。
閉包的形式function a() { var num = 1 function b() { console.log(num++) } return b } var c1 = a() c1() // "1" c1() // "2" var c2 = a() c2() // "1" c2() // "2"閉包的過(guò)程
寫的不是很嚴(yán)謹(jǐn)。可能省略了一些過(guò)程:
運(yùn)行函數(shù)a
創(chuàng)建函數(shù)a的VO,包括變量num和函數(shù)b
定義函數(shù)b的時(shí)候,會(huì)保存a的變量對(duì)象VO和全局變量對(duì)象到[[scope]]中
返回函數(shù)b,保存到c1
運(yùn)行c1
創(chuàng)建c1的作用域鏈,該作用域鏈保存了a的變量對(duì)象VO
創(chuàng)建c1的VO
運(yùn)行c1,這是發(fā)現(xiàn)需要訪問(wèn)變量num,在當(dāng)前VO中不存在,于是通過(guò)作用域鏈進(jìn)行訪問(wèn),找到了保存在a的VO中的num,對(duì)它進(jìn)行操作,num的值被設(shè)置成2
再次運(yùn)行c1,重復(fù)第二步的操作,num的值設(shè)置成3
一些問(wèn)題
通過(guò)上面的運(yùn)行結(jié)果,我們可以觀察到,c2所訪問(wèn)num變量跟c1訪問(wèn)的num變量不是同一個(gè)變量。我們可以修改一下代碼,來(lái)確認(rèn)自己的猜想
function a() { var x = {y : 4} function b() { return x } return b } var c1 = a() var c2 = a() c1 === c2() // false
因此我們可以確定,閉包所訪問(wèn)的變量,是每次運(yùn)行父函數(shù)都重新創(chuàng)建,互相獨(dú)立的。
注意,同一個(gè)函數(shù)中創(chuàng)建的自由變量是可以在不同的閉包共享的
function a() { var x = 0 function b() { console.log(x++) } function c() { console.log(x++) } return { b, c } } var r = a() r.b() // 0 r.c() // 1
補(bǔ)充一個(gè)查看作用域鏈和閉包的技巧
打開chrome控制臺(tái)
console.dir(r.b) f b() { [[Scopes]]: [ {x:0}, {type: "global", name: "", object: Window} ] }
最后
最后,我們?cè)賮?lái)總結(jié)一下執(zhí)行上下文的過(guò)程,加深下印象
var scope = "global scope"; function checkscope(a){ var scope2 = "local scope"; } checkscope(5);
創(chuàng)建全局上下文執(zhí)行棧
創(chuàng)建全局變量globalContext.VO.
創(chuàng)建checkscope函數(shù)
將全局變量VO保存為作用域鏈,設(shè)置到函數(shù)的內(nèi)部屬性[[scope]]
checkscope.[[scope]] = [ globalContext.VO ];
執(zhí)行checkscope函數(shù)
創(chuàng)建函數(shù)執(zhí)行上下文,將checkscope函數(shù)執(zhí)行上下文壓入執(zhí)行上下文棧
ECStack = [
checkscopeContext, globalContext
];
函數(shù)執(zhí)行上下文創(chuàng)建階段第一步是復(fù)制[[scope]],創(chuàng)建作用域鏈
checkscopeContext = { Scope: checkscope.[[scope]], }
第二步是創(chuàng)建活動(dòng)對(duì)象AO
checkscopeContext = { AO: { arguments: { 0: 5 length: 1 }, a: 5 scope2: undefined }, Scope: checkscope.[[scope]], }
第三步是將活動(dòng)對(duì)象AO放入作用域鏈頂端
checkscopeContext = { AO: { arguments: { 0: 5 length: 1 }, a: 5 scope2: undefined }, Scope: [AO, checkscope.[[scope]]], }
第四步,求出this,上下文創(chuàng)建階段結(jié)束
這里的this等于window
進(jìn)入函數(shù)執(zhí)行階段
隨著函數(shù)執(zhí)行,修改AO的值
AO: { arguments: { 0: 5 length: 1 }, a: 5 scope2: "local scope" },
函數(shù)執(zhí)行完畢
函數(shù)上下文從執(zhí)行上下文棧彈出.
ECStack = [ globalContext ];
文章寫的比較長(zhǎng),涉及的范圍也比較廣,可能有不少的錯(cuò)誤,希望大家可以指正。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/95903.html
摘要:引言滿滿的干貨,面試必系列,參考大量資料,并集合自己的理解以及相關(guān)的面試題,對(duì)核心知識(shí)點(diǎn)中的作用域閉包上下文進(jìn)行了梳理。本篇重點(diǎn)介紹閉包和。所以,有另一種說(shuō)法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 滿滿的干貨,面試必bei系列,參考大量資料,并集...
摘要:內(nèi)部的稱為內(nèi)部函數(shù)或閉包函數(shù)。過(guò)度使用閉包會(huì)導(dǎo)致性能下降。,閉包函數(shù)分為定義時(shí),和運(yùn)行時(shí)。循環(huán)會(huì)先運(yùn)行完畢,此時(shí),閉包函數(shù)并沒(méi)有運(yùn)行。閉包只能取得外部函數(shù)中的最后一個(gè)值。事件綁定種的匿名函數(shù)也是閉包函數(shù)。而對(duì)象中的閉包函數(shù),指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數(shù)閉包)。 在一個(gè)函數(shù)parent內(nèi)聲明另一個(gè)函數(shù)child,形成了嵌套。函數(shù)child使用了函數(shù)parent的參數(shù)...
摘要:閉包的學(xué)術(shù)定義先來(lái)參考下各大權(quán)威對(duì)閉包的學(xué)術(shù)定義百科閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。所以,有另一種說(shuō)法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 前言 上一章講解了閉包的底層實(shí)現(xiàn)細(xì)節(jié),我想大家對(duì)閉包的概念應(yīng)該也有了個(gè)大概印象,但是真要用簡(jiǎn)短的幾句話來(lái)說(shuō)清楚,這還真不是件容易的事。這里我們就來(lái)總結(jié)提煉下閉包的概念,以應(yīng)付那些非專人士的心血來(lái)潮。 閉包的學(xué)術(shù)...
摘要:一般來(lái)講,函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域,但是閉包的情況有所不同理解閉包的前提先理解另外兩個(gè)內(nèi)容作用域鏈垃圾回收作用域鏈當(dāng)代碼在執(zhí)行過(guò)程中,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。 閉包是javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包來(lái)實(shí)現(xiàn)。個(gè)人的理解是:函數(shù)中嵌套函數(shù)。 閉包的定義及其優(yōu)缺點(diǎn) 閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的...
摘要:如果不用的話,你實(shí)際上聲明了一個(gè)全局變量閉包有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域的變量,常見的創(chuàng)建方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),通過(guò)另一個(gè)函數(shù)訪問(wèn)這個(gè)函數(shù)的局部變量。 變量作用域 變量的作用域無(wú)非就是兩種:全局變量和局部變量。 函數(shù)內(nèi)部聲明變量的時(shí)候,一定要使用var命令。如果不用的話,你實(shí)際上聲明了一個(gè)全局變量! 閉包 有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域的變量,常見的創(chuàng)建方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建...
摘要:常見問(wèn)題說(shuō)到閉包相關(guān)的問(wèn)題,最典型的就是變量和指向這兩類問(wèn)題。如果有錯(cuò)誤或不嚴(yán)謹(jǐn)?shù)牡胤?,歡迎批評(píng)指正,如果喜歡,歡迎點(diǎn)贊。 引言 閉包這個(gè)詞對(duì)很多前端開發(fā)人員來(lái)說(shuō)既熟悉又陌生,熟悉是因?yàn)楹芏嗳硕加眠^(guò)閉包,但是用的時(shí)候不知道閉包,陌生是因?yàn)椴⒉焕斫忾]包,接下來(lái)這篇文章將會(huì)從多方面介紹閉包 定義 閉包是怎么定義的呢?當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)在當(dāng)前詞法作...
閱讀 2511·2021-11-16 11:44
閱讀 2127·2021-10-12 10:12
閱讀 2405·2021-09-22 15:22
閱讀 3119·2021-08-11 11:17
閱讀 1600·2019-08-29 16:53
閱讀 2742·2019-08-29 14:09
閱讀 3559·2019-08-29 14:03
閱讀 3441·2019-08-29 11:09