說到this
梨与,就不得不提到function
秉继,相信看過其它類似文章的同學(xué)也知道,正是由于調(diào)用function
的對(duì)象不同盗棵,才導(dǎo)致了this
的指向不同壮韭。所以以前老是去記憶每種調(diào)用function
的情況所對(duì)應(yīng)的this
,因?yàn)榍闆r有限而且很少纹因,所以這當(dāng)然是可行的——對(duì)于聰明人來說喷屋。所以我不得不思考另外一些方式來讓我記住。
那么首先我們需要明確的一個(gè)事情是:function也是對(duì)象
先來幾條綱領(lǐng):
1.函數(shù)執(zhí)行時(shí)瞭恰,首先看函數(shù)名前是否存在‘.’屯曹,存在-> ‘.’前面是誰,this就指向誰惊畏;不存在-> this指向window恶耽。
2.自執(zhí)行函數(shù)中的this指向window。
3.元素綁定事件后陕截,事件執(zhí)行時(shí)驳棱,回調(diào)函數(shù)中的this指向當(dāng)前事件元素。
4.構(gòu)造函數(shù)模式中(new Function)农曲,類中(函數(shù)體中)this.xxx = xxx社搅,this就是當(dāng)前類的實(shí)例驻债。
5.通過call、apply形葬、bind方法中第一個(gè)參數(shù)強(qiáng)行改變this指向合呐,call、apply笙以、bind參數(shù)中第一個(gè)參數(shù)就是方法調(diào)用者的this指向淌实。
當(dāng)call、apply猖腕、bind參數(shù)中第一個(gè)參數(shù)是空拆祈、null、undefined時(shí)倘感,this指向window
在原型模式中查找this方法步驟:
1)首先確定this的指向(上面5點(diǎn))
2)把this替換成對(duì)應(yīng)代碼塊
3)按照原型鏈機(jī)制放坏,一步步查找結(jié)果
同時(shí)我們還需要明確的一個(gè)事情是:
function
執(zhí)行時(shí)是在某個(gè)特定的上下文中執(zhí)行的。那什么是上下文呢老玛?
全局執(zhí)行環(huán)境
全局環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境淤年。全局執(zhí)行環(huán)境被認(rèn)為是window對(duì)象。因此所有全局變量和函數(shù)都是作為window對(duì)象的屬性和方法創(chuàng)建的蜡豹。代碼載入瀏覽器時(shí)麸粮,全局執(zhí)行環(huán)境被創(chuàng)建(當(dāng)我們關(guān)閉網(wǎng)頁或者瀏覽器時(shí)全局執(zhí)行環(huán)境才被銷毀)。比如在一個(gè)頁面中镜廉,第一次載入JS代碼時(shí)創(chuàng)建一個(gè)全局執(zhí)行環(huán)境弄诲。
這也是為什么閉包有一個(gè)內(nèi)存泄露的缺點(diǎn)。因?yàn)殚]包中外部函數(shù)被當(dāng)成了全局環(huán)境桨吊。所以不會(huì)被銷毀威根,一直保存在內(nèi)存中。函數(shù)執(zhí)行環(huán)境
? 每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境视乐。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí)洛搀,函數(shù)環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中。當(dāng)函數(shù)執(zhí)行完之后佑淀,棧將其環(huán)境彈出留美,把控制權(quán)返回給之前的執(zhí)行環(huán)境。函數(shù)執(zhí)行環(huán)境的變量對(duì)象是該函數(shù)的活動(dòng)對(duì)象(activation object)伸刃。
這就是上下文谎砾,函數(shù)執(zhí)行時(shí)它也需要一些額外的信息來支撐它的運(yùn)行。那么既然function是對(duì)象的話捧颅,就會(huì)有方法景图。而function
中最核心的方法是call
方法。因此我們就從這兒入手碉哑。call方法
先來看一下如何使用call方法:
function say(content) {
console.log("From " + this + ": Hello "+ content);
}
say.call("Bob", "World"); //==> From Bob: Hello World
·Step1: 把第二個(gè)到最后一個(gè)參數(shù)作為函數(shù)執(zhí)行時(shí)要傳入的參數(shù)
·Step2: 把函數(shù)執(zhí)行時(shí)的this指向第一個(gè)參數(shù)
·Step3: 在上面這個(gè)特殊的上下文中執(zhí)行函數(shù)
上面例子中挚币,我們通過call方法亮蒋,讓say函數(shù)執(zhí)行時(shí)的this指向"Bob",然后把"World"作為參數(shù)傳進(jìn)去妆毕,所以輸出結(jié)果是可以預(yù)見的慎玖。
js執(zhí)行函數(shù)時(shí)會(huì)默認(rèn)完成以上的步驟,你可以把直接調(diào)用函數(shù)理解為一種語法糖
比如
function say(word) {
console.log(world);
}
say("Hello world");
say.call(window, "Hello world");
以上可以把say("Hello world")
看做是say.call(window,"Hello world")
的語法糖笛粘。
這個(gè)結(jié)論非常關(guān)鍵
所以以后每次看見functionName(xxx)
的時(shí)候趁怔,你需要馬上在腦海中把它替換為functionName.call(window,xxxx)
,這對(duì)你理解this的指向非常重要薪前。不過也有例外润努,在ES5的strict mode中call的第一個(gè)參數(shù)不是window而是undefined。之后的例子我假設(shè)總是不在strictmode下序六,但你需要記住strictmode有一點(diǎn)兒不同任连。
對(duì)于匿名函數(shù)來說,上面的結(jié)論也是成立的
(function(name) {
//
})("aa");
//等價(jià)于
(function(name) {
//
}).call(window, "aa");
函數(shù)作為對(duì)象的方法被調(diào)用
直接來看代碼:
var person = {
name : "caibirdme",
run : function(time) {
console.log(this.name + "has been running for over "+ time+ " minutes");
}
};
person.run(30); //==> caibirdme has been running for over 30 minutes
//等價(jià)于
person.run.call(person, 30); // the same
你會(huì)發(fā)現(xiàn)這里call
的第一個(gè)參數(shù)是person
而不是window
例诀。
當(dāng)你明白了這兩點(diǎn),下意識(shí)地把函數(shù)調(diào)用翻譯成foo.call()
的形式裁着,明確call的第一個(gè)參數(shù)繁涂,那基本上this的問題就難不住你了。
還是來舉幾個(gè)例子吧
例一:
function hello(thing) {
console.log(this + " says hello " + thing);
}
person = { name: "caibirdme" }
person.hello = hello;
person.hello("world") // 相當(dāng)于執(zhí)行 person.hello.call(person, "world")
//caibirdme says hello world
hello("world") // 相當(dāng)于執(zhí)行 hello.call(window, "world")
//[object DOMWindow]world
例二:
var obj = {
x: 20,
f: function(){ console.log(this.x); }
};
obj.f(); // obj.f.call(obj)
//==> 20
obj.innerobj = {
x: 30,
f: function(){ console.log(this.x); }
}
obj.innerobj.f(); // obj.innerobj.f.call(obj.innerobj)
// ==> 30
例三:
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x); //this equals obj
// ==> 20
var foo = function(){ console.log(this.x); }
foo(); // foo.call(window)
//foo中this被指定為window二驰,所以==> 10
}
};
obj.f(); // obj.f.call(obj)
// ==> 20 10
由例三引出一個(gè)非常common的問題扔罪,如果我想讓foo輸出20怎么辦?這時(shí)候需要用到一點(diǎn)小技巧
例四:
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x);
var that = this; //使用that保留當(dāng)前函數(shù)執(zhí)行上下文的this
var foo = function(){ console.log(that.x); } //此時(shí)foo函數(shù)中的this仍然指向window桶雀,但我們使用that取得obj
foo(); // foo.call(window)
}
};
obj.f(); obj.f.call(obj)
// ==> 20 20
再來一個(gè)稍微難一點(diǎn)點(diǎn)的(但其實(shí)用call替換法一點(diǎn)兒也不難)
例五:
var x = 10;
var obj = {
x: 20,
f: function(){ console.log(this.x); }
};
obj.f(); // obj.f.call(obj)
// ==> 20
var fOut = obj.f;
fOut(); // fOut.call(window)
//==> 10
var obj2 = {
x: 30,
f: obj.f
}
obj2.f(); // obj2.f.call(obj2)
//==> 30
例五如果沒有明確:
es5中this是在執(zhí)行是才會(huì)被確認(rèn)的
是會(huì)出錯(cuò)的矿酵。
可能會(huì)認(rèn)為說 obj.f
那個(gè)函數(shù)定義在obj里面,那 this
就該指向obj矗积。
用于構(gòu)造函數(shù)
先看一段代碼:
func person(name) {
this.name = name;
}
var caibirdme = new person("deen");
// caibirdme.name == deen
函數(shù)在用作構(gòu)造函數(shù)時(shí)同樣可以用call方法去代替全肮,那這里怎么代替呢?
這里又需要明確一點(diǎn):
new constrcut()是一種創(chuàng)建對(duì)象的語法糖
它等價(jià)于
function person(name) {
this.name = name;
}
var foo = new person("deen");
//通過new創(chuàng)建了一個(gè)對(duì)象
//new是一種語法糖棘捣,new person等價(jià)于
var bar = (function(name) {
var _newObj = {
constructor : person,
__proto__ : person.prototype,
};
_newObj.constructor(name); // _newObj.constructor.call(_newObj, name)
return _newObj;
})();
new的時(shí)候this就指向新的對(duì)象
總結(jié)來說就是下面兩個(gè)等價(jià)變形:
· foo() ---> foo.call(window)
· obj.foo() --> obj.foo.call(obj)
只要理解以上兩個(gè)變形辜腺,this就不再是問題啦!乍恐!