函数的参数和返回值是函数与外部交互的重要接口,理解它们的特性对于编写高质量函数至关重要。本文档将详细介绍 JavaScript 中参数传递机制、参数特性、返回值处理以及相关最佳实践。
在 JavaScript 函数体系中,参数和返回值承担以下核心职责:
┌─────────────────────────────────────────────────────────┐
│ 函数调用过程 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 1. 参数传递阶段 │
│ • 实参求值(从左到右) │
│ • 参数绑定(形参 = 实参值) │
│ • 默认参数处理 │
│ • arguments 对象创建 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 2. 函数执行阶段 │
│ • 函数体代码执行 │
│ • return 语句处理 │
│ • 异常处理 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 3. 返回值处理阶段 │
│ • 返回值计算 │
│ • 控制权交还给调用者 │
│ • 返回值传递 │
└─────────────────────────────────────────────────────────┘// 函数声明时指定的参数称为形参(形式参数)
function add(a, b) {
return a + b
}
// 函数调用时传入的参数称为实参(实际参数)
add(1, 2) // 1 和 2 是实参术语说明:
JavaScript 中所有参数都是按值传递的,这是理解参数行为的核心概念。
传递的是值的副本,函数内部修改不影响外部变量:
function changeValue(x) {
x = 10
console.log('函数内部:', x) // 10
}
let a = 5
changeValue(a)
console.log('函数外部:', a) // 5,未改变传递的是引用的副本(内存地址),函数内部可以通过引用修改对象属性:
function changeObject(obj) {
obj.name = 'changed' // 修改引用指向的对象
}
let person = { name: 'original' }
changeObject(person)
console.log(person.name) // 'changed'
// 但重新赋值不会影响原对象
function reassignObject(obj) {
obj = { name: 'new' } // 重新赋值,改变引用本身
}
reassignObject(person)
console.log(person.name) // 'changed',未被重新赋值基本类型传参:
┌──────────┐ ┌──────────┐
│ 外部变量 │ │ 函数参数 │
│ a=5 │ ───────> │ x=5 │
└──────────┘ 值复制 └──────────┘
修改 x 不影响 a
引用类型传参:
┌──────────┐ ┌──────────┐
│ 外部变量 │ │ 函数参数 │
│ person │ │ obj │
│ ┌─────┐ │ │ ┌─────┐ │
│ │引用 │─┼──────────┼─>│引用 │ │
│ └─────┘ │ 引用复制 │ └─────┘ │
└──────────┘ └──────────┘
│ │
└──────────┬──────────┘
▼
┌─────────────┐
│ 共享的对象 │
│ {name:'xx'} │
└─────────────┘
修改属性会影响原对象
重新赋值不影响原引用JavaScript 对参数个数非常宽容,不会因为参数个数不匹配而报错:
function foo(a, b, c) {
console.log(a, b, c)
}
foo(1) // 1 undefined undefined
foo(1, 2) // 1 2 undefined
foo(1, 2, 3) // 1 2 3
foo(1, 2, 3, 4) // 1 2 3(多余的参数被忽略)注意: 未传入的参数值为 undefined,而不是 null 或其他值。
// ES5 方式 - 使用逻辑或运算符
function greet(name) {
name = name || 'Guest'
console.log('Hello, ' + name)
}
// ES6 方式 - 使用默认参数
function greet(name = 'Guest') {
console.log(`Hello, ${name}`)
}
greet() // Hello, Guest
greet('John') // Hello, John
greet('') // Hello, (空字符串也会使用默认值,ES5 方式会输出 Guest)function test(a = 'default') {
console.log(a)
}
test() // 'default'
test(undefined) // 'default'
test(null) // null(null 不触发默认值)
test(0) // 0
test('') // ''(空字符串不触发默认值)
test(false) // false重要: 只有参数值为 undefined 或未传入时,才会使用默认值。
默认参数可以是任意表达式,且只在需要时求值:
function getValue() {
console.log('计算默认值')
return 10
}
function foo(a = getValue()) {
console.log(a)
}
foo() // 输出: '计算默认值' 然后 10
foo(5) // 输出: 5(不执行 getValue)function required(paramName) {
throw new Error(`参数 ${paramName} 是必需的`)
}
function foo(a = required('a'), b = required('b')) {
return a + b
}
foo(1, 2) // 3
foo(1) // Error: 参数 b 是必需的默认参数有自己独立的作用域,这是一个重要的特性:
let x = 1
function foo(a = x) {
let x = 2 // 局部变量 x
console.log(a)
}
foo() // 1,默认参数使用外部的 x
// 参数之间的依赖
function bar(a = b, b = 2) {
console.log(a, b)
}
bar() // ReferenceError: Cannot access 'b' before initialization
// 正确的顺序
function baz(a = 1, b = a) {
console.log(a, b)
}
baz() // 1 1
baz(2) // 2 2
baz(2, 3) // 2 3作用域规则:
ES6 引入剩余参数,用于获取函数的多余参数,是替代 arguments 对象的现代方式。
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0)
}
sum(1, 2, 3, 4, 5) // 15
// 剩余参数必须是最后一个参数
function foo(a, b, ...rest) {
console.log(a) // 1
console.log(b) // 2
console.log(rest) // [3, 4, 5]
}
foo(1, 2, 3, 4, 5)// arguments 是类数组对象
function oldWay() {
console.log(arguments) // Arguments { 0: 1, 1: 2, 2: 3 }
console.log(Array.isArray(arguments)) // false
// 需要转换为数组才能使用数组方法
const args = Array.from(arguments)
}
// 剩余参数是真数组
function newWay(...args) {
console.log(args) // [1, 2, 3]
console.log(Array.isArray(args)) // true
// 可以直接使用数组方法
args.forEach(arg => console.log(arg))
}
oldWay(1, 2, 3)
newWay(1, 2, 3)| 特性 | arguments | 剩余参数 |
|---|---|---|
| 类型 | 类数组对象 | 真数组 |
| 箭头函数支持 | ❌ 不支持 | ✅ 支持 |
| 数组方法 | 需要转换 | 直接使用 |
| 可读性 | 较差 | 更好 |
| 性能 | 稍慢 | 更快 |
// 1. 数组合并
function mergeArrays(...arrays) {
return arrays.flat()
}
mergeArrays([1, 2], [3, 4], [5, 6]) // [1, 2, 3, 4, 5, 6]
// 2. 动态函数调用
function callMethod(obj, method, ...args) {
return obj[method](...args)
}
// 3. 构造器模式
class User {
constructor(name, ...roles) {
this.name = name
this.roles = roles
}
}function printUser({ name, age, city = 'Unknown' }) {
console.log(`Name: ${name}, Age: ${age}, City: ${city}`)
}
printUser({ name: 'John', age: 30 })
// Name: John, Age: 30, City: Unknown
// 配合默认参数使用
function createConfig({
host = 'localhost',
port = 3000,
debug = false
} = {}) {
return { host, port, debug }
}
createConfig() // { host: 'localhost', port: 3000, debug: false }
createConfig({ port: 8080 }) // { host: 'localhost', port: 8080, debug: false }function printCoordinates([x, y, z = 0]) {
console.log(`X: ${x}, Y: ${y}, Z: ${z}`)
}
printCoordinates([1, 2])
// X: 1, Y: 2, Z: 0// React 组件 props 解构
function UserProfile({ name, age, email, address = {} }) {
const { city, country } = address
// ...
}
// 配置对象模式
function setupApp({
api: {
baseUrl = '/api',
timeout = 5000
} = {},
ui: {
theme = 'light',
language = 'zh-CN'
} = {}
} = {}) {
return { baseUrl, timeout, theme, language }
}
// 函数选项模式
function fetchData(url, {
method = 'GET',
headers = {},
body = null,
credentials = 'same-origin'
} = {}) {
// 实现请求逻辑
}// 有返回值
function add(a, b) {
return a + b
}
// 无返回值(返回 undefined)
function greet(name) {
console.log('Hello, ' + name)
}
console.log(add(1, 2)) // 3
console.log(greet('John')) // Hello, John \n undefinedfunction divide(a, b) {
if (b === 0) {
return 'Cannot divide by zero'
}
return a / b
}
// 多重 guard clauses
function processUser(user) {
if (!user) return 'User is required'
if (!user.name) return 'User name is required'
if (!user.email) return 'User email is required'
// 处理用户数据
return { success: true, user }
}// 使用数组返回多个值
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)]
}
const [min, max] = getMinMax([1, 2, 3, 4, 5])
console.log(min, max) // 1 5
// 使用对象返回多个值(推荐)
function getUser() {
return { name: 'John', age: 30, city: 'New York' }
}
const { name, age, city } = getUser()
console.log(name, age, city) // John 30 New York// 返回函数 - 函数工厂
function multiplier(factor) {
return function (number) {
return number * factor
}
}
const double = multiplier(2)
const triple = multiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15
// 实际应用:中间件模式
function logger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`)
}
}
const infoLogger = logger('INFO')
const errorLogger = logger('ERROR')
infoLogger('Operation succeeded') // [INFO] Operation succeeded
errorLogger('Operation failed') // [ERROR] Operation failedclass Calculator {
constructor(value = 0) {
this.value = value
}
add(num) {
this.value += num
return this // 返回 this 实现链式调用
}
multiply(num) {
this.value *= num
return this
}
getResult() {
return this.value
}
}
const result = new Calculator(10)
.add(5)
.multiply(2)
.getResult()
console.log(result) // 30function processNumber(num) {
if (typeof num !== 'number') {
throw new TypeError(`Expected a number, got ${typeof num}`)
}
if (isNaN(num)) {
throw new Error('Expected a valid number, got NaN')
}
return num * 2
}
// 使用默认参数和类型检查
function processArray(arr = []) {
if (!Array.isArray(arr)) {
throw new TypeError('Expected an array')
}
return arr.map(item => item * 2)
}function setAge(age) {
if (typeof age !== 'number') {
throw new TypeError('Age must be a number')
}
if (age < 0 || age > 150) {
throw new RangeError('Age must be between 0 and 150')
}
this.age = age
}JavaScript 不支持传统函数重载,但可以通过参数特性模拟。
function greet() {
switch (arguments.length) {
case 0:
console.log('Hello!')
break
case 1:
console.log('Hello, ' + arguments[0])
break
case 2:
console.log(`Hello, ${arguments[0]} ${arguments[1]}`)
break
default:
console.log('Hello, everyone!')
}
}
greet() // Hello!
greet('John') // Hello, John
greet('John', 'Doe') // Hello, John Doefunction process(value) {
if (typeof value === 'number') {
console.log('Number:', value * 2)
} else if (typeof value === 'string') {
console.log('String:', value.toUpperCase())
} else if (Array.isArray(value)) {
console.log('Array:', value.join(', '))
} else if (value instanceof Date) {
console.log('Date:', value.toISOString())
} else if (typeof value === 'object' && value !== null) {
console.log('Object:', JSON.stringify(value))
}
}
process(10) // Number: 20
process('hello') // String: HELLO
process([1, 2, 3]) // Array: 1, 2, 3function createUser(options) {
const {
name = 'Guest',
age = 0,
email = '',
role = 'user',
permissions = []
} = options
if (!name) {
throw new Error('Name is required')
}
return {
name, age, email, role,
permissions, createdAt: new Date()
}
}
const user = createUser({
name: 'John',
age: 30,
role: 'admin',
permissions: ['read', 'write', 'delete']
})// ❌ 陷阱:意外修改传入的对象
function updateUser(user) {
user.name = 'modified' // 修改了原对象
return user
}
const original = { name: 'original' }
updateUser(original)
console.log(original.name) // 'modified'(原对象被修改)
// ✅ 正确:创建副本
function updateUserSafe(user) {
return { ...user, name: 'modified' }
}// ❌ 陷阱:引用类型的默认值在复杂场景
let defaultConfig = { debug: false }
function setup(config = defaultConfig) {
config.debug = true // 修改了默认对象
return config
}
setup() // { debug: true }
setup() // { debug: true }(defaultConfig 已被修改)
// ✅ 正确:使用不可变默认值
function setup(config = { debug: false }) {
return config
}// ❌ 陷阱:参数顺序容易出错
function createElement(tag, content, className, id, style) {
// 参数很多,调用时容易搞错顺序
}
// ✅ 正确:使用对象参数
function createElement(options) {
const { tag, content, className = '', id = '', style = '' } = options
// ...
}
createElement({
tag: 'div',
content: 'Hello',
className: 'container',
id: 'main',
style: 'color: red'
})// ❌ 不推荐:参数过多(超过 3-4 个)
function createUser(name, age, email, phone, address, city, country) {
// ...
}
// ✅ 推荐:使用对象参数
function createUser(userInfo) {
const { name, age, email, phone, address, city, country } = userInfo
// ...
}
// ✅ 推荐:使用配置对象 + 默认值
function createUser({
name = 'Guest',
age = 0,
email = '',
phone = '',
address = ''
} = {}) {
// ...
}// ❌ 不推荐:使用 || 运算符
function foo(a, b, c) {
a = a || 1 // 0, '', false 都会使用默认值
}
// ✅ 推荐:使用 ES6 默认参数
function foo(a = 1, b = 2, c = 3) {
// 只有 undefined 才使用默认值
}// ❌ 不推荐:使用 arguments
function sum() {
return Array.from(arguments).reduce((a, b) => a + b, 0)
}
// ✅ 推荐:使用剩余参数
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0)
}// ❌ 避免:修改参数(副作用)
function addItem(item, list = []) {
list.push(item)
return list
}
// ✅ 推荐:返回新数组(纯函数)
function addItem(item, list = []) {
return [...list, item]
}undefined 或未传入时才使用默认值... 语法,是真数组,优于 arguments|| 运算符arguments