JS中的箭頭函式與this

limingru發表於2019-03-04

JavaScript在ES6語法中新增了箭頭函式,相較於傳統函式,箭頭函式不僅更加簡潔,而且在this方面進行了改進。this作為JavaScript中比較詭異的存在,許多文章對於this的解釋也不盡相同,本篇文章試圖釐清JS中函式與this的關係。

一、JS中函式的寫法

1.常規函式的寫法

在ES6語法之前,JS中的函式由function關鍵字、params引數和被花括號包裹的函式體組成。為了與後面說到的箭頭函式相區別,我們先把這樣的函式叫做常規函式,常規函式既可以用宣告式寫法也可以用賦值式寫法。例子:

function test(name) {  //宣告式寫法
    console.log(name)
}
test(`Jerry`)

let test2 = function(name) {  //賦值式寫法
    console.log(name)
}
test2(`Tom`)
複製程式碼

2. 箭頭函式的寫法

ES6箭頭函式的引入,使函式的寫法變的更加簡潔,但在書寫上要遵循一定的規則。

規則一:箭頭函式只能用賦值式寫法,不能用宣告式寫法

例子:

const test = (name) => {
    console.log(name)
}
test(`Jerry`)
複製程式碼

規則二:如果引數只有一個,可以不加括號,如果沒有引數或者引數多於一個就需要加括號

例子:

const test = name => {
    console.log(name)
}
test(`Jerry`)

const test2 = (name1, name2) => {
    console.log(name1 + ` and ` + name2)
}
test2(`Tom`, `Jerry`)
複製程式碼

規則三:如果函式體只有一句話,可以不加花括號

例子:

const test = name => console.log(name) 
複製程式碼

規則四:如果函式體沒有括號,可以不寫return,箭頭函式會幫你return

例子:

const add = (p1, p2) => p1 + p2
add(10, 25)
複製程式碼

記住:函式體的花括號與return關鍵字同在。

從以上的例子我們可以看出,箭頭函式對常規函式的圓括號和花括號都進行了簡化。除了這些簡化,箭頭函式對於常規函式最大的優化之處在於this。

二、理解常規函式中this

在探討箭頭函式對於this的優化之前,我們先得明白this究竟是什麼,以及它是如何使用的。this是使用call方法呼叫函式時傳遞的第一個引數,它可以在函式呼叫時修改,在函式沒有呼叫的時候,this的值是無法確定。

如果沒有使用過call方法來呼叫函式的話,上面的對於this的定義可能不太明白。那麼我們需要先理解函式呼叫的兩種方法。

1. 純粹的函式呼叫

第一種方法最常見,例子如下:

function test(name) {
    console.log(name)
    console.log(this)
}
test(`Jerry`)  //呼叫函式
複製程式碼

這種方法我們使用最多,但是這種函式呼叫方法只是一種簡寫,它完整的寫法是下面這樣的:

function test(name) {
    console.log(name)
    console.log(this)
}
test.call(undefined, `Tom`)
複製程式碼

注意到上面呼叫函式的call方法了嗎?call方法接收的第一個引數就是this,這裡我們傳了一個undefined。那麼,依據定義,函式執行了之後打出來的this會是undefined嗎?也不是。

如果你傳的 context 就 null 或者 undefined,那麼 window 物件就是預設的 context(嚴格模式下預設 context 是 undefined)。

所以這裡我們打出來的this是Window物件。

2. 物件中函式的呼叫

直接看例子:

const obj = {
    name: `Jerry`,
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //第一種呼叫方法
obj.greet.call(obj) //第二種呼叫方法
複製程式碼

例子裡第一種呼叫方法只是第二種呼叫方法的語法糖,第二種才是完整的呼叫方法,而且第二種方法厲害的地方在於它可以手動指定this

手動指定this的例子:

const obj = {
    name: `Jerry`,
    greet: function() {
        console.log(this.name)
    }
}
obj.greet.call({name: `Spike`})  //打出來的是 Spike
複製程式碼

從上面的例子我們看到greet函式執行時this,已經被我們改過了。

3. 建構函式中this

建構函式裡的this稍微有點特殊,每個建構函式在new之後都會返回一個物件,這個物件就是this,也就是context上下文。

例子:

function Test() {
    this.name = `Tom`
}
let p = new Test()
console.log(typeof p)  //object
console.log(p.name)    // Tom
複製程式碼

4. window.setTimeout()和window.setInterval()中函式的呼叫

window.setTimeout()和window.setInterval()的函式中的this有些特殊,裡面的this預設是window物件。

簡單總結一下:函式完整的呼叫方法是使用call方法,包括test.call(context, name)obj.greet.call(context,name),這裡的context就是函式呼叫時的上下文,也就是this,只不過這個this是可以通過call方法來修改的;建構函式稍微特殊一點,它的this直接指向new之後返回的物件;window.setTimeout()window.setInterval()預設的是this是window物件。

三、理解箭頭函式中的this

上面關於this講了很多,this是函式用call方法呼叫時傳遞的第一個引數,而且它還可以手動更改,這樣要確定this的值就太麻煩了。不過,箭頭函式的出現給我們確定this幫了一些忙。

1. 箭頭函式的特性一:預設繫結外層this

上面提到:this的值是可以用call方法修改的,而且只有在呼叫的時候我們才能確定this的值。而當我們使用箭頭函式的時候,箭頭函式會預設幫我們繫結外層this的值,所以在箭頭函式中this的值和外層的this是一樣的。

不使用箭頭函式例子:

const obj = {
	a: function() { console.log(this) }    
}
obj.a()  //打出的是obj物件
複製程式碼

使用箭頭函式的例子:

const obj = {
    a: () => {
        console.log(this)
    }
}
obj.a()  //打出來的是window
複製程式碼

在使用箭頭函式的例子裡,因為箭頭函式預設不會使用自己的this,而是會和外層的this保持一致,最外層的this就是window物件。

2. 箭頭函式的特性二:不能用call方法修改裡面的this

這個也很好理解,我們之前一直在說,函式的this可以用call方法來手動指定,而為了減少this的複雜性,箭頭函式無法用call方法來指定this。

例子:

const obj = {
    a: () => {
        console.log(this)
    }
}
obj.a.call(`123`)  //打出來的結果依然是window物件
複製程式碼

因為上文我們說到window.setTimeout()中函式裡的this預設是window,我們也可以通過箭頭函式使它的this和外層的this保持一致:

window.setTimeout()的例子:

const obj = {
    a: function() {
        console.log(this)
        window.setTimeout(() => { 
            console.log(this) 
        }, 1000)
    }
}
obj.a.call(obj)  //第一個this是obj物件,第二個this還是obj物件
複製程式碼

想必大家明白了,函式obj.a沒有使用箭頭函式,因為它的this還是obj,而setTimeout裡的函式使用了箭頭函式,所以它會和外層的this保持一致,也是obj;如果setTimeout裡的函式沒有使用箭頭函式,那麼它打出來的應該是window物件。

四、多層物件巢狀裡函式的this

這裡是筆者在學習時遇到的一點疑惑。箭頭函式裡的this是和外層保持一致的,但是如果這個外層有好多層,那它是和哪層保持一致呢?

直接上例子:

const obj = {
    a: function() { console.log(this) },
    b: {
    	c: function() {console.log(this)}
	}
}
obj.a()  // 打出的是obj物件, 相當於obj.a.call(obj)
obj.b.c() //打出的是obj.b物件, 相當於obj.b.c.call(obj.b)
複製程式碼

上面的程式碼都符合直覺,接下來把obj.b.c對應的函式換成箭頭函式,結果如下:

const obj = {
    a: function() { console.log(this) },
    b: {
    	c: () => {console.log(this)}
	}
}
obj.a()   //沒有使用箭頭函式打出的是obj
obj.b.c()  //打出的是window物件!!
複製程式碼

obj.a呼叫後打出來的是obj物件,而obj.b.c呼叫後打出的是window物件而非obj,這表示多層物件巢狀裡箭頭函式裡this是和最最外層保持一致的。

上面的內容就是筆者學習箭頭函式中梳理出來的知識點,如有錯誤,請批評指正!這是我在掘金上寫的第三篇文章,感謝閱讀!

本文參考:this 的值到底是什麼?一次說清楚

相關文章