筆者最近在看 你不知道的JavaScript上卷,里面關(guān)于 this
的講解個(gè)人覺得非常精彩。JavaScript
中的 this
算是一個(gè)核心的概念,有一些同學(xué)會(huì)對(duì)其有點(diǎn)模糊和小恐懼,究其原因,現(xiàn)在對(duì) this
討論的文章很多,讓我們覺得 this
無規(guī)律可尋,就像一個(gè)幽靈一樣
創(chuàng)新互聯(lián)公司主營(yíng)昆明網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都App制作,昆明h5微信平臺(tái)小程序開發(fā)搭建,昆明網(wǎng)站營(yíng)銷推廣歡迎昆明等地區(qū)企業(yè)咨詢
如果你還沒弄懂 this
,或者對(duì)它比較模糊,這篇文章就是專門為你準(zhǔn)備的,如果你相對(duì)比較熟悉了,那你也可以當(dāng)做復(fù)習(xí)鞏固你的知識(shí)點(diǎn)
本篇文章,算是一篇讀書筆記,當(dāng)然也加上了很多我的個(gè)人理解,我覺得肯定對(duì)大家有所幫助
在理解 this
之前,我們先來看下什么是執(zhí)行上下文
簡(jiǎn)而言之,執(zhí)行上下文是評(píng)估和執(zhí)行 JavaScript
代碼的環(huán)境的抽象概念。每當(dāng) Javascript 代碼在運(yùn)行的時(shí)候,它都是在執(zhí)行上下文中運(yùn)行
JavaScript 中有三種執(zhí)行上下文類型
window
對(duì)象(瀏覽器的情況下),并且設(shè)置 this
的值等于這個(gè)全局對(duì)象。一個(gè)程序中只會(huì)有一個(gè)全局執(zhí)行上下文eval
函數(shù)執(zhí)行上下文 — 執(zhí)行在 eval
函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文,但由于 JavaScript 開發(fā)者并不經(jīng)常使用 eval
,所以在這里我不會(huì)討論它這里我們先得出一個(gè)結(jié)論,非嚴(yán)格模式和嚴(yán)格模式中 this 都是指向頂層對(duì)象(瀏覽器中是window)
console.log(this === window); // true
'use strict'
console.log(this === window); // true
this.name = 'vnues';
console.log(this.name); // vnues
后面我們的討論更多的是針對(duì)函數(shù)執(zhí)行上下文
this
是在運(yùn)行時(shí)進(jìn)行綁定的,并不是在編寫時(shí)綁定,它的上下文取決于函數(shù)調(diào) 用時(shí)的各種條件
牢記:this
的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式
當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(有時(shí)候也稱為執(zhí)行上下文)。這個(gè)記錄會(huì)包 含函數(shù)在哪里被調(diào)用(調(diào)用棧)、函數(shù)的調(diào)用方法、傳入的參數(shù)等信息。this
就是記錄的 其中一個(gè)屬性,會(huì)在函數(shù)執(zhí)行的過程中用到
看個(gè)實(shí)例,理解為什么要用 this
,有時(shí)候,我們需要實(shí)現(xiàn)類似如下的代碼:
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify(context);
console.log(greeting);
}
var me = {
name: "Kyle"
};
speak(me); //hello, 我是 KYLE
這段代碼的問題,在于需要顯示傳遞上下文對(duì)象,如果代碼越來越復(fù)雜,這種方式會(huì)讓你的代碼看起來很混亂,用 this
則更加的優(yōu)雅
var me = {
name: "Kyle"
};
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello, I'm " + identify.call(this);
console.log(greeting);
}
speak.call(me); // Hello, 我是 KYLE
下面我們來看在函數(shù)上下文中的綁定規(guī)則,有以下四種
new
綁定最常用的函數(shù)調(diào)用類型:獨(dú)立函數(shù)調(diào)用,這個(gè)也是優(yōu)先級(jí)最低的一個(gè),此事 this
指向全局對(duì)象。注意:如果使用嚴(yán)格模式(strict mode
),那么全局對(duì)象將無法使用默認(rèn)綁定,因此 this
會(huì)綁定 到 undefined
,如下所示
var a = 2; // 變量聲明到全局對(duì)象中
function foo() {
console.log(this.a); // 輸出 a
}
function bar() {
'use strict';
console.log(this); // undefined
}
foo();
bar();
還可以我們開頭說的:this
的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式
先來看一個(gè)例子:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
當(dāng)調(diào)用 obj.foo()
的時(shí)候,this
指向 obj 對(duì)象。當(dāng)函數(shù)引用有上下文對(duì)象時(shí),隱式綁定規(guī)則會(huì)把函數(shù)調(diào)用中的 this
綁定到這個(gè)上下文對(duì)象。因?yàn)檎{(diào) 用 foo()
時(shí) this
被綁定到 obj,因此 this.a 和 obj.a 是一樣的
記?。?strong>對(duì)象屬性引用鏈中只有最頂層或者說最后一層會(huì)影響調(diào)用位置
function foo() {
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
間接引用
另一個(gè)需要注意的是,你有可能(有意或者無意地)創(chuàng)建一個(gè)函數(shù)的“間接引用”,在這 種情況下,調(diào)用這個(gè)函數(shù)會(huì)應(yīng)用默認(rèn)綁定規(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
另一個(gè)需要注意的是,你有可能(有意或者無意地)創(chuàng)建一個(gè)函數(shù)的“間接引用”,在這 種情況下,調(diào)用這個(gè)函數(shù)會(huì)應(yīng)用默認(rèn)綁定規(guī)則
賦值表達(dá)式 p.foo = o.foo
的返回值是目標(biāo)函數(shù)的引用,因此調(diào)用位置是 foo()
而不是 p.foo()
或者 o.foo()
。根據(jù)我們之前說過的,這里會(huì)應(yīng)用默認(rèn)綁定
在分析隱式綁定時(shí),我們必須在一個(gè)對(duì)象內(nèi)部包含一個(gè)指向函數(shù)的屬性,并通過這個(gè)屬性間接引用函數(shù),從而把 this
間接(隱式)綁定到這個(gè)對(duì)象上。 那么如果我們不想在對(duì)象內(nèi)部包含函數(shù)引用,而想在某個(gè)對(duì)象上強(qiáng)制調(diào)用函數(shù),該怎么
做呢?
Javascript
中提供了 apply
、call
和 bind
方法可以讓我們實(shí)現(xiàn)
不同之處在于,call()
和 apply()
是立即執(zhí)行函數(shù),并且接受的參數(shù)的形式不同:
call(this, arg1, arg2, ...)
apply(this, [arg1, arg2, ...])
而 bind()
則是創(chuàng)建一個(gè)新的包裝函數(shù),并且返回,而不是立刻執(zhí)行
bind(this, arg1, arg2, ...)
看如下的例子:
function foo(b) {
console.log(this.a + '' + b);
}
var obj = {
a: 2,
foo: foo
};
var a = 1;
foo('Gopal'); // 1Gopal
obj.foo('Gopal'); // 2Gopal
foo.call(obj, 'Gopal'); // 2Gopal
foo.apply(obj, ['Gopal']); // 2Gopal
let bar = foo.bind(obj, 'Gopal');
bar(); // 2Gopal
被忽略的 this
如果你把 null
或者 undefined
作為 this
的綁定對(duì)象傳入 call
、apply
或者 bind
,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則
function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 2
利用這個(gè)用法使用 apply(..)
來“展開”一個(gè)數(shù)組,并當(dāng)作參數(shù)傳入一個(gè)函數(shù)。
類似地,bind(..)
可以對(duì)參數(shù)進(jìn)行柯里化(預(yù)先設(shè)置一些參數(shù))
function foo(a, b) {
console.log("a:" + a + ", b:" + b);
}
// 把數(shù)組“展開”成參數(shù)
foo.apply(null, [2, 3]); // a:2, b:3
// 使用 bind(..) 進(jìn)行柯里化
var bar = foo.bind(null, 2);
bar(3); // a:2, b:3
當(dāng)我們使用構(gòu)造函數(shù) new
一個(gè)實(shí)例的時(shí)候,這個(gè)實(shí)例的 this
指向是什么呢?
我們先來看下使用 new
來調(diào)用函數(shù),或者說發(fā)生構(gòu)造函數(shù)調(diào)用時(shí),會(huì)執(zhí)行什么操作,如下:
__proto__
和構(gòu)造函數(shù)的 prototype
綁定this
原理實(shí)現(xiàn)類似如下:
function create (ctr) {
// 創(chuàng)建一個(gè)空對(duì)象
let obj = new Object()
// 鏈接到構(gòu)造函數(shù)的原型對(duì)象中
let Con = [].shift.call(arguments)
obj.__proto__ = Con.prototype
// 綁定this
let result = Con.apply(obj, arguments);
// 如果返回是一個(gè)對(duì)象,則直接返回這個(gè)對(duì)象,否則返回實(shí)例
return typeof result === 'object'? result : obj;
}
注意:let result = Con.apply(obj, arguments);
實(shí)際上就是指的是新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this
function Foo(a) {
this.a = a;
}
var bar = new Foo(2);
console.log(bar.a); // 2
我們之前介紹的四條規(guī)則已經(jīng)可以包含所有正常的函數(shù)。但是 ES6 中介紹了一種無法使用 這些規(guī)則的特殊函數(shù)類型:箭頭函數(shù)
箭頭函數(shù)不使用 this
的四種標(biāo)準(zhǔn)規(guī)則,而是根據(jù)定義時(shí)候的外層(函數(shù)或者全局)作用域來決 定 this
。也就是說箭頭函數(shù)不會(huì)創(chuàng)建自己的 this
,它只會(huì)從自己的作用域鏈的上一層繼承 this
function foo() {
// 返回一個(gè)箭頭函數(shù)
// this 繼承自 foo()
return (a) => {
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
也會(huì)綁定到 obj1
,箭頭函數(shù)的綁定無法被修改。(new
也不 行!)
判斷是否為箭頭函數(shù),是則按照箭頭函數(shù)的規(guī)則
否則如果要判斷一個(gè)運(yùn)行中函數(shù)的 this
綁定,就需要找到這個(gè)函數(shù)的直接調(diào)用位置。找到之后就可以順序應(yīng)用下面這四條規(guī)則來判斷 this
的綁定對(duì)象
new
調(diào)用?綁定到新創(chuàng)建的對(duì)象call
或者 apply
(或者 bind
)調(diào)用?綁定到指定的對(duì)象undefined
,否則綁定到全局對(duì)象如下圖所示:
[譯] 理解 JavaScript 中的執(zhí)行上下文和執(zhí)行棧
你不知道的JavaScript上卷