本文源于本人關(guān)于《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》(曾探著)的閱讀總結(jié)。想詳細(xì)了解具體內(nèi)容建議閱讀該書捣辆。
1. this
this總是指向一個(gè)對(duì)象蔬螟,而具體指向哪個(gè)對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動(dòng)態(tài)綁定的。
this的指向
除去with和eval汽畴,實(shí)際應(yīng)用中旧巾,this指向分為以下四類:
- 作為對(duì)象的方法調(diào)用
- 作為普通函數(shù)調(diào)用
- 構(gòu)造器調(diào)用
- Function.prototype.call & Function.prototype.apply
作為對(duì)象的方法調(diào)用
var obj = {
a: 1,
getA: function(){
console.log(this === obj); // true
console.log(this.a); // 1
}
}
obj.getA();
這個(gè)時(shí)候,由于是obj調(diào)用的getA()函數(shù)忍些,故this指向obj鲁猩。
作為普通函數(shù)調(diào)用
var obj = {
name: aaaa,
getName: function(){
console.log(this === obj); // false
console.log(this.name); // yozo
}
}
window.name = yozo;
var getGlobalName = function(){
console.log(this.name);
}
var getName = obj.getName;
getGlobalName(); // yozo;
getName();
只要函數(shù)沒(méi)有調(diào)用者,則this就指向全局對(duì)象罢坝,在瀏覽器中的Js里廓握,全局對(duì)象是window。嚴(yán)格模式中嘁酿,this不指向全局對(duì)象疾棵。
這里主要看getName
,它本身只是一個(gè)函數(shù)痹仙,由于被抽離出來(lái)了,故執(zhí)行getName()
時(shí)殉了,它是沒(méi)有調(diào)用者的开仰,和getGlobalName
一樣,作為普通函數(shù)調(diào)用。
構(gòu)造器調(diào)用
var Myclass = function(){
this.name = 'yozo';
};
var obj = new Myclass();
console.log(obj.name); // yozo
這個(gè)時(shí)候的this代表被新創(chuàng)建出來(lái)的那個(gè)對(duì)象众弓,這里指obj
恩溅。
但是:
var Myclass = function(){
this.name = 'yozo';
return {
name: 'ann'
}
};
var obj = new Myclass();
console.log(obj.name); // ann
當(dāng)顯示返回一個(gè)對(duì)象時(shí),那么這個(gè)時(shí)候顯示返回的這個(gè)對(duì)象會(huì)被obj引用谓娃,故這個(gè)時(shí)候 obj.name就是ann而不是yozo脚乡。
Function.prototype.call & Function.prototype.apply
var obj1 = {
name: 'yozo',
getName: function(){
console.log(this.name);
}
}
var obj2 = {
name: 'ann'
}
console.log(obj1.getName()); // yozo
console.log(obj1.getName.call(obj2)); // ann
- 第一個(gè)console中,
getName
的調(diào)用者是obj1滨达,則this指向obj1奶稠; - 第二個(gè)console中,
getName
的調(diào)用者本是obj1捡遍,但是這個(gè)時(shí)候出現(xiàn)了.call(obj2)
锌订。就好像是打電話告訴obj2:“喂,obj2画株,你去執(zhí)行obj1的getName
方法吧”辆飘,故這個(gè)時(shí)候調(diào)用者就變成了obj2,我們知道谓传,this總是指向調(diào)用者蜈项,故這個(gè)時(shí)候this就指代obj2。
2. call和apply
call和apply的區(qū)別
剛剛我們已經(jīng)說(shuō)了call的作用了续挟,就是改變函數(shù)的調(diào)用者紧卒。那么apply的作用呢?也是改變函數(shù)的調(diào)用者庸推。區(qū)別在于常侦,call和apply都是Function.prototype上的方法,故只有函數(shù)才能調(diào)用這兩個(gè)函數(shù)贬媒, 既然是函數(shù)的call和apply聋亡,那么函數(shù)在執(zhí)行的時(shí)候總會(huì)有參數(shù)吧,那么改變了調(diào)用者之后际乘,函數(shù)的參數(shù)怎么處理坡倔?
- call: 把參數(shù)一個(gè)一個(gè)列舉出來(lái)(不固定參數(shù),第一個(gè)為新的調(diào)用者脖含,其他的為傳入該函數(shù)的其他參數(shù))
- apply: 把參數(shù)一鍋傳(只有兩個(gè)參數(shù)罪塔,第一個(gè)為新的調(diào)用者,另一個(gè)參數(shù)為參數(shù)數(shù)組)
var func = function(a, b, c){
console.log([a, b, c]);
}
func.apply(null,[1, 2, 3]);
func.call(null, 1, 2, 3);
這個(gè)函數(shù)沒(méi)this养葵,所以不需要調(diào)用者征堪,我們只用來(lái)區(qū)別call和apply的用法:
- call:從第二個(gè)參數(shù)開(kāi)始,一一對(duì)應(yīng)func的各個(gè)參數(shù)
- apply:參數(shù)數(shù)組與func的各個(gè)參數(shù)一一對(duì)應(yīng)关拒。
call和apply用途
- 改變this指向(改變調(diào)用者)
document.getElementById('div1').onclick = function(){
console.log(this.id); // div1
var func = function(){
console.log(this.id);
}
func(); // undefined (無(wú)調(diào)用者)
func.call(this); // div1
}
onclick回調(diào)函數(shù)中this表示document.getElementById('div1')佃蚜, 但是執(zhí)行func()的時(shí)候沒(méi)有調(diào)用者庸娱,默認(rèn)調(diào)用者為window,但是我們希望它仍能保持指向document.getElementById('div1')谐算,則把this傳給了func熟尉,讓他調(diào)用該函數(shù)。
- Function.prototype.bind:將一個(gè)函數(shù)的this固定為某個(gè)對(duì)象洲脂,之后使用時(shí)就不用再使用call和apply了斤儿。
簡(jiǎn)易版:
Function.prototype.bind = function (context) {
var self = this;
return function () {
self.apply(context, arguments);
}
}
var obj1 = {
name: 'yozo',
getName: function () {
console.log(this.name);
}
}
var obj2 = {
name: 'ann'
}
var obj2getName = obj1.getName.bind(obj2);
obj2getName(); // ann
這時(shí)的this就已經(jīng)被固定為obj2了。
這個(gè)版本在固定對(duì)象時(shí)不能保存參數(shù)恐锦,故升級(jí)版:
Function.prototype.bind = function () {
var self = this; // 保留該函數(shù)的調(diào)用者
var context = [].shift.call(arguments); // 獲取參數(shù)數(shù)組中第一個(gè)參數(shù)往果,即需要被固定的那個(gè)對(duì)象
var args = [].slice.call(arguments); // 保存剩下的參數(shù)
// 返回一個(gè)新的函數(shù)
return function () {
// 改變調(diào)用者為需要被固定的那個(gè)對(duì)象,將原本剩下的參數(shù)踩蔚,和新傳入的參數(shù)組成一個(gè)參數(shù)數(shù)組并執(zhí)行
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
}
var obj = {
name: 'yozo'
}
var func = function (a, b, c, d) {
console.log(this.name);
console.log([a, b, c, d]);
}.bind(obj, 1, 2);
func(3, 4); // 1, 2, 3, 4
- 借用其他對(duì)象的方法:之前的
obj1.getName.call(obj2)
就是obj2借用obj1的方法棚放。我們常用的是借用Object或者Array的prototype的方法:
(function(){
Array.prototype.push.call(arguments, 3, 4);
console.log(arguments);
})(1, 2)
// 1, 2, 3, 4
這其實(shí)是arguments參數(shù)數(shù)組對(duì)象利用了真數(shù)組對(duì)象,參數(shù)數(shù)組為偽數(shù)組(DOM節(jié)點(diǎn)數(shù)組也是)馅闽,但是偽數(shù)組可以借用真數(shù)組的方法飘蚯。
偽數(shù)組需要滿足兩個(gè)條件:
- 對(duì)象本身要可以存取屬性
- 對(duì)象length屬性可以讀寫