在一個(gè)對象中綁定函數(shù)孝常,稱為這個(gè)對象的方法。
在 JavaScript 中蚓哩,對象的定義是這樣的:
var xiaoming = {
name: '小明',
birth: 1990
};
我們給 xiaoming 綁定一個(gè)函數(shù)构灸,就可以做更多的事情。比如岸梨,寫個(gè) 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
綁定到對象上的函數(shù)稱為 方法,和普通函數(shù)也沒啥區(qū)別曹阔,但是它在內(nèi)部使用了一個(gè) this
關(guān)鍵字半开,這個(gè)東東是什么?
在一個(gè)方法內(nèi)部赃份,this 是一個(gè)特殊變量寂拆,它始終指向當(dāng)前對象,也就是 xiaoming 這個(gè)變量抓韩。 所以纠永,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, 正常結(jié)果
getAge(); // NaN
如果以對象的方法形式調(diào)用谒拴,比如 xiaoming.age()
尝江,該函數(shù)的 this
指向被調(diào)用的對象,也就是 xiaoming
英上,這是符合我們預(yù)期的炭序。
如果單獨(dú)調(diào)用函數(shù),比如 getAge()
善延,此時(shí)少态,該函數(shù)的 this
指向全局對象,也就是 window
易遣,所以單獨(dú)調(diào)用函數(shù) getAge()
返回了 NaN。
要保證 this
指向正確嫌佑,必須用 obj.xxx()
的形式調(diào)用豆茫!
由于這是一個(gè)巨大的設(shè)計(jì)錯(cuò)誤,要想糾正可沒那么簡單屋摇。ECMA 決定揩魂,在 strict 模式下讓函數(shù)的 this
指向 undefined
,因此炮温,在 strict 模式下火脉,你會(huì)得到一個(gè)錯(cuò)誤:
'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
但這只是讓錯(cuò)誤及時(shí)暴露出來,并沒有解決 this 應(yīng)該指向的正確位置。
再看另一個(gè)例子倦挂,我們把上面的方法重構(gòu)了一下:
'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
結(jié)果又報(bào)錯(cuò)了畸颅!原因是 this
指針只在 age
方法的函數(shù)內(nèi)指向 xiaoming
,在函數(shù)內(nèi)部定義的函數(shù)方援,this
又指向 undefined
了C怀础(在非 strict 模式下,它重新指向全局對象 window
7赶贰)
修復(fù)的辦法也不是沒有送火,我們用一個(gè) that
變量首先捕獲 this
:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法內(nèi)部一開始就捕獲this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 26
apply
雖然在一個(gè)獨(dú)立的函數(shù)調(diào)用中,根據(jù)是否是 strict 模式先匪,this
指向 undefined
或 window
种吸,不過,我們還是可以控制 this
的指向的呀非!
要指定函數(shù)的 this
指向哪個(gè)對象坚俗,可以用函數(shù)本身的 apply()
方法,它接收兩個(gè)參數(shù)姜钳,第一個(gè)參數(shù)就是需要綁定的 this
變量坦冠,第二個(gè)參數(shù)是 Array
,表示函數(shù)本身的參數(shù)哥桥。
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, 參數(shù)為空
使用參數(shù)的情況見下例:
function getAge(y) {
// var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
getAge.apply(xiaoming, [2016]); // 26辙浑,傳入?yún)?shù)2016
另一個(gè)與 apply()
類似的方法是 call()
,唯一區(qū)別是:
apply()
把參數(shù)打包成 Array 再傳入拟糕;call()
把參數(shù)按順序傳入判呕。
getAge.call(xiaoming, 2016); // 26
傳入多個(gè)參數(shù)見下例:
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()
弦赖,我們還可以動(dòng)態(tài)改變函數(shù)的行為。
JavaScript 的所有對象都是動(dòng)態(tài)的抵代,即使內(nèi)置的函數(shù)腾节,我們也可以重新指向新的函數(shù)。
現(xiàn)在假定我們想統(tǒng)計(jì)一下代碼一共調(diào)用了多少次 parseInt()
荤牍,我們手動(dòng)在 paeselnt()
函數(shù)內(nèi)部拯救一個(gè) count
變量案腺,來統(tǒng)計(jì)被調(diào)用是次數(shù):
var count = 0;
var oldParseInt = parseInt; // 保存原函數(shù)
window.parseInt = function () {
count += 1; // 統(tǒng)計(jì)調(diào)用次數(shù)
return oldParseInt.apply(null, arguments); // 調(diào)用原函數(shù)
};
// 測試:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3