如同我们所看到的, ES6 中引入来箭头函数, 相比 ES5 来讲是最为直观而明显的特性.
在 ES6 之前, 声明一个函数:
- function add(a, b) {
- return a + b;
- }
- add(1, 2); // 3
如果用箭头函数的形式写:
- const add = (a, b) => a + b;
- add(1, 2); // 3
计算圆面积的例子:
- const square = (r) => {
- const PI = 3.14;
- return PI*r*r;
- }
- square(4);
形式上的改变仅仅是一部分, 接下来关注一个很有用的特性: 默认值.
默认值
在 ES6 之前, 函数内给定参数的默认值, 只能这样:
- function add(a, b) {
- console.log(arguments[0]);
- a = a || 2000;
- b = b || 333;
- return a + b;
- }
- add(); // 2333
但是此方案遇到 boolean 为 false 的 js 值无法工作, 例如:
add(0); // 2333
显然, 结果错误. 运算中 a 仍旧采用了默认值.
所以, 当函数传参为
null, undefined, '', 0, -0, NaN
时, 短路操作符 || 会给定默认值.
修订版成了这样:
- function add(a, b) {
- a = (typeof a !== 'undefined') ? a : 2000;
- b = (typeof b !== 'undefined') ? b : 333;
- return a + b;
- }
- add(0); // 333
而在 ES6 中, 只需要给参数直接赋予默认值即可:
- const add = (a = 2000, b = 333) => a + b;
- add(); // 2333
- add(0); // 333
默认值对 arguments 的影响
在 ES5 中:
- function add(a, b) {
- console.log(arguments.length);
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- a = 200;
- b = 33;
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- }
- add(1, 2);
- // 2
- // true
- // true
- // true
- // true
如果在严格模式下运行, 结果是这样的:
- function add(a, b) {
- 'use strict';
- console.log(arguments.length);
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- a = 200;
- b = 33;
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- }
- add(1, 2);
- // 2
- // true
- // true
- // false
- // false
在 ES5 中的非严格模式下, 更改参数值将会同步的更改 arguments 对象内的参数值, 而在严格模式下不会更改.
对于 ES6 来说, 无论是否在严格模式下, 更改参数值的行为都不会同步更改 arguments 对象.
- function add(a = 2000, b = 333) {
- console.log(arguments.length);
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- a = 200;
- b = 33;
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- }
- add(0);
- // 1
- // true
- // false
- // false
- // false
再来看个例子:
- const add = (a = 2000, b = 333) => {
- console.log(arguments.length);
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- a = 200;
- b = 33;
- console.log(a === arguments[0]);
- console.log(b === arguments[1]);
- }
- add(0);
- // Uncaught ReferenceError: arguments is not defined
另外需要注意的点是, 箭头函数没有自己的 arguments 对象. 但它可以访问外围函数的 arguments 对象:
- function add(a = 2000, b = 333) {
- return (() => arguments[0] + arguments[1])();
- }
- add(); // NaN
- add(200, 33); // 233
由于箭头函数没有自己的 arguments 对象, 我们想要对箭头函数的参数进行扩展和处理, 所以 ES6 中引入了 rest 参数.
rest 参数
ES6 内 rest 参数的实现实际上更符合现代编程语言的直觉. 在 ES5 中操作 arguments 对象的时候, 由于 arguments 对象并非数组对象, 需要先把它转换为数组对象来使用数组的方法.
- function numSort() {
- return Array.prototype.slice.call(arguments).sort();
- }
- numSort(3, 6, 5, 1); // [1, 3, 5, 6]
而如果使用 rest 参数:
- function numSort(...nums) {
- return nums.sort()
- }
- numSort(3, 6, 5, 1); // [1, 3, 5, 6]
nums 是一个纯数组对象, 在 python 中类似的写法也称为不定参数的写法. 因为 python 的参数个数是指定的, 使用不定参数的方式是直接以字典或列表作为参数直接传进去, 这样不够优雅, 有了 *args 的写法.
而在 js 中本身具有 arguments 对象, 天然可以利用它来对参数进行操作, 引入箭头函数没有 arguments 对象, 弥补这样的一个不足, 引入 rest 参数这种更优雅的方式.
需要注意, 如果同时传入 rest 参数和非 rest 参数:
- function numSort(first, ...other) {
- return other.sort()
- }
- numSort(3, 6, 5, 1); // [1, 5, 6]
rest 参数代表的也是一个指定的参数列表, 而非全部参数. 另外, 如果对调普通参数和 rest 参数的位置:
- function numSort(...other, last) {
- return other.sort()
- }
- numSort(3, 6, 5, 1); // Uncaught SyntaxError: Rest parameter must be last formal parameter
表明了, rest 参数只能位于参数列表的最后一位.
this
在 ES6 的箭头函数中, this 值总是绑定到函数的定义:
- const obj = {
- name: 'Rainy',
- say: function () {
- setTimeout(() => {
- console.log(`I'm ${this.name}`);
- })
- }
- }
- obj.say(); // I'm Rainy
而如果是普通函数, 由于 setTimeout 函数内的 this 指向 window, 所以找不到 name 值:
- const obj = {
- name: 'Rainy',
- say: function () {
- setTimeout(function () {
- console.log(`I'm ${this.name}`);
- })
- }
- }
- obj.say(); // I'm
可以将箭头函数的该行为看作:
- const obj = {
- name: 'Rainy',
- say: function () {
- const self = this;
- setTimeout(function () {
- console.log(`I'm ${self.name}`);
- })
- }
- }
- obj.say(); // I'm Rainy
实际上这是由于箭头函数本身没有 this, 其中的 this 实际上是外部函数的 this 绑定.
且箭头函数中的 this 值无法改变:
- const obj = {
- name: 'Rainy',
- say: function () {
- setTimeout((() => console.log(`I'm ${this.name}`)).bind({name:'Null'}))
- }
- }
- obj.say(); // I'm Rainy
而非箭头函数:
- const obj = {
- name: 'Rainy',
- say: function () {
- setTimeout((function () {console.log(`I'm ${this.name}`)}).bind({name:'Null'}))
- }
- }
- obj.say(); // I'm Null
也正因为如此, 箭头函数无法作为构造函数, 因为 this 值无法绑定至构造函数的实例.
参考
深入理解 ES6
实战 ES2015
- MDN https://developer.mozilla.org/zh-CN/
- -EOF-
- Update
2018-05-16: 增加 rest 参数相关
来源: https://juejin.im/entry/5afb9483f265da0b84559865