JavaScript設計模式與開發實踐 | 02 - this、call和apply

this

JavaScript的this老是指向一個對象,至於指向哪一個對象,是在運行時基於函數的執行環境的動態綁定的,而非函數被聲明時的環境。設計模式

this的指向

this的指向大體能夠分爲如下4類:數組

  • 做爲對象的方法調用瀏覽器

  • 做爲普通函數調用app

  • 構造器調用函數

  • Function.prototype.callFunction.prototype.apply調用this

1.做爲對象的方法調用prototype

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

// 聲明obj對象
var obj = {
    a: 'a屬性的值',    // a 屬性
    getA: function(){  // getA()方法
        console.log(this === obj);  // 輸出:true
        console.log(this.a);  // 輸出: a屬性的值
    }
};

obj.getA();

2.做爲普通函數調用code

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

window.name = 'globalName';  // 聲明全局對象的name屬性

var getName = function(){  // 定義getName()函數
    return this.name;
};

// 調用函數
console.log(getName());  //輸出: globalName
window.name = 'globalName';  // 聲明全局對象的name屬性

var myObject = {  // 聲明myObject對象
    name: 'objectName';
    getName: function(){  // 定義getName()方法
        return this.name;
    }
}

var getName = myObject.getName;  // 將getName()方法賦給變量getName

console.log(getName());  // 輸出: globalName

3.構造器調用

JavaScript沒有類,但能夠從構造器中建立對象,也提供了new運算符用於調用構造器。

大部分JavaScript函數均可以看成構造器使用。構造器的外表跟普通函數同樣,他們的區別在於被調用的方式。即,使用new運算符建立對象時,就是將函數看成構造器調用。當用new運算符調用函數時,該函數總會返回一個對象,此時,構造器裏的this指向返回的這個對象。

var myClass = function(){
    this.name = 'className';
};

var obj = new myClass();
console.log(obj.name);  // 輸出:seven

但,若是構造器顯式地返回了一個object類型的對象,那麼此函數將返回這個object類型的對象,而不是函數自己所定義的對象,例如:

var myClass = function(){
    this.name = 'className';
    return {  //顯式地返回一個對象
        name: 'anne'
    }
};

var obj = new myClass();
console.log(obj.name);  //  輸出:anne

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

var myClass = function(){
    this.name = 'className';
    return 'anne';  // 返回string類型
};

var obj = new myClass();
console.log(obj.name);  //  輸出:className

4.Function.prototype.call 或 Function.prototype.apply調用

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

var A = {
    name: 'ObjectA',
    getName: function(){
        return this.name;
    }
};

var B = {
    name: 'ObjectB'
};

console.log(A.getName()); // 做爲對象的方法調用,輸出:ObjectA
console.log(A.getName.call(B)); // 輸出:ObjectB

丟失的this

咱們常常會由於this的指向與咱們的期待不一樣,而出現undefined的狀況,例如:

var obj = {
    name: 'objName';
    getName: function(){
        return this.name;
    }
};

// 做爲對象的方法調用,指向obj對象
console.log(obj.getName());   // 輸出:objName

// 做爲普通函數調用,指向全局對象window,name屬性還沒有定義
var getName2 = obj.getName;
console.log(getName2());  // 輸出:Lundefined

call 和 apply

ECAMScript3給Function的原型定義了兩個方法,分別是Function.prototype.call 或 Function.prototype.apply。在一些函數式風格的代碼編寫中,call和apply方法尤其有用。

call和apply的區別

Function.prototype.call 或 Function.prototype.apply的做用如出一轍,區別僅在於傳入參數形式的不一樣。

apply接受兩個參數,第一個參數制定了函數體內this對象的指向,第二個函數爲一個帶下標的集合,這個集合能夠是數組,也能夠是類數組。apply方法把這個集合中的元素做爲參數傳遞給被調用的函數。

var func = function(a, b, c){
  console.log([a, b, c]);  // 輸出:[1,2,3]
};

func.apply(null, [1, 2, 3]);

call傳入的參數數量不固定,第一個參數也是表明了函數體內的this指向,從第二個參數開始日後,每一個參數依次被傳入函數:

var func = function(a, b, c){
  console.log([a, b, c]);  // 輸出:[1,2,3]
};

func.call(null, 1, 2, 3);

當調用一個函數時,JavaScript的解釋器並不會計較形參和實參在數量、類型、以及順序上的區別,JavaScript的參數在內部就是用一個數組來表示的。從這個意義上說,apply比call的使用率更高,咱們沒必要關心具體有多少參數被傳入函數,只要用apply一股腦地推過去就能夠了。

當使用call或apply的時候,若是咱們傳入的第一個參數爲null,函數體內的this會指向默認的宿主對象,在瀏覽器中則是window:

var func = function(a, b, c){
  console.log(this);
};

func.apply(null, [1, 2, 3]);  //輸出:window對象
func.call(null, 1, 2, 3);  //輸出:window對象

call和apply的用途

  • 改變this指向

  • Function.prototype.bind

  • 借用其餘對象的方法

1.改變this指向

call和apply最多見的用途是改變函數內部的this指向:

var A = {
  name: 'nameA';
};

var B = {
  name: 'nameB';
};

window.name = 'nameWindow';

var getName = function(){
  conlole.log(this.name);
};

getName();  // 以普通函數調用,指向了window對象,輸出:nameWindow
getName.call(A);  // 改變了this的指向,指向了傳入的對象,輸出:nameA
getName.call(B);  // 改變了this的指向,指向了傳入的對象,輸出:nameB

2.Function.prototype.bind

大部分高級瀏覽器都實現了內置的Function.prototype.bind,用來指定函數內部的this指向。
若沒有原生的Function.prototype.bind實現,能夠經過模擬一個:

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

var obj = {
 name: "objName"
};

var func = function(){
  console.log(this.name);  // 輸出:objName
}.bind(obj);

func();

咱們經過Function.prototype.bind來「包裝」func函數,而且傳入一個對象context看成參數,這個context對象就是咱們想要修正的this對象,即讓函數內部的this指向這個對象。

3.借用其餘對象的方法

咱們知道,杜鵑即不會築巢,也不會孵雛,而是把本身的蛋寄託給雲雀等其餘鳥類,讓它們代爲孵化和養育。在JavaScript中也存在相似的借用現象。

場景一:借用構造函數
經過這種技術,可以實現一些相似繼承的效果:

var A = function(name){
 this.name = name;
};

var B = function(){  // 借用A的構造函數
  A.apply(this, arguments); 
};

B.prototype.getName = function(){
  return this.name;
};

var b = new B('baby');
console.log(b.getName());  // 輸出:baby

場景二:類數組對象的操做
函數的參數列表arguments是一個類數組對象,雖然它也有下標,但它並不是真正的數組,因此也不能像數組同樣,進行排序操做或者往集合裏添加一個新的元素。這時,能夠借用Array.prototype對象上的方法。

好比,想往arguments中添加一個新的元素,能夠借用Array.prototype.push:

(function(){
  Array.prototype.push.call(arguments, 3);
  console.log(arguments); // 輸出:[1,2,3]
})(1, 2);

想把arguments轉成真正的數組的時候,能夠借用Array.prototype.slice方法;想截去arguments列表中的頭一個元素時,能夠借用Array.prototype.shift方法。

PS:本節內容爲《JavaScript設計模式與開發實踐》第二章 筆記。

相關文章
相關標籤/搜索