三、js中This之謎

當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(執(zhí)行上下文)。

這個(gè)記錄會(huì)包含函數(shù)在哪里被調(diào)用(調(diào)用棧)、函數(shù)的調(diào)用方法、傳入的參數(shù)>等信息。

this就是記錄的其中一個(gè)屬性,會(huì)在函數(shù)執(zhí)行的過程中用到。

this既不指向函數(shù)自身也不指向函數(shù)的作用域。

this實(shí)際上是在函數(shù)被調(diào)用時(shí)發(fā)生的綁定,它指向什么完全取決于函數(shù)在哪里被調(diào)用。
在嚴(yán)格模式下,一般函數(shù)調(diào)用的時(shí)候this指向undefined。

一、為什么要使用this

  • this提供了一種更優(yōu)雅的方式來隱式的“傳遞”一個(gè)對(duì)象的引用,因此可以將API設(shè)計(jì)的更加簡(jiǎn)潔并且易于復(fù)用。
    eg:
var me = {  
    name: "fenfei"  
};  
  
//不使用this,調(diào)用  
function speak(name){  
    console.log("Hello, I'm "+ name);  
}  
speak(me.name);     //Hello, I'm fenfei  
  
//使用this,調(diào)用  
function speak(){  
    console.log("Hello, I'm "+ this.name);  
}  
speak.call(me);     //Hello, I'm fenfei  

二、this存在的兩個(gè)誤解

(1)this指向函數(shù)自身;
(2)this指向函數(shù)的作用域。

作用域無法通過JavaScript代碼訪問,它存在于JavaScript引擎內(nèi)部。每當(dāng)把this和詞法作用域的查找混合使用時(shí),一定要提醒自己,這是無法實(shí)現(xiàn)的!

this是在運(yùn)行時(shí)進(jìn)行綁定的,并不是在編寫時(shí)綁定,它的上下文取決于函數(shù)調(diào)用時(shí)的各種條件。this的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用位置(也就是函數(shù)的調(diào)用方式)!

三、綁定規(guī)則

1)默認(rèn)綁定

  • 最常用的函數(shù)調(diào)用類型:獨(dú)立函數(shù)調(diào)用??梢园堰@條規(guī)則看作是無法應(yīng)用其他規(guī)則時(shí)的默認(rèn)規(guī)則。
    eg:
function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2

2)隱式規(guī)則

  • 隱式綁定的規(guī)則是調(diào)用位置是否有上下文對(duì)象,或者說是否被某個(gè)對(duì)象擁有或者包含。
function foo() {
  console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

。當(dāng)foo()被調(diào)用時(shí),this被綁定到obj,因此this.a和obj.a是一樣。

  • 但有時(shí)候會(huì)出現(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的一個(gè)引用,但是實(shí)際上,它引用的是foo函數(shù)本身。
因此此時(shí)的bar()其實(shí)是一個(gè)不帶任何修飾的函數(shù)調(diào)用,應(yīng)用了默認(rèn)綁定。
3)顯示綁定

  • call與apply
    。在JavaScript中 call與apply就像this的父母一般,讓this住哪它就得住哪,不得不聽話!當(dāng)無參數(shù)時(shí),當(dāng)前對(duì)象為window
    eg:
var name="全局";
var xpg={
    name:"局部"
};
function getName(){
    alert(this.name);
}
getName(xpg);//全局
getName.call(xpg);//局部
getName.call();//全局

。其中this身處函數(shù)getName中。無論this身處何處,一定要找到函數(shù)運(yùn)行時(shí)的位置。此時(shí)函數(shù)getName運(yùn)行時(shí)的位置,對(duì)于
(1)getName(xpg);//全局
顯然,函數(shù)getName所在的對(duì)象是window,因此this的安身之處定然在window,即指向window對(duì)象,則getName返回的this.name其實(shí)是window.name,因此alert出來的是“全局”!
(2)getName.call(xpg);//局部
。其中,call指定this的安身之處就是在xpg對(duì)象,因?yàn)閠his被迫只能在xpg那安家,則此時(shí)this指向xpg對(duì)象, this.name其實(shí)是xpg.name,因此alert出來的是“局部”!

  • bind()
    。bind方法是es5開始提供的,所以ie9+才支持
    eg:
function f(){  
   return this.a;  
} 

var g = f.bind({a : "test"});   
//想把某個(gè)對(duì)象作為this的時(shí)候,就把它傳進(jìn)去,得到一個(gè)新對(duì)象g
console.log(g()); // test      
 //重復(fù)調(diào)用的時(shí)候,this已經(jīng)指向bind參數(shù)。
 //這對(duì)于我們綁定一次需要重復(fù)調(diào)用依然實(shí)現(xiàn)綁定的話,會(huì)比apply和call更加高效(看下面這個(gè)例子)
var o = {a : 37, f : f, g : g};  
console.log(o.f(), o.g()); // 37, test   
//o.f()通過對(duì)象的屬性調(diào)用,this指向?qū)ο髈;
//比較特殊的是即使我們把新綁定的方法作為對(duì)象的屬性調(diào)用,
//o.g()依然會(huì)按之前的綁定去走,所以答案是test不是g

4)new綁定

  • new的this綁定不能被修改!
  • new調(diào)用函數(shù)會(huì)自動(dòng)執(zhí)行下面操作:
    (1)創(chuàng)建(或者說構(gòu)造)一個(gè)全新的對(duì)象;
    (2)這個(gè)新對(duì)象會(huì)被執(zhí)行[[原型]]連接;
    (3)這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this;
    (4)如果函數(shù)沒有返回其他對(duì)象,那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。
    eg:
function Person(name,age) {
  this.name = name
  this.age = age
  console.log("我也只不過是個(gè)普通函數(shù)")
}
Person("zxt",22) // "我也只不過是個(gè)普通函數(shù)"
console.log(name) // "zxt"
console.log(age) // 22
var zxt = new Person("zxt",22) // "我也只不過是個(gè)普通函數(shù)"
console.log(zxt.name) // "zxt"
console.log(zxt.age) // 22
  • 上面這個(gè)例子中,首先定義了一個(gè) Person 函數(shù),既可以普通調(diào)用,也可以以構(gòu)造函數(shù)的形式的調(diào)用。
    。當(dāng)普通調(diào)用時(shí),則按照正常的函數(shù)執(zhí)行,輸出一個(gè)字符串。
    。如果是通過一個(gè)new操作符,則構(gòu)造了一個(gè)新的對(duì)象。
    。普通調(diào)用時(shí),前面已經(jīng)介紹過,此時(shí)應(yīng)用默認(rèn)綁定規(guī)則,this綁定在了全局 對(duì)象上,此時(shí)全局對(duì)象上會(huì)分別增加 name 和 age 兩個(gè)屬性。
    。當(dāng)通過new操作符調(diào)用時(shí),函數(shù)會(huì)返回一個(gè)對(duì)象,從輸出結(jié)果上來看 this 對(duì)象綁定在了這個(gè)返回的對(duì)象上。
  • 因此,所謂的new綁定是指通過new操作符來調(diào)用函數(shù)時(shí),會(huì)產(chǎn)生一個(gè)新對(duì)象,并且會(huì)把構(gòu)造函數(shù)內(nèi)的this綁定到這個(gè)對(duì)象上。

四、優(yōu)先級(jí)

  • new綁定>call\apply等顯示綁定>隱式綁定>默認(rèn)綁定。

了解了函數(shù)調(diào)用中this綁定的四條規(guī)則,需要做的就是找到函數(shù)的調(diào)用位置并判斷對(duì)應(yīng)哪條規(guī)則。

(1)函數(shù)是否是new綁定?如果是,this綁定的是新創(chuàng)建的對(duì)象。
var bar = new Foo();
(2)函數(shù)是否通過call、apply顯示綁定或硬綁定?如果是,this綁定的是指定的對(duì)象。
var bar = foo.call(obj);
(3)函數(shù)是否在某個(gè)上下文對(duì)象中隱式調(diào)用?如果是,this綁定的是那個(gè)上下文對(duì)象。
var bar = obj.foo();
(4)上述全不是,則使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到undefined,否則綁定到全局window對(duì)象。
var bar = foo();

new綁定和call、apply無法一起使用,因此不能使用new foo.call(obj).

五、this綁定例外

1)被忽略的綁定

  • 如果你把null或者undefined作為this的綁定對(duì)象傳入call、apply或者bind。
    這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際應(yīng)用的是默認(rèn)綁定規(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
  • 賦值表達(dá)式p.foo = o.foo的返回值是目標(biāo)函數(shù)的引用,因此調(diào)用位置是foo()而不是p.foo()或者o.foo()。
    3)當(dāng)前對(duì)象不明確時(shí)的this
  • 當(dāng)沒有明確的執(zhí)行時(shí)的當(dāng)前對(duì)象時(shí),this指向全局對(duì)象window。
    例如對(duì)于全局變量引用的函數(shù)上我們有:
var name = "Tom";
var Bob = {
    name: "Bob",
    show: function(){
        alert(this.name);
    }
}
var show = Bob.show;
show();  //Tom

。你可能也能理解成show是window對(duì)象下的方法,所以執(zhí)行時(shí)的當(dāng)前對(duì)象時(shí)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í)行時(shí)的當(dāng)前對(duì)象是全局對(duì)象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))" />

  • 分析:對(duì)于dom元素的一個(gè)onclick(或其他如onblur等)屬性,它為所屬的html元素所擁有,直接在它觸發(fā)的函數(shù)里寫this,this應(yīng)該指向該html元素。

(2)給dom元素注冊(cè)js函數(shù)

  • a、不正確的方式
<script type="text/javascript">
  function thisTest(){
  alert(this.value); // 彈出undefined, this在這里指向??
}
</script>
<input id="btnTest" type="button" value="提交" onclick="thisTest()" />

。分析:onclick事件直接調(diào)用thisTest函數(shù),程序就會(huì)彈出undefined。
因?yàn)閠hisTest函數(shù)是在window對(duì)象中定義的, 所以thisTest的擁有者(作用域)是window,thisTest的this也是window。而window是沒有value屬性的,所以就報(bào)錯(cuò)了。

  • b、正確的方式
<input id="btnTest" type="button" value="提交" />
<script type="text/javascript">
  function thisTest(){
  alert(this.value); 
}
document.getElementById("btnTest").onclick=thisTest; 
//給button的onclick事件注冊(cè)一個(gè)函數(shù)
</script>

。分析:在前面的示例中,thisTest函數(shù)定義在全局作用域(這里就是window對(duì)象),所以this指代的是當(dāng)前的window對(duì)象。
而通過document.getElementById(“btnTest”).onclick=thisTest;這樣的形式,其實(shí)是將btnTest的onclick屬性設(shè)置為thisTest函數(shù)的一個(gè)副本,在btnTest的onclick屬性的函數(shù)作用域內(nèi),this歸btnTest所有,this也就指向了btnTest。

  • 因?yàn)槎鄠€(gè)不同的HTML元素雖然創(chuàng)建了不同的函數(shù)副本,但每個(gè)副本的擁有者都是相對(duì)應(yīng)的HTML元素,各自的this也都指向它們的擁有者,不會(huì)造成混亂。
    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); //第一個(gè)按鈕函數(shù)
var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二個(gè)按鈕函數(shù)
</script>

其彈出的結(jié)果是:

//第一個(gè)按鈕
function onclick(){
  thisTest()
}
//第二個(gè)按鈕
function thisTest(){
  this.value="提交中";
}

7)this詞法(ES6:箭頭函數(shù))

  • 箭頭函數(shù)不使用function關(guān)鍵字定義,而是使用“胖箭頭”的操作符=>定義;箭頭函數(shù)不使用this的四種標(biāo)準(zhǔn)規(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()內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí)foo()的this。由于foo()的this被綁定到obj1,bar(引用箭頭函數(shù))的this也被綁定到obj1,而箭頭函數(shù)的綁定無法修改。(new的也不能?。?/strong>

箭頭函數(shù)和new的this綁定不能被修改!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容