開篇說明
隨着科技的發展和歷史的變遷,我對 this 的理解也越來越深入了,還記得之前寫的一篇關於 this 的文章,自己看起來都感覺“味同嚼蠟”😂,所以更新一篇,將功補過,哈哈~~,我們拿五種情況來說明 this 的指向問題,優先級從低到高,但是在這之前我們先說一下 js 的作用域鏈的問題。
作用域鏈
案例一
function outer() {
var name = 'outer';
function inner() {
console.log( 'name: ', name );
}
inner();
}
outer();
- 大家想一下打印出來的 name 是啥呢?
- 此時假裝過大家思考了10秒~
- 打印出來的是
outer
- 原因是什麼呢?
- 明明 inner 函數中是沒有 name 這個屬性的,他怎麼會打印出外層的 outer 函數中定義的 name 呢?
- 因爲 js 在執行函數的過程中,如果某個變量找不到,他會沿着「!!!:
函數定義時
」的作用域鏈去外層找是否有這個變量,如果外層沒有,繼續向外層的外層去找找找。。。最終找到全局對象上(一般情況下是window)如果還沒有,那麼就會報錯;在我們這個案例中,查找的順序爲:inner ? -> outer ? -> window ?
- 如果按照上面的作用域鏈最終到 window 對象上也沒有找到這個變量,那麼瀏覽器就會拋出錯誤
Error: name is not defiend
五種情況的 this 指向
一、函數的自然執行
先解釋下什麼叫自然執行?
function func() {
console.log( 'this: ', this );
};
let obj = {
a: func
};
func(); // 自然執行
obj.a(); // 不是自然執行
func.call( obj ); // 不是自然執行
總結:自然執行:爲函數直接執行,這樣: func();
1.直接執行,不通過 '.' 操作符執行
2.不使用 new 操作符
3.不使用 call、apply、bind
總之:就是光突突地執行:func();
函數自然執行的情況下:
- 在非嚴格模式下的 js 中,指向
window
- 在嚴格模式「“use strict”」下,指向
undefined
- 在 node 環境下,指向 node 的全局對象
global
二、被對象的訪問操作符訪問出來,並執行
什麼是對象的訪問操作符?其實就是訪問對象中屬性的兩種方式,我們平時肯定也很常用,請看下面
function func() {
console.log( 'this: ', this );
}
let obj = {
// a 屬性的值指向了 func 函數的引用,這裏不清楚可以查閱下 js 中的基本數據類型和複雜數據類型的區別
name: 'obj',
a: func
}
obj.a(); // 被 obj 通過「對象.鍵名」的方式訪問出來並執行
obj[a](); // 被 obj 通過「對象[鍵名]」的方式訪問出來執行
上面兩種執行方式中,this 的指向被隱式(偷偷)綁定到 obj 上了
這種情況我們只需要記住一句話:函數不管被多少層訪問符,層層訪問到,他總是指向離他最近的調用對象上,下面舉例說明:
function func() {
console.log( 'this ', this );
}
let obj = {
name: 'obj',
father: {
name: 'father',
son: {
a: func,
name: 'son'
}
}
}
obj.father.son.a(); // this 指向 son ,因爲是離他最近的調用者
三、通過 new 操作符執行函數
也就是調用構造函數的方式了,也就是我們用 javaScript 造出來的“類”的概念。
function func(name) {
this.name = name;
console.log( 'this: ', this );
}
let a = new func('a'); // this 指向 a 對象
let b = new func('b'); // this 指向 b 對象
只要通過 new 操作符調用函數,那麼 this 會隱式(偷偷地)指向返回的實例對象。
下面出一道題,大家可以嘗試思考一下,打印出的 this 是什麼?
可以運行輸出驗證一下自己的答案哦!
function outer() {
let name = 'outer';
function inner() {
let name = 'inner';
console.log( 'this: ', this );
}
return inner;
}
let a = outer();
a();
四、call、apply、bind
先來說下 call 和 apply
call 和 apply 都是可以把其他函數借過來自己用一下,而且還不用在自己身上擴展這個方法,舉例說明:
let a = {
name: '我是a'
}
let b = {
name: 'b',
sayName: function() {
console.log( 'name: ', this.name );
}
}
b.call( a ); // name: '我是a'
b.apply( a ); // name: '我是a'
// call 和 apply 的區別就是傳參方式的不同
// 這裏不做過多說明
// b.call( a, 1, 2, 3 ) b.apply( a, [1, 2, 3] );
我們的兩個對象 a 和 b,他們都有名爲 name 的屬性,但是 a 中沒有 sayName
的方法,但是他可以借用 b 的 sayName
方法,但是在借用 b 的 sayName
方法的過程中,this 被隱式(偷偷地)指向了被借用的對象上,在這裏就是 a
bind
只講解最簡單的使用方式,也是借用函數,並隱式改變 this 的指向,但跟 call 和 apply 不同的是:bind 返回一個新函數,舉例說明:
let a = {
name: 'a',
log: function () {
console.log( 'this: ', this );
}
}
let b = {
name: 'b',
}
let c = a.log.bind( b );
c(); // this 指向對象 b
// `c()` 相當於執行 `a.log.call( b )`
五、ES6 的箭頭函數
我們平時寫代碼的時候經常聽到一種說法:你的項目中要不就都用箭頭函數,要不就都不用,別把 this 搞亂,對吧,但是我們搞清楚 this 在不同執行情景下的指向之後,哪種寫法我們就可以自己斟酌了,或者混着用😏(不推薦)
我們先看下概念:箭頭函數的 this 指向「定義時
」離他最近的一個非箭頭函數的 this 指向的對象,舉例說明:
function outer() {
let name = 'outer';
let inner = () => {
let name = 'inner';
console.log( 'this: ', this );
}
inner();
}
outer(); // this: window
// 1.箭頭函數的 this 指向「`定義時`」離他最近的一個非箭頭函數的 this 指向的對象
// 2.那麼我們從 inner 向外層看,外層 outer 就是離 inner 最近的非箭頭函數,
// 那麼他的 this 指向就是箭頭函數 inner 的 this 指向
// 3.我們分析 outer,outer 是自然執行的(上面說過自然執行了),那麼 outer 的 this
// 指向爲 window,所以 inner 的 this 執行也是 window
function outer() {
let name = 'outer';
let inner = () => {
let name = 'inner';
console.log( 'this: ', this );
}
inner();
}
outer.call( outer ); // this: ?
// 這個留給大家自己分析了,歡迎提問哦~
this 的優先級問題
ES6箭頭函數 > call、apply、bind > 自然執行、對象訪問操作符執行、new 調用
!
:大於號中間的多種情況爲並列關係