理解JavaScript中的作用域和上下文_第1頁(yè)
理解JavaScript中的作用域和上下文_第2頁(yè)
理解JavaScript中的作用域和上下文_第3頁(yè)
理解JavaScript中的作用域和上下文_第4頁(yè)
理解JavaScript中的作用域和上下文_第5頁(yè)
已閱讀5頁(yè),還剩9頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、優(yōu)就業(yè)JS教程-理解JavaScript中的作用域和上下文Java對(duì)于作用域(Scope)和上下文(Context)的實(shí)現(xiàn)是這門語(yǔ)言的一個(gè)非常獨(dú)到的地方,部分歸功于其獨(dú)特的靈活性。 函數(shù)可以接收不同的的上下文和作用域。這些概念為Java中的很多強(qiáng)大的設(shè)計(jì)模式提供了堅(jiān)實(shí)的基礎(chǔ)。 然而這也概念也非常容易給開發(fā)人員帶來(lái)困惑。為此,本文將全面的剖析這些概念,并闡述不同的設(shè)計(jì)模式是如何利用它們的。Statement作者: 景莊,Web開發(fā)者,主要關(guān)注Java、Node.js、React、Docker等。上下文(Context)和作用域(Scope)首先需要知道的是,上下文和作用域是兩個(gè)完全不同的概念。多

2、年來(lái),我發(fā)現(xiàn)很多開發(fā)者會(huì)混淆這兩個(gè)概念(包括我自己), 錯(cuò)誤的將兩個(gè)概念混淆了。平心而論,這些年來(lái)很多術(shù)語(yǔ)都被混亂的使用了。函數(shù)的每次調(diào)用都有與之緊密相關(guān)的作用域和上下文。從根本上來(lái)說(shuō),作用域是基于函數(shù)的,而上下文是基于對(duì)象的。 換句話說(shuō),作用域涉及到所被調(diào)用函數(shù)中的變量訪問,并且不同的調(diào)用場(chǎng)景是不一樣的。上下文始終是this關(guān)鍵字的值, 它是擁有(控制)當(dāng)前所執(zhí)行代碼的對(duì)象的引用。變量作用域一個(gè)變量可以被定義在局部或者全局作用域中,這建立了在運(yùn)行時(shí)(runtime)期間變量的訪問性的不同作用域范圍。 任何被定義的全局變量,意味著它需要在函數(shù)體的外部被聲明,并且存活于整個(gè)運(yùn)行時(shí)(runtime

3、),并且在任何作用域中都可以被訪問到。 在ES6之前,局部變量只能存在于函數(shù)體中,并且函數(shù)的每次調(diào)用它們都擁有不同的作用域范圍。 局部變量只能在其被調(diào)用期的作用域范圍內(nèi)被賦值、檢索、操縱。需要注意,在ES6之前,Java不支持塊級(jí)作用域,這意味著在if語(yǔ)句、switch語(yǔ)句、for循環(huán)、while循環(huán)中無(wú)法支持塊級(jí)作用域。 也就是說(shuō),ES6之前的Java并不能構(gòu)建類似于Java中的那樣的塊級(jí)作用域(變量不能在語(yǔ)句塊外被訪問到)。但是, 從ES6開始,你可以通過let關(guān)鍵字來(lái)定義變量,它修正了var關(guān)鍵字的缺點(diǎn),能夠讓你像Java語(yǔ)言那樣定義變量,并且支持塊級(jí)作用域??磧蓚€(gè)例子:ES6之前,我們

4、使用var關(guān)鍵字定義變量:function func() if (true) var tmp = 123;console.log(tmp); / 123之所以能夠訪問,是因?yàn)関ar關(guān)鍵字聲明的變量有一個(gè)變量提升的過程。而在ES6場(chǎng)景,推薦使用let關(guān)鍵字定義變量:function func() if (true) let tmp = 123;console.log(tmp); / ReferenceError: tmp is not defined這種方式,能夠避免很多錯(cuò)誤。什么是this上下文上下文通常取決于函數(shù)是如何被調(diào)用的。當(dāng)一個(gè)函數(shù)被作為對(duì)象中的一個(gè)方法被調(diào)用的時(shí)候,this被設(shè)置為調(diào)

5、用該方法的對(duì)象上:var obj = foo: function()alert(this = obj);obj.foo(); / true這個(gè)準(zhǔn)則也適用于當(dāng)調(diào)用函數(shù)時(shí)使用new操作符來(lái)創(chuàng)建對(duì)象的實(shí)例的情況下。在這種情況下,在函數(shù)的作用域內(nèi)部this的值被設(shè)置為新創(chuàng)建的實(shí)例:function foo()alert(this);new foo() / foofoo() / window當(dāng)調(diào)用一個(gè)為綁定函數(shù)時(shí),this默認(rèn)情況下是全局上下文,在瀏覽器中它指向window對(duì)象。需要注意的是,ES5引入了嚴(yán)格模式的概念, 如果啟用了嚴(yán)格模式,此時(shí)上下文默認(rèn)為undefined。執(zhí)行環(huán)境(executio

6、n context)Java是一個(gè)單線程語(yǔ)言,意味著同一時(shí)間只能執(zhí)行一個(gè)任務(wù)。當(dāng)Java解釋器初始化執(zhí)行代碼時(shí), 它首先默認(rèn)進(jìn)入全局執(zhí)行環(huán)境(execution context),從此刻開始,函數(shù)的每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境。這里會(huì)經(jīng)常引起新手的困惑,這里提到了一個(gè)新的術(shù)語(yǔ)執(zhí)行環(huán)境(execution context),它定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。 它更偏向于作用域的作用,而不是我們前面討論的上下文(Context)。請(qǐng)務(wù)必仔細(xì)的區(qū)分執(zhí)行環(huán)境和上下文這兩個(gè)概念(注:英文容易造成混淆)。 說(shuō)實(shí)話,這是個(gè)非常糟糕的命名約定,但是它是ECMA規(guī)范制定的,你還是遵

7、守吧。每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中(execution stack)。在函數(shù)執(zhí)行完后,棧將其環(huán)境彈出, 把控制權(quán)返回給之前的執(zhí)行環(huán)境。ECMA程序中的執(zhí)行流正是由這個(gè)便利的機(jī)制控制著。執(zhí)行環(huán)境可以分為創(chuàng)建和執(zhí)行兩個(gè)階段。在創(chuàng)建階段,解析器首先會(huì)創(chuàng)建一個(gè)變量對(duì)象(variable object,也稱為活動(dòng)對(duì)象 activation object), 它由定義在執(zhí)行環(huán)境中的變量、函數(shù)聲明、和參數(shù)組成。在這個(gè)階段,作用域鏈會(huì)被初始化,this的值也會(huì)被最終確定。 在執(zhí)行階段,代碼被解釋執(zhí)行。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(variabl

8、e object),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。 需要知道,我們無(wú)法手動(dòng)訪問這個(gè)對(duì)象,只有解析器才能訪問它。作用域鏈(The Scope Chain)當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain)。作用域鏈的用途是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。 作用域鏈包含了在環(huán)境棧中的每個(gè)執(zhí)行環(huán)境對(duì)應(yīng)的變量對(duì)象。通過作用域鏈,可以決定變量的訪問和標(biāo)識(shí)符的解析。 注意,全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈的最后一個(gè)對(duì)象。我們來(lái)看一個(gè)例子:var color = blue;function changeColor()var another

9、Color = red;function swapColors()var tempColor = anotherColor;anotherColor = color;color = tempColor;/ 這里可以訪問color, anotherColor, 和 tempColor/ 這里可以訪問color 和 anotherColor,但是不能訪問 tempColorswapColors();changeColor();/ 這里只能訪問colorconsole.log(Color is now + color);上述代碼一共包括三個(gè)執(zhí)行環(huán)境:全局環(huán)境、changeColor()的局部環(huán)境、s

10、wapColors()的局部環(huán)境。 上述程序的作用域鏈如下圖所示:從上圖發(fā)現(xiàn)。內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但是外部環(huán)境不能訪問內(nèi)部環(huán)境中的任何變量和函數(shù)。 這些環(huán)境之間的聯(lián)系是線性的、有次序的。對(duì)于標(biāo)識(shí)符解析(變量名或函數(shù)名搜索)是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過程。搜索過程始終從作用域鏈的前端開始, 然后逐級(jí)地向后(全局執(zhí)行環(huán)境)回溯,直到找到標(biāo)識(shí)符為止。閉包閉包是指有權(quán)訪問另一函數(shù)作用域中的變量的函數(shù)。換句話說(shuō),在函數(shù)內(nèi)定義一個(gè)嵌套的函數(shù)時(shí),就構(gòu)成了一個(gè)閉包, 它允許嵌套函數(shù)訪問外層函數(shù)的變量。通過返回嵌套函數(shù),允許你維護(hù)對(duì)外部函數(shù)中局部變量、參數(shù)、和內(nèi)函數(shù)聲明的訪問。

11、 這種封裝允許你在外部作用域中隱藏和保護(hù)執(zhí)行環(huán)境,并且暴露公共接口,進(jìn)而通過公共接口執(zhí)行進(jìn)一步的操作??梢钥磦€(gè)簡(jiǎn)單的例子:function foo()var localVariable = private variable;return function bar()return localVariable;var getLocalVariable = foo();getLocalVariable() / private variable模塊模式最流行的閉包類型之一,它允許你模擬公共的、私有的、和特權(quán)成員:var Module = (function()var privateProperty =

12、 foo;function privateMethod(args)/ do somethingreturn publicProperty: ,publicMethod: function(args)/ do something,privilegedMethod: function(args)return privateMethod(args);)();模塊類似于一個(gè)單例對(duì)象。由于在上面的代碼中我們利用了(function() . )();的匿名函數(shù)形式,因此當(dāng)編譯器解析它的時(shí)候會(huì)立即執(zhí)行。 在閉包的執(zhí)行上下文的外部唯一可以訪問的對(duì)象是位于返回對(duì)象中的公共方法和屬性。然而,因?yàn)閳?zhí)行上下文被保存的

13、緣故, 所有的私有屬性和方法將一直存在于應(yīng)用的整個(gè)生命周期,這意味著我們只有通過公共方法才可以與它們交互。另一種類型的閉包被稱為立即執(zhí)行的函數(shù)表達(dá)式(IIFE)。其實(shí)它很簡(jiǎn)單,只不過是一個(gè)在全局環(huán)境中自執(zhí)行的匿名函數(shù)而已:(function(window)var foo, bar;function private()/ do somethingwindow.Module = public: function()/ do something;)(this);對(duì)于保護(hù)全局命名空間免受變量污染而言,這種表達(dá)式非常有用,它通過構(gòu)建函數(shù)作用域的形式將變量與全局命名空間隔離, 并通過閉包的形式讓它們存在于

14、整個(gè)運(yùn)行時(shí)(runtime)。在很多的應(yīng)用和框架中,這種封裝源代碼的方式用處非常的流行, 通常都是通過暴露一個(gè)單一的全局接口的方式與外部進(jìn)行交互。Call和Apply這兩個(gè)方法內(nèi)建在所有的函數(shù)中(它們是Function對(duì)象的原型方法),允許你在自定義上下文中執(zhí)行函數(shù)。 不同點(diǎn)在于,call函數(shù)需要參數(shù)列表,而apply函數(shù)需要你提供一個(gè)參數(shù)數(shù)組。如下:var o = ;function f(a, b) return a + b;/ 將函數(shù)f作為o的方法,實(shí)際上就是重新設(shè)置函數(shù)f的上下文f.call(o, 1, 2); / 3f.apply(o, 1, 2); / 3兩個(gè)結(jié)果是相同的,函數(shù)f在對(duì)

15、象o的上下文中被調(diào)用,并提供了兩個(gè)相同的參數(shù)1和2。在ES5中引入了Ftotype.bind方法,用于控制函數(shù)的執(zhí)行上下文,它會(huì)返回一個(gè)新的函數(shù), 并且這個(gè)新函數(shù)會(huì)被永久的綁定到bind方法的第一個(gè)參數(shù)所指定的對(duì)象上,無(wú)論該函數(shù)被如何使用。 它通過閉包將函數(shù)引導(dǎo)到正確的上下文中。對(duì)于低版本瀏覽器,我們可以簡(jiǎn)單的對(duì)它進(jìn)行實(shí)現(xiàn)如下(polyfill):if(!(bind in Ftotype)Ftotype.bind = function()var fn = this,context = arguments0,args = Arra

16、totype.slice.call(arguments, 1);return function()return fn.apply(context, args.concat(arguments);bind()方法通常被用在上下文丟失的場(chǎng)景下,例如面向?qū)ο蠛褪录幚?。之所以要這么做, 是因?yàn)楣?jié)點(diǎn)的addEventListener方法總是為事件處理器所綁定的節(jié)點(diǎn)的上下文中執(zhí)行回調(diào)函數(shù), 這就是它應(yīng)該表現(xiàn)的那樣。但是,如果你想要使用高級(jí)的面向?qū)ο蠹夹g(shù),或需要你的回調(diào)函數(shù)成為某個(gè)方法的實(shí)例, 你將需要手動(dòng)調(diào)整上下文。這就是bind方法所帶來(lái)的便利之處:function MyClass()thi

17、s.element = document.(div);this.element.addEventListener(click, this.onClick.bind(this), false);MyCtotype.onClick = function(e)/ do something;回顧上面bind方法的源代碼,你可能會(huì)注意到有兩次調(diào)用涉及到了Array的slice方法:Atotype.slice.call(arguments, 1);.slice.call(arguments);我們知道,arguments對(duì)象并不是一個(gè)真正的數(shù)組,而是一個(gè)類數(shù)組對(duì)象,雖然具有

18、length屬性,并且值也能夠被索引, 但是它們不支持原生的數(shù)組方法,例如slice和push。但是,由于它們具有和數(shù)組類似的行為,數(shù)組的方法能夠被調(diào)用和劫持, 因此我們可以通過類似于上面代碼的方式達(dá)到這個(gè)目的,其核心是利用call方法。這種調(diào)用其他對(duì)象方法的技術(shù)也可以被應(yīng)用到面向?qū)ο笾?,我們可以在Java中模擬經(jīng)典的繼承方式:MyCtotype.init = function()/ call the superclass init method in the context of the MyClass instanceMySuperCtotype.init.

19、apply(this, arguments);也就是利用call或apply在子類(MyClass)的實(shí)例中調(diào)用超類(MySuperClass)的方法。ES6中的箭頭函數(shù)ES6中的箭頭函數(shù)可以作為Ftotype.bind()的替代品。和普通函數(shù)不同,箭頭函數(shù)沒有它自己的this值, 它的this值繼承自外圍作用域。對(duì)于普通函數(shù)而言,它總會(huì)自動(dòng)接收一個(gè)this值,this的指向取決于它調(diào)用的方式。我們來(lái)看一個(gè)例子:var obj = / .addAll: function (pieces) var self = this;_.each(pieces, function (piece) self.add(piece););,/ .在上面的例子中,最直接的想法是直接使用this.add(piece),但不幸的是,在Java中你不能這么做, 因?yàn)閑ach的回調(diào)函數(shù)并未從外層繼承this值。在該回調(diào)函數(shù)中,this的值為window或undefined, 因此,我們使用臨時(shí)變量self來(lái)將外部的this值導(dǎo)入內(nèi)部。我們還有兩種方法解決這個(gè)問題:使用ES5中的bind()方法var obj = / .addAll: function (pieces) _.each(pieces, function (piece

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論