ES6 允许为函数参数设置默认值,大大简化了参数处理逻辑。
基本语法#
ES5 的做法#
// ES5 处理默认值function greet(name, greeting) { name = name || '朋友' greeting = greeting || '你好' console.log(greeting + ',' + name)}
greet() // '你好,朋友'greet('张三') // '你好,张三'
// 🔶 问题:无法区分 undefined 和 falsy 值greet('张三', '') // '你好,张三'(空字符串被当作 false)ES6 默认参数#
function greet(name = '朋友', greeting = '你好') { console.log(`${greeting},${name}`)}
greet() // '你好,朋友'greet('张三') // '你好,张三'greet('张三', '') // ',张三'(空字符串有效)greet('张三', undefined) // '你好,张三'(undefined 触发默认值)🤔 只有 undefined 会触发默认值,null 和其他 falsy 值不会:
function test(a = 1, b = 2) { console.log(a, b)}
test(undefined, null) // 1 nulltest(0, '') // 0 ''test(false, NaN) // false NaN表达式作为默认值#
默认值可以是任意表达式,惰性求值:
function getDefault() { console.log('计算默认值') return 100}
function test(value = getDefault()) { return value}
test(1) // 不会打印,返回 1test() // 打印 '计算默认值',返回 100实际应用:
// 必填参数检查function required(name) { throw new Error(`参数 ${name} 是必填的`)}
function createUser(name = required('name'), age = 18) { return { name, age }}
createUser('张三') // { name: '张三', age: 18 }// createUser(); // Error: 参数 name 是必填的
// 生成唯一 IDlet id = 0function createItem(name, itemId = ++id) { return { id: itemId, name }}
createItem('A') // { id: 1, name: 'A' }createItem('B') // { id: 2, name: 'B' }createItem('C', 100) // { id: 100, name: 'C' }与解构结合#
参数解构的默认值#
// 解构参数 + 默认值function createUser({ name = '匿名', age = 18 } = {}) { return { name, age }}
createUser() // { name: '匿名', age: 18 }createUser({}) // { name: '匿名', age: 18 }createUser({ name: '张三' }) // { name: '张三', age: 18 }createUser({ name: '张三', age: 25 }) // { name: '张三', age: 25 }🔶 注意两层默认值的区别:
// 方式 1:只有解构默认值function foo({ a = 1, b = 2 }) { console.log(a, b)}// foo(); // TypeError: 无法解构 undefined
// 方式 2:整体默认值function bar({ a, b } = { a: 1, b: 2 }) { console.log(a, b)}bar() // 1 2bar({}) // undefined undefined(整体参数不是 undefined,不触发默认值)
// 方式 3:两层默认值(推荐)function baz({ a = 1, b = 2 } = {}) { console.log(a, b)}baz() // 1 2baz({}) // 1 2baz({ a: 10 }) // 10 2数组解构默认值#
function sum([a = 0, b = 0] = []) { return a + b}
sum() // 0sum([]) // 0sum([1]) // 1sum([1, 2]) // 3参数作用域#
默认参数形成一个独立的作用域:
const x = 1
function foo(x, y = x) { console.log(y)}
foo(2) // 2(y 的默认值是参数 x,不是全局 x)更复杂的例子:
let x = 1
function foo( x, y = function () { x = 2 }) { var x = 3 y() console.log(x)}
foo() // 3console.log(x) // 1
// 解释:// 1. 参数 (x, y = ...) 形成独立作用域// 2. y 函数中的 x 是参数作用域的 x// 3. 函数体内 var x = 3 是另一个 x// 4. y() 修改的是参数作用域的 x// 5. console.log(x) 打印的是函数体的 x参数位置#
带默认值的参数应放在后面#
// 不推荐:默认参数在前function greet(greeting = '你好', name) { console.log(`${greeting},${name}`)}
greet('张三') // '张三,undefined'(没达到预期)greet(undefined, '张三') // '你好,张三'(必须显式传 undefined)
// 推荐:默认参数在后function greet2(name, greeting = '你好') { console.log(`${greeting},${name}`)}
greet2('张三') // '你好,张三'中间的默认参数#
function range(start = 0, end, step = 1) { // 处理省略中间参数的情况 if (end === undefined) { end = start start = 0 }
const result = [] for (let i = start; i < end; i += step) { result.push(i) } return result}
range(5) // [0, 1, 2, 3, 4]range(1, 5) // [1, 2, 3, 4]range(0, 10, 2) // [0, 2, 4, 6, 8]length 属性#
有默认值的参数不计入 length:
;(function (a) {}).length // 1;(function (a = 1) {}).length // 0;(function (a, b = 1) {}).length // 1;(function (a, b, c = 1) {}).length // 2
// rest 参数也不计入;(function (a, ...rest) {}).length // 1实战应用#
配置对象模式#
function createServer({ port = 3000, host = 'localhost', https = false, cors = true, timeout = 30000,} = {}) { console.log(`Server: ${https ? 'https' : 'http'}://${host}:${port}`) console.log(`CORS: ${cors}, Timeout: ${timeout}ms`)}
createServer() // 使用全部默认值createServer({ port: 8080 }) // 只改端口createServer({ https: true, port: 443 }) // HTTPS工厂函数#
function createPerson( name, { age = 0, gender = '未知', email = '', address = {} } = {}) { return { name, age, gender, email, address: { city: address.city || '未知', country: address.country || '中国', }, }}
createPerson('张三')// {// name: '张三',// age: 0,// gender: '未知',// email: '',// address: { city: '未知', country: '中国' }// }函数重载模拟#
function createElement(tag, options = {}) { const { id, className, text, children = [], attrs = {} } = options
const element = document.createElement(tag)
if (id) element.id = id if (className) element.className = className if (text) element.textContent = text
Object.entries(attrs).forEach(([key, value]) => { element.setAttribute(key, value) })
children.forEach((child) => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)) } else { element.appendChild(child) } })
return element}
// 简单用法createElement('div')
// 完整用法createElement('div', { id: 'container', className: 'wrapper', attrs: { 'data-id': '123' }, children: [createElement('p', { text: 'Hello' })],})日期格式化#
function formatDate( date = new Date(), { year = true, month = true, day = true, hour = false, minute = false, second = false, separator = '-', timeSeparator = ':', } = {}) { const parts = []
if (year) parts.push(date.getFullYear()) if (month) parts.push(String(date.getMonth() + 1).padStart(2, '0')) if (day) parts.push(String(date.getDate()).padStart(2, '0'))
let result = parts.join(separator)
const timeParts = [] if (hour) timeParts.push(String(date.getHours()).padStart(2, '0')) if (minute) timeParts.push(String(date.getMinutes()).padStart(2, '0')) if (second) timeParts.push(String(date.getSeconds()).padStart(2, '0'))
if (timeParts.length) { result += ' ' + timeParts.join(timeSeparator) }
return result}
formatDate() // '2024-01-09'formatDate(new Date(), { hour: true, minute: true }) // '2024-01-09 10:30'formatDate(new Date(), { separator: '/' }) // '2024/01/09'小结#
| 特性 | 说明 |
|---|---|
| 触发条件 | 只有 undefined 触发默认值 |
| 表达式 | 默认值可以是表达式,惰性求值 |
| 作用域 | 参数形成独立作用域 |
| 位置 | 默认参数应放在参数列表末尾 |
| length | 默认参数不计入函数 length |
默认参数让函数签名更清晰,配合解构使用可以实现灵活的配置对象模式。