標(biāo)簽: rest
spread
箭頭函數(shù)
JavaScript
ES6
前端
web
本博客版權(quán)歸本人和饑人谷所有,轉(zhuǎn)載需說明來源
內(nèi)容轉(zhuǎn)載自阮一峰老師的ES6入門
1.rest參數(shù)
ES6 引入 rest 參數(shù)(形式為“...變量名”),用于獲取函數(shù)的多余參數(shù)骡尽,這樣就不需要使用arguments對象了。rest 參數(shù)搭配的變量是一個數(shù)組昭娩,該變量將多余的參數(shù)放入數(shù)組中咧欣。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
上面代碼的add函數(shù)是一個求和函數(shù)臭墨,利用 rest 參數(shù)藏鹊,可以向該函數(shù)傳入任意數(shù)目的參數(shù)润讥。
下面是一個 rest 參數(shù)代替arguments變量的例子。
// arguments變量的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest參數(shù)的寫法
const sortNumbers = (...numbers) => numbers.sort();
上面代碼的兩種寫法伙判,比較后可以發(fā)現(xiàn)象对,rest 參數(shù)的寫法更自然也更簡潔。
rest 參數(shù)中的變量代表一個數(shù)組宴抚,所以數(shù)組特有的方法都可以用于這個變量。下面是一個利用 rest 參數(shù)改寫數(shù)組push方法的例子甫煞。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
注意菇曲,rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個參數(shù)),否則會報錯抚吠。
// 報錯
function f(a, ...b, c) {
// ...
}
函數(shù)的length屬性常潮,不包括 rest 參數(shù)。
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
2.擴(kuò)展運算符
含義
擴(kuò)展運算符(spread)是三個點(...)楷力。它好比 rest 參數(shù)的逆運算喊式,將一個數(shù)組轉(zhuǎn)為用逗號分隔的參數(shù)序列。
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]
該運算符主要用于函數(shù)調(diào)用萧朝。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
上面代碼中岔留,array.push(...items)和add(...numbers)這兩行,都是函數(shù)的調(diào)用检柬,它們的都使用了擴(kuò)展運算符献联。該運算符將一個數(shù)組,變?yōu)閰?shù)序列。
擴(kuò)展運算符與正常的函數(shù)參數(shù)可以結(jié)合使用里逆,非常靈活进胯。
function f(v, w, x, y, z) { }
var args = [0, 1];
f(-1, ...args, 2, ...[3]);
替代數(shù)組apply方法
由于擴(kuò)展運算符可以展開數(shù)組,所以不再需要apply方法原押,將數(shù)組轉(zhuǎn)為函數(shù)的參數(shù)了胁镐。
// ES5的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的寫法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);
下面是擴(kuò)展運算符取代apply方法的一個實際的例子,應(yīng)用Math.max方法诸衔,簡化求出一個數(shù)組最大元素的寫法盯漂。
// ES5的寫法
Math.max.apply(null, [14, 3, 77])
// ES6的寫法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
上面代碼表示,由于JavaScript不提供求數(shù)組最大元素的函數(shù)署隘,所以只能套用Math.max函數(shù)采幌,將數(shù)組轉(zhuǎn)為一個參數(shù)序列芳誓,然后求最大值。有了擴(kuò)展運算符以后,就可以直接用Math.max了雨膨。
另一個例子是通過push函數(shù),將一個數(shù)組添加到另一個數(shù)組的尾部幢泼。
// ES5的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
上面代碼的ES5寫法中渡嚣,push方法的參數(shù)不能是數(shù)組,所以只好通過apply方法變通使用push方法脾还。有了擴(kuò)展運算符伴箩,就可以直接將數(shù)組傳入push方法。
下面是另外一個例子鄙漏。
// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);
3.箭頭函數(shù)
基本用法
ES6允許使用“箭頭”(=>)定義函數(shù)嗤谚。
var f = v => v;
上面的箭頭函數(shù)等同于:
var f = function(v) {
return v;
};
如果箭頭函數(shù)不需要參數(shù)或需要多個參數(shù),就使用一個圓括號代表參數(shù)部分怔蚌。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭頭函數(shù)的代碼塊部分多于一條語句巩步,就要使用大括號將它們括起來,并且使用return語句返回桦踊。
var sum = (num1, num2) => { return num1 + num2; }
由于大括號被解釋為代碼塊椅野,所以如果箭頭函數(shù)直接返回一個對象,必須在對象外面加上括號籍胯。
var getTempItem = id => ({ id: id, name: "Temp" });
箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用竟闪。
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
箭頭函數(shù)使得表達(dá)更加簡潔。
const isEven = n => n % 2 == 0;
const square = n => n * n;
上面代碼只用了兩行杖狼,就定義了兩個簡單的工具函數(shù)炼蛤。如果不用箭頭函數(shù),可能就要占用多行本刽,而且還不如現(xiàn)在這樣寫醒目鲸湃。
箭頭函數(shù)的一個用處是簡化回調(diào)函數(shù)赠涮。
// 正常函數(shù)寫法
[1,2,3].map(function (x) {
return x * x;
});
// 箭頭函數(shù)寫法
[1,2,3].map(x => x * x);
另一個例子是
// 正常函數(shù)寫法
var result = values.sort(function (a, b) {
return a - b;
});
// 箭頭函數(shù)寫法
var result = values.sort((a, b) => a - b);
下面是rest參數(shù)與箭頭函數(shù)結(jié)合的例子。
const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
使用注意點
箭頭函數(shù)有幾個使用注意點暗挑。
(1)函數(shù)體內(nèi)的this對象笋除,就是定義時所在的對象,而不是使用時所在的對象炸裆。
(2)不可以當(dāng)作構(gòu)造函數(shù)垃它,也就是說,不可以使用new命令烹看,否則會拋出一個錯誤国拇。
(3)不可以使用arguments對象,該對象在函數(shù)體內(nèi)不存在惯殊。如果要用酱吝,可以用Rest參數(shù)代替。
(4)不可以使用yield命令土思,因此箭頭函數(shù)不能用作Generator函數(shù)务热。
上面四點中,第一點尤其值得注意己儒。this對象的指向是可變的崎岂,但是在箭頭函數(shù)中,它是固定的闪湾。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
上面代碼中冲甘,setTimeout的參數(shù)是一個箭頭函數(shù),這個箭頭函數(shù)的定義生效是在foo函數(shù)生成時途样,而它的真正執(zhí)行要等到100毫秒后江醇。如果是普通函數(shù),執(zhí)行時this應(yīng)該指向全局對象window何暇,這時應(yīng)該輸出21嫁审。但是,箭頭函數(shù)導(dǎo)致this總是指向函數(shù)定義生效時所在的對象(本例是{id: 42})赖晶,所以輸出的是42。
箭頭函數(shù)可以讓setTimeout里面的this辐烂,綁定定義時所在的作用域遏插,而不是指向運行時所在的作用域。下面是另一個例子纠修。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭頭函數(shù)
setInterval(() => this.s1++, 1000);
// 普通函數(shù)
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
上面代碼中胳嘲,Timer函數(shù)內(nèi)部設(shè)置了兩個定時器,分別使用了箭頭函數(shù)和普通函數(shù)扣草。前者的this綁定定義時所在的作用域(即Timer函數(shù))颜屠,后者的this指向運行時所在的作用域(即全局對象)。所以鹰祸,3100毫秒之后甫窟,timer.s1被更新了3次,而timer.s2一次都沒更新蛙婴。
箭頭函數(shù)可以讓this指向固定化粗井,這種特性很有利于封裝回調(diào)函數(shù)。下面是一個例子街图,DOM事件的回調(diào)函數(shù)封裝在一個對象里面浇衬。
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
上面代碼的init方法中,使用了箭頭函數(shù)餐济,這導(dǎo)致這個箭頭函數(shù)里面的this耘擂,總是指向handler對象。否則絮姆,回調(diào)函數(shù)運行時醉冤,this.doSomething這一行會報錯,因為此時this指向document對象滚朵。
this指向的固定化冤灾,并不是因為箭頭函數(shù)內(nèi)部有綁定this的機制,實際原因是箭頭函數(shù)根本沒有自己的this辕近,導(dǎo)致內(nèi)部的this就是外層代碼塊的this韵吨。正是因為它沒有this,所以也就不能用作構(gòu)造函數(shù)移宅。
所以归粉,箭頭函數(shù)轉(zhuǎn)成ES5的代碼如下。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
上面代碼中漏峰,轉(zhuǎn)換后的ES5版本清楚地說明了糠悼,箭頭函數(shù)里面根本沒有自己的this,而是引用外層的this浅乔。
請問下面的代碼之中有幾個this倔喂?
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
上面代碼之中,只有一個this靖苇,就是函數(shù)foo的this席噩,所以t1、t2贤壁、t3都輸出同樣的結(jié)果悼枢。因為所有的內(nèi)層函數(shù)都是箭頭函數(shù),都沒有自己的this脾拆,它們的this其實都是最外層foo函數(shù)的this馒索。
除了this莹妒,以下三個變量在箭頭函數(shù)之中也是不存在的,指向外層函數(shù)的對應(yīng)變量:arguments绰上、super旨怠、new.target。
function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]
上面代碼中渔期,箭頭函數(shù)內(nèi)部的變量arguments运吓,其實是函數(shù)foo的arguments變量。
另外疯趟,由于箭頭函數(shù)沒有自己的this拘哨,所以當(dāng)然也就不能用call()、apply()信峻、bind()這些方法去改變this的指向倦青。
(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']
上面代碼中,箭頭函數(shù)沒有自己的this盹舞,所以bind方法無效产镐,內(nèi)部的this指向外部的this。
長期以來踢步,JavaScript語言的this對象一直是一個令人頭痛的問題癣亚,在對象方法中使用this,必須非常小心获印。箭頭函數(shù)”綁定”this述雾,很大程度上解決了這個困擾。