国产99久久精品_欧美日本韩国一区二区_激情小说综合网_欧美一级二级视频_午夜av电影_日本久久精品视频

最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

深入理解JavaScript中的作用域和上下文(圖)

來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 20:24:17
文檔

深入理解JavaScript中的作用域和上下文(圖)

深入理解JavaScript中的作用域和上下文(圖):JavaScript對于作用域(Scope)和上下文(Context)的實現(xiàn)是這門語言的一個非常獨到的地方,部分歸功于其獨特的靈活性。 函數(shù)可以接收不同的的上下文和作用域。這些概念為JavaScript中的很多強大的設(shè)計模式提供了堅實的基礎(chǔ)。 然而這也概念也非常容易給開發(fā)
推薦度:
導(dǎo)讀深入理解JavaScript中的作用域和上下文(圖):JavaScript對于作用域(Scope)和上下文(Context)的實現(xiàn)是這門語言的一個非常獨到的地方,部分歸功于其獨特的靈活性。 函數(shù)可以接收不同的的上下文和作用域。這些概念為JavaScript中的很多強大的設(shè)計模式提供了堅實的基礎(chǔ)。 然而這也概念也非常容易給開發(fā)
JavaScript對于作用域(Scope)和上下文(Context)的實現(xiàn)是這門語言的一個非常獨到的地方,部分歸功于其獨特的靈活性。 函數(shù)可以接收不同的的上下文和作用域。這些概念為JavaScript中的很多強大的設(shè)計模式提供了堅實的基礎(chǔ)。 然而這也概念也非常容易給開發(fā)人員帶來困惑。為此,本文將全面的剖析這些概念,并闡述不同的設(shè)計模式是如何利用它們的。

上下文(Context)和作用域(Scope)

首先需要知道的是,上下文和作用域是兩個完全不同的概念。多年來,我發(fā)現(xiàn)很多開發(fā)者會混淆這兩個概念(包括我自己), 錯誤的將兩個概念混淆了。平心而論,這些年來很多術(shù)語都被混亂的使用了。

函數(shù)的每次調(diào)用都有與之緊密相關(guān)的作用域和上下文。從根本上來說,作用域是基于函數(shù)的,而上下文是基于對象的。 換句話說,作用域涉及到所被調(diào)用函數(shù)中的變量訪問,并且不同的調(diào)用場景是不一樣的。上下文始終是this關(guān)鍵字的值, 它是擁有(控制)當(dāng)前所執(zhí)行代碼的對象的引用。

變量作用域

一個變量可以被定義在局部或者全局作用域中,這建立了在運行時(runtime)期間變量的訪問性的不同作用域范圍。 任何被定義的全局變量,意味著它需要在函數(shù)體的外部被聲明,并且存活于整個運行時(runtime),并且在任何作用域中都可以被訪問到。 在ES6之前,局部變量只能存在于函數(shù)體中,并且函數(shù)的每次調(diào)用它們都擁有不同的作用域范圍。 局部變量只能在其被調(diào)用期的作用域范圍內(nèi)被賦值、檢索、操縱。

需要注意,在ES6之前,JavaScript不支持塊級作用域,這意味著在if語句、switch語句、for循環(huán)、while循環(huán)中無法支持塊級作用域。 也就是說,ES6之前的JavaScript并不能構(gòu)建類似于Java中的那樣的塊級作用域(變量不能在語句塊外被訪問到)。但是, 從ES6開始,你可以通過let關(guān)鍵字來定義變量,它修正了var關(guān)鍵字的缺點,能夠讓你像Java語言那樣定義變量,并且支持塊級作用域。看兩個例子:

ES6之前,我們使用var關(guān)鍵字定義變量:

function func() {
 if (true) {
 var tmp = 123;
 }
 console.log(tmp); // 123
}

之所以能夠訪問,是因為var關(guān)鍵字聲明的變量有一個變量提升的過程。而在ES6場景,推薦使用let關(guān)鍵字定義變量:

function func() {
 if (true) {
 let tmp = 123;
 }
 console.log(tmp); // ReferenceError: tmp is not defined
}

這種方式,能夠避免很多錯誤。

什么是this上下文

上下文通常取決于函數(shù)是如何被調(diào)用的。當(dāng)一個函數(shù)被作為對象中的一個方法被調(diào)用的時候,this被設(shè)置為調(diào)用該方法的對象上:

var obj = {
 foo: function(){
 alert(this === obj); 
 }
};

obj.foo(); // true

這個準(zhǔn)則也適用于當(dāng)調(diào)用函數(shù)時使用new操作符來創(chuàng)建對象的實例的情況下。在這種情況下,在函數(shù)的作用域內(nèi)部this的值被設(shè)置為新創(chuàng)建的實例:

function foo(){
 alert(this);
}

new foo() // foo
foo() // window

當(dāng)調(diào)用一個為綁定函數(shù)時,this默認(rèn)情況下是全局上下文,在瀏覽器中它指向window對象。需要注意的是,ES5引入了嚴(yán)格模式的概念, 如果啟用了嚴(yán)格模式,此時上下文默認(rèn)為undefined

執(zhí)行環(huán)境(execution context)

JavaScript是一個單線程語言,意味著同一時間只能執(zhí)行一個任務(wù)。當(dāng)JavaScript解釋器初始化執(zhí)行代碼時, 它首先默認(rèn)進(jìn)入全局執(zhí)行環(huán)境(execution context),從此刻開始,函數(shù)的每次調(diào)用都會創(chuàng)建一個新的執(zhí)行環(huán)境。

這里會經(jīng)常引起新手的困惑,這里提到了一個新的術(shù)語——執(zhí)行環(huán)境(execution context),它定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。 它更偏向于作用域的作用,而不是我們前面討論的上下文(Context)。請務(wù)必仔細(xì)的區(qū)分執(zhí)行環(huán)境和上下文這兩個概念(注:英文容易造成混淆)。 說實話,這是個非常糟糕的命名約定,但是它是ECMAScript規(guī)范制定的,你還是遵守吧。

每個函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個函數(shù)時,函數(shù)的環(huán)境就會被推入一個環(huán)境棧中(execution stack)。在函數(shù)執(zhí)行完后,棧將其環(huán)境彈出, 把控制權(quán)返回給之前的執(zhí)行環(huán)境。ECMAScript程序中的執(zhí)行流正是由這個便利的機制控制著。

執(zhí)行環(huán)境可以分為創(chuàng)建和執(zhí)行兩個階段。在創(chuàng)建階段,解析器首先會創(chuàng)建一個變量對象(variable object,也稱為活動對象 activation object), 它由定義在執(zhí)行環(huán)境中的變量、函數(shù)聲明、和參數(shù)組成。在這個階段,作用域鏈會被初始化,this的值也會被最終確定。 在執(zhí)行階段,代碼被解釋執(zhí)行。

每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。 需要知道,我們無法手動訪問這個對象,只有解析器才能訪問它。

作用域鏈(The Scope Chain)

當(dāng)代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈(scope chain)。作用域鏈的用途是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。 作用域鏈包含了在環(huán)境棧中的每個執(zhí)行環(huán)境對應(yīng)的變量對象。通過作用域鏈,可以決定變量的訪問和標(biāo)識符的解析。 注意,全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈的最后一個對象。我們來看一個例子:

var color = "blue";

function changeColor(){
 var anotherColor = "red";

 function swapColors(){
 var tempColor = anotherColor;
 anotherColor = color;
 color = tempColor;

 // 這里可以訪問color, anotherColor, 和 tempColor
 }

 // 這里可以訪問color 和 anotherColor,但是不能訪問 tempColor
 swapColors();
}

changeColor();

// 這里只能訪問color
console.log("Color is now " + color);

上述代碼一共包括三個執(zhí)行環(huán)境:全局環(huán)境、changeColor()的局部環(huán)境、swapColors()的局部環(huán)境。 上述程序的作用域鏈如下圖所示:

scope chain example

從上圖發(fā)現(xiàn)。內(nèi)部環(huán)境可以通過作用域鏈訪問所有的外部環(huán)境,但是外部環(huán)境不能訪問內(nèi)部環(huán)境中的任何變量和函數(shù)。 這些環(huán)境之間的聯(lián)系是線性的、有次序的。

對于標(biāo)識符解析(變量名或函數(shù)名搜索)是沿著作用域鏈一級一級地搜索標(biāo)識符的過程。搜索過程始終從作用域鏈的前端開始, 然后逐級地向后(全局執(zhí)行環(huán)境)回溯,直到找到標(biāo)識符為止。

閉包

閉包是指有權(quán)訪問另一函數(shù)作用域中的變量的函數(shù)。換句話說,在函數(shù)內(nèi)定義一個嵌套的函數(shù)時,就構(gòu)成了一個閉包, 它允許嵌套函數(shù)訪問外層函數(shù)的變量。通過返回嵌套函數(shù),允許你維護(hù)對外部函數(shù)中局部變量、參數(shù)、和內(nèi)函數(shù)聲明的訪問。 這種封裝允許你在外部作用域中隱藏和保護(hù)執(zhí)行環(huán)境,并且暴露公共接口,進(jìn)而通過公共接口執(zhí)行進(jì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 = 'foo';

 function privateMethod(args){
 // do something
 }

 return {

 publicProperty: '',

 publicMethod: function(args){
 // do something
 },

 privilegedMethod: function(args){
 return privateMethod(args);
 }
 };
})();

模塊類似于一個單例對象。由于在上面的代碼中我們利用了(function() { ... })();的匿名函數(shù)形式,因此當(dāng)編譯器解析它的時候會立即執(zhí)行。 在閉包的執(zhí)行上下文的外部唯一可以訪問的對象是位于返回對象中的公共方法和屬性。然而,因為執(zhí)行上下文被保存的緣故, 所有的私有屬性和方法將一直存在于應(yīng)用的整個生命周期,這意味著我們只有通過公共方法才可以與它們交互。

另一種類型的閉包被稱為立即執(zhí)行的函數(shù)表達(dá)式(IIFE)。其實它很簡單,只不過是一個在全局環(huán)境中自執(zhí)行的匿名函數(shù)而已:

(function(window){

 var foo, bar;

 function private(){
 // do something
 }

 window.Module = {

 public: function(){
 // do something 
 }
 };

})(this);

對于保護(hù)全局命名空間免受變量污染而言,這種表達(dá)式非常有用,它通過構(gòu)建函數(shù)作用域的形式將變量與全局命名空間隔離, 并通過閉包的形式讓它們存在于整個運行時(runtime)。在很多的應(yīng)用和框架中,這種封裝源代碼的方式用處非常的流行, 通常都是通過暴露一個單一的全局接口的方式與外部進(jìn)行交互。

Call和Apply

這兩個方法內(nèi)建在所有的函數(shù)中(它們是Function對象的原型方法),允許你在自定義上下文中執(zhí)行函數(shù)。 不同點在于,call函數(shù)需要參數(shù)列表,而apply函數(shù)需要你提供一個參數(shù)數(shù)組。如下:

var o = {};

function f(a, b) {
 return a + b;
}

// 將函數(shù)f作為o的方法,實際上就是重新設(shè)置函數(shù)f的上下文
f.call(o, 1, 2); // 3
f.apply(o, [1, 2]); // 3

兩個結(jié)果是相同的,函數(shù)f在對象o的上下文中被調(diào)用,并提供了兩個相同的參數(shù)12

在ES5中引入了Function.prototype.bind方法,用于控制函數(shù)的執(zhí)行上下文,它會返回一個新的函數(shù), 并且這個新函數(shù)會被永久的綁定到bind方法的第一個參數(shù)所指定的對象上,無論該函數(shù)被如何使用。 它通過閉包將函數(shù)引導(dǎo)到正確的上下文中。對于低版本瀏覽器,我們可以簡單的對它進(jìn)行實現(xiàn)如下(polyfill):

if(!('bind' in Function.prototype)){
 Function.prototype.bind = function(){
 var fn = this, 
 context = arguments[0], 
 args = Array.prototype.slice.call(arguments, 1);
 return function(){
 return fn.apply(context, args.concat(arguments));
 }
 }
}

bind()方法通常被用在上下文丟失的場景下,例如面向?qū)ο蠛褪录幚怼V砸@么做, 是因為節(jié)點的addEventListener方法總是為事件處理器所綁定的節(jié)點的上下文中執(zhí)行回調(diào)函數(shù), 這就是它應(yīng)該表現(xiàn)的那樣。但是,如果你想要使用高級的面向?qū)ο蠹夹g(shù),或需要你的回調(diào)函數(shù)成為某個方法的實例, 你將需要手動調(diào)整上下文。這就是bind方法所帶來的便利之處:

function MyClass(){
 this.element = document.createElement('p');
 this.element.addEventListener('click', this.onClick.bind(this), false);
}

MyClass.prototype.onClick = function(e){
 // do something
};

回顧上面bind方法的源代碼,你可能會注意到有兩次調(diào)用涉及到了Arrayslice方法:

Array.prototype.slice.call(arguments, 1);
[].slice.call(arguments);

我們知道,arguments對象并不是一個真正的數(shù)組,而是一個類數(shù)組對象,雖然具有l(wèi)ength屬性,并且值也能夠被索引, 但是它們不支持原生的數(shù)組方法,例如slicepush。但是,由于它們具有和數(shù)組類似的行為,數(shù)組的方法能夠被調(diào)用和劫持, 因此我們可以通過類似于上面代碼的方式達(dá)到這個目的,其核心是利用call方法。

這種調(diào)用其他對象方法的技術(shù)也可以被應(yīng)用到面向?qū)ο笾校覀兛梢栽贘avaScript中模擬經(jīng)典的繼承方式:

MyClass.prototype.init = function(){
 // call the superclass init method in the context of the "MyClass" instance
 MySuperClass.prototype.init.apply(this, arguments);
}

也就是利用callapply在子類(MyClass)的實例中調(diào)用超類(MySuperClass)的方法。

ES6中的箭頭函數(shù)

ES6中的箭頭函數(shù)可以作為Function.prototype.bind()的替代品。和普通函數(shù)不同,箭頭函數(shù)沒有它自己的this值, 它的this值繼承自外圍作用域。

對于普通函數(shù)而言,它總會自動接收一個this值,this的指向取決于它調(diào)用的方式。我們來看一個例子:

var obj = {

 // ...

 addAll: function (pieces) {
 var self = this;
 _.each(pieces, function (piece) {
 self.add(piece);
 });
 },

 // ...

}

在上面的例子中,最直接的想法是直接使用this.add(piece),但不幸的是,在JavaScript中你不能這么做, 因為each的回調(diào)函數(shù)并未從外層繼承this值。在該回調(diào)函數(shù)中,this的值為windowundefined, 因此,我們使用臨時變量self來將外部的this值導(dǎo)入內(nèi)部。我們還有兩種方法解決這個問題:

使用ES5中的bind()方法

var obj = {

 // ...

 addAll: function (pieces) {
 _.each(pieces, function (piece) {
 this.add(piece);
 }.bind(this));
 },

 // ...

}

使用ES6中的箭頭函數(shù)

var obj = {

 // ...

 addAll: function (pieces) {
 _.each(pieces, piece => this.add(piece));
 },

 // ...

}

在ES6版本中,addAll方法從它的調(diào)用者處獲得了this值,內(nèi)部函數(shù)是一個箭頭函數(shù),所以它集成了外部作用域的this值。

注意:對回調(diào)函數(shù)而言,在瀏覽器中,回調(diào)函數(shù)中的thiswindowundefined(嚴(yán)格模式),而在Node.js中, 回調(diào)函數(shù)的thisglobal。實例代碼如下:

function hello(a, callback) {
 callback(a);
}

hello('weiwei', function(a) {
 console.log(this === global); // true
 console.log(a); // weiwei
});

小結(jié)

在你學(xué)習(xí)高級的設(shè)計模式之前,理解這些概念非常的重要,因為作用域和上下文在現(xiàn)代JavaScript中扮演著的最基本的角色。 無論我們談?wù)摰氖情]包、面向?qū)ο蟆⒗^承、或者是各種原生實現(xiàn),上下文和作用域都在其中扮演著至關(guān)重要的角色。 如果你的目標(biāo)是精通JavaScript語言,并且深入的理解它的各個組成,那么作用域和上下文便是你的起點。

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文檔

深入理解JavaScript中的作用域和上下文(圖)

深入理解JavaScript中的作用域和上下文(圖):JavaScript對于作用域(Scope)和上下文(Context)的實現(xiàn)是這門語言的一個非常獨到的地方,部分歸功于其獨特的靈活性。 函數(shù)可以接收不同的的上下文和作用域。這些概念為JavaScript中的很多強大的設(shè)計模式提供了堅實的基礎(chǔ)。 然而這也概念也非常容易給開發(fā)
推薦度:
標(biāo)簽: 理解 上下文 作用域
  • 熱門焦點

最新推薦

猜你喜歡

熱門推薦

專題
Top
主站蜘蛛池模板: 日本一区二区三区不卡在线视频 | 国产视频不卡 | 国产91久久精品一区二区 | 中文字幕欧美日韩 | 国产成人一区二区三区免费观看 | 精品在线一区二区 | 亚洲精品免费在线 | 人人添人人澡人人澡人人人爽 | 欧美在线观看网站 | 国产精品v一区二区三区 | 精品一区二区在线 | 日韩精品一区二区三区视频 | 国产午夜在线观看 | 国产成人精品一区二区免费视频 | 在线免费观看国产 | 久久久久国产一级毛片高清版 | 欧美精品首页 | 亚洲专区欧美 | 欧美在线观看日韩欧美在线观看 | 国产 日韩 欧美 综合 | 国产日韩欧美中文字幕 | 久久久一本 | 另类国产精品一区二区 | 精品二区| 高清国产在线 | 毛片综合| 成人欧美一区二区三区视频不卡 | 九草伊人 | 成人a毛片久久免费播放 | 国产成人三级经典中文 | 国产91精品久久久久999 | 国产精品一区二区午夜嘿嘿嘿小说 | 亚洲国产成人久久一区www妖精 | 福利一区三区 | 亚洲国产精品免费在线观看 | 亚洲视频二| 国产日韩亚洲欧美 | 免费看操片 | 欧美视频日韩专区午夜 | 天天躁日日躁狠狠躁中文字幕老牛 | 欧美交性又色又爽又黄 |