一、使用this的原因(why)
對于前端開發(fā)者來說慨蛙,this關(guān)鍵字是JavaScript中最復(fù)雜的機(jī)制之一,不同的位置使用纪挎,指向是不一樣的期贫,所以它到底有什么作用呢?為什么要花大量的時(shí)間去研究他呢
- this提供了一種優(yōu)雅的方式來隱式的“傳遞”一個對象的引用异袄,可以方便我們簡潔的設(shè)計(jì)API通砍。特別是當(dāng)結(jié)構(gòu)越來越復(fù)雜時(shí)。
function GetObj (name) {
this.name = name
}
GetObj.prototype.showName = function (){
alert(this.name) // this指代GetObj這個對象
}
let p1 = new GetObj('張三')
p1.showName()
二烤蜕、了解this(what----this到底是什么)
1封孙、對this的誤解
- 認(rèn)為this指向函數(shù)自身
function foo(num) {
console.log('foo===', num) // foo: 6 // foo: 7 // foo: 8 // foo: 9
// 記錄 foo 被調(diào)用的次數(shù)
this.count++
}
foo.count = 0
for(var i = 0; i< 10; i++){
if (i > 5) {
foo( i );
}
}
console.log( foo.count ); // 0
this并沒有指向自身的foo函數(shù),而是指向了window讽营。
- 認(rèn)為this的作用域指向函數(shù)的作用域
在某種情況下它 是正確的虎忌,但是在其他情況下它卻是錯誤的
function foo(){
var a = 2;
this.bar();
}
function bar() {
console.log(this); //window
console.log(this.a); // undefined
}
foo();
這個a作用域存在于foo函數(shù)中,所以在window這個作用域中找不到橱鹏。但是由于var定義的a膜蠢,在詞法分析階段會將a進(jìn)行變量提升堪藐,所以window中會有一個a,但是沒有值
2挑围、與自然語言的對比
實(shí)際上js中的this和我們自然語言中的代詞有類似性礁竞。比如英語中我們寫"小明,你吃了么贪惹?"
注意上面的代詞"小明",我們當(dāng)然可以這樣寫:"小明苏章,小明吃了么?" ,這種情況下我們沒有使用this去重用代替小明奏瞬。
主要和執(zhí)行時(shí)候的上下文環(huán)境有關(guān)聯(lián)
3枫绅、那this到底是什么呢?
this只跟函數(shù)的調(diào)用位置有關(guān)硼端,是在函數(shù)被調(diào)用時(shí)發(fā)生綁定的并淋;this的指向取決于函數(shù)在哪里被調(diào)用。
this指向的最終對象珍昨,跟調(diào)用位置以及應(yīng)用的綁定規(guī)則有關(guān)
三县耽、綁定規(guī)則(how--如何尋找函數(shù)的調(diào)用位置)
1、 默認(rèn)綁定
- 最常見的綁定規(guī)則镣典,獨(dú)立函數(shù)調(diào)用時(shí)默認(rèn)綁定兔毙,也可以看做無法應(yīng)用其他綁定規(guī)則時(shí)的默認(rèn)規(guī)則
function foo() {
console.log( this.a ); // this指向window
}
var a = 2;
foo(); // 2
- this指向
- 在全局環(huán)境中,this 指向全局對象兄春,在瀏覽器中澎剥,它就是 window 對象。
- 普通函數(shù)是在全局環(huán)境中被調(diào)用:
非嚴(yán)格模式下:指向全局對象window
嚴(yán)格模式下:指向undefined(使用嚴(yán)格模式的時(shí)候赶舆,全局對象無法使用默認(rèn)綁定)
function foo() {
"use strict";
console.log( this ); // undefined
console.log( this.a ); // Uncaught TypeError: Cannot read property 'a' of undefined
}
"use strict";
console.log( this ) // window
var a = 2;
foo();
2哑姚、 隱式綁定
- 當(dāng)函數(shù)的調(diào)用存在上下文對象,或者說
是否被某個對象擁有或者包含
芜茵。this將會被綁定到這個上下文對象叙量。
this 始終會指向直接調(diào)用函數(shù)的上一級對象
function foo() {
console.log( this.a ); // 指向obj
}
var obj = {
a: 2,
foo: foo //--->foo: function () { console.log( this.a ); }
};
obj.foo(); // 2
- 函數(shù)嵌套,每個function函數(shù)(非箭頭函數(shù))在每次調(diào)用時(shí)都會在函數(shù)內(nèi)生成一個自己的this九串。
當(dāng)兩個函數(shù)嵌套定義時(shí)绞佩,內(nèi)層函數(shù)中的this與外層函數(shù)中的this是完全獨(dú)立的。函數(shù)內(nèi)this的值是在函數(shù)調(diào)用時(shí)才確定的蒸辆,函數(shù)的調(diào)用方式不同征炼,this也就不同
1,當(dāng)函數(shù)直接調(diào)用時(shí) fn(); 函數(shù)內(nèi)this的值是window對象躬贡,(在js嚴(yán)格模式"use strict";下谆奥,函數(shù)內(nèi)this的值是null)
2,當(dāng)把這個函數(shù)賦值給一個對象的方法obj.abc = fn;
調(diào)用obj.abc()時(shí)拂玻,函數(shù)內(nèi)this的值是obj對象,也就是函數(shù)所在的對象酸些。
var obj = {
y: function() {
console.log(this === obj); // true
console.log(this); // Object {y: function}
fn(); // 嵌套的函數(shù)不是對象的方法宰译,直接調(diào)用,所以this指向window
function fn() {
console.log(this === obj); // false
console.log(this); // Window 全局對象
}
}
}
obj.y();
- 問題:被隱式綁定的函數(shù)會丟失綁定對象魄懂,會應(yīng)用默認(rèn)綁定沿侈,從而把 this 綁定到全局對象或者 undefined 上,取決于是否是嚴(yán)格模式市栗。
1缀拭、 函數(shù)引用傳遞給新的全局變量
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // bar是全局變量
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global",bar() 其實(shí)是一個不帶任何修飾的函數(shù)調(diào)用
2填帽、將函數(shù)作為參數(shù)傳入回調(diào)函數(shù)中
function foo() {
console.log( this.a );
}
function doFoo(fn) { // fn 其實(shí)引用的是 foo
fn(); // <-- 調(diào)用位置蛛淋!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局對象的屬性
doFoo( obj.foo ); // "oops, global"
3、顯式綁定
使用 call()和apply() 方法進(jìn)行強(qiáng)制綁定
- 他們的第一個參數(shù)都是指定函數(shù)運(yùn)行時(shí)的this指向篡腌,第一個參數(shù)不傳(參數(shù)為空)褐荷。或者為null嘹悼、undefined叛甫。默認(rèn) this 指向全局對象(非嚴(yán)格模式)或 undefined(嚴(yán)格模式)。
var x = 1;
var obj = {
x: 2
}
function fn() {
console.log(this);
console.log(this.x);
}
fn.call(obj)
// Object {x: 2}
// 2
fn.apply(obj)
// Object {x: 2}
// 2
fn.call()
// Window 全局對象
// 1
fn.apply(null)
// Window 全局對象
// 1
fn.call(undefined)
// Window 全局對象
// 1
- 使用 call() 和 apply() 傳入的this對象為原始值(字符串類型杨伙,布爾類型或者數(shù)字類型)其监,這個原始值就會被轉(zhuǎn)換成它的對象形式(new String()、new Bollean()限匣、new Number())
- 區(qū)別
call()的第二個以及后續(xù)的參數(shù)是一個列表
apply()的第二個參數(shù)是一個數(shù)組棠赛,所有的參數(shù)放在這個數(shù)組中
參數(shù)列表和參數(shù)數(shù)組都將作為函數(shù)的參數(shù)進(jìn)行執(zhí)行
var x = 1
var obj = {
x: 2
}
function sum (y, z) {
console.log(this.x + y +z)
}
sum(3, 4) // 8
sum.call(obj, 3, 4) // 9
sum.apply(obj, [3, 4]) // 9
使用 bind() 方法進(jìn)行強(qiáng)制綁定
調(diào)用 f.bind(someObject) 會創(chuàng)建一個與 f 具有相同函數(shù)體和作用域的函數(shù),但是在這個新函數(shù)中膛腐,新函數(shù)的 this 會永久的指向 bind 傳入的第一個參數(shù)
,無論這個函數(shù)是如何被調(diào)用的鼎俘。
var x = 1
var obj1 = {
x: 2
}
var obj2 = {
x: 3
}
function fn () {
console.log(this)
console.log(this.x)
}
var a = fn.bind(obj1)
var b = a.bind(obj2)
fn() // window 1
a() // {x: 2} 2
b() // {x: 2} 2
a.call(obj2) // {x: 2} 2
在上面的例子中哲身,雖然我們嘗試給函數(shù) a 重新指定 this 的指向,但是它依舊指向第一次 bind 傳入的對象贸伐,即使是使用 call 或 apply 方法也不能改變這一事實(shí)勘天,即永久的指向 bind 傳入的第一次參數(shù)。
4捉邢、new綁定
使用 new 關(guān)鍵字脯丝,通過構(gòu)造函數(shù)生成一個實(shí)例對象。此時(shí)伏伐,this 便指向這個新對象
var x = 1;
function Fn() {
this.x = 2;
console.log(this); // Fn {x: 2}
}
var obj = new Fn(); // obj和Fn(..)調(diào)用中的this進(jìn)行綁定
console.log(obj.x) // 2
綁定規(guī)則優(yōu)先級
如果某個調(diào)用位置可以應(yīng)用多條規(guī)則該怎么辦宠进?為了 解決這個問題就必須給這些規(guī)則設(shè)定優(yōu)先級。
毫無疑問藐翎,默認(rèn)綁定的優(yōu)先級別最低材蹬。
隱式綁定和顯示綁定誰的優(yōu)先級別更高呢实幕?
function fn(){
console.log(this.a)
}
let obj1 = {
a: 1,
fn: fn
}
let obj2 = {
a: 2,
fn: fn
}
obj1.fn()//1
obj2.fn()//2
obj1.fn.call(obj2)//2
obj2.fn.apply(obj1)//1
- 可以看到,顯式綁定優(yōu)先級更高
隱式綁定和new()綁定誰的優(yōu)先級別更高呢堤器?
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a ); // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a ); // 3
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a ); // 4
- 可以看到 new 綁定比隱式綁定優(yōu)先級高昆庇。
但是 new 綁定和顯式綁定誰的優(yōu)先級更高呢
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar(3);
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
bar 被硬綁定到 obj1 上,但是 new bar(3) 并沒有像我們預(yù)計(jì)的那樣把 obj1.a 修改為 3闸溃。相反整吆,new 修改了硬綁定(到 obj1 的)調(diào)用 bar(..) 中的 this。因?yàn)槭褂昧?new 綁定辉川,我們得到了一個名字為 baz 的新對象表蝙,并且 baz.a 的值是 3。
當(dāng)某個函數(shù)調(diào)用應(yīng)用了這四種規(guī)則中的多條员串,那么優(yōu)先級:new綁定 > 顯示綁定 > 隱式綁定 > 默認(rèn)綁定
5勇哗、箭頭函數(shù)中this指向
箭頭函數(shù)表達(dá)式的語法比函數(shù)表達(dá)式更簡潔,并且沒有自己的this寸齐,arguments欲诺,super或new.target
。箭頭函數(shù)表達(dá)式更適用于那些本來需要匿名函數(shù)的地方渺鹦,并且它不能用作構(gòu)造函數(shù)扰法。
箭頭函數(shù)沒有自己的this綁定。箭頭函數(shù)中使用的this毅厚,其實(shí)是直接包含它的那個函數(shù)或函數(shù)表達(dá)式中的this
var a = 1;
let obj = {
a: 2,
fn1: () => {
console.log('fn1',this.a) // 1 this指向window
let fn3 = () => {
console.log('fn3',this.a) // 1 this指向window
}
fn3()
let fn4 = () => {
console.log('fn4',this.a) // 1 this指向window
}
fn4.call(obj)
// 由于箭頭函數(shù)沒有自己的this指針塞颁,通過 call() 或apply() 方法調(diào)用一個函數(shù)時(shí),只能傳遞參數(shù)吸耿,他們的第一個參數(shù)會被忽略祠锣。
fn4.apply(obj)
},
fn2: function() {
console.log('fn2',this.a) // 2 this指向obj
}
}
obj.fn1()
obj.fn2()
箭頭函數(shù)的this看外層的是否有函數(shù),如果有咽安,外層函數(shù)的this就是內(nèi)部箭頭函數(shù)的this伴网,如果沒有,則this是window妆棒。- 從父作用域繼承this
同 bind 一樣澡腾,箭頭函數(shù)也很“頑固”,無法通過 call 和 apply 來改變 this 的指向糕珊,即傳入的第一個參數(shù)被忽略动分。
圖片轉(zhuǎn)自掘金小冊-前端面試之道
總結(jié)
如果要判斷一個運(yùn)行中函數(shù)的 this 綁定,就需要找到這個函數(shù)的直接調(diào)用位置红选。找到之后 就可以順序應(yīng)用下面這四條規(guī)則來判斷 this 的綁定對象澜公。
- 由 new 調(diào)用?綁定到新創(chuàng)建的對象纠脾。
- 由 call 或者 apply(或者 bind)調(diào)用玛瘸?綁定到指定的對象蜕青。
- 由上下文對象調(diào)用?綁定到那個上下文對象糊渊。
- 默認(rèn):在嚴(yán)格模式下綁定到 undefined右核,否則綁定到全局對象。
一定要注意渺绒,有些調(diào)用可能在無意中使用默認(rèn)綁定規(guī)則贺喝。如果想“更安全”地忽略 this 綁 定,你可以使用一個 DMZ 對象宗兼,比如 ? = Object.create(null)躏鱼,以保護(hù)全局對象。
ES6 中的箭頭函數(shù)并不會使用四條標(biāo)準(zhǔn)的綁定規(guī)則殷绍,而是根據(jù)當(dāng)前的詞法作用域來決定 this染苛,具體來說,箭頭函數(shù)會繼承外層函數(shù)調(diào)用的 this 綁定(無論 this 綁定到什么)主到。這 其實(shí)和 ES6 之前代碼中的 self = this 機(jī)制一樣茶行。
https://www.cnblogs.com/kidsitcn/p/10985338.html
https://zhuanlan.zhihu.com/p/71490991
https://zhuanlan.zhihu.com/p/28536635
嵌套函數(shù)指向