JavaScript之this__Java

來源:互聯網
上載者:User
JavaScript之this 1. 為什麼使用this
先看個例子:
function identity() {return this.name.toUpperCase();}function speak() {return "Hello, i'm " + identity.call(this);}var me = {name: 'rod chen'}var you = {name: "others in Aug"}console.log(identity.call(me));  //ROD CHENconsole.log(identity.call(you)); //OTHERS IN AUGconsole.log(speak.call(me));     //Hello, i'm ROD CHEN  console.log(speak.call(you));    //Hello, i'm OTHERS IN AUG
輸出的結果很明顯,對於call的用法前面文章有提到,第一個參數就是傳入到函數裡的this的值。
這段代碼可以在不同的內容物件( me 和 you )中重複使用函數 identify() 和 speak() ,如果我們不適用this的話,那就需要identity和speak顯示傳入一個內容物件,就像下面的方式
function identity(context) {return context.name.toUpperCase();}function speak(context) {return "Hello, i'm " + identity(context);}var me = {name: 'rod chen'}var you = {name: "others in Aug"}console.log(identity(me));console.log(identity(you));console.log(speak(me));console.log(speak(you));
Summary:this 提供了一種更優雅的方式來隱式“傳遞”一個對象引用,因此可以將API設計得更加簡潔
並且易於複用。隨著使用模式越來越複雜,顯式傳遞內容物件會讓代碼變得越來越混亂,使用 this 則不會
這樣

2. 關於this的誤解
(1) 指向自身
誤解:很容易將this理解成函數對象自身。
其實不是,先看了一個例子:

console.log 語句產生了 4 條輸出,證明 foo(..) 確實被調用了 4 次,但是 foo.count 仍然是 0。顯然從字面意思來理解 this 是錯誤的。執行 foo.count = 0 時,的確向函數對象 foo 添加了一個屬性 count 。但是函數內部代碼this.count 中的 this 並不是指向那個函數對象,所以雖然屬性名稱相同,根對象卻並不相
同,困惑隨之產生,其實counter是window對象的,從上面的例子中也可以看到,執行的方式是window.foo(),所以this指向的是全域範圍window對象。

(2) this範圍 誤解: this指向函數的範圍。
this 在任何情況下都不指向函數的詞法範圍。在 JavaScript 內部,範圍確實和對象類似,可見的標識符都是它的屬性。但是範圍“對象”無法通過 JavaScript代碼訪問,它存在於 JavaScript 引擎內部。但是我們是可以訪問this對象的。
function foo() {var a = 2;this.bar();}function bar() {"use strict";console.log( this.a );}foo(); //undefined

3. this到底是什麼
this 是在運行時進行綁定的,並不是在編寫時綁定,它的上下文取決於函數調用時的各種條件。 this 的綁定和函式宣告的位置沒有任何關係,只取決於函數的調用方式。當一個函數被調用時,會建立一個活動記錄(有時候也稱為執行內容)。這個記錄會包含函數在哪裡被調用(調用棧)、函數的調用方法、傳入的參數等資訊。 this就是記錄的其中一個屬性,會在函數執行的過程中用到。

方法調用的方式: this指向調用這個方法的對象,或者call,apply函數顯示傳入的參數。
函數的方式調用: window或者undefined

4. this全面解析
(1). 調用位置
調用位置就是函數在代碼中被調用的位置(而不是聲明的位置),最重要的是要分析調用棧(就是為了到達當前執行位置所調用的所有函數)。我們關心的調用位置就在當前正在執行的函數的前一個調用中。
function baz() {// 當前調用棧是:baz// 因此,當前調用位置是全域範圍console.log( "baz" );bar(); // <-- bar 的調用位置}function bar() {// 當前調用棧是 baz -> bar// 因此,當前調用位置在 baz 中console.log( "bar" );foo(); // <-- foo 的調用位置}function foo() {// 當前調用棧是 baz -> bar -> foo// 因此,當前調用位置在 bar 中console.log( "foo" );}baz(); // <-- baz 的調用位置
可以把調用棧想象成一個函數調用鏈,但是這種方式太累了,因為當我們在項目裡方法可能存在不同的檔案模組重,這樣做的話太麻煩了。我們可以使用開發人員工具來查看:如下圖中紅色框部分


(2) 綁定規則
a. 獨立函數調用 可以把這條規則看作是無法應用其他規則時的預設規則。在代碼中, foo() 是直接使用不帶任何修飾的函數引用進行調用的,因此只能使用
預設綁定,無法應用其他規則。這個時候的this值為window或者undefined


b. 隱式綁定 1) 隱式綁定 另一條需要考慮的規則是調用位置是否有內容物件,或者說是否被某個對象擁有或者包含。
function foo() {console.log( this.a );}var obj = {a: 2,foo: foo};obj.foo(); // 2
首先需要注意的是 foo() 的聲明方式,及其之後是如何被當作引用屬性添加到 obj 中的。但是無論是直接在 obj 中定義還是先定義再添加為引用屬性,這個函數嚴格來說都不屬於obj 對象,只是obj內的foo屬性指向了全域範圍下foo函數的位置。
然而,調用位置會使用 obj 上下文來引用函數,因此你可以說函數被調用時 obj 對象“擁有”或者“包含”它。無論你如何稱呼這個模式,當 foo() 被調用時,它的落腳點確實指向 obj 對象。

Note: 對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置(也就是最靠近調用函數的位置)
function foo() {console.log( this.a );}var obj2 = {a: 42,foo: foo};var obj1 = {a: 2,obj2: obj2};obj1.obj2.foo(); // 42

2)隱式丟失 一個最常見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用預設綁定,從而把 this 綁定到全域對象或者undefined上。
function foo() {console.log( this.a );}var obj = {a: 2,foo: foo};var bar = obj.foo; // 函數別名。var a = "oops, global"; // a 是全域對象的屬性bar(); // "oops, global"
雖然 bar 是 obj.foo 的一個引用,但是實際上,它引用的是 foo 函數本身,因此此時的bar() 其實是一個不帶任何修飾的函數調用,因此應用了預設綁定,這也說明了this的值是調用方式來決定的。


c. 顯示綁定 分析隱式綁定時,我們必須在一個對象內部包含一個指向函數的屬性,並通過這個屬性間接引用函數,從而把 this 間接(隱式)綁定到這個對象上。那麼如果我們不想在對象內部包含函數引用,而想在某個對象上強制調用函數,該怎麼做呢。
那就是 call(..) 和 apply(..) 。對於call和apply的用法這裡就不多說了,前面的文章裡有講過,可以查看文章 JavaScript函數,範圍以及閉包。
1) 硬綁定 bind
function foo(something) {console.log( this.a, something );return this.a + something;}var obj = {a:2};var bar = foo.bind( obj );var b = bar( 3 ); // 2 3console.log( b ); // 5
bind(..) 會返回一個硬式編碼新函數,它會把參數設定為 this的上下文並調用原始函數,this的值有我建立的函數類決定,而不是調用者。

2)API調用的上下文 第三方庫的許多函數,以及 JavaScript 語言和宿主環境中許多新的內建函數,都提供了一個可選的參數,通常被稱為“上下文”(context),其作用和 bind(..) 一樣,確保你的回呼函數使用指定的 this 。
請看圖示:


d. new 綁定 使用 new 來調用函數,或者說發生建構函式調用時,會自動執行下面的操作。
1). 建立(或者說構造)一個全新的對象。
2). 這個新對象會被執行 [[ 原型 ]] 串連。
3). 這個新對象會綁定到函數調用的 this 。
4). 如果函數沒有返回其他對象,那麼 new運算式中的函數調用會自動返回這個新對象。


(3). 優先順序
現在我們可以根據優先順序來判斷函數在某個調用位置應用的是哪條規則。可以按照下面的
順序來進行判斷:
1). 函數是否在 new 中調用( new 綁定)。如果是的話 this 綁定的是新建立的對象。
var bar = new foo()
2). 函數是否通過 call 、 apply (顯式綁定)或者硬綁定調用。如果是的話, this 綁定的是
指定的對象。
var bar = foo.call(obj2)
3). 函數是否在某個內容物件中調用(隱式綁定)。如果是的話, this 綁定的是那個上
下文對象。
var bar = obj1.foo()
4). 如果都不是的話,使用預設綁定。如果在strict 模式下,就綁定到 undefined ,否則綁定到
全域對象。
var bar = foo()

(4). 箭頭函數
 ES6 中介紹了一種無法使用這些規則的特殊函數類型:箭頭函數。
 箭頭函數並不是使用 function 關鍵字定義的,而是使用被稱為“胖箭頭”的操作符 => 定義的。箭頭函數不使用 this的四種標準規則,而是根據外層(函數或者全域)範圍來決
定this.


箭頭函數最常用於回呼函數中,例如事件處理器或者定時器
function foo() {setTimeout(() => {// 這裡的 this 在此法上繼承自 foo()console.log( this.a );},100);}var obj = {a:2};foo.call( obj ); // 2function foo1() {setTimeout(function(){// 這裡的 this 是windowconsole.log( this.a );},100);}var obj = {a:2};foo1.call( obj ); // undefined


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.