" />

JavaScript必須掌握的基礎(chǔ) ---> this

2020-5-30    seo達(dá)人

this

this是我們?cè)跁?shū)寫(xiě)代碼時(shí)最常用的關(guān)鍵詞之一,即使如此,它也是JavaScript最容易被最頭疼的關(guān)鍵詞。那么this到底是什么呢?


如果你了解執(zhí)行上下文,那么你就會(huì)知道,其實(shí)this是執(zhí)行上下文對(duì)象的一個(gè)屬性:


executionContext = {

   scopeChain:[ ... ],

   VO:{

       ...

   },

   this:  ?

}

執(zhí)行上下文中有三個(gè)重要的屬性,作用域鏈(scopeChain)、變量對(duì)象(VO)和this。


this是在進(jìn)入執(zhí)行上下文時(shí)確定的,也就是在函數(shù)執(zhí)行時(shí)才確定,并且在運(yùn)行期間不允許修改并且是永久不變的


在全局代碼中的this

在全局代碼中this 是不變的,this始終是全局對(duì)象本身。


var a = 10;

this.b = 20;

window.c = 30;


console.log(this.a);

console.log(b);

console.log(this.c);


console.log(this === window) // true

// 由于this就是全局對(duì)象window,所以上述 a ,b ,c 都相當(dāng)于在全局對(duì)象上添加相應(yīng)的屬性

如果我們?cè)诖a運(yùn)行期嘗試修改this的值,就會(huì)拋出錯(cuò)誤:


this = { a : 1 } ; // Uncaught SyntaxError: Invalid left-hand side in assignment

console.log(this === window) // true

函數(shù)代碼中的this

在函數(shù)代碼中使用this,才是令我們最容易困惑的,這里我們主要是對(duì)函數(shù)代碼中的this進(jìn)行分析。


我們?cè)谏厦嬲f(shuō)過(guò)this的值是,進(jìn)入當(dāng)前執(zhí)行上下文時(shí)確定的,也就是在函數(shù)執(zhí)行時(shí)并且是執(zhí)行前確定的。但是同一個(gè)函數(shù),作用域中的this指向可能完全不同,但是不管怎樣,函數(shù)在運(yùn)行時(shí)的this的指向是不變的,而且不能被賦值。


function foo() {

   console.log(this);

}


foo();  // window

var obj={

   a: 1,

   bar: foo,

}

obj.bar(); // obj

函數(shù)中this的指向豐富的多,它可以是全局對(duì)象、當(dāng)前對(duì)象、或者是任意對(duì)象,當(dāng)然這取決于函數(shù)的調(diào)用方式。在JavaScript中函數(shù)的調(diào)用方式有一下幾種方式:作為函數(shù)調(diào)用、作為對(duì)象屬性調(diào)用、作為構(gòu)造函數(shù)調(diào)用、使用apply或call調(diào)用。下面我們將按照這幾種調(diào)用方式一一討論this的含義。


作為函數(shù)調(diào)用

什么是作為函數(shù)調(diào)用:就是獨(dú)立的函數(shù)調(diào)用,不加任何修飾符。


function foo(){

   console.log(this === window); // true

   this.a = 1;

   console.log(b); // 2

}

var b = 2;

foo();

console.log(a); // 1

上述代碼中this綁定到了全局對(duì)象window。this.a相當(dāng)于在全局對(duì)象上添加一個(gè)屬性 a 。


在嚴(yán)格模式下,獨(dú)立函數(shù)調(diào)用,this的綁定不再是window,而是undefined。


function foo() {

   "use strict";

   console.log(this===window); // false

   console.log(this===undefined); // true

}

foo();

這里要注意,如果函數(shù)調(diào)用在嚴(yán)格模式下,而內(nèi)部代碼執(zhí)行在非嚴(yán)格模式下,this 還是會(huì)默認(rèn)綁定為 window。


function foo() {

   console.log(this===window); // true

}



(function() {

   "use strict";

   foo();

})()

對(duì)于在函數(shù)內(nèi)部的函數(shù)獨(dú)立調(diào)用 this 又指向了誰(shuí)呢?


function foo() {

   function bar() {

       this.a=1;

       console.log(this===window); // true

   }

   bar()

}

foo();

console.log(a); // 1

上述代碼中,在函數(shù)內(nèi)部的函數(shù)獨(dú)立調(diào)用,此時(shí)this還是被綁定到了window。


總結(jié):當(dāng)函數(shù)作為獨(dú)立函數(shù)被調(diào)用時(shí),內(nèi)部this被默認(rèn)綁定為(指向)全局對(duì)象window,但是在嚴(yán)格模式下會(huì)有區(qū)別,在嚴(yán)格模式下this被綁定為undefined。


作為對(duì)象屬性調(diào)用

var a=1;

var obj={

   a: 2,

   foo: function() {

       console.log(this===obj); // true

       console.log(this.a); // 2

   }

}

obj.foo();

上述代碼中 foo屬性的值為一個(gè)函數(shù)。這里稱 foo 為 對(duì)象obj 的方法。foo的調(diào)用方式為 對(duì)象 . 方法 調(diào)用。此時(shí) this 被綁定到當(dāng)前調(diào)用方法的對(duì)象。在這里為 obj 對(duì)象。


再看一個(gè)例子:


var a=1;

var obj={

   a: 2,

   bar: {

       a: 3,

       foo: function() {

           console.log(this===bar); // true

           console.log(this.a); // 3

       }

   }

}

obj.bar.foo();

遵循上面說(shuō)的規(guī)則 對(duì)象 . 屬性 。這里的對(duì)象為 obj.bar 。此時(shí) foo 內(nèi)部this被綁定到了 obj.bar 。 因此 this.a 即為 obj.bar.a 。


再來(lái)看一個(gè)例子:


var a=1;

var obj={

   a: 2,

   foo: function() {

       console.log(this===obj); // false

       console.log(this===window); // true

       console.log(this.a); // 1

   }

}


var baz=obj.foo;

baz();

這里 foo 函數(shù)雖然作為對(duì)象obj 的方法。但是它被賦值給變量 baz 。當(dāng)baz調(diào)用時(shí),相當(dāng)于 foo 函數(shù)獨(dú)立調(diào)用,因此內(nèi)部 this被綁定到 window。


使用apply或call調(diào)用

apply和call為函數(shù)原型上的方法。它可以更改函數(shù)內(nèi)部this的指向。


var a=1;

function foo() {

   console.log(this.a);

}

var obj1={

   a: 2

}

var obj2={

   a: 3

}

var obj3={

   a: 4

}

var bar=foo.bind(obj1);

bar();// 2  this => obj1

foo(); // 1  this => window

foo.call(obj2); // 3  this => obj2

foo.call(obj3); // 4  this => obj3

當(dāng)函數(shù)foo 作為獨(dú)立函數(shù)調(diào)用時(shí),this被綁定到了全局對(duì)象window,當(dāng)使用bind、call或者apply方法調(diào)用時(shí),this 被分別綁定到了不同的對(duì)象。


作為構(gòu)造函數(shù)調(diào)用

var a=1;

function Person() {

   this.a=2;  // this => p;

}

var p=new Person();

console.log(p.a); // 2

上述代碼中,構(gòu)造函數(shù) Person 內(nèi)部的 this 被綁定為 Person的一個(gè)實(shí)例。


總結(jié):


當(dāng)我們要判斷當(dāng)前函數(shù)內(nèi)部的this綁定,可以依照下面的原則:


函數(shù)是否在是通過(guò) new 操作符調(diào)用?如果是,this 綁定為新創(chuàng)建的對(duì)象

var bar = new foo();     // this => bar;

函數(shù)是否通過(guò)call或者apply調(diào)用?如果是,this 綁定為指定的對(duì)象

foo.call(obj1);  // this => obj1;

foo.apply(obj2);  // this => obj2;

函數(shù)是否通過(guò) 對(duì)象 . 方法調(diào)用?如果是,this 綁定為當(dāng)前對(duì)象

obj.foo(); // this => obj;

函數(shù)是否獨(dú)立調(diào)用?如果是,this 綁定為全局對(duì)象。

foo(); // this => window

DOM事件處理函數(shù)中的this

1). 事件綁定


<button id="btn">點(diǎn)擊我</button>


// 事件綁定


function handleClick(e) {

   console.log(this); // <button id="btn">點(diǎn)擊我</button>

}

       document.getElementById('btn').addEventListener('click',handleClick,false);  //   <button id="btn">點(diǎn)擊我</button>

       

document.getElementById('btn').onclick= handleClick; //  <button id="btn">點(diǎn)擊我</button>

根據(jù)上述代碼我們可以得出:當(dāng)通過(guò)事件綁定來(lái)給DOM元素添加事件,事件將被綁定為當(dāng)前DOM對(duì)象。


2).內(nèi)聯(lián)事件


<button onclick="handleClick()" id="btn1">點(diǎn)擊我</button>

<button onclick="console.log(this)" id="btn2">點(diǎn)擊我</button>


function handleClick(e) {

   console.log(this); // window

}


//第二個(gè) button 打印的是   <button id="btn">點(diǎn)擊我</button>

我認(rèn)為內(nèi)聯(lián)事件可以這樣理解:


//偽代碼


<button onclick=function(){  handleClick() } id="btn1">點(diǎn)擊我</button>

<button onclick=function() { console.log(this) } id="btn2">點(diǎn)擊我</button>

這樣我們就能理解上述代碼中為什么內(nèi)聯(lián)事件一個(gè)指向window,一個(gè)指向當(dāng)前DOM元素。(當(dāng)然瀏覽器處理內(nèi)聯(lián)事件時(shí)并不是這樣的)


定時(shí)器中的this

定時(shí)器中的 this 指向哪里呢?


function foo() {

   setTimeout(function() {

       console.log(this); // window

   },1000)

}

foo();  

再來(lái)看一個(gè)例子


var name="chen";

var obj={

   name: "erdong",

   foo: function() {

       console.log(this.name); // erdong

       setTimeout(function() {

           console.log(this.name); // chen

       },1000)

   }

}

obj.foo();

到這里我們可以看到,函數(shù) foo 內(nèi)部this指向?yàn)檎{(diào)用它的對(duì)象,即:obj 。定時(shí)器中的this指向?yàn)?window。那么有什么辦法讓定時(shí)器中的this跟包裹它的函數(shù)綁定為同一個(gè)對(duì)象呢?


1). 利用閉包:


var name="chen";

var obj={

   name: "erdong",

   foo: function() {

       console.log(this.name) // erdong

       var that=this;

       setTimeout(function() {

           // that => obj

           console.log(that.name); // erdong

       },1000)

   }

}

obj.foo();

利用閉包的特性,函數(shù)內(nèi)部的函數(shù)可以訪問(wèn)含義訪問(wèn)當(dāng)前詞法作用域中的變量,此時(shí)定時(shí)器中的 that 即為包裹它的函數(shù)中的 this 綁定的對(duì)象。在下面我們會(huì)介紹利用 ES6的箭頭函數(shù)實(shí)現(xiàn)這一功能。


當(dāng)然這里也可以適用bind來(lái)實(shí)現(xiàn):


var name="chen";

var obj={

   name: "erdong",

   foo: function() {

       console.log(this.name); // erdong

       setTimeout(function() {

           // this => obj

           console.log(this.name); // erdong

       }.bind(this),1000)

   }

}

obj.foo();

被忽略的this

如果你把 null 或者 undefined 作為 this 的綁定對(duì)象傳入 call 、apply或者bind,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)例 this 被綁定為對(duì)應(yīng)上述規(guī)則。


var a=1;

function foo() {

   console.log(this.a); // 1  this => window

}

var obj={

   a: 2

}

foo.call(null);

var a=1;

function foo() {

   console.log(this.a); // 1  this => window

}

var obj={

   a: 2

}

foo.apply(null);

var a=1;

function foo() {

   console.log(this.a); // 1  this => window

}

var obj={

   a: 2

}

var bar = foo.bind(null);

bar();

bind 也可以實(shí)現(xiàn)函數(shù)柯里化:


function foo(a,b) {

   console.log(a,b); // 2  3

}

var bar=foo.bind(null,2);

bar(3);

更復(fù)雜的例子:


var foo={

   bar: function() {

       console.log(this);

   }

};


foo.bar(); // foo

(foo.bar)(); // foo


(foo.bar=foo.bar)(); // window

(false||foo.bar)();  // window

(foo.bar,foo.bar)();  // window

上述代碼中:


foo.bar()為對(duì)象的方法調(diào)用,因此 this 綁定為 foo 對(duì)象。


(foo.bar)() 前一個(gè)() 中的內(nèi)容不計(jì)算,因此還是 foo.bar()


(foo.bar=foo.bar)() 前一個(gè) () 中的內(nèi)容計(jì)算后為 function() { console.log(this); } 所以這里為匿名函數(shù)自執(zhí)行,因此 this 綁定為 全局對(duì)象 window


后面兩個(gè)實(shí)例同上。


這樣理解會(huì)比較好:


(foo.bar=foo.bar)  括號(hào)中的表達(dá)式執(zhí)行為 先計(jì)算,再賦值,再返回值。

(false||foo.bar)()    括號(hào)中的表達(dá)式執(zhí)行為 判斷前者是否為 true ,若為true,不計(jì)算后者,若為false,計(jì)算后者并返回后者的值。

(foo.bar,foo.bar)   括號(hào)中的表達(dá)式之行為分別計(jì)算 “,” 操作符兩邊,然后返回  “,” 操作符后面的值。

箭頭函數(shù)中的this

箭頭函數(shù)時(shí)ES6新增的語(yǔ)法。


有兩個(gè)作用:


更簡(jiǎn)潔的函數(shù)

本身不綁定this

代碼格式為:


// 普通函數(shù)

function foo(a){

   // ......

}

//箭頭函數(shù)

var foo = a => {

   // ......

}


//如果沒(méi)有參數(shù)或者參數(shù)為多個(gè)


var foo = (a,b,c,d) => {

   // ......

}

我們?cè)谑褂闷胀ê瘮?shù)之前對(duì)于函數(shù)的this綁定,需要根據(jù)這個(gè)函數(shù)如何被調(diào)用來(lái)確定其內(nèi)部this的綁定對(duì)象。而且常常因?yàn)檎{(diào)用鏈的數(shù)量或者是找不到其真正的調(diào)用者對(duì) this 的指向模糊不清。在箭頭函數(shù)出現(xiàn)后其內(nèi)部的 this 指向不需要再依靠調(diào)用的方式來(lái)確定。


箭頭函數(shù)有幾個(gè)特點(diǎn)(與普通函數(shù)的區(qū)別)


箭頭函數(shù)不綁定 this 。它只會(huì)從作用域鏈的上一層繼承 this。

箭頭函數(shù)不綁定arguments,使用reset參數(shù)來(lái)獲取實(shí)參的數(shù)量。

箭頭函數(shù)是匿名函數(shù),不能作為構(gòu)造函數(shù)。

箭頭函數(shù)沒(méi)有prototype屬性。

不能使用 yield 關(guān)鍵字,因此箭頭函數(shù)不能作為函數(shù)生成器。

這里我們只討論箭頭函數(shù)中的this綁定。


用一個(gè)例子來(lái)對(duì)比普通函數(shù)與箭頭函數(shù)中的this綁定:


var obj={

   foo: function() {

       console.log(this); // obj

   },

   bar: () => {

       console.log(this); // window

   }

}

obj.foo();

obj.bar();

上述代碼中,同樣是通過(guò)對(duì)象 . 方法調(diào)用一個(gè)函數(shù),但是函數(shù)內(nèi)部this綁定確是不同,只因一個(gè)數(shù)普通函數(shù)一個(gè)是箭頭函數(shù)。


用一句話來(lái)總結(jié)箭頭函數(shù)中的this綁定:


個(gè)人上面說(shuō)的它會(huì)從作用域鏈的上一層繼承 this ,說(shuō)法并不是很正確。作用域中存放的是這個(gè)函數(shù)當(dāng)前執(zhí)行上下文與所有父級(jí)執(zhí)行上下文的變量對(duì)象的集合。因此在作用域鏈中并不存在 this 。應(yīng)該說(shuō)是作用域鏈上一層對(duì)應(yīng)的執(zhí)行上下文中繼承 this 。


箭頭函數(shù)中的this繼承于作用域鏈上一層對(duì)應(yīng)的執(zhí)行上下文中的this


var obj={

   foo: function() {

       console.log(this); // obj

   },

   bar: () => {

       console.log(this); // window

   }

}

obj.bar();

上述代碼中obj.bar執(zhí)行時(shí)的作用域鏈為:


scopeChain = [

   obj.bar.AO,

   global.VO

]

根據(jù)上面的規(guī)則,此時(shí)bar函數(shù)中的this指向?yàn)槿謭?zhí)行上下文中的this,即:window。


再來(lái)看一個(gè)例子:


var obj={

   foo: function() {

       console.log(this); // obj

       var bar=() => {

           console.log(this); // obj

       }

       bar();

   }

}

obj.foo();

在普通函數(shù)中,bar 執(zhí)行時(shí)內(nèi)部this被綁定為全局對(duì)象,因?yàn)樗亲鳛楠?dú)立函數(shù)調(diào)用。但是在箭頭函數(shù)中呢,它卻綁定為 obj 。跟父級(jí)函數(shù)中的 this 綁定為同一對(duì)象。


此時(shí)它的作用域鏈為:


scopeChain = [

    bar.AO,

    obj.foo.AO,

    global.VO

]

這個(gè)時(shí)候我們就差不多知道了箭頭函數(shù)中的this綁定。


繼續(xù)看例子:


var obj={

   foo: () => {

       console.log(this); // window

       var bar=() => {

           console.log(this); // window

       }

       bar();

   }

}

obj.foo();

這個(gè)時(shí)候怎么又指向了window了呢?


我們還看當(dāng) bar 執(zhí)行時(shí)的作用域鏈:


scopeChain = [

    bar.AO,

    obj.foo.AO,

    global.VO

]

當(dāng)我們找bar函數(shù)中的this綁定時(shí),就會(huì)去找foo函數(shù)中的this綁定。因?yàn)樗抢^承于它的。這時(shí) foo 函數(shù)也是箭頭函數(shù),此時(shí)foo中的this綁定為window而不是調(diào)用它的obj對(duì)象。因此 bar函數(shù)中的this綁定也為全局對(duì)象window。


我們?cè)诨仡^看上面關(guān)于定時(shí)器中的this的例子:


var name="chen";

var obj={

   name: "erdong",

   foo: function() {

       console.log(this.name); // erdong

       setTimeout(function() {

           console.log(this); // chen

       },1000)

   }

}

obj.foo();

這時(shí)我們就可以很簡(jiǎn)單的讓定時(shí)器中的this與foo中的this綁定為同一對(duì)象:


var name="chen";

var obj={

   name: "erdong",

   foo: function() {

       // this => obj

       console.log(this.name); // erdong

       setTimeout(() =>  {

           // this => foo中的this => obj

           console.log(this.name); // erdong

       },1000)

   }

}

obj.foo();

分享本文至:

日歷

鏈接

個(gè)人資料

存檔