1.参数默认值
ES6之前变通的方法为
function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World
如果参数y
赋值了,但是对应的布尔值为false
,则该赋值不起作用。为了避免这个问题还要先判断参数y是否被赋值
if (typeof y === 'undefined') { y = 'World'; }
ES6
function log(x, y = 'World') { console.log(x, y); }
注意
1.参数变量是默认声明的,所以不能用let
或const
再次声明。
function foo(x = 5) { let x = 1; // error const x = 2; // error }
2.使用参数默认值时,函数不能有同名参数
// 不报错 function foo(x, x, y) { // ... } // 报错 function foo(x, x, y = 1) { // ... }
3.参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
let x = 99; function foo(p = x + 1) { console.log(p); } foo() // 100 x = 100; foo() // 101
与解构赋值默认值结合使用
function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined
只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo
的参数是一个对象时,变量x
和y
才会通过解构赋值生成。如果函数foo
调用时没提供参数,变量x
和y
就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5
上面代码指定,如果没有提供参数,函数foo
的参数默认为一个空对象。
function fetch(url, { body = '', method = 'GET', headers = {} }) { console.log(method); } fetch('http://example.com', {}) // "GET" fetch('http://example.com') // 报错 function fetch(url, { body = '', method = 'GET', headers = {} } = {}) { console.log(method); } fetch('http://example.com') // "GET"
// 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; } // 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
注意
1.定义了默认值的参数,应该是函数的尾参数。
2.指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
3.一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2 let x = 1; function f(y = x) { let x = 2; console.log(y); } f() // 1 function f(y = x) { let x = 2; console.log(y); } f() // ReferenceError: x is not defined
2.rest参数
ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。
function add(...values) { }
arguments
对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call
先将其转为数组。
Array.prototype.slice.call(arguments)
注意
1.rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
2.函数的length
属性,不包括 rest 参数。
3.严格模式
'use strict'
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
4.name属性
函数的name
属性,返回该函数的函数名。
function foo() {} foo.name // "foo"
如果将一个匿名函数赋值给一个变量,ES5 的name
属性,会返回空字符串,而 ES6 的name
属性会返回实际的函数名。
var f = function () {}; // ES5 f.name // "" // ES6 f.name // "f"
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name
属性都返回这个具名函数原本的名字。
const bar = function baz() {}; // ES5 ES6 bar.name // "baz"
Function
构造函数返回的函数实例,name
属性的值为anonymous
。
(new Function).name // "anonymous"
bind
返回的函数,name
属性值会加上bound
前缀。
function foo() {}; foo.bind({}).name // "bound foo" (function(){}).bind({}).name // "bound "
5.箭头函数
var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; }; //如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。 var sum = (num1, num2) => { return num1 + num2; } //如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。 let getTempItem = id => ({ id: id, name: "Temp" }); //与变量解构结合使用 const full = ({ first, last }) => first + ' ' + last; // 等同于 function full(person) { return person.first + ' ' + person.last; }
注意
1.函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。而箭头函数的this指向是固定的,指向定义生效时所在的对象。
2.不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
3.不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
4.不可以使用yield
命令,因此箭头函数不能用作 Generator 函数。
function Timer() { this.s1 = 0; this.s2 = 0; // 箭头函数 setInterval(() => this.s1++, 1000); // 普通函数 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
这种特性很有利于封装回调函数。
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); } };
此处this指向了handler,否则回调时运行会指向document对象。
实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。
不适用场景
1.定义对象的方法,且该方法内部包括this
。
const cat = { lives: 9, jumps: () => { this.lives--; } }
如果是普通函数,该方法内部的this
指向cat
;如果写成上面那样的箭头函数,使得this
指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps
箭头函数定义时的作用域就是全局作用域。
2.需要动态this
的时候
var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); });
6.尾调用优化
尾调用指某个函数的最后一步是调用另一个函数。
function f(x){ return g(x); } //不属于 function f(x){ let y = g(x); return y; } function f(x){ return g(x) + 1; }