带参数默认值的函数
通常在封装公共方法的时候,为了考虑方法的通用性,我们会给它配置对应的参数。为了方便调用,会配置一份初始的参数,当调用者不传该参数的时候,方法也可以正常使用。
下面以封装一个 ajax 方法为例,对比 ES5 和 ES6 对默认参数的使用方法。
ES5 中的模拟默认参数
function request(url, method, timeout, callback) {
method = method || 'POST'
timeout = timeout || 5000
callback = callback || function () {}
// 剩下的逻辑...
}
/**
* 此时,如果 timeout 为 0,依然会走后面的逻辑即 5000
*/
// 更安全的版本
function request(url, method, timeout, callback) {
method = typeof method !== 'undefined' ? method : 'POST'
timeout = typeof timeout !== 'undefined' ? timeout : 5000
callback = typeof callback !== 'undefined' ? callback : function () {}
console.log(method, timeout, callback)
// 剩下的逻辑...
}
ES6 中参数默认值
// 如果没有传参数,则使用参数默认值
function request(url, method='POST', timeout=5000, callback=function () {}) {
}
参数默认值如何影响 arguments 对象
在 ES5 中,我们可以通过 arguments 对象获取实参列表(类数组)
function getParams(one, two) {
console.log(arguments) // { '0': 1, '1': 2 }
one = 3 // 非严格模式下通过修改形参标识符会映射到 arguments 对象上
two = 4
console.log(arguments) // { '0': 3, '1': 4 }
}
// 严格模式下,则不会发生这样的情况,更加符合我们的期望
function getParams(one, two) {
'use strict'
console.log(arguments) // { '0': 1, '1': 2 }
one = 3 // 非严格模式下通过修改形参标识符会映射到 arguments 对象上
two = 4
console.log(arguments) // { '0': 1, '1': 2 }
}
getParams(1, 2)
ES6在使用了默认参数值的情况下,基本和 ES5 严格模式下表现一致
使用不具名参数
JavaScript 中的函数调用,在实参数量大于形参,或者形参数量大于实参的时候,都可以正常运行。
如何获取不定项形参的实参
// 一个累加器,用于计算传入的所有形参之和
function calculateCount() {
var count = 0
for (var prop in arguments) {
count += arguments[prop]
}
return count
}
// ES6 的剩余参数
function calculateCount(...numArr) {
// numArr 是一个实参列表的数组(不同于 arguments 的类数组)
return numArr.reduce((accumulator, currentValue) => accumulator + currentValue)
}
剩余参数的限制条件:
- 函数只能有一个剩余参数,并且它必须被放在最后
- 是剩余参数不能在对象字面量的 setter 属性中使用
let obj = {
get name() {
return 1
},
set name(...value) {
obj.name = value
}
}
obj.name = '123' // SyntaxError: Setter function argument must not be a rest parameter
console.log(obj.name)
冷知识
设计剩余参数是为了替代 ES 中的arguments。原先在 ES4 中就移除了arguments并添加了剩余参数,以便允许向函数传入不限数量的参数。尽管 ES4 从未被实施,但这个想法被保持下来并在 ES6 中被重新引入,虽然arguments仍未在语言中被移除。
扩展运算符
与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。
基本使用
var a = [1, 2, 3, 4]
var b = [...a] // [1, 2, 3, 4]
var c = {...a} // {0: 1, 1: 2, 2: 3, 3: 4}
ES6 的 name 属性
定义函数有各种各样的方式,在 JS 中识别函数就变得很有挑战性。此外,匿名函数表达式的流行使得调试有点困难,经常导致堆栈跟踪难以被阅读与解释。正因为此, ES6 给所有函数添加了name属性。
function foo() {}
console.log(foo.name) // 'foo'
const bar = function () {}
console.log(bar.name) // 'bar'
函数的双重用途:new 调用 & 直接调用
如何保证构造函数只能通过 new 的方式调用
JS 为函数提供了两个不同的内部方法:[[Call]]与[[Construct]]。当函数未使用new进行调用时,[[call]]方法会被执行,运行的是代码中显示的函数体。而当函数使用new进行调用时,[[Construct]]方法则会被执行,负责创建一个被称为新目标的新的对象,并且使用该新目标作为this去执行函数体。拥有[[Construct]]方法的函数被称为构造器。
function Person(name) {
console.log(this)
if (this instanceof Person) {
this.name = name
} else {
throw new Error('You must use new with Person.')
}
}
const person = new Person('Jack') // this => Person
const student = Person('Tom') // this => window or undefined(严格模式下)
new.target 元属性
元属性指的是“非对象”(例如new)上的一个属性,并提供关联到它的目标的附加信息。当函数的[[Construct]]方法被调用时,new.target会被填入new运算符的作用目标,该目标通常是新创建的对象实例的构造器,并且会成为函数体内部的this值。而若[[Call]]被执行,new.target的值则会是undefined。
function Person(name) {
console.log(new.target)
if (typeof new.target !== 'undefined') {
this.name = name
} else {
throw new Error('You must use new with Person.')
}
}
new Person('java') // new.target 为 Person
Person('java') // new.target 为 undefined
块级函数
箭头函数
特征:
- 没有this、super、arguments,也没有new.target绑定:this、super、arguments、以及函数内部的new.target的值由所在的、最靠近的非箭头函数来决定
- 不能被使用new调用:箭头函数没有[[Construct]]方法,因此不能被用为构造函数,使用new调用箭头函数会抛出错误。
- 没有原型:既然不能对箭头函数使用new,那么它也不需要原型,也就是没有prototype属性。
- 不能更改this:this的值在函数内部不能被修改,在函数的整个生命周期内其值会保持不变。this的值由声明时的环境决定
var someHandler = {
init: function () {
console.log(this, 'init')
},
doSomething: () => {
console.log(this, 'doSomething')
}
}
someHandler.init() // this => someHandler
someHandler.doSomething() // this => window
- 没有arguments对象:既然箭头函数没有arguments绑定,你必须依赖于具名参数或剩余参数来访问函数的参数。
- 不允许重复的具名参数:箭头函数不允许拥有重复的具名参数,无论是否在严格模式下;而相对来说,传统函数只有在严格模式下才禁止这种重复。
var func = function (a, a) {}
var arrowFunc = (a, a) => {} // SyntaxError: Duplicate parameter name not allowed in this context
基本形式
var foo = (val) => {
// doSomething...
return val
}
// 等效于
var foo = val => val