最近因为参与小程序的开发,本身就支持ES6的语法,所以也是对ES6一次不错的实践机会,接下去会逐一的将学习过程中涉及的常用语法和注意事项罗列出来,加深印象。
函数参数默认值
ES6之前,不能直接给函数参数指定默认值,只能用变通的方法,例如:
function log(x, y) {
y = y || 'word';
console.log(x, y);
}
log('hello'); // hello, word
log('hello', 'clearlove07'); // hello clearlove07
但上例中y
对应的值如果是false
的话,该赋值就不起作用了,例如y
是''
空字符串:
log('hello', ''); // hello word
为了避免这个问题,通常还需要对y
进行判断,看是否有赋值,如果没有再使用默认值。
ES6允许为函数的参数设置默认值,直接写在参数定义后面,例如:
function foo(x, y = 'word') {
console.log(x, y);
}
log('hello'); // hello word
log('hello', 'clearlove07'); // hello clearlove07
log('hello', ''); // hello
除了简约,这种写法还有两个好处:
代码自说明,阅读代码的人通过参数就可以一目了然的清楚哪些参数是非必填的,不需要查看文档就可以了解。
可扩展性强,调用函数方哪怕不传这个参数值,也不会影响函数的执行。
注意事项:
参数变量一旦指定默认值,函数体内不能再次声明
function foo(x = 3) {
let x = 4; // error
const x = 5; // error
}
参数有默认值是,函数参数不能同名
// 不会报错
function foo(x, x, y) {
...
}
// 报错
function foo(x, x, y = 1) {
...
}
// SyntaxError: Duplicate parameter name not allowed in this context
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。例如:
function fetch(url, { body = '', method = 'GET', headers = {} }){
console.log(method);
}
fetch('https://segmentfault.com/u/clearlove07', {});
// 'GET'
fetch('https://segmentfault.com/u/clearlove07');
// Uncaught TypeError: Cannot match against 'undefined' or 'null'
上面代码中,fetch的第二个参数是个对象的话,那么就可以个这个对象赋值默认值,但这种写法不能省略第二个参数,否则会报错。那如果希望第二个参数也有默认值,则需要双重赋值:
function fetch(url, {body = '', method = 'GET', headers = {}} = {} ){
console.log(method);
}
fetch('https://segmentfault.com/u/clearlove07'); // 'GET'
还有一个比较有意思的写法:
// 写法一
function foo1({x = 0, y = 0} = {}) {
console.log([x, y]);
}
// 写法二
function foo2({x, y} = {x:0, y:0}) {
console.log([x, y]);
}
// 函数没有参数情况下
foo1(); // [0, 0]
foo2(); // [0, 0]
// 参数x, y都有值的情况下
foo1({x: 1, y: 2}); // [1, 2]
foo2({x: 1, y: 2}); // [1, 2]
// x有值, y无值的情况下
foo1({x: 1}); // [1, 0]
foo2({x: 1}); // [1, undefined]
// x和y都无值的情况下
foo1({}); // [0, 0]
foo2({}); // [undefined, undefined]
foo1({z: 1}); // [0, 0]
foo2({z: 1}); // [undefined, undefined]
上面两种写法共同点是: 有默认值。
区别在于:
写法一: 默认值是个空对象,但是设置了对象解构赋值的默认值
写法二: 默认值是个有具体属性的对象,但是没有设置对象解构赋值的默认值
参数默认值的位置
通常情况下,定义了函数的默认值的参数,应该放在参数列表的后面,这样比较清晰那些参数可以省略的。如果是在非尾部的参数设置默认值,实际上这个参数是没办法省略的。
function foo(x = 1, y) {
return [x, y];
}
foo(); // [1, undefined]
foo(2); // [2, undefined]
foo(, 2); // Uncaught SyntaxError: Unexpected token ,
foo(undefined, 2); // [undefined, 2]
如果传入undefined,则会触发默认值的行为, 但是null则不会
function foo(x = 4, y) {
return [x, y];
}
foo(undefined, null); // [4, null]
函数length属性
函数声明/函数表达式都具有length
属性,这个属性返回函数参数长度。
指定函数参数默认之后, 函数的length
属性值将不包含指定默认值的参数个数。
let foo = function(x, y = 1) {}
foo.length // 1
let foo = function(x = 1, y) {}
foo.length // 0
let foo = function(x, y = 1, z) {}
foo.length // 1
rest参数
用于获取函数剩余参数,替代arguments
对象,形式为...args
。rest变量是数组形式。
function add(...values) {
let sum = 0;
for(let val for values) {
sum += val;
}
return sum
}
add(1, 2, 3); // 6
add()
为求和函数,可以传递任意长度的参数进行求和。
使用rest
参数替代arguments
例子:
function numSort() {
return Array.prototype.slice.call(arguments).sort();
}
// 替代方案
const numSort = (...nums) => nums.sort()
后面这种方法比较简洁,清晰明了。
rest参数中的变量代表一个数组,所以所有数组的方法都可以用于这个参数,例如:
function push(arr, ...nums) {
nums.forEach(num => {
arr.push(num);
})
return arr;
}
let arr = []
push(arr, 1, 2, 3, 4); // [1, 2, 3, 4]
注意:
rest参数后面不能有其他参数,即它为最后一个参数,否则会报错。
函数的
length
属性, 不包含rest参数
箭头函数
如果 return 值就只有一行表达式,可以省去 return,默认表示该行是返回值,否则需要加一个大括号和 return。
let foo = (x) => x * x
// 等价于
let foo = function(x) { return x * x; }
let foo = (x, y) => {
let total = x + y;
return total;
}
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错
let obj = () => { id: '1', age: 20 }
// 正常
let obj = () => ({id: '1', age: 20})
箭头函数的一个用处是简化回调函数。
[1, 2, 3].map(function(x) {
return x * x
})
// 简化
[1, 2, 3].map(x => x * x)
rest 参数与箭头函数结合的例子。
let concatArr = (num, ....items) => [num, items]
concatArr(1, 2, 3, 4) // [1, [2, 3, 4]]
使用注意事项:
-
函数体内
this
指向是定义时所在对象,而非执行时所在对象。
普通函数的this
是可变的,我们通常把函数归为两种状态: 定义时/执行时。函数中的this
始终指向执行时所在的对象。比如全局函数执行时,this
指向的是window
。对象的方法执行时,this
指向的是该对象,这就是函数this
的可变性,但箭头函数中的this
是固定不变的。看下面的例子:function obj() { setTimeout(() => console.log(this.id), 1000) } let id = 1 obj.call({id: 2}); // 2
-
箭头函数里并没有存储
this
,将箭头函数转义成ES5:// ES6 function obj() { setTimeout( () => console.log(this.id), 1000) } // ES5 function obj() { var _this = this setTimeout(function() { console.log(_this.id) }, 1000) }
由于箭头函数没有自己的
this
,所以当然也不能使用call()
、apply()
、bind()
等方法来改变this
的指向。例如:(function() { return [ (() => this.id).bind({ id: 'inner' })() ]; }).call({ id: 'outer' }) // outer
上面代码中箭头函数没有自己的
this
,所以bind
无效。内部的this
指向外部的this
。 -
在对象中使用箭头函数,
this
执行的是window
。let obj = { id: 1, foo: () =>{ console.log(this.id) } } let id = 2 obj.foo() // 2
不可以当做构造函数,也就是不能使用
new
,否则会报错。不可以使用
arguments
对象,该对象在函数内不存在。可以用rest
参数代替。不可以使用
yield
命令,因为箭头函数不能当做Generator
函数。
箭头函数可以让this
指向固化,这种特性有利于与封装回调函数。下面例子中,DOM时间的回调函数封装在一个对象里面。
let handler = {
id: 'handler',
init: function() {
document.addEventListener('click', event => this.doSomething(event.type), false)
},
doSomething: function(type) {
console.log('Handling ': type + ' id ' + this.id)
}
}
上面代码的init
方法中,使用了箭头函数,这就导致了这个箭头函数里面的this
总是指向handler
对象。否则回调函数允许时,this.doSomething
这一行就好报错,因为this
指向的是window
对象。this
指向的固化,并不是因为箭头函数内容有绑定this
的机制,实际原因是因为箭头函数内部根本就没有this
,而是一直指向外层代码块的this
。正因为箭头函数没有this
,所以也不能作为构造函数。