在一個對象中綁定函數,稱為這個對象的方法。
在 JavaScript 中,對象的定義是這樣的:
var xiaoming = {
name: '小明',
birth: 1990
};
我們給 xiaoming 綁定一個函數,就可以做更多的事情。比如,寫個 age()
方法,返回 xiaoming 的年齡:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 26
綁定到對象上的函數稱為 方法,和普通函數也沒啥區別,但是它在內部使用了一個 this
關鍵字,這個東東是什么?
在一個方法內部,this 是一個特殊變量,它始終指向當前對象,也就是 xiaoming 這個變量。 所以,this.birth
可以拿到 xiaoming
的 birth
屬性。
讓我們拆開寫:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26, 正常結果
getAge(); // NaN
如果以對象的方法形式調用,比如 xiaoming.age()
,該函數的 this
指向被調用的對象,也就是 xiaoming
,這是符合我們預期的。
如果單獨調用函數,比如 getAge()
,此時,該函數的 this
指向全局對象,也就是 window
,所以單獨調用函數 getAge()
返回了 NaN。
要保證 this
指向正確,必須用 obj.xxx()
的形式調用!
由于這是一個巨大的設計錯誤,要想糾正可沒那么簡單。ECMA 決定,在 strict 模式下讓函數的 this
指向 undefined
,因此,在 strict 模式下,你會得到一個錯誤:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
var fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined
但這只是讓錯誤及時暴露出來,并沒有解決 this 應該指向的正確位置。
再看另一個例子,我們把上面的方法重構了一下:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
結果又報錯了!原因是 this
指針只在 age
方法的函數內指向 xiaoming
,在函數內部定義的函數,this
又指向 undefined
了!(在非 strict 模式下,它重新指向全局對象 window
!)
修復的辦法也不是沒有,我們用一個 that
變量首先捕獲 this
:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法內部一開始就捕獲this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 26
apply
雖然在一個獨立的函數調用中,根據是否是 strict 模式,this
指向 undefined
或 window
,不過,我們還是可以控制 this
的指向的!
要指定函數的 this
指向哪個對象,可以用函數本身的 apply()
方法,它接收兩個參數,第一個參數就是需要綁定的 this
變量,第二個參數是 Array
,表示函數本身的參數。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26
getAge.apply(xiaoming, []); // 26, this指向xiaoming, 參數為空
使用參數的情況見下例:
function getAge(y) {
// var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
getAge.apply(xiaoming, [2016]); // 26,傳入參數2016
另一個與 apply()
類似的方法是 call()
,唯一區別是:
apply()
把參數打包成 Array 再傳入;call()
把參數按順序傳入。
getAge.call(xiaoming, 2016); // 26
傳入多個參數見下例:
function getAge(i, j, k) {
console.log(i + '年,小明' + (i - this.birth) + '歲。');
console.log(j + '年,小明' + (j - this.birth) + '歲。');
console.log(k + '年,小明' + (k - this.birth) + '歲。');
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
getAge.apply(xiaoming,[2014,2015,2016])
2014年,小明24歲。
2015年,小明25歲。
2016年,小明26歲。
getAge.call(xiaoming,2014,2015,2016)
2014年,小明24歲。
2015年,小明25歲。
2016年,小明26歲。
裝飾器
利用 apply()
,我們還可以動態改變函數的行為。
JavaScript 的所有對象都是動態的,即使內置的函數,我們也可以重新指向新的函數。
現在假定我們想統計一下代碼一共調用了多少次 parseInt()
,我們手動在 paeselnt()
函數內部拯救一個 count
變量,來統計被調用是次數:
var count = 0;
var oldParseInt = parseInt; // 保存原函數
window.parseInt = function () {
count += 1; // 統計調用次數
return oldParseInt.apply(null, arguments); // 調用原函數
};
// 測試:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3