首頁 >web前端 >js教程 >javascript中的作用域和上下文使用簡要概述_基礎知識

javascript中的作用域和上下文使用簡要概述_基礎知識

WBOY
WBOY原創
2016-05-16 17:10:27820瀏覽

javascript中的作用域(scope)和上下文(context)是這門語言的獨特之處,這部分歸功於他們帶來的靈活性。每個函數有不同的變數上下文和作用域。這些概念是javascript中一些強大的設計模式的後盾。然而這也給開發人員帶來很大困惑。以下全面揭示了javascript中的上下文和作用域的不同,以及各種設計模式如何使用他們。

上下文 vs 作用域

首先需要澄清的問題是上下文和作用域是不同的概念。多年來我注意到許多開發者經常將這兩個術語混淆,錯誤的將一個描述為另一個。平心而論,這些術語變得非常混亂不堪。

每個函數呼叫都有與之相關的作用域和上下文。從根本上說,範圍是基於函數(function-based)而上下文是基於物件(object-based)。換句話說,作用域是和每次函數呼叫時變數的存取有關,並且每次呼叫都是獨立的。上下文總是關鍵字 this 的值,是呼叫目前可執行程式碼的物件的參考。

變數作用域

變數能夠被定義在局部或全域作用域,這導致運行時變數的存取來自不同的作用域。全域變數需被宣告在函數體外,在整個運行過程中都存在,能在任何作用域中存取和修改。局部變數僅在函數體內定義,且每次函數呼叫都有不同的作用域。這個主題是僅在呼叫中的賦值,求值和對值的操作,不能存取作用域以外的值。

目前javascript不支援區塊級作用域,區塊級作用域指在if語句,switch語句,循環語句等語句區塊中定義變數,這表示變數不能在語句區塊之外被存取。當前任何在語句塊中定義的變數都能在語句塊之外存取。然而,這種情況很快就會改變,let 關鍵字已經正式加入ES6規格。用它來取代var關鍵字可以將局部變數聲明為區塊級作用域。

"this" 上下文

上下文通常取決於一個函數如何被呼叫。當函數作為物件的方法被呼叫時,this 被設定為呼叫方法的物件:

複製程式碼 程式碼如下:

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

object.foo(); // true

同樣的原理適用於當呼叫函數時透過new的操作符建立一個物件的實例。以這種方式呼叫時,this 的值將被設定為新建立的實例:
複製程式碼 程式碼如下:

function foo(){
alert(this);
}

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

當呼叫一個未綁定函數,this 將被預設為全域上下文(global context) 或window物件(如果在瀏覽器中)。然而如果函數在嚴格模式下被執行("use strict"),this的值將被預設為undefined。
執行上下文和作用域鏈

javascript是一個單線程語言,這意味著在瀏覽器中同時只能做一件事情。當javascript解釋器初始執行程式碼,它首先預設竟如全域上下文。每次呼叫一個函數將會建立一個新的執行上下文。

這裡常發生混淆,這術語」執行上下文(execution context)「這裡的所要表達的意思是作用域,不是前面討論的上下文。這是槽糕的命名,然而這術語ECMAScript規範所定義的,無奈的遵守吧。

每次新建立一個執行上下文,會被加到作用域鏈的頂部,又是也成為執行或呼叫堆疊。瀏覽器總是運行在位於作用域鏈頂部目前執行上下文。一旦完成,它(當前執行上下文)將從棧頂被移除並且將控制權歸還給先前的執行上下文。例如:
複製程式碼 程式碼如下:

function first(){
second( );
function second(){
third();
function third(){
fourth();
function fourth(){
// do something
}
}
}
}
first();

運行前面的程式碼將會導致巢狀的函數被從上倒下執行直到 fourth 函數,此時作用域鏈從上到下為: fourth, third, second, first, global。 fourth 函數能夠存取全域變數和任何在first,second和third函數中定義的變量,就如同存取自己的變數一樣。一旦fourth函數執行完成,fourth暈高興上下文將被從作用域鏈頂端移除並且執行將返回到thrid函數。這一過程持續進行直到所有程式碼已完成執行。

不同執行情境之間的變數命名衝突透過攀爬作用域鏈解決,從局部直到全域。這意味著具有相同名稱的局部變數在作用域鏈中有更高的優先權。

簡單的說,每次你試圖存取函數執行上下文中的變數時,尋找進程總是從自己的變數物件開始。如果在自己的變數物件中沒發現要找的變量,繼續搜尋作用域鏈。它將攀爬作用域鏈檢查每一個執行上下文的變數物件去尋找和變數名稱匹配的值。

閉包

當一個嵌套的函數在定義(作用域)的外面被訪問,以至它可以在外部函數返回後被執行,此時一個閉包形成。它(閉包)維護(在內部函數中)對外部函數中局部變量,arguments和函數聲明的存取。封裝允許我們從外部作用域中隱藏和保護執行上下文,而暴露公共接口,透過接口進一步操作。一個簡單的例子看起來如下:
複製程式碼 程式碼如下:

function foo() {
var local = 'private variable';
return function bar(){
return local;
}
}

var getLocalVariable = foo() var getLocalVariable = foo >getLocalVariable() // private variable

其中最受歡迎的閉包類型是廣為人知的模組模式。它允許你模擬公共的,私有的和特權成員:
複製代碼 代碼如下:

var Module = (function(){
var privateProperty = 'foo';

function privateMethod(args){
//do something
}

return {

publicProperty: "",

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

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

模組其實有些類似於單例,在末尾加上一對括號,當解釋器解釋完後立即執行(立即執行函數)。閉包執行上下位的外部唯一可用的成員是傳回物件中公用的方法和屬性(例如Module.publicMethod)。然而,所有的私有屬性和方法在整個程式的生命週期中都將存在,由於(閉包)使執行上下文收到保護,和變數的互動要透過公用的方法。

另一種類型的閉包叫做立即呼叫函數表達式(immediately-invoked function expression IIFE),無非是一個在window上下文中的自調用匿名函數(self-invoked anonymous function)。

複製程式碼 程式碼如下:
function(window){


function(window){
> a = 'foo', b = 'bar';

function private(){
// do something
}

window.Module = {


window.Module = {

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

})(this);
對保護全域命名空間,這種表達式非常有用,所有在函數體內聲明的變數都是局部變量,並透過閉包在整個運行環境保持存在。這種封裝原始碼的方式對程式和框架都是非常流行的,通常暴露單一全域介面與外界互動。


Call 和 Apply複製代碼


代碼如下:


function user(first, last, age){
// do something
} user.call(window, 'John', 'Doe', 30); user.apply (window, ['John', 'Doe', 30]);
実行結果は同じです。ユーザー関数はウィンドウ コンテキストで呼び出され、同じ 3 つのパラメーターが提供されます。

ECMAScript 5 (ES5) では、コンテキストを制御する Function.prototype.bind メソッドが導入され、この関数 (コンテキスト) は、バインド メソッドの最初のパラメーターに永続的にバインドされます。関数の呼び出し方法。クロージャを通じて関数のコンテキストを修正します。サポートされていないブラウザの場合の解決策は次のとおりです。
コードをコピーします。 コードは次のとおりです。

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


これは、オブジェクト指向やイベント処理などのコンテキスト損失でよく使用されます。これが必要なのは、ノードの addEventListener メソッドがイベント ハンドラーがバインドされているノードとして関数実行のコンテキストを常に維持するためであり、これが重要です。ただし、高度なオブジェクト指向技術を使用し、コールバック関数のコンテキストをメソッドのインスタンスとして維持する必要がある場合は、コンテキストを手動で調整する必要があります。これはバインドによってもたらされる利便性です:


関数MyClass() {
this.element = document.createElement('div');
this.element.addEventListener('click', this.onClick.bind(this), false);
🎜>
MyClass.prototype.onClick = function(e){
// do something
};


バインド関数のソース コードを振り返ると、次の行は、配列のメソッドを呼び出す比較的単純なコードであることに気づくかもしれません:


コードをコピー コードは次のとおりです: Array.prototype.slice.call(arguments, 1);

興味深いことに、ここで、引数オブジェクトは実際には配列ではないことに注意してください。多くの場合、配列のような ) オブジェクトとして説明されます。これは、nodelist (document.getElementsByTagName() メソッド) によって返される結果です。これらには長さ属性が含まれており、値にインデックスを付けることができますが、スライスやプッシュなどのネイティブの配列メソッドをサポートしていないため、配列ではありません。ただし、配列と同様に動作するため、配列メソッドを呼び出してハイジャックすることができます。配列のようなコンテキストで配列メソッドを実行する場合は、上記の例に従ってください。
他のオブジェクト メソッドを呼び出すこの手法は、JavaScript で古典的な継承 (クラス継承) をエミュレートするときにオブジェクト指向にも適用されます:



コードをコピー コードは次のとおりです。 MyClass.prototype.init = function(){
// 「MyClass」のコンテキストでスーパークラスの init メソッドを呼び出します。 " インスタンス
MySuperClass.prototype.init.apply(this, argument);
}


サブクラス (MyClass) のインスタンスでスーパークラス (MySuperClass) のメソッドを呼び出すことによって、この強力なデザインパターンを再現できます。

結論

最新の JavaScript ロールではスコープとコンテキストが重要かつ基本的な役割を果たすため、高度なデザイン パターンを学習し始める前にこれらの概念を理解することが非常に重要です。クロージャ、オブジェクト指向、継承、またはさまざまなネイティブ実装について話す場合でも、コンテキストとスコープが重要な役割を果たします。 JavaScript 言語をマスターし、そのコンポーネントを深く理解することが目標の場合、スコープとコンテキストを出発点にする必要があります。

翻訳者の補足

作者が実装したバインド関数は、bind によって返される関数を呼び出すときにパラメータを渡すことができません。次のコードはこの問題を修正します。 🎜>



コードをコピーします
コードは次のとおりです: if(!('bind' Function.prototype)){ Function.prototype.bind = function(){
var fn = this, context = argument[0], args = Array.prototype.slice.call(arguments, 1);
return function(){
return fn.apply(context, args.concat(arguments));//修正
}
}
}

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn