函数参数的默认值
默认参数作用域与暂时性死区
给多个参数定义默认值实际上跟使用 let 关键字顺序声明变量一样。
function makeKing(name = 'Henry', numerals = 'VIII') {
return `King ${name} ${numerals}`
}
console.log(makeKing()) // King Henry VIII
因为参数是按顺序初始化的,所以后定义默认值的参数可以引用先定义的参数。
function makeKing(name = 'Henry', numerals = name) {
return `King ${name} ${numerals}`
}
console.log(makeKing()) // King Henry Henry
参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的。像这样就会抛出错误:
function makeKing(name = numerals, numerals = 'VIII') {
return `King ${name} ${numerals}`
}
console.log(makeKing()) // Cannot access 'numerals' before initialization
参数也存在于自己的作用域中,它们不能引用函数体的作用域:
function makeKing(name = 'Henry', numerals = defaultNumeral) {
let defaultNumeral = 'VIII'
return `King ${name} ${numerals}`
}
函数参数的默认值与解构赋值的默认值结合使用
function fetch(url, { body = '', method = 'GET', headers = {}}) {
console.log(method)
}
fetch('http://example.com', {}) // GET
fetch('http://example.com') // 不能省略第二个参数,报错
上面代码,如果函数 fetch 的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时就出现了双重默认值。
双重默认值
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method)
}
fetch('http://example.com') // GET
上面代码中,函数 fetch 没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量 method 才会取到默认值 GET。
触发默认值
如果传入 undefined 将触发该参数等于默认值,null 则没有这个效果。
function foo(x = 5,y = 6){
console.log(x, y)
}
foo(undefined,undefined) // 5 6
foo(undefined,null) // 5 null
参数默认值应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。
function throwIfMissing() {
throw new Error('Missing parameter')
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided
}
foo()
从上面代码还可以看到,参数 mustBeProvided 的默认值等于 throwIfMissing 函数的运行结果,这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。
另外,可以将默认值设为 undefined 表明这个参数是可以省略的。
function foo(optional = undefined) {...}
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时是不会出现的。
var x = 1
function f(x, y = x) {
console.log(y)
}
f(2) // 2
上面代码中,参数 y 的默认值等于变量 x 。调用函数 f 时,参数形成一个单独的作用域。在这个作用域内,默认值变量 x 指向第一个参数 x ,而不是全局变量 x 所以输出 2。
let x = 1
function f(y = x) {
let x = 2
console.log(y)
}
f() // 1
上面代码中,函数 f 调用时,参数 y = x 形成一个单独的作用域。这个作用域里面,变量 x 本身没有定义,所以指向外层的全局变量 x。
函数调用时,函数体内部的局部变量 x 影响不到默认值变量 x 。作用域链原理,只能访问外层作用域,不能访问内存作用域。
如果此时全局变量 x 不存在,就会报错。
function f( y = x) {
let x = 2
console.log(y)
}
f() // ReferenceError: x is not defined
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。
let foo = 'outer'
function bar(func = ()=> foo) {
let foo = 'inner'
console.log(func())
}
bar() // outer
上面代码中,函数 bar 的参数 func 的默认值是一个匿名函数,返回值为变量 foo 。
函数参数形成的单独作用域里面,并没有定义变量 foo,所以 foo 指向外层的全局变量 foo,因此输出 outer。
扩展参数
在给函数传参时,有时候可能不需要传一个数组,而是要分别传入数组的元素。假设有如下函数定义,它会将所有传入的参数累加起来:
let values = [1, 2, 3, 4]
function getSum() {
let sum = 0
for (let i = 0; i < arguments.length; ++i) {
sum += arguments[i]
}
return sum
}
这个函数希望将所有加数逐个传递进来,然后通过迭代 arguments 对象来实现累加。如果不使用扩展操作符,想把定义在这个函数外面的数组拆分,那么就得求助于 apply() 方法:
getSum.apply(null, values) // 10
但在 ES6 中可以通过扩展操作符极为简洁的实现这种操作。对可迭代对象应用扩展操作符,并将其作为一个参数传入,可以将可迭代对象拆分,并将迭代返回的每个值单独传入。比如,使用扩展操作符可以将前面例子的数组像这样直接传给函数:
getSum(...values) // 10
剩余参数
收集参数的结果会得到一个实例 Array,是一个数组。
// 顺序累加 values 中的所有值
function getSum(...values) {
return values.reduce((x,y) => x + y, 0)
}
getSum(1,2,3) // 6
剩余参数的前面如果还有命名参数,则只会收集其余的参数;如果没有则会得到空数组。因为剩余参数的结果可变,所以只能把它作为最后一个参数。
function ignoreFirst(firstValue, ...values) {
console.log(values)
}
ignoreFirst() // []
ignoreFirst(1) // []
ignoreFirst(1, 2) // [2]
ignoreFirst(1, 2, 3) // [2,3]
箭头函数
箭头函数的this
箭头函数不绑定 this ,其 this 就是外层作用域(首先要有作用域)的 this。
箭头函数中的 this 指向外层作用域的 this ,而在这里它的外层 {} 不构成单独的作用域,所以继续找,直到找到全局作用域 window。
let obj = {
log:function() {
setTimeout(()=> {
console.log(this) // obj log是个函数,函数有作用域,所以 this 在 log 这找到了
},1000)
setTimeout(function() {
console.log(this) // window
},1000)
}
}
obj.log()
arguments
箭头函数没有 arguments。
因为箭头函数没有 arguments,自身找不到 arguments 会向上层作用域查找,此处会去 window 中查找,window 中没有 arguments 属性,所以报错。node 中有 arguments。
var foo = ()=> { console.log(arguments) }
foo() // arguments is not defined
简化回调函数
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => ({ id: id, name: "Temp" })