[javascript] this 的綁定機(jī)制

為什么要學(xué)習(xí) this?

  1. 閱讀源碼需要;很多 js 庫里面都有關(guān)于 this 以及 this 綁定的用法,所以我們需要深入學(xué)習(xí)了解 this 幫助我們高效閱讀源碼。
  2. 提高編程能力;this 提供了一種更優(yōu)雅的方式來隱式“傳遞”一個(gè)對(duì)象的引用,我們可以使用 this 編寫出更加高級(jí)優(yōu)雅的代碼。

關(guān)于 this 的誤解?

  1. 指向自身
    從字面意思上理解確實(shí)是說得通的。在我剛剛接觸 javascript this 的時(shí)候,我也以為它是指向于自身,這個(gè)主要是受到以前 python 編程經(jīng)驗(yàn)的影響,覺得 this 指向?qū)嵗旧?。事?shí)上,javascript 通過 new 實(shí)例化操作后的實(shí)例也確實(shí)指向于實(shí)例本身,不過 javascript 里面的 this 的情況更為多樣和多變。

  2. 指向作用域
    this 指向作用域,有些情況下是正確的,但是在其他情況下卻是錯(cuò)誤的。

function foo() {
  const a = 2;
  this.bar();
}

function bar() {
  console.log(this.a);
}

foo();

打印出來的結(jié)果是 undefined,這個(gè)我們想象的不一樣;我們以為應(yīng)該是 2。所以 this 并不是指向作用域的。

this 到底是什么?

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

當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(也稱為執(zhí)行上下文)。這個(gè)記錄包含函數(shù)在哪里被調(diào)用(調(diào)用棧),函數(shù)調(diào)用方法以及傳入?yún)?shù)等信息。this 就是其中的一個(gè)屬性,會(huì)在函數(shù)執(zhí)行的過程中用到。

調(diào)用位置

在正式開始 this 學(xué)習(xí)之前,我們先通過下面這個(gè)例子學(xué)習(xí)下調(diào)用位置。

function baz() {
  // 當(dāng)前調(diào)用棧是:baz,因此,當(dāng)前調(diào)用位置是全局作用域
  console.log("baz");
  bar();
}

function bar() {
  // 當(dāng)前調(diào)用棧是 baz -> bar,因此,當(dāng)前調(diào)用位置在 baz 中
  console.log("bar");
  foo();
}

function foo() {
  // 當(dāng)前調(diào)用棧是 baz -> bar -> foo,因此,當(dāng)前調(diào)用位置在 bar 中
  console.log("foo");
}

baz();

this 的綁定規(guī)則

  1. 默認(rèn)綁定(獨(dú)立函數(shù)調(diào)用)
    可以把這條規(guī)則看做是無法應(yīng)用其他規(guī)則的默認(rèn)規(guī)則。
    如上面的調(diào)用位置的例子,如果我們?cè)诿總€(gè)函數(shù)里面打印 this,我們會(huì)發(fā)現(xiàn) this 都執(zhí)行 Window 對(duì)象。(嚴(yán)格模式下 thisundefined
function baz() {
  console.log(this, "baz");
  bar();
}

function bar() {
  console.log(this, "bar");
  foo();
}

function foo() {
  console.log(this, "foo");
}

baz();
  1. 隱式調(diào)用(調(diào)用位置是否有上下文對(duì)象,或者說是否被某個(gè)對(duì)象擁有或包圍,不過這種說法可能會(huì)造成一些誤導(dǎo))
function foo() {
  console.log(this.a);
}

const obj = {
  a: 2,
  foo: foo
};

obj.foo(); // 2

對(duì)象屬性引用鏈只有最頂層或者最后一層會(huì)影響調(diào)用位置。

function foo() {
  console.log(this.a);
}

const obj2 = {
  a: 42,
  foo: foo
};

const obj1 = {
  a: 2,
  obj2: obj2
};

obj1.obj2.foo(); // 42
隱式丟失
function foo() {
  console.log(this.a);
}

const obj = {
  a: 2,
  foo: foo
};

const bar = obj.foo;

const a = 'test';

bar(); // "test";

雖然 barobj.foo 的一個(gè)引用,但實(shí)際上,它引用的是 foo 函數(shù)本身,因此此時(shí) bar() 其實(shí)是一個(gè)不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定。

function foo() {
  console.log(this.a);
}

function doFoo(fn) {
  // 這里存在一個(gè)隱性賦值,fn = obj.foo
  fn();
}

const obj = {
  a: 2,
  foo: foo
};

const a = "test";

doFoo(obj.foo); // "test"

參數(shù)傳遞其實(shí)就是一種隱式賦值,這是我們需要注意的,這樣的問題開發(fā)中如果不太清楚,很容易迷惑。

  1. 顯示綁定
    直接指定 this 的綁定對(duì)象,有三種方法:bind,call,apply
function foo() {
  console.log(this.a);
}

const obj = {
  a: 2
};

foo.call(obj); // 2

通過 foo.call(...),我們可以在調(diào)用 foo 時(shí)強(qiáng)制把 this 綁定到 obj 上。

但是顯示綁定也不能解決綁定丟失的問題,通過借助 bind 方法我們可以實(shí)現(xiàn)硬綁定。

  1. new 綁定
function foo(a) {
  this.a = a;
}

const bar = new foo(2);
console.log(bar.a); // 2

使用 new 來調(diào)用 foo(...) 時(shí),我們會(huì)構(gòu)造一個(gè)新對(duì)象并把它綁定到 foo(...) 調(diào)用的 this 上。

綁定優(yōu)先級(jí)

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

例外:關(guān)于箭頭函數(shù)的 this

箭頭函數(shù)是一種無法使用我們上面四種規(guī)則的特殊函數(shù)類型。

function foo() {
  return (a) => {
    console.log(this.a);
  }
}

const obj1 = {
  a: 2
};

const obj2 = {
  a: 3
};

const bar = foo.call(obj1);
bar.call(obj2); // 2,不是 3

foo() 內(nèi)部創(chuàng)建的箭頭函數(shù)會(huì)捕獲調(diào)用時(shí) foo()this。由于 foo()this 綁定到了 obj1,barthis 也會(huì)綁定到 obj1,箭頭函數(shù)的綁定無法被修改。(new 也不行)。

箭頭函數(shù)常用于回調(diào)函數(shù)中,例如事件處理或定時(shí)器:

function foo() {
  setTimeout(() => { console.log(this.a) }, 100);
}

const obj = { a: 2 };

foo.call(obj); // 2

總結(jié):

  1. 由 new 調(diào)用?綁定到新創(chuàng)建的對(duì)象。
  2. 由 call 或者 apply(或者 bind)調(diào)用?綁定到指定的對(duì)象。
  3. 由上下文對(duì)象調(diào)用?綁定到那個(gè)上下文對(duì)象。
  4. 默認(rèn):在嚴(yán)格模式下綁定到 undefined,否則綁定到全局對(duì)象。

參考:《你不知道的 Javascript(上卷)》

最后編輯于
?著作權(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)容