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