當一個函數(shù)被調用時,會創(chuàng)建一個活動記錄(執(zhí)行上下文)丧叽。
這個記錄會包含函數(shù)在哪里被調用(調用棧)卫玖、函數(shù)的調用方法、傳入的參數(shù)>等信息踊淳。
this就是記錄的其中一個屬性假瞬,會在函數(shù)執(zhí)行的過程中用到。
this既不指向函數(shù)自身也不指向函數(shù)的作用域迂尝。
this實際上是在函數(shù)被調用時發(fā)生的綁定脱茉,它指向什么完全取決于函數(shù)在哪里被調用。
在嚴格模式下雹舀,一般函數(shù)調用的時候this指向undefined芦劣。
一、為什么要使用this
- this提供了一種更優(yōu)雅的方式來隱式的“傳遞”一個對象的引用说榆,因此可以將API設計的更加簡潔并且易于復用虚吟。
eg:
var me = {
name: "fenfei"
};
//不使用this,調用
function speak(name){
console.log("Hello, I'm "+ name);
}
speak(me.name); //Hello, I'm fenfei
//使用this签财,調用
function speak(){
console.log("Hello, I'm "+ this.name);
}
speak.call(me); //Hello, I'm fenfei
二串慰、this存在的兩個誤解
(1)this指向函數(shù)自身;
(2)this指向函數(shù)的作用域唱蒸。
作用域無法通過JavaScript代碼訪問邦鲫,它存在于JavaScript引擎內部。每當把this和詞法作用域的查找混合使用時,一定要提醒自己庆捺,這是無法實現(xiàn)的古今!
this是在運行時進行綁定的,并不是在編寫時綁定滔以,它的上下文取決于函數(shù)調用時的各種條件捉腥。this的綁定和函數(shù)聲明的位置沒有任何關系,只取決于函數(shù)的調用位置(也就是函數(shù)的調用方式)你画!
三抵碟、綁定規(guī)則
1)默認綁定
- 最常用的函數(shù)調用類型:獨立函數(shù)調用』捣耍可以把這條規(guī)則看作是無法應用其他規(guī)則時的默認規(guī)則拟逮。
eg:
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
2)隱式規(guī)則
- 隱式綁定的規(guī)則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含适滓。
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
敦迄。當foo()被調用時,this被綁定到obj粒竖,因此this.a和obj.a是一樣颅崩。
- 但有時候會出現(xiàn)隱式丟失。
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數(shù)
var a = "oops, global"; //
bar(); // "oops, global"
蕊苗。雖然bar是obj.foo的一個引用沿后,但是實際上,它引用的是foo函數(shù)本身朽砰。
因此此時的bar()其實是一個不帶任何修飾的函數(shù)調用尖滚,應用了默認綁定。
3)顯示綁定
- call與apply
瞧柔。在JavaScript中 call與apply就像this的父母一般漆弄,讓this住哪它就得住哪,不得不聽話造锅!當無參數(shù)時撼唾,當前對象為window
eg:
var name="全局";
var xpg={
name:"局部"
};
function getName(){
alert(this.name);
}
getName(xpg);//全局
getName.call(xpg);//局部
getName.call();//全局
。其中this身處函數(shù)getName中哥蔚。無論this身處何處倒谷,一定要找到函數(shù)運行時的位置。此時函數(shù)getName運行時的位置,對于
(1)getName(xpg);//全局
顯然糙箍,函數(shù)getName所在的對象是window渤愁,因此this的安身之處定然在window,即指向window對象深夯,則getName返回的this.name其實是window.name抖格,因此alert出來的是“全局”!
(2)getName.call(xpg);//局部
。其中雹拄,call指定this的安身之處就是在xpg對象收奔,因為this被迫只能在xpg那安家,則此時this指向xpg對象办桨, this.name其實是xpg.name筹淫,因此alert出來的是“局部”!
- bind()
呢撞。bind方法是es5開始提供的,所以ie9+才支持
eg:
function f(){
return this.a;
}
var g = f.bind({a : "test"});
//想把某個對象作為this的時候饰剥,就把它傳進去殊霞,得到一個新對象g
console.log(g()); // test
//重復調用的時候,this已經(jīng)指向bind參數(shù)汰蓉。
//這對于我們綁定一次需要重復調用依然實現(xiàn)綁定的話绷蹲,會比apply和call更加高效(看下面這個例子)
var o = {a : 37, f : f, g : g};
console.log(o.f(), o.g()); // 37, test
//o.f()通過對象的屬性調用,this指向對象o;
//比較特殊的是即使我們把新綁定的方法作為對象的屬性調用顾孽,
//o.g()依然會按之前的綁定去走祝钢,所以答案是test不是g
4)new綁定
- new的this綁定不能被修改!
- new調用函數(shù)會自動執(zhí)行下面操作:
(1)創(chuàng)建(或者說構造)一個全新的對象若厚;
(2)這個新對象會被執(zhí)行[[原型]]連接拦英;
(3)這個新對象會綁定到函數(shù)調用的this;
(4)如果函數(shù)沒有返回其他對象测秸,那么new表達式中的函數(shù)調用會自動返回這個新對象疤估。
eg:
function Person(name,age) {
this.name = name
this.age = age
console.log("我也只不過是個普通函數(shù)")
}
Person("zxt",22) // "我也只不過是個普通函數(shù)"
console.log(name) // "zxt"
console.log(age) // 22
var zxt = new Person("zxt",22) // "我也只不過是個普通函數(shù)"
console.log(zxt.name) // "zxt"
console.log(zxt.age) // 22
- 上面這個例子中,首先定義了一個 Person 函數(shù)霎冯,既可以普通調用铃拇,也可以以構造函數(shù)的形式的調用。
沈撞。當普通調用時慷荔,則按照正常的函數(shù)執(zhí)行,輸出一個字符串缠俺。
显晶。如果是通過一個new操作符,則構造了一個新的對象。
晋修。普通調用時吧碾,前面已經(jīng)介紹過,此時應用默認綁定規(guī)則墓卦,this綁定在了全局 對象上倦春,此時全局對象上會分別增加 name 和 age 兩個屬性。
。當通過new操作符調用時睁本,函數(shù)會返回一個對象尿庐,從輸出結果上來看 this 對象綁定在了這個返回的對象上。 - 因此呢堰,所謂的new綁定是指通過new操作符來調用函數(shù)時抄瑟,會產生一個新對象,并且會把構造函數(shù)內的this綁定到這個對象上枉疼。
四皮假、優(yōu)先級
- new綁定>call\apply等顯示綁定>隱式綁定>默認綁定。
了解了函數(shù)調用中this綁定的四條規(guī)則骂维,需要做的就是找到函數(shù)的調用位置并判斷對應哪條規(guī)則惹资。
(1)函數(shù)是否是new綁定?如果是航闺,this綁定的是新創(chuàng)建的對象褪测。
var bar = new Foo();
(2)函數(shù)是否通過call、apply顯示綁定或硬綁定潦刃?如果是侮措,this綁定的是指定的對象。
var bar = foo.call(obj);
(3)函數(shù)是否在某個上下文對象中隱式調用乖杠?如果是分扎,this綁定的是那個上下文對象。
var bar = obj.foo();
(4)上述全不是滑黔,則使用默認綁定笆包。如果在嚴格模式下,就綁定到undefined略荡,否則綁定到全局window對象庵佣。
var bar = foo();
new綁定和call、apply無法一起使用汛兜,因此不能使用new foo.call(obj).
五巴粪、this綁定例外
1)被忽略的綁定
- 如果你把null或者undefined作為this的綁定對象傳入call、apply或者bind粥谬。
這些值在調用時會被忽略肛根,實際應用的是默認綁定規(guī)則。
eg:
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 2
2)間接引用
eg:
function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
- 賦值表達式p.foo = o.foo的返回值是目標函數(shù)的引用漏策,因此調用位置是foo()而不是p.foo()或者o.foo()派哲。
3)當前對象不明確時的this - 當沒有明確的執(zhí)行時的當前對象時,this指向全局對象window掺喻。
例如對于全局變量引用的函數(shù)上我們有:
var name = "Tom";
var Bob = {
name: "Bob",
show: function(){
alert(this.name);
}
}
var show = Bob.show;
show(); //Tom
芭届。你可能也能理解成show是window對象下的方法储矩,所以執(zhí)行時的當前對象時window。但局部變量引用的函數(shù)上褂乍,卻無法這么解釋:
var name = "window";
var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var Tom = {
name: "Tom",
showName: function(){
var fun = Bob.showName;
fun();
}
};
Tom.showName(); //window
4)在瀏覽器中setTimeout持隧、setInterval和匿名函數(shù)執(zhí)行時的當前對象是全局對象window:
var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
setTimeout(this.showName, 1000);
}
};
nameObj.waitShowName();
5)軟綁定
eg:
var count=2;
var obj={
count:0,
cool:function coolFn(){
console.log(this.count);//0
var self=this;
if(self.count<1){
setTimeout(function timer(){
self.count++;
console.log("awesome?");
console.log(self.count);//1
console.log(this.count);//2
},100);
}
}
};
obj.cool();
6)dom事件中的this
(1)直接在dom元素中使用
<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />
- 分析:對于dom元素的一個onclick(或其他如onblur等)屬性,它為所屬的html元素所擁有逃片,直接在它觸發(fā)的函數(shù)里寫this屡拨,this應該指向該html元素。
(2)給dom元素注冊js函數(shù)
- a褥实、不正確的方式
<script type="text/javascript">
function thisTest(){
alert(this.value); // 彈出undefined, this在這里指向??
}
</script>
<input id="btnTest" type="button" value="提交" onclick="thisTest()" />
呀狼。分析:onclick事件直接調用thisTest函數(shù),程序就會彈出undefined性锭。
因為thisTest函數(shù)是在window對象中定義的赠潦, 所以thisTest的擁有者(作用域)是window,thisTest的this也是window草冈。而window是沒有value屬性的,所以就報錯了瓮增。
- b怎棱、正確的方式
<input id="btnTest" type="button" value="提交" />
<script type="text/javascript">
function thisTest(){
alert(this.value);
}
document.getElementById("btnTest").onclick=thisTest;
//給button的onclick事件注冊一個函數(shù)
</script>
。分析:在前面的示例中绷跑,thisTest函數(shù)定義在全局作用域(這里就是window對象)拳恋,所以this指代的是當前的window對象。
而通過document.getElementById(“btnTest”).onclick=thisTest;這樣的形式砸捏,其實是將btnTest的onclick屬性設置為thisTest函數(shù)的一個副本谬运,在btnTest的onclick屬性的函數(shù)作用域內,this歸btnTest所有垦藏,this也就指向了btnTest梆暖。
- 因為多個不同的HTML元素雖然創(chuàng)建了不同的函數(shù)副本,但每個副本的擁有者都是相對應的HTML元素掂骏,各自的this也都指向它們的擁有者轰驳,不會造成混亂。
eg:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" />
<input id="btnTest2" type="button" value="提交2" />
<script type="text/javascript">
function thisTest(){
this.value="提交中";
}
var btn=document.getElementById("btnTest1");
alert(btn.onclick); //第一個按鈕函數(shù)
var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二個按鈕函數(shù)
</script>
其彈出的結果是:
//第一個按鈕
function onclick(){
thisTest()
}
//第二個按鈕
function thisTest(){
this.value="提交中";
}
7)this詞法(ES6:箭頭函數(shù))
- 箭頭函數(shù)不使用function關鍵字定義弟灼,而是使用“胖箭頭”的操作符=>定義级解;箭頭函數(shù)不使用this的四種標準規(guī)則,而是根據(jù)外層(函數(shù)或者全局)作用域來決定this田绑。
eg:
function foo(){
return (a)=>{
//this繼承自foo
console.log(this.a);
};
}
var obj1={
a:2
}
var obj2={
a:3
}
var bar=foo.call(obj1);
bar.call(obj2);//2不是3勤哗!
- foo()內部創(chuàng)建的箭頭函數(shù)會捕獲調用時foo()的this。由于foo()的this被綁定到obj1掩驱,bar(引用箭頭函數(shù))的this也被綁定到obj1芒划,而箭頭函數(shù)的綁定無法修改冬竟。(new的也不能!)
箭頭函數(shù)和new的this綁定不能被修改腊状!