ES6 函数的扩展
函数参数的默认值
认识函数参数的默认值
调用函数的时候传参了,就用传递的参数;如果没传参,就用默认值
函数参数默认值的基本用法
// 之前的默认值实现方式
const multiply = (x, y) => {
if (typeof y === 'undefined') {
y = 3
}
return x * y
}
console.log(multiply(2, 2)) // 4
console.log(multiply(2)) // 6
// ES6 默认值实现方式
const multiply = (x, y = 3) => {
return x * y
}
console.log(multiply(2, 2)) // 4
console.log(multiply(2)) // 6
默认值的生效条件
不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效。
注意:null 就是 null,不会使用默认值。
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
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')
// 报错
上面代码中,如果函数fetch()
的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
console.log(method)
}
fetch('http://example.com')
// "GET"
上面代码中,函数fetch
没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method
才会取到默认值GET
。
注意,函数参数的默认值生效以后,参数解构赋值依然会进行。
function f({ a, b = 'world' } = { a: 'hello' }) {
console.log(b)
}
f() // world
上面示例中,函数f()
调用时没有参数,所以参数默认值{ a: 'hello' }
生效,然后再对这个默认值进行解构赋值,从而触发参数变量b
的默认值生效。
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
上面代码中,有默认值的参数都不是尾参数。这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined
。
如果传入undefined
,将触发该参数等于默认值,null
则没有这个效果。
function foo(x = 5, y = 6) {
console.log(x, y)
}
foo(undefined, null)
// 5 null
上面代码中,x
参数对应undefined
,结果触发了默认值,y
参数等于null
,就没有触发默认值。
函数参数默认值的应用
接收很多参数的时候
// 普通时候
const logUser = (username = 'zjr', age = 18, sex = 'male') => {
console.log(username, age, sex)
}
// 需要能够记住参数的顺序,如果参数较多那么需要配合文档,使用不方便
logUser('jerry', 18, 'male')
// ------------------------------------------------------------
// 接收一个对象作为参数
// 不需要记住参数的顺序
const logUser = (options) => {
console.log(options.username, options.age, options.sex)
}
logUser({
username: 'jerry',
age: 18,
sex: 'male'
})
// ------------------------------------------------------------
// 再优化
const logUser = ({ username, age, sex }) => {
console.log(username, age, sex)
}
logUser({
username: 'jerry',
age: 18,
sex: 'male'
})
// ------------------------------------------------------------
// 引入默认值
const logUser = ({ username = 'zjr', age = 18, sex = 'male' }) => {
console.log(username, age, sex)
}
// 其实是解构赋值原理
logUser({ username: 'jerry' }) // jerry 18 male
logUser({}) // zjr 18 male
logUser() // 报错,因为这样相当于传了一个 undefined,不符合解构赋值
// ------------------------------------------------------------
// 再优化(函数默认值 + 解构赋值 + 解构赋值默认值)
const logUser = ({ username = 'zjr', age = 18, sex = 'male' } = {}) => {
console.log(username, age, sex)
}
logUser() // zjr 18 male
/*
解释:
1、options 与 {username = 'zjr', age = 18, sex = 'male'} 互等
2、{username = 'zjr', age = 18, sex = 'male'} = {} 其实就是 options = {}
3、由于 logUser() 的实参为 undefined,所以默认值为 {}
4、再因为 {username = 'zjr', age = 18, sex = 'male'} = {} 是解构赋值
5、由于 {} 内为 undefined,所以解构赋值启用默认值
5、所以真正的形参为 {username = 'zjr', age = 18, sex = 'male'}
注明:这样做的好处是增加函数的健壮性!
*/
某一个参数不得省略,如果省略就抛出一个错误
function throwIfMissing() {
throw new Error('Missing parameter')
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided
}
foo()
// Error: Missing parameter
上面代码的foo
函数,如果调用的时候没有参数,就会调用默认值throwIfMissing
函数,从而抛出一个错误。
从上面代码还可以看到,参数mustBeProvided
的默认值等于throwIfMissing
函数的运行结果(注意函数名throwIfMissing
之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。
另外,可以将参数默认值设为undefined
,表明这个参数是可以省略的。
function foo(optional = undefined) { ··· }
rest 参数
前言
剩余语法(Rest syntax 也可以叫剩余参数)看起来和展开语法完全相同都是使用 ...
的语法糖,不同之处在于剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来成为一个整体。
函数参数
在讲解剩余参数前,我们先来看看,剩余参数在函数参数中都解决了哪些问题?为什么会引入剩余参数的概念?
在 ES5 中,函数经常会传入不定参数,在传入不定参数时,ES5 的给出的解决方案是通过 arguments
对象来获取函数调用时传递的参数。 arguments
对象不是一个数组,它是一个类数组对象,所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。
一个简单的类数组对象是长这样的:
var arrLike = {
0: 'name',
1: 'age',
2: 'job',
length: 3
}
而它所对应的数组应该是这样子的:
var arr = ['name', 'age', 'job']
这里我们说类数组对象与数组的性质相似,是因为类数组对象在访问、赋值、获取长度上的操作与数组是一致的,具体内容可查阅相关的类数组使用。
在函数体中定义了 Arguments 对象,其包含函数的参数和其它属性,以 arguments
变量来指代。下面我们看个实例:
function fn() {
console.log(arguments)
}
fn('imooc', 7, 'ES6')
在控制台中打印出上面的代码结果,如下图所示:在定义函数的时候没有给定参数,但是通过 arguments
对象可以拿到传入的参数。可以看到 arguments
中包含了函数传递的参数、length 等属性,length 属性表示的是实参的长度,即调用函数的时候传入的参数个数。这样我们就对 arguments
对象有了一定的了解。
在 ES5 的开发模式下,想要使用传递的参数,则需要按位置把对应的参数取出来。尽管 arguments
是一个类数组且可遍历的变量,但它终究不是数组,它不支持数组方法,因此我们不能调用 arguments.forEeach (…)
等数组的方法。需要使用一些特殊的方法转换成数组使用,如:
function fn() {
var arr = [].slice.call(arguments)
console.log(arr)
}
fn('ES6')
// ["ES6"]
fn('imooc', 7, 'ES6')
// ["imooc", 7, "ES6"]
终于借助 call
方法把 arguments
转化成一个真正的数组了。但是这样无疑是一个繁琐的过程,而且不容易理解。这时 ES6 给出了它的完美解决方案 —— 剩余参数,那剩余参数是如何在函数传参中使用的呢?下面我们来看看实例:
语法:const add = (x, y, z, ...args) => {};
function fn(...args) {
console.log(args)
}
fn('ES6')
// ["ES6"]
fn('imooc', 7, 'ES6')
// ["imooc", 7, "ES6"]
使用方式很简单在函数定义时使用 ...
紧接着跟一个收集的参数,这个收集的参数就是我们所传入不定参数的集合 —— 也就是数组。这样就很简单地摆脱了 arguments 的束缚。另外,还可以指定一个默认的参数,如下示例:
function fn(name, ...args) {
console.log(name) // 基础参数
console.log(args) // 剩下的参数组成的数组
}
fn('ES6')
// 'ES6'
// []
fn('imooc', 7, 'ES6')
// "imooc"
// [7, "ES6"]
上面的代码中给函数第一个参数,声明一个变量 name,剩余的参数会被 ...
收集成一个数组,这就是剩余参数。引入剩余参数就是为了能替代函数内部的 arguments
,由于 arguments
对象不具备数组的方法,所以很多时候在使用之前要先转换成一个数组。而剩余参数本来就是一个数组,避免了这多余的一步,使用起来既优雅又自然。
注意事项
箭头函数的剩余参数
箭头函数的参数部分即使只有一个剩余参数,也不能省略圆括号。
const add = (...args) => {};
使用剩余参数替代 arguments 获取实际参数
- 剩余参数是一个 “真数组”,arguments 是一个 “伪数组”
- 剩余参数的名字可以自定义
剩余参数的位置
剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错。
剩余参数的应用
作为数组的应用:
const add = (...args) => {
let sum = 0
for (let i = 0; i < args.length; i++) {
sum += args[i]
} // 当然此处,arguments 也可以
return sum
}
console.log(add()) // 0
console.log(add(1, 1)) // 2
console.log(add(1, 2, 3)) // 6
与解构赋值结合使用:
(剩余参数不一定非要作为函数参数使用)
- 与数组解构赋值结合
let array = [1, 2, 3, 4, 5]
let [a, b, ...others] = array
console.log(a) // 1
console.log(b) // 2
console.log(others) // [3,4,5]
- 与对象解构赋值结合
const { x, y, ...z } = { a: 3, x: 1, y: 2, b: 4 }
console.log(x, y, z)
// 1 2 { a: 3, b: 4 }
// 这里的剩余参数是个对象(准确的应该叫:剩余元素)
const func = ({ x, y, ...z }) => {
console.log(x, y, z) // 1 2 { a: 3, b: 4 }
}
func({ a: 3, x: 1, y: 2, b: 4 })
- 在函数传参的时候也可以是和解构一起使用
function fun(...[a, b, c]) {
return a + b + c
}
fun('1') // NaN (b 和 c 都是 undefined)
fun(1, 2, 3) // 6
fun(1, 2, 3, 4) // 6 多余的参数不会被获取到
上面的代码中,a、b、c 会去解构传入参数,加上有剩余语法的作用,对应的值从数组中的项解构出来,在函数内部直接使用解构出来的参数即可。剩余语法看起来和展开语法完全相同,不同点在于,剩余参数用于解构数组和对象。
小结
本节结合了 ES5 函数中的 arguments
对象引入了为什么 ES6 会引入剩余参数的概念,可以看到剩余参数所带来的好处。本节内容可以总结以下几点:
- 剩余参数是为了能替代函数内部的 arguments 而引入的;
- 和展开语法相反,剩余参数是将多个单个元素聚集起来形成一个单独的个体的过程。
箭头函数
ES6 引入了箭头函数(Arrow Functions),是一种更简洁和灵活的函数定义方式。箭头函数通常与其他 ES6 特性(如解构赋值、模板字符串等)结合使用,使得代码更加现代化和易读。
基本语法
箭头函数的基本语法如下:
const functionName = (parameter1, parameter2, ...) => {
// 函数体
return result;
};
箭头函数通过箭头(=>
)来定义函数,参数列表在括号内,函数体在箭头后面的花括号内。如果函数体只有一行代码并且返回一个表达式,可以省略花括号和return
关键字。
const add = (a, b) => a + b
console.log(add(3, 5)) // 输出:8
没有参数或多个参数
当箭头函数没有参数或有多个参数时,需要使用括号来表示参数列表。
// 没有参数
const greet = () => console.log('Hello!')
greet() // 输出:Hello!
// 多个参数
const multiply = (x, y) => x * y
console.log(multiply(3, 4)) // 输出:12
箭头函数与 this
箭头函数与传统函数有一个重要的区别:箭头函数没有自己的this
,它继承自外层作用域的this
。
在传统函数中,this
的值是在函数调用时动态确定的,取决于调用方式(比如函数是否作为对象的方法调用、是否使用new
关键字等)。而在箭头函数中,this
继承自上层作用域,指向的是定义箭头函数的外层函数或代码块的this
。
// 传统函数中的this
const person = {
name: 'John',
sayHello: function () {
console.log(`Hello, my name is ${this.name}.`)
}
}
person.sayHello() // 输出:Hello, my name is John.
// 箭头函数中的this
const person2 = {
name: 'Alice',
sayHello: () => {
console.log(`Hello, my name is ${this.name}.`)
}
}
person2.sayHello() // 输出:Hello, my name is undefined.
在上面的例子中,传统函数的sayHello
方法使用了对象的name
属性,因此this
指向了person
对象,输出了正确的结果。而箭头函数中的sayHello
方法继承了外层代码块的this
(在浏览器环境中,这里指向window
),因此输出了undefined
。
使用注意事项
- 如果箭头函数只有一个参数,可以省略参数列表的括号。
- 如果箭头函数的函数体为空或只有一个表达式,可以省略花括号和
return
关键字。 - 避免在对象方法中使用箭头函数,因为箭头函数会继承外层作用域的
this
,可能导致this
的指向错误。 - 在需要动态绑定
this
的场景(如事件处理函数),应使用传统函数而非箭头函数。
箭头函数在 JavaScript 代码中广泛使用,特别是在简单的回调函数和数组方法中,能够减少代码量并提高代码的可读性。
函数参数的尾逗号
ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
function clownsEverywhere(param1, param2) {
/* ... */
}
clownsEverywhere('foo', 'bar')
上面代码中,如果在param2
或bar
后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere
添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function clownsEverywhere(param1, param2) {
/* ... */
}
clownsEverywhere('foo', 'bar')
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
catch 命令的参数省略
JavaScript 语言的try...catch
结构,以前明确要求catch
命令后面必须跟参数,接受try
代码块抛出的错误对象。
try {
// ...
} catch (err) {
// 处理错误
}
上面代码中,catch
命令后面带有参数err
。
很多时候,catch
代码块可能用不到这个参数。但是,为了保证语法正确,还是必须写。ES2019 做出了改变,允许catch
语句省略参数。
try {
// ...
} catch {
// ...
}