摘要:正文執行環境也稱為環境是中最為重要的一個概念。執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。簡而言之,執行環境是基于對象的,而作用域是基于函數的。
前述
在我們學習Javascript過程中,常常會遇到作用域(Scope)和執行上下文(Context)等概念。其中,執行上下文與this關鍵字的關系密切。
有面向對象編程經驗的各位,對于this關鍵字再熟悉不過了,因此我們很容易地把它和面向對象的編程方式聯系在一起,它指向利用構造器新創建出來的對象;在ECMAScript中,也支持this,然而, 正如大家所熟知的,this不僅僅只用來表示創建出來的對象。
在接下來的博文我們講介紹Javascript的作用域和執行上下文,以及它們的異同之處。
正文
執行環境(Execution context)也稱為“環境”是Javascript中最為重要的一個概念。執行環境定義了變量或函數有權訪問的其他數據,決定了它們各自的行為。每個執行環境都有一個與之關聯的變量對象,環境中定義的所有變量和函數都保存在這個對象中。
看到了執行環境的定義有點頭昏了,簡而言之“每個執行環境都有一個與之關聯的變量對象”;這里我們有一個疑問就是這個變量對象是怎樣定義的呢?
接下來,讓我們看一下變量對象的定義,具體實現如下:
/** * Execution context skeleton. */ activeExecutionContext = { // variable object. VO: {...}, this: thisValue };
通過上面的偽代碼我們知道對象字面量activeExecutionContext,它包含一個變量對象VO和this屬性。
這說明了this與上下文的可執行代碼類型有關,其值在進入上下文階段就確定了,并且在執行代碼階段是不能改變的(關于this使用可以閱讀《Javascript this 的一些學習總結》)。
作用域(Scope)控制著變量和參數的可見性及生命周期。
簡而言之,執行環境是基于對象的,而作用域是基于函數的。
作用域
我們將通過一個例子介紹作用域的使用,首先,我們定義了一個函數FooA()和FooB,示例代碼如下:
/** * Defines a function. */ var FooA = function(){ var a = 1; var FooB = function(){ var b = 2; console.log(a, b); // outputs: 1, 2 } console.log(a, b); // Error! b is not defined } FooA();
在示例中,第二個log輸出變量為未定義,這是由于在Javascript中定義在函數里面的參數和變量在函數外部是不可見的,而在一個函數內部任何位置定義的參數和變量,在該函數內部任何地方都是可見的。
執行環境
首先,我們定義了對象字面量o,它包含一個屬性x和方法m(),示例代碼如下:
/** * Defines a literal object. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; console.log(x, this.x); // outputs 1, 23 } } o.m();
示例中的兩個變量和屬性x都能被訪問,但它們被訪問的方式是截然不同,在log中訪問第一個x是通過作用域方式訪問了本地變量x,而this.x是通過執行上下文方式訪問對象o的屬性x,因此輸出值也不盡相同。
上下文問題
接下來,我們修改一下前面的例子,在方法m()中添加一個函數f(),示例代碼如下:
/** * Defines a literal object. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, this.x); // outputs 1, undefined } f(); } } o.m();
上面,我們通過調用方法m()來輸出x的值,由于方法m()的具體實現是通過調用函數f()來實現。
當我們調用對象o的方法m()時,發現this.x是未定義的。
這究竟是什么原因呢?回憶前面的例子,由于方法m()獲取了對象o的上下文,所以this是指向對象o的,難道是函數f()沒有獲取對象o的上下文,因此它不清楚this指向哪個對象?
首先讓我們回顧一下函數和方法以及屬性和變量的區別:方法和對象關聯,如:object.myMethod = function() {},而函數非對象關聯:var myFunc = function {};同樣屬性也是對象關系的,如:object.myProperty = 23,而變量:var myProperty = 23。
因為我們提到上下文是基于對象的,所以函數f()不能獲取對象o的執行上下文。
我們是否可以讓函數f()獲取對象o的執行上下文呢?我們仔細地想一下,既然函數f()不清楚this指向的對象,那么可以直接調用對象的屬性就OK了。
/** * Fixs broken context issue. * @type {Object} */ var o = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, o.x); // outputs 1, 23 } f(); } } o.m();
我們在函數f()中直接調用對象o的屬性x,這樣函數f()就無需獲取執行上下文直接調用對象的屬性了。
現在,我們又遇到一個新的問題了,如果對象不是o而是p,那么我們就需要修改函數f()中的對象了,更嚴重的情況就是我們沒有辦法確定具體是哪個對象,示例代碼如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, this.x); // outputs 1, undefined } f(); } } var instance1 = new C(); instance1.m();
上下文實例問題
上面,我們定義了函數C和它的原型對象,而且我們可以通過new方式創建C對象實例instance1,按照前面的方法解決Broken Context問題,具體實現如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:23, m: function(){ var x = 1; var f = function(){ console.log(x, instance1.x); // outputs 1, undefined } f(); } } var instance1 = new C(); instance1.m();
如果我們在創建一個C的對象實例instance2,那么我們就不能指定函數f()中的對象了。
其實,this是對象實例的抽象,當實例有多個甚至成千上百個的時候,我們需要通過this引用這些對象實例。
因此,指定對象方法不能有效解決Broken Context問題,我們還是需要使用this來引用對象,前面我們講到由于函數f()沒有獲取對象o的執行上下文,因此它不清楚this指向哪個對象,所以輸出this.x未定義,那么我們是否可以讓函數f()獲取對象的執行上下文。
跨作用域的上下文
我們想想既然方法是基于對象的,而且可以獲取對象的執行上下文,那么我們直接把f()定義為方法好了。
現在,我們在C對象原型中定義方法f(),示例代碼如下:
/** * Defines a literal object. * @constructor */ var C = function(){} C.prototype = { x:10, m: function(){ var x = 1; this.f(); }, f: function(){ console.log(x, this.x); // Reference ERROR!! } } var instance1 = new C(); instance1.m();
好啦,我們在C對象原型中定義方法f(),那么方法f()就可以獲取對象的執行上下文。
現在,我們在Firefox運行以上代碼,結果輸出Reference ERROR,這究竟是什么原因呢?我們想了一下問題出于變量x中,由于方法f()不能獲取方法m()的作用域,所以變量x不在方法f()中。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88099.html
摘要:總結本博文通過介紹執行上下文和作用域的異同的使用以及變量對象,讓我們加深對語言特性的理解。首先,我們介紹了執行上下文和的的關系,并且執行上下文是具有對象的然后,介紹了作用域使變量在作用域范圍內可見,并且作用域是基于函數的。 接上一篇Javascript Context和Scope的學習總結01【轉自cnblogs的JKhuang】(可能是segmentfault對單篇文章發布字數有限制...
摘要:函數上下文中的值是函數調用者提供并且由當前調用表達式的形式而定的。然而,由于對于來說沒有任何意義,因此會隱式轉換為全局對象。這里注意到四個表達式中,只有第一個表達式是指向對象的,而其他三個表達式則執行。 摘要 相信有C++、C#或Java等編程經驗的各位,對于this關鍵字再熟悉不過了。由于Javascript是一種面向對象的編程語言,它和C++、C#或Java一樣都包含this關鍵字...
摘要:發生這種情況的條件是當引用類型值的對象恰好為活躍對象。總結本文介紹中的使用,更重要的是幫助我們能更好地理解值在全局函數構造函數以及一些特例的情況中值的變化。然而,由于對于來說沒有任何意義,因此會隱式轉換為全局對象。 接上一篇Javascript this 的一些學習總結02【轉自cnblogs的JKhuang】 引用類型以及this的null值 對于前面提及的情形,還有例外的情況,當調...
摘要:正式由于作用域鏈的這種關系,我們就不難理解,為什么和不能通過作用域鏈向上搜索,因為對和的搜索在當前執行函數的活動對象就停止了。 對于Javascript程序員來說,閉包總會讓你覺得既熟悉又陌生,然而它對于開發人員來說卻非常重要,javascript里的許多設計模式中都用到了閉包,此處以函數作用域為例。 //示例代碼 var a=1; function foo(){ ...
摘要:此時的作用域鏈包含了兩個對象的活動對象和對象。閉包的應用場景保護函數內的變量安全。依然如前例,由于閉包,函數中的一直存在于內存中,因此每次執行,都會給自加。 引子 JS的閉包一直是很多人不理解,也是在使用過程中經常出現問題的地方。每次看文章都會有所了解閉包,但是,用起來還是不對,而且錯誤百出,其關鍵問題還是出在對其不理解,不了解。此文章會不定期更新以及完善,希望在我學習的時候,讓大家也...
閱讀 2508·2023-04-25 17:37
閱讀 1189·2021-11-24 10:29
閱讀 3696·2021-09-09 11:57
閱讀 692·2021-08-10 09:41
閱讀 2243·2019-08-30 15:55
閱讀 2811·2019-08-30 15:54
閱讀 1942·2019-08-30 15:53
閱讀 895·2019-08-30 15:43