this 指向問(wèn)題

日期:2019 年 9 月 5 日

this 指向問(wèn)題

介紹

this 指向問(wèn)題一直是 js 中一個(gè)令人頭疼的問(wèn)題,這幾天得空,復(fù)習(xí)了一下以前的知識(shí),順便整理了有關(guān) js 中 this 指向的知識(shí)點(diǎn)。

this 綁定

很多人對(duì) this 指向一直都存在一個(gè)誤區(qū):this 寫(xiě)在誰(shuí)里面就指向誰(shuí)。但其實(shí)這是不對(duì)的,this 既不指向函數(shù)自身,也不指函數(shù)的詞法作用域,它實(shí)際是在函數(shù)被調(diào)用時(shí)才發(fā)生的綁定,也就是說(shuō)this具體指向什么,取決于你是怎么調(diào)用的函數(shù)。


this 存在4種綁定規(guī)則:

  • 默認(rèn)綁定
  • 隱式綁定
  • 顯示綁定
  • new綁定

這 4 種綁定規(guī)則的優(yōu)先級(jí)是從低到高的

1、默認(rèn)綁定
function foo(){
        console.log(this.a);
}
var a = 2;
foo();  // 打印結(jié)果?

打印結(jié)果:2

因?yàn)閒oo()是直接調(diào)用的(獨(dú)立函數(shù)調(diào)用),沒(méi)有應(yīng)用其他的綁定規(guī)則,這里進(jìn)行了默認(rèn)綁定,將全局對(duì)象( window )綁定this上,所以this.a 就解析成了全局變量中的a,即 2


注意:在嚴(yán)格模式下(strict mode),全局對(duì)象將無(wú)法使用默認(rèn)綁定( this 指向 undefined ),即執(zhí)行會(huì)報(bào) undefined 的錯(cuò)誤:

function foo(){
        “use strict”;
        console.log(this.a);
}
var a = 2;
foo();  //  Uncaught  TypeError : Cannot read property 'a' of undefined

另外,這幾天正在學(xué) Typescript,剛好也遇到了一些 this 指向的問(wèn)題,這里也說(shuō)道說(shuō)道,看一看下面的幾種情況:

例一:

let a = 233;
let obj1 = {
    a: 666,
    fn: function(){
        console.log(this.a);
    }
}

obj1.fn();    //  輸出: 666

例二:

let a = 233;
let obj2 = {
    a: 888,
    fn: function(){
        console.log("外層:", this.a);
        return function(){
            console.log("內(nèi)層:", this.a);
        }
    }
}

let ha = obj2.fn();  //  外層: 888
ha();    //  內(nèi)層: undefined

例三:

var a = 233;
let obj2 = {
    a: 888,
    fn: function(){
        console.log("外層:", this.a);
        return function(){
            console.log("內(nèi)層:", this.a);
        }
    }
}

let ha = obj2.fn();  //  外層: 888
ha();    //  內(nèi)層: 233

比較之下我們可以看到,對(duì)象的 fn 屬性是一個(gè)函數(shù),對(duì)象調(diào)用這個(gè)函數(shù),函數(shù)的執(zhí)行上下文就是這個(gè)對(duì)象,所以外層的 this 指向?qū)ο鬀](méi)有問(wèn)題;

然后 fn 還返回了一個(gè)函數(shù),這個(gè)函數(shù)里面的 this (內(nèi)層 this)指向的是 window 而不是對(duì)象 obj2;

有人就要問(wèn)了既然指向 window,為什么例二中內(nèi)層的輸出不是 233 呢?這個(gè)問(wèn)題問(wèn)得好,這就涉及到了 ES6 與 ES5 中變量聲明方面的區(qū)別了:

  • ES5聲明變量只有兩種方式:var 和 function
  • ES6有 let、const、import、class 再加上 ES5 的 var、function 共有六種聲明變量的方式
  • 還需要了解頂層對(duì)象:瀏覽器環(huán)境中頂層對(duì)象是window,Node中是global對(duì)象
  • ES5中,頂層對(duì)象的屬性等價(jià)于全局變量 (敲黑板了啊)
  • ES6中,有所改變:var、function聲明的全局變量,依然是頂層對(duì)象的屬性;let、const、class聲明的全局變量不屬于頂層對(duì)象的屬性,也就是說(shuō)ES6開(kāi)始,全局變量和頂層對(duì)象的屬性開(kāi)始分離、脫鉤

所以ES6非嚴(yán)格模式下,與var聲明的全局變量都會(huì)成為window的屬性:

a = 1;
console.log(window.a);  // 1
var b = 2;
console.log(window.b);  // 2

而使用let聲明的全局變量,不會(huì)成為window的屬性:

let c = 3;
console.log(window.c);  // undefined

關(guān)于這方面問(wèn)題,更多請(qǐng)移步原文:

關(guān)于let聲明的變量在window下無(wú)法獲取的問(wèn)題

2、隱式綁定

除了直接對(duì)函數(shù)進(jìn)行調(diào)用外,有些情況是,函數(shù)的調(diào)用是在某個(gè)對(duì)象上觸發(fā)的,即調(diào)用位置上存在上下文對(duì)象

function foo(){
        console.log(this.a);
}
var a = 2;
var obj = {
        a : 3,
        foo : foo
}
obj.foo();    // ?

輸出結(jié)果: 3

這里foo函數(shù)被當(dāng)做引用屬性,被添加到obj對(duì)象上。這里的調(diào)用過(guò)程是這樣的:

獲取obj.foo屬性 -> 根據(jù)引用關(guān)系找到foo函數(shù),執(zhí)行調(diào)用,所以這里對(duì)foo的調(diào)用存在上下文對(duì)象obj,this進(jìn)行了隱式綁定,即this綁定到了obj上,所以this.a被解析成了obj.a,即 3

當(dāng)存在多層調(diào)用鏈時(shí):

function foo(){
        console.log(this.a);
}
var a = 2;
var obj1 = {
        a : 4,
        foo : foo
}
var obj2 = {
        a : 3,
        obj1 : obj1
}
obj2.obj1.foo();    // ?

輸出結(jié)果:4

同樣,我們看下函數(shù)的調(diào)用過(guò)程:

先獲取obj2.obj1 -> 通過(guò)引用獲取到obj1對(duì)象,再訪問(wèn) obj1.foo -> 最后執(zhí)行foo函數(shù)調(diào)用
這里調(diào)用鏈不只一層,存在obj1、obj2兩個(gè)對(duì)象,那么隱式綁定具體會(huì)綁哪個(gè)對(duì)象。這里原則是獲取最后一層調(diào)用的上下文對(duì)象,即obj1,所以結(jié)果顯然是 4

3、顯示綁定

對(duì)于隱式綁定,this值在調(diào)用過(guò)程中會(huì)動(dòng)態(tài)變化,可是我們就想綁定指定的對(duì)象,這時(shí)就用到了顯示綁定

顯示綁定主要是通過(guò)改變對(duì)象的prototype關(guān)聯(lián)對(duì)象,這里不展開(kāi)講。具體使用上,可以通過(guò)這兩個(gè)方法 call() 或 apply() 來(lái)實(shí)現(xiàn)(大多數(shù)函數(shù)及自己創(chuàng)建的函數(shù)默認(rèn)都提供這兩個(gè)方法)

call 與apply 是同樣的作用,區(qū)別只是其他參數(shù)的設(shè)置上:

/*apply()方法*/
function.apply(thisObj[, argArray])

/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);

它們各自的定義:

apply:調(diào)用一個(gè)對(duì)象的一個(gè)方法,用另一個(gè)對(duì)象替換當(dāng)前對(duì)象。例如:B.apply(A, arguments);即A對(duì)象應(yīng)用B對(duì)象的方法。

call:調(diào)用一個(gè)對(duì)象的一個(gè)方法,用另一個(gè)對(duì)象替換當(dāng)前對(duì)象。例如:B.call(A, args1,args2);即A對(duì)象調(diào)用B對(duì)象的方法。

它們的共同之處:

都“可以用來(lái)代替另一個(gè)對(duì)象調(diào)用一個(gè)方法,將一個(gè)函數(shù)的對(duì)象上下文從初始的上下文改變?yōu)橛蓆hisObj指定的新對(duì)象”。

它們的不同之處:

apply:最多只能有兩個(gè)參數(shù)——新this對(duì)象和一個(gè)數(shù)組argArray。如果給該方法傳遞多個(gè)參數(shù),則把參數(shù)都寫(xiě)進(jìn)這個(gè)數(shù)組里面,當(dāng)然,即使只有一個(gè)參數(shù),也要寫(xiě)進(jìn)數(shù)組里。如果argArray不是一個(gè)有效的數(shù)組或arguments對(duì)象,那么將導(dǎo)致一個(gè)TypeError。如果沒(méi)有提供argArray和thisObj任何一個(gè)參數(shù),那么Global對(duì)象將被用作thisObj,并且無(wú)法被傳遞任何參數(shù)。

call:它可以接受多個(gè)參數(shù),第一個(gè)參數(shù)與apply一樣,后面則是一串參數(shù)列表。這個(gè)方法主要用在js對(duì)象各方法相互調(diào)用的時(shí)候,使當(dāng)前this實(shí)例指針保持一致,或者在特殊情況下需要改變this指針。如果沒(méi)有提供thisObj參數(shù),那么 Global 對(duì)象被用作thisObj。 實(shí)際上,apply和call的功能是一樣的,只是傳入的參數(shù)列表形式不同。

有關(guān) apply 與 call 的比較,詳情請(qǐng)移步原文:

apply() 與 call() 的區(qū)別



OK,回歸正題,我們來(lái)看看顯示綁定:

function foo(){
        console.log(this.a);
}
var a = 2;
var obj1 = {
        a : 3,
        foo : foo
}
var obj2 = {
        a : 4,
        obj1 : obj1
}
foo.call(obj1);    // ?
foo.call(obj2);   // ?

結(jié)果:

3

4

這里因?yàn)轱@示的申明了要綁定的對(duì)象,所以this就被綁定到了 obj 上,打印的結(jié)果自然就是obj1.a 和 obj2.a

4、new 綁定
var a = 2;
function foo(a){
    this.a = a;
}
let bar1 = new foo(3);
console.log(bar1.a); // ?
let bar2 = new foo(4);
console.log(bar2.a); // ?

結(jié)果:

3

4

因?yàn)槊看握{(diào)用生成的是全新的對(duì)象,該對(duì)象又會(huì)自動(dòng)綁定到this上,所以答案顯而易見(jiàn)

綁定規(guī)則優(yōu)先級(jí)

上面也說(shuō)過(guò),這里在重復(fù)一下。優(yōu)先級(jí)是這樣的,以按照下面的順序來(lái)進(jìn)行判斷:

函數(shù)是否在new中調(diào)用(new綁定)?如果是的話this綁定的是新創(chuàng)建的對(duì)象;
函數(shù)是否通過(guò)call、apply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this綁定的是 指定的對(duì)象;
函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)?如果是的話,this綁定的是那個(gè)上下文對(duì)象;
如果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定undefined,否則綁定到全局對(duì)象。


規(guī)則例外:

在顯示綁定中,對(duì)于 null 和 undefined 的綁定將不會(huì)生效

function foo(){
        console.log(this.a);
}
var a = 2;
foo.call(null);  //  2
foo.call(undefined);  //  2

這種情況主要是用在不關(guān)心this的具體綁定對(duì)象(用來(lái)忽略this),而傳入null實(shí)際上會(huì)進(jìn)行默認(rèn)綁定,導(dǎo)致函數(shù)中可能會(huì)使用到全局變量,與預(yù)期不符

所以對(duì)于要忽略this的情況,可以傳入一個(gè)空對(duì)象?,該對(duì)象通過(guò)Object.create(null)創(chuàng)建。這里不用{}的原因是,?是真正意義上的空對(duì)象,它不創(chuàng)建Object.prototype委托,{}和普通對(duì)象一樣,有原型鏈委托關(guān)系

箭頭函數(shù)中的 this

箭頭函數(shù)的this指向:

  1. 箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this;
  2. 箭頭函數(shù)里的this指向無(wú)法被call,apply,bind 改變;
var obj = {
        foo(){
            console.log(this);
        },
        bar : ()=>{
            console.log(this);
        }
}
obj.foo();  //  { foo: foo(), bar: bar() }
obj.bar();  //  window

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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