Javascript Context和Scope的一些學習總結

來源:互聯網
上載者:User
1.1.1 摘要

在我們學習Javascript過程中,常常會遇到範圍(Scope)和執行內容(Context)等概念。其中,執行內容與this關鍵字的關係密切。

有物件導向編程經驗的各位,對於this關鍵字再熟悉不過了,因此我們很容易地把它和物件導向的編程方式聯絡在一起,它指向利用構造器新建立出來的對象;在ECMAScript中,也支援this,然而, 正如大家所熟知的,this不僅僅只用來表示建立出來的對象。

在接下來的博文我們講介紹Javascript的範圍和執行內容,以及它們的異同之處。

目錄
  • 範圍
  • 執行環境
  • 上下文問題
  • 上下文執行個體問題
  • 跨範圍的上下文
  • 使用上下文解決範圍問題
  • 使用範圍解決上下文問題
1.1.2 本文

執行環境(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()中。

使用上下文解決範圍問題

我們處於兩難的境地方法f()既要擷取執行內容,又要擷取方法m()的範圍;如果我們要擷取方法m()的範圍,那麼我們需要把方法f()定義在m()中。

接下來,我們把方法f()定義在m()中,具體實現如下:

/** * Defines a literal object. * @constructor */var C = function(){}C.prototype = {    x:23,    m: function(){        var x = 1;        this.f = function(){            console.log(x, this.x); // outputs 1, 23        }        this.f();    }}var instance1 = new C();instance1.m();

現在我們通過this調用方法f(),它現在可以擷取對象instance1執行內容,並且也可以擷取方法m()的範圍,所以方法f()可以擷取屬性和變數x的值。

使用範圍解決上下文問題

接下來,繼續看一個例子,我們要在函數setTimeout()中調用方法onTimeout(),具體定義如下:

/** * setTimeout function with Broken Context issue * @type {Object} */var o = {    x:23,    onTimeout: function(){        console.log("x:", this.x);    },    m: function(){        setTimeout(function(){            this.onTimeout(); // ERROR: this.onTimeout is not a function        }, 1);    }}o.m();

同樣在函數setTimeout()中調用方法onTimeout()失敗,我們知道這是由於方法onTimeout()不能擷取對象執行內容。

我們知道在方法m()中可以擷取對象執行內容,所以可以通過臨時變數引用this指向的對象,執行個體代碼如下:

/** * Fixs setTimeout function with Broken Context issue. * @type {Object} */var o = {    x:23,    onTimeout: function(){        console.log("x:", this.x); // outputs 23    },    m: function(){                // Keeps instance reference.        var self = this;        setTimeout(function(){           // Gets m scrope.             self.onTimeout();        }, 1);    }}o.m();

上面,我們通過臨時變數self儲存了this的引用,由於setTimeout()函數可以擷取m()的範圍,所用我們可以通過self. onTimeout()的方式調用onTimeout()方法。

1.1.3 總結

本博文通過介紹執行內容和範圍的異同、this的使用以及變數對象,讓我們加深對Javascript 語言特性的理解。

首先,我們介紹了執行內容和this的的關係,並且執行內容是具有對象的;然後,介紹了範圍使變數在範圍範圍內可見,並且範圍是基於函數的。

我們通過具體的例子介紹了在不同的範圍和執行內容中,對this和變數的影響加深了範圍和執行內容的理解,從而協助我們更好的閱讀和編寫代碼。

參考
  • http://blog.goddyzhao.me/post/10020230352/execution-context
  • http://clubajax.org/javascript-scope-and-context-not-the-same-thing/
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.