三典奉、js中This之謎

當一個函數(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綁定不能被修改腊状!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诱咏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缴挖,更是在濱河造成了極大的恐慌袋狞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件映屋,死亡現(xiàn)場離奇詭異苟鸯,居然都是意外死亡,警方通過查閱死者的電腦和手機棚点,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門早处,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘫析,你說我怎么就攤上這事砌梆。” “怎么了贬循?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵咸包,是天一觀的道長。 經(jīng)常有香客問我杖虾,道長烂瘫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任奇适,我火速辦了婚禮坟比,結果婚禮上,老公的妹妹穿的比我還像新娘嚷往。我一直安慰自己葛账,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布间影。 她就那樣靜靜地躺著注竿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魂贬。 梳的紋絲不亂的頭發(fā)上巩割,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音付燥,去河邊找鬼宣谈。 笑死,一個胖子當著我的面吹牛键科,可吹牛的內容都是我干的闻丑。 我是一名探鬼主播漩怎,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗦嗡!你這毒婦竟也來了勋锤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侥祭,失蹤者是張志新(化名)和其女友劉穎叁执,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矮冬,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡谈宛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了胎署。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吆录。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琼牧,靈堂內的尸體忽然破棺而出恢筝,到底是詐尸還是另有隱情,我是刑警寧澤巨坊,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布滋恬,位于F島的核電站,受9級特大地震影響抱究,放射性物質發(fā)生泄漏。R本人自食惡果不足惜带斑,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一鼓寺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勋磕,春花似錦妈候、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赶站,卻和暖如春幔虏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贝椿。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工想括, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烙博。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓瑟蜈,卻偏偏與公主長得像烟逊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铺根,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容