箭頭函數(shù)是匿名函數(shù)渣慕,ES5匿名函數(shù)的語法糖嘶炭;但又增加了ES5所沒有的一些優(yōu)點(diǎn)抱慌,接下來我們一起來看一看箭頭函數(shù)
// ES5
vat tt = function tt() {
return 55 + 99;
}
// ES6
vat tt = () => 55 + 99
// 是不是一對(duì)比,寫法的差異就看出來了
ES6增加了箭頭函數(shù):
let func = value => value;
// 相當(dāng)于
let func = function () {
return value;
}
// 如果需要給函數(shù)傳入多個(gè)參數(shù):
let func = (value, num) => value * num;
// 如果函數(shù)的代碼塊需要多條語句:
let func = (value, num) => {
return value * num;
}
// 如果需要直接返回一個(gè)對(duì)象:
let func = (value, num) => ({total: value * num});
// 與變量解構(gòu)結(jié)合
let func = ({value, num}) => ({total: value * num})
// 使用
var result = func({
value: 10,
num: 10
})
console.log(result); // {total: 100}
以上是箭頭函數(shù)的用法眨猎,了解了箭頭函數(shù)的用法之后呢抑进,我們一起來進(jìn)一步的看一看箭頭函數(shù),到底和我們的普通函數(shù)有什么區(qū)別睡陪,好處在哪寺渗?
1.沒有this
箭頭函數(shù)沒有this,所以需要通過查找作用域鏈來確定this的值兰迫。
這就意味著如果箭頭函數(shù)被非箭頭函數(shù)包含信殊,this綁定的就是最近一層非箭頭函數(shù)的this。
模擬一個(gè)實(shí)際開發(fā)中的例子:
我們的需求是點(diǎn)擊一個(gè)按鈕汁果,改變?cè)摪粹o的背景色鸡号。
為了方便開發(fā),我們抽離一個(gè)Button組件须鼎,當(dāng)需要使用的時(shí)候直接:傳入元素id值即可綁定該元素點(diǎn)擊時(shí)改變背景色的事件
new Button('button');
// HTML代碼如下:
<button id="button">點(diǎn)擊變色</button>
// javascript代碼:
function Button(id) {
this.element = document.querySelector('#' + id);
this.bindEvent();
}
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", this.setBgColor, false);
}
Button.prototype.setBgColor = function() {
this.element.style.backgroundColor = '#abcdef';
}
vat button = new Button('button');
看著好像沒有問題鲸伴,結(jié)果卻是報(bào)錯(cuò) Uncaught TypeError: Cannot read property 'style' of undefined
這是因?yàn)楫?dāng)使用addEventListener() 為一個(gè)元素注冊(cè)事件的時(shí)候,事件函數(shù)里的this值是該元素的引用晋控。
所以如果我們?cè)趕etBgColor中console.log(this), this指向的是按鈕元素汞窗,那this.element就是undefined, 報(bào)錯(cuò)自然就理所當(dāng)然了赡译。
也許你會(huì)問仲吏,既然this都指向了按鈕元素,那我們直接修改setBgColor函數(shù)為:
Button.prototype.setBgColor = function() {
this.style.backgroundColor = '#abcdef';
}
不就可以解決這個(gè)問題了蝌焚?
確實(shí)可以這樣做裹唆,但是在實(shí)際開發(fā)中,我們可能會(huì)在setBgColor中還調(diào)用其他的函數(shù)只洒,比如寫成這樣:
Button.prototype.setBgColor = function() {
this.setElementColor();
this.setOtherElementColor();
}
所以我們還是希望setBgColor中的this是指向?qū)嵗龑?duì)象的许帐,這樣就可以調(diào)用其他的函數(shù)。
利用ES5毕谴,我們一般會(huì)這樣做:
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", this.setBgColor.bind(this), false);
}
為避免addEventListener的影響成畦,使用bind強(qiáng)制綁定setBgColor()的this為實(shí)例對(duì)象
使用ES6,我們可以更好的解決這個(gè)問題:
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", event => this.setBgColor(event), false);
}
由于箭頭函數(shù)沒有this涝开,所以會(huì)向外層查找this的值循帐,即bindEvent中的this,此時(shí)this指向?qū)嵗龑?duì)象舀武,所以可以正確的調(diào)用this.setBgColor方法拄养,而this.setBgColor中的this也會(huì)正確指向?qū)嵗龑?duì)象。
在這里再額外提一點(diǎn)银舱,就是注意bindEvent和setBgColor在這里使用的是普通函數(shù)的形式瘪匿,而非箭頭函數(shù)跛梗,如果我們改成箭頭函數(shù),會(huì)導(dǎo)致函數(shù)里的this指向window對(duì)象(非嚴(yán)格模式下)柿顶。
最后,因?yàn)榧^函數(shù)沒有this操软,所以也不能用call()嘁锯、apply()、bind()這些方法改變this的指向聂薪,可以看一個(gè)例子:
var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1
2.沒有arguments
箭頭函數(shù)沒有自己的arguments對(duì)象家乘,這不一定是件壞事,因?yàn)榧^函數(shù)可以訪問外圍函數(shù)的arguments對(duì)象:
function constant() {
return () => arguments[0];
}
var result = constant(1);
console.log(result()); // 1
那如果我們就是要訪問箭頭函數(shù)的參數(shù)呢藏澳?
你可以通過命名參數(shù)或者rest參數(shù)的形式訪問參數(shù):
let nums = (...nums) => nums;
3.不能通過new關(guān)鍵字調(diào)用
Javascript函數(shù)有兩個(gè)內(nèi)部方法:[[Call]]和[[Construct]]仁锯。
當(dāng)通過new調(diào)用函數(shù)時(shí),執(zhí)行[[Construct]]方法翔悠,創(chuàng)建一個(gè)實(shí)例對(duì)象业崖,然后再執(zhí)行函數(shù)體,將this綁定到實(shí)例上蓄愁。
當(dāng)直接調(diào)用的時(shí)候双炕,執(zhí)行[[Call]]方法,直接執(zhí)行函數(shù)體撮抓。
箭頭函數(shù)并沒有[[Construct]]方法妇斤,不能被用作構(gòu)造函數(shù),如果通過new的方式調(diào)用丹拯,會(huì)報(bào)錯(cuò)站超。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
4.沒有原型
由于不能使用new調(diào)用箭頭函數(shù),所以也沒有構(gòu)建原型的需求乖酬,于是箭頭函數(shù)也不存在prototype這個(gè)屬性死相。
5.沒有super
連原型都沒有,自然也不能通過super來訪問原型的屬性咬像,所以箭頭函數(shù)也沒有super的媳纬,不過跟this、arguments一樣施掏,這些值由外圍最近一層非箭頭函數(shù)決定钮惠。
在出現(xiàn)了箭頭函數(shù)之后是不是就可以舍棄原來的普通函數(shù)了呢?答案一定是否定的七芭,一些情況下我們最好不要去進(jìn)行箭頭函數(shù)的操作素挽,那都有哪些情況呢?
(1) 在對(duì)象上定義函數(shù)
先來看下面這段代碼
var obj = {
array: [1, 2, 3],
sum: () => {
console.log(this === window); // => true
return this.array.reduce((result, item) => result + item);
}
}
obj.sum();
// Throws "TypeError: Cannot read property 'reduce' of undefined"
sum方法定義在obj對(duì)象上狸驳,當(dāng)調(diào)用的時(shí)候我們發(fā)現(xiàn)拋出了一個(gè)TypeError预明,因?yàn)楹瘮?shù)中的this是window對(duì)象缩赛,所以this.array也是undefined。原因也很簡(jiǎn)單撰糠,相信只要了解es6 箭頭函數(shù)的都知道
箭頭函數(shù)沒有它自己的this值酥馍,箭頭函數(shù)內(nèi)的this值繼承自外圍作用域
解決方法也很簡(jiǎn)單,就是不用唄阅酪。這里可以用es6里函數(shù)表達(dá)式的簡(jiǎn)潔語法旨袒,在這種情況下,this值就取決于函數(shù)的調(diào)用方式了术辐。
var obj = {
array: [1, 2, 3],
sum() {
console.log(this === obj); // => true
return this.array.reduce((result, item) => result + item);
}
}
obj.sum(); // => 6
通過object.method()語法調(diào)用的方法使用非箭頭函數(shù)定義砚尽,這些函數(shù)需要從調(diào)用者的作用域中獲取一個(gè)有意思的this值。
(2)在原型上定義函數(shù)
在對(duì)象原型上定義函數(shù)也是遵循著一樣的規(guī)則
function Person(pName) {
this.pName = pName;
}
Person.prototype.sayName = () => {
console.log(this === window); // => true
return this.pName;
}
var person = new Person('zhangSan');
person.sayName(); // => undefined
// 使用function函數(shù)表達(dá)式
function Person(pName) {
this.pName = pName;
}
Person.prototype.sayName = function() {
console.log(this === person); // => true
return this.pName;
}
var person = new Person('zhangSan');
person.sayName(); // => zhangSan
(3)動(dòng)態(tài)上下文中的回調(diào)函數(shù)
this是js中非常強(qiáng)大的特點(diǎn)辉词,他讓函數(shù)可以根據(jù)其調(diào)用方式動(dòng)態(tài)的改變上下文必孤,然后箭頭函數(shù)直接在聲明時(shí)就綁定了this對(duì)象,所以不再是動(dòng)態(tài)的瑞躺。
在客戶端敷搪,在dom元素上綁定事件監(jiān)聽函數(shù)是非常普遍的行為,在dom事件被觸發(fā)時(shí)幢哨,回調(diào)函數(shù)中的this指向該dom购啄,可當(dāng)我們使用箭頭函數(shù)時(shí):
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'clicked button';
})
因?yàn)檫@個(gè)回調(diào)的箭頭函數(shù)是在全局上下文中被定義的,所以他的this是window嘱么。所以當(dāng)this是由目標(biāo)對(duì)象決定時(shí)狮含,我們應(yīng)該使用函數(shù)表達(dá)式:
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'clicked button';
})
(4)構(gòu)造函數(shù)中
在構(gòu)造函數(shù)中,this指向新創(chuàng)建的對(duì)象實(shí)例
this instanceOf MyFunction === true
需要注意的是曼振,構(gòu)造函數(shù)不能使用箭頭函數(shù)几迄,如果這樣做會(huì)拋出異常
var Person = (name) => {
this.name = name;
}
var person = new Person('zhangSan');
// Uncaught TypeError: Person is not a constructor
理論上來說也是不能這么做的,因?yàn)榧^函數(shù)在創(chuàng)建時(shí)this對(duì)象就綁定了冰评,更不會(huì)指向?qū)ο髮?shí)例映胁。
(5)太簡(jiǎn)短的函數(shù)
箭頭函數(shù)可以讓語句寫的非常的簡(jiǎn)潔,但是一個(gè)真實(shí)的項(xiàng)目甲雅,一般由多個(gè)開發(fā)者共同協(xié)作完成解孙,就算由單人完成,后期也并不一定是同一個(gè)人維護(hù)抛人,箭頭函數(shù)有時(shí)候并不會(huì)讓人很好的理解弛姜,比如
let multiply = (a, b) => b === undefined ? b => a * b : a * b;
let double = multiply(2);
double(3); // => 6
multiply(2,3); // => 6
這個(gè)函數(shù)的作用就是當(dāng)只有一個(gè)參數(shù)a時(shí),返回接受一個(gè)參數(shù)b返回a * b的函數(shù)妖枚,接受兩個(gè)參數(shù)時(shí)直接返回乘積廷臼,這個(gè)函數(shù)可以很好的工作并且看起來很簡(jiǎn)潔,但是從第一眼看去并不是很好理解。為了讓這個(gè)函數(shù)更好的讓人理解荠商,我們可以為這個(gè)箭頭函數(shù)加一對(duì)花括號(hào)寂恬,并加上return語句買或者直接使用函數(shù)表達(dá)式:
function multiply(a, b) {
if (b === undefined) {
return function (b) {
return a * b;
}
}
return a * b;
}
let double = multiplu(2);
double(3); // => 6;
multiply(2, 3); // => 6
總結(jié)
最后,關(guān)于箭頭函數(shù)莱没,引用MDN的介紹就是:箭頭函數(shù)表達(dá)式的語法比函數(shù)表達(dá)式更短初肉,并且不綁定自己的this,arguments饰躲,super牙咏。
毫無疑問,箭頭函數(shù)帶來了很多便利属铁。恰當(dāng)?shù)氖褂眉^函數(shù)可以讓我們避免使用早起的.bind()函數(shù)或者需要固定上下文的地方并且讓代碼更加簡(jiǎn)潔眠寿。
箭頭函數(shù)也有一些不便利的地方躬翁。我們?cè)谛枰獎(jiǎng)討B(tài)上下文的地方不能使用箭頭函數(shù):定義需要?jiǎng)討B(tài)上下文的函數(shù)焦蘑,構(gòu)造函數(shù),需要this對(duì)象作為目標(biāo)的回調(diào)函數(shù)以及用箭頭函數(shù)難以理解的語句盒发。在其他情況下例嘱,請(qǐng)盡情使用箭頭函數(shù)。