摘要:全局作用域?qū)?yīng)全局變量,局部作用域?qū)?yīng)局部變量。通常理解的局部作用域是定義在函數(shù)內(nèi)部的作用域。作用域鏈當(dāng)我們定義一個(gè)全局變量的時(shí)候,它就在全局環(huán)境里,作用域鏈只有一條。
變量對(duì)象
js 解析器是如何找到我們定義的函數(shù)和變量的?
實(shí)際是通過VO(Varible Object)來存儲(chǔ)執(zhí)行上下文所需要存儲(chǔ)的比如變量、函數(shù)聲明、函數(shù)參數(shù)。進(jìn)一步我們還需要區(qū)分全局變量對(duì)象和函數(shù)變量對(duì)象。
我們定義一個(gè)全局變量就會(huì)有一個(gè)全局變量對(duì)象,里面有剛定義的全局變量
定義函數(shù),除了有全局變量對(duì)象外,還有函數(shù)變量對(duì)象。
簡(jiǎn)單說分為全局作用域、和局部作用域。全局作用域?qū)?yīng)全局變量,局部作用域?qū)?yīng)局部變量。通常理解的局部作用域是定義在函數(shù)內(nèi)部的作用域。
作用域鏈當(dāng)我們定義一個(gè)全局變量的時(shí)候,它就在全局環(huán)境里,作用域鏈只有一條。當(dāng)我們定義一個(gè)函數(shù),首先在定義的時(shí)候,或者說在js流執(zhí)行的時(shí)候,會(huì)將函數(shù)和變量申明提前,而且函數(shù)申明比變量更提前。在申明函數(shù)的時(shí)候,會(huì)生成一個(gè)scope屬性。此時(shí),[[scope]]里面只包含了全局對(duì)象【Global Object】。而如果, 我們?cè)贏的內(nèi)部定義一個(gè)B函數(shù),那B函數(shù)同樣會(huì)創(chuàng)建一個(gè)[[scope]]屬性,B的[[scope]]屬性包含了兩個(gè)對(duì)象,一個(gè)是A的活動(dòng)對(duì)象【Activation Object】【對(duì)于函數(shù)來說】一個(gè)是全局對(duì)象,A的活動(dòng)對(duì)象上面,全局對(duì)象在下面。以此類摧,每個(gè)函數(shù)的都在定義的時(shí)候創(chuàng)建自己的[[scope]],里面保存著一個(gè)類似于棧的格式的數(shù)據(jù)。
// 外部函數(shù) function A(){ // 內(nèi)部函數(shù) function B(){ } }執(zhí)行環(huán)境
了解函數(shù)執(zhí)行環(huán)境前先看下三個(gè)概念。
全局執(zhí)行環(huán)境
我們通??梢栽诖a第一行使用類似String、Math等這樣的全局函數(shù),為何能直接使用到?
是因?yàn)槲覀冊(cè)诔跏蓟a以前,js引擎會(huì)把全局的一些東西,我理解成[[global]]會(huì)初始化到VO里面。
偽代碼:
[[global]] = { Math : ..., String : ..., window : global, // 執(zhí)行全局對(duì)象本身 } String(10) // [[global]].String(10) window.a = 10 // [[global]].window.a = 10 this.b = 10 // [[global]].b = 10
激活對(duì)象(通常在函數(shù)中才有)
Active Object 縮寫AO,該對(duì)象有函數(shù)的arguments類數(shù)組對(duì)象和this對(duì)象。
**慕課網(wǎng)這一章節(jié)說,函數(shù)里面 AO === VO
慕課網(wǎng)--Js深入淺出--閉包章節(jié)
變量初始化階段
VO或者AO按照如下順序初始化:
函數(shù)參數(shù)(如未傳入,初始化該參數(shù)值為undefined)
函數(shù)聲明(如發(fā)生命名沖突會(huì)覆蓋)
變量聲明(初始化變量值為undefined,若發(fā)生命名沖突會(huì)忽略)
通過一個(gè)實(shí)例來說明變量初始化階段
function test (a, b) { var c = 10 function d(){} var e = function _e(){} (function(){}) // 括號(hào)括起來的匿名函數(shù),但并沒執(zhí)行,此時(shí)函數(shù)申明不會(huì)提前 // 這里多說下,括起來的不管是匿名函數(shù),還是正常申明的函數(shù),不僅不會(huì)申明提前,還會(huì) // 形成一個(gè)閉包,只有通過自執(zhí)行才能調(diào)用。在全局作用域或在申請(qǐng)的局部作用域調(diào)用都 // 是undefined.這里一個(gè)簡(jiǎn)單粗暴的解釋是為何沒有提前,如果提前就留下一個(gè)括號(hào),豈不 // 是很奇怪。哈哈。 b = 20 } test(10) AO(test) = { a: 10, // a 已傳入,所以有值 b: undefined, // b 未傳入,undefined,并且局部變量b發(fā)生命名沖突,但被忽略 c: undefined, // 局部變量 d: , // 函數(shù)申明 e: undefined // 命名函數(shù)_e,賦值給了局部變量e,undefined } // 分析發(fā)現(xiàn),局部變量c和e兩個(gè)局部變量都是undefined,因?yàn)殚_頭就說了,變量申明被前置,所以在初始化的時(shí)候就是undefined,比如我們?cè)趘ar e = function _e() {} 前面調(diào)用e //() 肯定會(huì)報(bào)錯(cuò)語法錯(cuò)誤,e不是一個(gè)函數(shù)。
每個(gè)函數(shù)運(yùn)行時(shí)都會(huì)產(chǎn)生一個(gè)執(zhí)行環(huán)境,而且同一個(gè)函數(shù)運(yùn)行多次時(shí),會(huì)多次產(chǎn)生不同的執(zhí)行環(huán)境。js為每一個(gè)執(zhí)行環(huán)境關(guān)聯(lián)了一個(gè)變量對(duì)象。環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。 全局執(zhí)行環(huán)境是最外圍的執(zhí)行環(huán)境,它所關(guān)聯(lián)的對(duì)象就是我們熟知的window對(duì)象。js的執(zhí)行順序是根據(jù)函數(shù)的調(diào)用來決定的,當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),該函數(shù)環(huán)境的變量對(duì)象就被壓入一個(gè)環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧將該函數(shù)的變量對(duì)象彈出,把控制權(quán)交給之前的執(zhí)行環(huán)境變量對(duì)象。
var scope = "global"; function fn1(){ return scope; } function fn2(){ return scope; } fn1(); fn2();
當(dāng)函數(shù)被執(zhí)行的時(shí)候,就是進(jìn)入這個(gè)函數(shù)的執(zhí)行環(huán)境,首先會(huì)創(chuàng)建一個(gè)它自己的活動(dòng)對(duì)象【Activation Object】(這個(gè)對(duì)象中包含了this、參數(shù)(arguments)、局部變量(包括命名的參數(shù))的定義,當(dāng)然全局對(duì)象是沒有arguments的)和一個(gè)變量對(duì)象的作用域鏈[[scope chain]],然后,把這個(gè)執(zhí)行環(huán)境的[scope]按順序復(fù)制到[[scope chain]]里(也有博客說把作用域[[scope chain]]鏈賦值給[[scope]]),最后把這個(gè)活動(dòng)對(duì)象推入到[[scope chain]]的頂部。這樣[[scope chain]]就是一個(gè)有序的棧,這樣保了對(duì)執(zhí)行環(huán)境有權(quán)訪問的所有變量和對(duì)象的有序訪問。
分析下:
當(dāng)執(zhí)行fn1的時(shí)候,VO中fn1會(huì)指向fn1的執(zhí)行環(huán)境,因?yàn)楹瘮?shù)申明前置,所以在VO中已經(jīng)存在function。
可以看到fn1活動(dòng)對(duì)象里并沒有scope變量,于是沿著作用域鏈(scope chain)向后尋找,結(jié)果在全局變量對(duì)象里找到了scope,所以就返回全局變量對(duì)象里的scope值。
引入閉包代碼
function outer(){ var scope = "outer"; return function inner(){ return scope; } }
調(diào)用outer()
這里有點(diǎn)不解的是?如果outer()直接調(diào)用,然后再調(diào)用inner即outer()()不會(huì)造成閉包,但把outer賦值給一個(gè)全局變量,上面閉包代碼的寫法,就會(huì)成為一個(gè)閉包。
var fn = outer(); fn();
分析下:
執(zhí)行outer函數(shù),在VO對(duì)象中outer會(huì)指向它的執(zhí)行環(huán)境,當(dāng)因?yàn)樽兞可昝髑爸?,fn在初始化變量的時(shí)候是undefined。所以在outer函數(shù)執(zhí)行中的時(shí)候,他依然是undefined.
如圖,outer執(zhí)行完后,執(zhí)行環(huán)境不再被VO中的outer所指向,即執(zhí)行環(huán)境已被銷毀。
當(dāng)?shù)谝淮螆?zhí)行fn到底發(fā)生了什么?執(zhí)行fn即執(zhí)行inner函數(shù),此時(shí)outer的執(zhí)行環(huán)境已被銷毀(而且只能有并且只有一個(gè)執(zhí)行環(huán)境),而outer()執(zhí)行后又賦值給了fn,所以可以這么理解,inner執(zhí)行的時(shí)候執(zhí)行環(huán)境也賦值給了fn。由于fn這個(gè)全局變量一直存在,除非你手動(dòng)置為null或關(guān)閉瀏覽器,所以可以認(rèn)為inner這個(gè)函數(shù)的執(zhí)行環(huán)境就一直存在,那么inner函數(shù)執(zhí)行環(huán)境的相關(guān)變量就一直存在。
通過上面加粗文字的理解,趁熱打鐵,再來看一個(gè)函數(shù):
function outer() { var a = 1 return function inner() { a ++ } } var f = outer() console.log(f()) // 2 console.log(f()) // 3
為何f()第二次執(zhí)行的時(shí)候,會(huì)是3。結(jié)合上面加粗字體的理解,f()在執(zhí)行完后,outer的AO并沒有被釋放,當(dāng)?shù)诙螆?zhí)行f()的時(shí)候,由于inner函數(shù)AO并沒有變量a,所以沿著[[scope]]chain 中查找,結(jié)果在outer的AO中找到了變量a,但此時(shí)變量a已經(jīng)是2了,所以再a ++ 后就是3了。除非我們關(guān)閉瀏覽器,或者將f = null,再次執(zhí)行f(),就會(huì)回到初始化的狀態(tài)。
理解了閉包再來看下面這個(gè)代碼就很好理解了。
function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定義一個(gè)帶參函數(shù) result[i] = function(num){ // num 形參 // 實(shí)際num這個(gè)形參拷貝了實(shí)參i這個(gè)變量的一個(gè)副本到arguments里。 // i 的變化就不會(huì)影響到這個(gè)副本的變化。 console.log("arguments", arguments) // innerarg 的執(zhí)行環(huán)境里面引用了result[i]這個(gè)數(shù)組函數(shù)的活動(dòng)對(duì)象 // 當(dāng)執(zhí)行innerarg 的時(shí)候,作用域鏈會(huì)去找num,在result[i]這個(gè)數(shù)組活動(dòng)對(duì)象的 // arguments 找到了 return function innerarg(){ console.log("num", num) return num; } }(i);//預(yù)先執(zhí)行函數(shù)寫法 //把i當(dāng)成參數(shù)傳進(jìn)去 實(shí)參 } console.log("result", result) return result; } var fn = outer() fn[0]() fn[1]()
起初我覺得上面那個(gè)代碼實(shí)在是太復(fù)雜,為何要在result[i]這個(gè)函數(shù)數(shù)組里再返回一個(gè)函數(shù)。起初我是這么寫的。結(jié)果發(fā)現(xiàn)我錯(cuò)了,但又對(duì)匿名函數(shù)自執(zhí)行,又重新正確的認(rèn)識(shí)了下。
function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){//注:i是outer()的局部變量 result[i] = function(num){ console.log(num) return i; }(i) // result[i]表面上是被一個(gè)函數(shù)所賦值,但這是一個(gè)自執(zhí)行函數(shù),自執(zhí)行函數(shù)又 // 返回了它的實(shí)參。所以如果我們調(diào)用fn[0]()就會(huì)提示fn[0]不是一個(gè)function. // 你修改成result[i] = (function(num){...}(i))也是一個(gè)自執(zhí)行,只不過 // 多了一層閉包。 } console.log("result", result) return result;//返回一個(gè)函數(shù)對(duì)象數(shù)組 //這個(gè)時(shí)候會(huì)初始化result.length個(gè)關(guān)于內(nèi)部函數(shù)的作用域鏈 } var fn = outer(); fn[0] fn[1]參考文獻(xiàn)
csdn博文
一篇cn博客--2012年對(duì)scope解釋比較透徹
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107163.html
摘要:當(dāng)閉包函數(shù)外部包含了一個(gè)匿名函數(shù)指向全局匿名函數(shù)執(zhí)行具有全局性指向原理每個(gè)函數(shù)在被調(diào)用時(shí)都會(huì)自動(dòng)取得兩個(gè)特殊變量和。過度使用閉包可能會(huì)導(dǎo)致內(nèi)存占用過多閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值所以要注意寫法 面試的時(shí)候一直會(huì)被問到什么是閉包,以前也不是很在意,更沒有去總結(jié)和歸納. 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。 ...
摘要:所以,全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象。講到這里,可能你已經(jīng)對(duì)執(zhí)行環(huán)境執(zhí)行環(huán)境對(duì)象變量對(duì)象作用域作用域鏈的理解已經(jīng)他們之間的關(guān)系有了一個(gè)較清晰的認(rèn)識(shí)。 JavaScript中的執(zhí)行環(huán)境、作用域、作用域鏈、閉包一直是一個(gè)非常有意思的話題,很多博主和大神都分享過相關(guān)的文章。這些知識(shí)點(diǎn)不僅比較抽象,不易理解,更重要的是與這些知識(shí)點(diǎn)相關(guān)的問題在面試中高頻出現(xiàn)。之前我也看過...
摘要:前言這段時(shí)間一直在消化作用域鏈和閉包的相關(guān)知識(shí)。而作用域鏈則是這套規(guī)則這套規(guī)則的具體運(yùn)行。是變量對(duì)象的縮寫那這樣放有什么好處呢我們知道作用域鏈保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問權(quán)限的變量和函數(shù)的有序訪問。 前言:這段時(shí)間一直在消化作用域鏈和閉包的相關(guān)知識(shí)。之前看《JS高程》和一些技術(shù)博客,對(duì)于這些概念的論述多多少少不太清楚或者不太完整,包括一些大神的技術(shù)文章。這也給我的學(xué)習(xí)上造成了一些困惑,...
摘要:目錄執(zhí)行環(huán)境與作用域鏈立即執(zhí)行函數(shù)閉包知識(shí)點(diǎn)什么是閉包使用閉包的意義與注意點(diǎn)閉包的具體應(yīng)用小結(jié)這是基本語法的函數(shù)部分的第篇文章,主要講述了中比較重要的知識(shí)點(diǎn)閉包在講閉包之前,在上一篇函數(shù)二的基礎(chǔ)上,進(jìn)一步深化執(zhí)行環(huán)境和作用域鏈的知識(shí)點(diǎn),并補(bǔ) 目錄 1.執(zhí)行環(huán)境與作用域鏈 2. 立即執(zhí)行函數(shù) 3. 閉包知識(shí)點(diǎn) 3.1 什么是閉包 3.2 使用閉包的意義與注意點(diǎn) 3.3 閉包的具體應(yīng)用 4...
摘要:前言最近在學(xué)前幾天看到兩道題剛開始看懵懵懂懂這幾天通過各種查資料慢慢的理解頓悟了對(duì)匿名函數(shù)閉包立即執(zhí)行函數(shù)的理解也更深了一點(diǎn)在此分享給大家我的理解與總結(jié)希望能幫助大家理解因?yàn)檫@篇文章是我用心總結(jié)的查閱了很多的資料所以總結(jié)的比較細(xì)篇幅較長(zhǎng)如果 前言 最近在學(xué)JS,前幾天看到兩道題,剛開始看懵懵懂懂,這幾天通過各種查資料,慢慢的理解,頓悟了,對(duì)匿名函數(shù),閉包,立即執(zhí)行函數(shù)的理解也更深了一點(diǎn)...
閱讀 3599·2021-09-13 10:28
閱讀 1948·2021-08-10 09:43
閱讀 1022·2019-08-30 15:44
閱讀 3193·2019-08-30 13:14
閱讀 1850·2019-08-29 16:56
閱讀 2947·2019-08-29 16:35
閱讀 2854·2019-08-29 12:58
閱讀 872·2019-08-26 13:46