JavaScript在ES6語法中新增了箭頭函數,相較於傳統函數,箭頭函數不只更加簡潔,並且在this方面進行了改進。this做爲JavaScript中比較詭異的存在,許多文章對於this的解釋也不盡相同,本篇文章試圖釐清JS中函數與this的關係。bash
在ES6語法以前,JS中的函數由function關鍵字、params參數和被花括號包裹的函數體組成。爲了與後面說到的箭頭函數相區別,咱們先把這樣的函數叫作常規函數,常規函數既能夠用聲明式寫法也能夠用賦值式寫法。例子:函數
function test(name) { //聲明式寫法
console.log(name)
}
test('Jerry')
let test2 = function(name) { //賦值式寫法
console.log(name)
}
test2('Tom')
複製代碼
ES6箭頭函數的引入,使函數的寫法變的更加簡潔,但在書寫上要遵循必定的規則。學習
規則一:箭頭函數只能用賦值式寫法,不能用聲明式寫法優化
例子:ui
const test = (name) => {
console.log(name)
}
test('Jerry')
複製代碼
規則二:若是參數只有一個,能夠不加括號,若是沒有參數或者參數多於一個就須要加括號this
例子:spa
const test = name => {
console.log(name)
}
test('Jerry')
const test2 = (name1, name2) => {
console.log(name1 + ' and ' + name2)
}
test2('Tom', 'Jerry')
複製代碼
規則三:若是函數體只有一句話,能夠不加花括號code
例子:cdn
const test = name => console.log(name)
複製代碼
規則四:若是函數體沒有括號,能夠不寫return,箭頭函數會幫你return對象
例子:
const add = (p1, p2) => p1 + p2
add(10, 25)
複製代碼
記住:函數體的花括號與return關鍵字同在。
從以上的例子咱們能夠看出,箭頭函數對常規函數的圓括號和花括號都進行了簡化。除了這些簡化,箭頭函數對於常規函數最大的優化之處在於this。
在探討箭頭函數對於this的優化以前,咱們先得明白this到底是什麼,以及它是如何使用的。this是使用call方法調用函數時傳遞的第一個參數,它能夠在函數調用時修改,在函數沒有調用的時候,this的值是沒法肯定。
若是沒有使用過call方法來調用函數的話,上面的對於this的定義可能不太明白。那麼咱們須要先理解函數調用的兩種方法。
第一種方法最多見,例子以下:
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對象。
直接看例子:
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,已經被咱們改過了。
構造函數裏的this稍微有點特殊,每一個構造函數在new以後都會返回一個對象,這個對象就是this,也就是context上下文。
例子:
function Test() {
this.name = 'Tom'
}
let p = new Test()
console.log(typeof p) //object
console.log(p.name) // Tom
複製代碼
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是函數用call方法調用時傳遞的第一個參數,並且它還能夠手動更改,這樣要肯定this的值就太麻煩了。不過,箭頭函數的出現給咱們肯定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對象。
這個也很好理解,咱們以前一直在說,函數的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是和外層保持一致的,可是若是這個外層有好多層,那它是和哪層保持一致呢?
直接上例子:
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 的值究竟是什麼?一次說清楚