一滩报、關于this
1. 為什么使用this
-
this
提供了一種更優(yōu)雅的方式來隱式“傳遞”一個對象引用,因此可以將API
設計 得更加簡潔并且易于復用。 -
this
使函數(shù)可以自動引用合適的上下文對象呛伴。
2. 對this
的誤解
- 指向自身
- 指向函數(shù)的作用域
3. this
是什么臣疑?
-
this
既不指向函數(shù)自身也不指向函數(shù)的詞法作用域; -
this
的指向馒胆,是在函數(shù)被調(diào)用的時候確定的缨称,也就是執(zhí)行上下文被創(chuàng)建時確定的; -
this
的指向和函數(shù)聲明的位置沒有任何關系祝迂,只取決于函數(shù)的調(diào)用位置(也就是函數(shù)的調(diào)用方法)睦尽; - 在函數(shù)執(zhí)行過程中,
this
一旦被確定型雳,就不可更改了当凡。
var a = 10;
var obj = {
a: 20
}
function fn () {
this = obj; // 這句話試圖修改this,運行后會報錯
console.log(this.a);
}
fn();
二纠俭、this
的全面解析
(一)調(diào)用位置
調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置)沿量。
尋找調(diào)用位置就是尋找“函數(shù)被調(diào)用的位置”,其中最重要的是要分析調(diào)用棧(就是為了到達當前執(zhí)行位置所調(diào)用的所有函數(shù))冤荆。我們關心的調(diào)用位置就在當前正在執(zhí)行的函數(shù)的前一個調(diào)用中朴则。
調(diào)用棧又稱“執(zhí)行棧”钓简,棧是一種后進先出的數(shù)據(jù)結(jié)構(gòu)乌妒。調(diào)用棧主要用于記錄代碼執(zhí)行位置汹想、當前執(zhí)行環(huán)境。
function baz() {
// 當前調(diào)用棧是:baz
// 因此撤蚊,當前調(diào)用位置是全局作用域
console.log( "baz" );
bar(); // <-- bar 的調(diào)用位置
}
function bar() {
// 當前調(diào)用棧是 baz -> bar
// 因此古掏,當前調(diào)用位置在 baz 中
console.log( "bar" );
foo(); // <-- foo 的調(diào)用位置
}
function foo() {
// 當前調(diào)用棧是 baz -> bar -> foo
// 因此,當前調(diào)用位置在 bar 中
console.log( "foo" );
}
baz(); // <-- baz 的調(diào)用位置
(二)綁定規(guī)則
-
默認綁定(獨立函數(shù)調(diào)用——無法應用其他規(guī)則時的默認規(guī)則)侦啸,
this
指向全局對象window
槽唾。
function foo() {
console.log( this.a ); // this指向全局對象
}
var a = 2;
foo(); // 2
對于默認綁定來說,決定this
綁定對象的并不是調(diào)用位置是否處于嚴格模式匹中,而是函數(shù)體是否處于嚴格模式夏漱。
如果函數(shù)體處于嚴格模式,this
會被綁定到undefined
顶捷,否則this
會被綁定到全局對象挂绰。
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo(); // 2,嚴格模式下與 foo() 的調(diào)用位置無關
})();
- 隱式綁定
在一個函數(shù)上下文中服赎,this
由調(diào)用者提供葵蒂,由調(diào)用函數(shù)的方式來決定。如果調(diào)用者函數(shù)重虑,被某一個對象所擁有践付,那么該函數(shù)在調(diào)用時,內(nèi)部的this
指向該對象缺厉。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
// 調(diào)用位置會使用 obj 上下文來引用函數(shù)永高,
// 因此你可以說函數(shù)被調(diào)用時 obj 對象“擁有”或者“包含”它
// 所以此時的 this 指向調(diào)用 foo 函數(shù)的 obj 對象
對象屬性引用鏈中只有最頂層或者說最后一層會影響調(diào)用位置,也就是說this
指向最終調(diào)用函數(shù)的對象提针。舉例來說:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42命爬,此時的 this 指向 obj2 對象
隱式丟失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函數(shù)別名!函數(shù)的引用而不是函數(shù)的調(diào)用7薄K峭稹!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"
// 雖然 bar 是 obj.foo 的一個引用嗜价,但是實際上艇抠,它引用的是foo 函數(shù)本身,
// 因此此時的 bar() 其實是一個不帶任何修飾的函數(shù)調(diào)用久锥,因此應用了默認綁定
-
顯式綁定(
call(..)
和apply(..)
方法)
call()
和apply()
方法家淤,它們的第一個參數(shù)是一個對象,它們會把這個對象綁定到this
瑟由,接著在調(diào)用函數(shù)時指定這個this
媒鼓。
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2
// 在調(diào)用 foo 時強制把它的 this 綁定到 obj 上
new
綁定
在JavaScript
中,構(gòu)造函數(shù)只是一些使用new
操作符時被調(diào)用的函數(shù)错妖。它們并不會屬于某個類绿鸣,也不會實例化一個類。實際上暂氯,它們甚至都不能說是一種特殊的函數(shù)類型潮模,它們只是被new
操作符調(diào)用的普通函數(shù)而已。
使用new
來調(diào)用函數(shù)痴施,或者說發(fā)生構(gòu)造函數(shù)調(diào)用時擎厢,會自動執(zhí)行下面的操作:
- 創(chuàng)建(或者說構(gòu)造)一個全新的對象;
- 將構(gòu)造函數(shù)的作用域賦給新對象(因此
this
就指向了這個新對象)辣吃;- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性动遭、方法等);
- 如果函數(shù)沒有返回其他對象神得,那么
new
表達式中的函數(shù)調(diào)用會自動返回這個新對象厘惦。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
// 使用new 來調(diào)用foo(..)時,我們會構(gòu)造一個新對象并把它綁定到foo(..)調(diào)用中的this上
(三)判斷 this
this
的判斷可以按照下面的優(yōu)先級順序來判斷函數(shù)在某個調(diào)用位置應用的是哪條規(guī)則:
- 函數(shù)是否在
new
中調(diào)用(new
綁定)兔甘?
如果是的話丹皱,this
綁定的是新創(chuàng)建的對象溯革。
var bar = new foo();
- 函數(shù)是否通過
call
、apply
(顯式綁定)或者硬綁定調(diào)用羡玛?如果是的話,this
綁定的是指定的對象宗苍。
var bar = foo.call(obj2);
- 函數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)稼稿?如果是的話,
this
綁定的是那個上下文對象讳窟。
var bar = obj1.foo();
- 如果都不是的話让歼,使用默認綁定。如果在嚴格模式下挪钓,就綁定到
undefined
是越,否則綁定到全局對象。
var bar = foo();
(四)綁定例外
- 被忽略的
this
null
或者undefined
作為this
的綁定對象傳入call
碌上、apply
或者bind
倚评,這些值在調(diào)用時會被忽略,實際應用的是默認綁定規(guī)則馏予。
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
- 間接引用
間接引用最容易在賦值時發(fā)生天梧;間接引用時,調(diào)用這個函數(shù)會應用默認綁定規(guī)則霞丧。
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 函數(shù)的引用
// 因此調(diào)用位置是 foo() 而不是 p.foo() 或者 o.foo()
(五)this
詞法
箭頭函數(shù)并不是使用function
關鍵字定義的,而是使用被稱為“胖箭頭”的操作符 =>
定 義的。
箭頭函數(shù)不使用 this
的四種標準規(guī)則后豫,而是根據(jù)外層(函數(shù)或者全局)作用域來決定 this
悉尾。
function foo() {
// 返回一個箭頭函數(shù)
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ù)會捕獲調(diào)用時 foo() 的 this挫酿。
// 由于 foo() 的 this 綁定到 obj1构眯, bar(引用箭頭函數(shù))的 this 也會綁定到 obj1,
// this一旦被確定早龟,就不可更改惫霸,所以箭頭函數(shù)的綁定無法被修改。(new 也不行4械堋)
小結(jié)
如果要判斷一個運行中函數(shù)的this
綁定壹店,就需要找到這個函數(shù)的直接調(diào)用位置。找到之后就可以順序應用下面這四條規(guī)則來判斷 this
的綁定對象芝加。
- 由
new
調(diào)用硅卢?綁定到新創(chuàng)建的對象。- 由
call
或者apply
(或者bind
)調(diào)用妖混?綁定到指定的對象老赤。- 由上下文對象調(diào)用?綁定到那個上下文對象制市。
- 默認:在嚴格模式下綁定到
undefined
抬旺,否則綁定到全局對象。
ES6
中的箭頭函數(shù)并不會使用四條標準的綁定規(guī)則祥楣,而是根據(jù)當前的詞法作用域來決定 this
开财,具體來說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this
綁定(無論 this
綁定到什么)误褪。這其實和 ES6
之前代碼中的 self = this
機制一樣责鳍。