this、call 和 apply ----- JavaScript設計模式與開發實踐 (二)電子書.pdf

在 JavaScript 編程中,this 關鍵字老是讓初學者感到迷惑,Function.prototype.call 和
Function.prototype.apply 這兩個方法也有着普遍的運用。咱們有必要在學習設計模式以前先理解
這幾個概念。html

2.1 this
跟別的語言截然不同的是,JavaScript 的 this 老是指向一個對象,而具體指向哪一個對象是在運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境。編程

2.1.1 this的指向
除去不經常使用的 with 和 eval 的狀況,具體到實際應用中,this 的指向大體能夠分爲如下 4 種。
** 做爲對象的方法調用。
 做爲普通函數調用。
 構造器調用。
 Function.prototype.call 或 Function.prototype.apply 調用。**設計模式

  1. 做爲對象的方法調用

當函數做爲對象的方法被調用時,this 指向該對象:數組

var obj = { 
 a: 1, 
 getA: function(){ 
 alert ( this === obj ); // 輸出:true 
 alert ( this.a ); // 輸出: 1 
 } 
}; 
obj.getA();
  1. 做爲普通函數調用

當函數不做爲對象的屬性被調用時,也就是咱們常說的普通函數方式,此時的 this 老是指
向全局對象。在瀏覽器的 JavaScript 裏,這個全局對象是 window 對象。瀏覽器

window.name = 'globalName'; 
var getName = function(){ 
 return this.name; 
}; 
console.log( getName() ); // 輸出:globalName
或者
window.name = 'globalName123'; 
function getName (){ 
 return this.name; 
}; 
console.log( getName() ); // 輸出:globalName123

或者:app

window.name = 'globalName'; 
var myObject = { 
 name: 'sven', 
 getName: function(){ 
 return this.name; 
 } 
}; 
var getName = myObject.getName; 
console.log( getName() ); // globalName

註解:由於getName()調用,只是普通函數的調用,因此這裏函數內的this是指向全局的。var getName = myObject.getName就是把myObject對象的getName屬性方法給getName。由於這裏的this 是指向全局,因此結果是globalName。函數

有時候咱們會遇到一些困擾,好比在 div 節點的事件函數內部,有一個局部的 callback 方法,
callback 被做爲普通函數調用時,callback 內部的 this 指向了 window,但咱們每每是想讓它指向
該 div 節點,見以下代碼:學習

<html> 
 <body> 
 <div id="div1">我是一個 div</div> 
 </body> 
 <script> 
 window.id = 'window'; 
 document.getElementById( 'div1' ).onclick = function(){ 
 alert ( this.id ); // 輸出:'div1' 
 var callback = function(){ 
 alert ( this.id ); // 輸出:'window' 
 } 
 callback(); 
 }; 
 </script> 
</html>

此時有一種簡單的解決方案,能夠用一個變量保存 div 節點的引用:
圖靈社區會員 軒轅 專享 尊重版權
26 第 2 章 this、call 和 applythis

document.getElementById( 'div1' ).onclick = function(){ 
 var that = this; // 保存 div 的引用
 var callback = function(){ 
 alert ( that.id ); // 輸出:'div1' 
 } 
 callback(); 
};

在 ECMAScript 5 的 strict 模式下,這種狀況下的 this 已經被規定爲不會指向全局對象,而
是 undefined:prototype

function func(){ 
 "use strict" 
 alert ( this ); // 輸出:undefined 
} 
func();
  1. 構造器調用

JavaScript 中沒有類,可是能夠從構造器中建立對象,同時也提供了 new 運算符,使得構造
器看起來更像一個類。
除了宿主提供的一些內置函數,大部分 JavaScript 函數均可以看成構造器使用。構造器的外
表跟普通函數如出一轍,它們的區別在於被調用的方式。當用 new 運算符調用函數時,該函數總
會返回一個對象,一般狀況下,構造器裏的 this 就指向返回的這個對象,見以下代碼:

var MyClass = function(){ 
 this.name = 'sven'; 
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 輸出:sven

註釋:咱們這裏要知道new的操做符都爲咱們作了哪4件事。
new操做符幹了如下三步:
1.先建立了一個新的空對象
2.而後讓這個空對象的__proto__指向函數的原型prototype
3.將對象做爲函數的this傳進去,若是return 出來東西是對象的話就直接返回 return 的內容,沒有的話就返回建立的這個對象

對應僞代碼:

對於const a = new Foo();,new幹了如下事情
 const o = new Object();//建立了一個新的空對象o
 o.__proto__ = Foo.prototype;//讓這個o對象的 __proto__指向函數的原型prototype
 Foo.call(o);//this指向o對象
 a = o;//將o對象賦給a對象

但用 new 調用構造器時,還要注意一個問題,若是構造器顯式地返回了一個 object 類型的對象,那麼這次運算結果最終會返回這個對象,而不是咱們以前期待的 this

var MyClass = function(){ 
 this.name = 'sven'; 
 return { // 顯式地返回一個對象
 name: 'anne' 
 } 
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 輸出:anne

若是構造器不顯式地返回任何數據,或者是返回一個非對象類型的數據,就不會形成上述
問題:
若是構造器不顯式地返回任何數據,或者是返回一個非對象類型的數據,就不會形成上述
問題:

var MyClass = function(){ 
 this.name = 'sven' 
 return 'anne'; // 返回 string 類型
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 輸出:sven
  1. Function.prototype.call 或 Function.prototype.apply 調用

跟普通的函數調用相比,用 Function.prototype.call 或 Function.prototype.apply 能夠動態地
改變傳入函數的 this:

var obj1 = { 
 name: 'sven', 
 getName: function(){ 
 return this.name; 
 } 
}; 
var obj2 = { 
 name: 'anne' 
}; 
console.log( obj1.getName() ); // 輸出: sven 
console.log( obj1.getName.call( obj2 ) ); // 輸出:anne

call 和 apply 方法能很好地體現 JavaScript 的函數式語言特性,在 JavaScript 中,幾乎每一次
編寫函數式語言風格的代碼,都離不開 call 和 apply。在 JavaScript 諸多版本的設計模式中,也
用到了 call 和 apply。在下一節會詳細介紹它們。

2.1.2 丟失的this
這是一個常常遇到的問題,咱們先看下面的代碼:

var obj = { 
 myName: 'sven', 
 getName: function(){ 
 return this.myName; 
 } 
}; 
console.log( obj.getName() ); // 輸出:'sven' 
var getName2 = obj.getName; 
console.log( getName2() ); // 輸出:undefined

當調用 obj.getName 時,getName 方法是做爲 obj 對象的屬性被調用的,根據 2.1.1 節提到的規
律,此時的 this 指向 obj 對象,因此 obj.getName()輸出'sven'。

當用另一個變量 getName2 來引用 obj.getName,而且調用 getName2 時,根據 2.1.2 節提到的
規律,此時是普通函數調用方式,this 是指向全局 window 的,因此程序的執行結果是 undefined。

2.2.1 call和apply的區別
Function.prototype.call 和 Function.prototype.apply 都是很是經常使用的方法。它們的做用一模
同樣,區別僅在於傳入參數形式的不一樣。
apply 接受兩個參數,第一個參數指定了函數體內 this 對象的指向,第二個參數爲一個帶下
標的集合,這個集合能夠爲數組,也能夠爲類數組,apply 方法把這個集合中的元素做爲參數傳
遞給被調用的函數:

var func = function( a, b, c ){ 
 alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ] 
}; 
func.apply( null, [ 1, 2, 3 ] );

在這段代碼中,參數 一、二、3 被放在數組中一塊兒傳入 func 函數,它們分別對應 func 參數列
表中的 a、b、c。
call 傳入的參數數量不固定,跟 apply 相同的是,第一個參數也是表明函數體內的 this 指向,
從第二個參數開始日後,每一個參數被依次傳入函數:

var func = function( a, b, c ){ 
 alert ( [ a, b, c ] ); // 輸出 [ 1, 2, 3 ] 
}; 
func.call( null, 1, 2, 3 );

當調用一個函數時,JavaScript 的解釋器並不會計較形參和實參在數量、類型以及順序上的
區別,JavaScript 的參數在內部就是用一個數組來表示的。從這個意義上說,apply 比 call 的使用
率更高,咱們沒必要關心具體有多少參數被傳入函數,只要用 apply 一股腦地推過去就能夠了。
call 是包裝在 apply 上面的一顆語法糖,若是咱們明確地知道函數接受多少個參數,並且想一目瞭然地表達形參和實參的對應關係,那麼也能夠用 call 來傳送參數。

  1. Function.prototype.bind

大部分高級瀏覽器都實現了內置的 Function.prototype.bind,用來指定函數內部的 this指向,
即便沒有原生的 Function.prototype.bind 實現,咱們來模擬一個也不是難事,代碼以下:
圖靈社區會員 軒轅 專享 尊重版權
32 第 2 章 this、call 和 apply

Function.prototype.bind = function( context ){ 
 var self = this; // 保存原函數
 return function(){ // 返回一個新的函數
 return self.apply( context, arguments ); // 執行新的函數的時候,會把以前傳入的 context 
 // 看成新函數體內的 this 
 } 
}; 
var obj = { 
 name: 'sven' 
}; 
var func = function(){ 
 alert ( this.name ); // 輸出:sven 
}.bind( obj); 
func();

咱們經過 Function.prototype.bind 來「包裝」func 函數,而且傳入一個對象 context 看成參
數,這個 context 對象就是咱們想修正的 this 對象。
在 Function.prototype.bind 的內部實現中,咱們先把 func 函數的引用保存起來,而後返回一
個新的函數。當咱們在未來執行 func 函數時,實際上先執行的是這個剛剛返回的新函數。在新
函數內部,self.apply( context, arguments )這句代碼纔是執行原來的 func 函數,而且指定 context
對象爲 func 函數體內的 this。
這是一個簡化版的 Function.prototype.bind 實現。

call是改變函數內部的this指向的。

相關文章
相關標籤/搜索