函数扩展
本章将详细介绍ECMAScript 6及后续版本中引入的函数新特性,包括箭头函数、默认参数、剩余参数、扩展运算符等,这些特性让函数更加灵活和强大。
10.1 箭头函数
箭头函数是ES6引入的一种更简洁的函数语法,它在语法上更紧凑,并且不绑定自己的this、arguments、super或new.target。
箭头函数语法
// 基本语法
let add = (a, b) => a + b;
// 单参数可以省略括号
let square = x => x * x;
// 无参数需要空括号
let sayHello = () => "Hello!";
// 多行函数体需要大括号和return
let multiply = (a, b) => {
let result = a * b;
return result;
};
// 返回对象字面量需要加括号
let createUser = (name, age) => ({
id: Math.random(),
name: name,
age: age
});
箭头函数与普通函数的区别
// 1. this绑定的区别
let obj = {
name: "张三",
// 普通函数
regularFunction: function() {
console.log(this.name); // "张三"
// 内部函数的this指向全局对象
setTimeout(function() {
console.log(this.name); // undefined (浏览器中)或全局对象的name
}, 100);
},
// 箭头函数
arrowFunction: function() {
console.log(this.name); // "张三"
// 箭头函数继承外层的this
setTimeout(() => {
console.log(this.name); // "张三"
}, 100);
}
};
obj.regularFunction();
obj.arrowFunction();
箭头函数的应用场景
// 数组方法中的回调函数
let numbers = [1, 2, 3, 4, 5];
// map
let doubled = numbers.map(n => n * 2);
// filter
let evens = numbers.filter(n => n % 2 === 0);
// reduce
let sum = numbers.reduce((acc, n) => acc + n, 0);
// 事件处理器
let button = document.getElementById('myButton');
button.addEventListener('click', event => {
console.log('按钮被点击了', event);
});
// Promise链
fetch('/api/data')
.then(response => response.json())
.then(data => processData(data))
.catch(error => console.error(error));
10.2 默认参数
ES6允许为函数参数设置默认值,当调用函数时未提供参数或参数为undefined时使用默认值。
基本默认参数
// 基本默认参数
function greet(name = "匿名用户", greeting = "你好") {
return `${greeting}, ${name}!`;
}
console.log(greet()); // "你好, 匿名用户!"
console.log(greet("张三")); // "你好, 张三!"
console.log(greet("李四", "欢迎")); // "欢迎, 李四!"
复杂默认参数
// 使用表达式作为默认值
function addTax(price, tax = price * 0.1) {
return price + tax;
}
console.log(addTax(100)); // 110
console.log(addTax(100, 15)); // 115
// 使用函数调用作为默认值
function createId(prefix = Math.random().toString(36).substr(2, 9)) {
return prefix;
}
console.log(createId()); // 随机ID
console.log(createId("user_")); // "user_"
默认参数与解构结合
// 对象参数默认值
function createUser({name = "匿名", age = 0, city = "未知"} = {}) {
return {name, age, city};
}
console.log(createUser()); // {name: "匿名", age: 0, city: "未知"}
console.log(createUser({name: "张三", age: 25})); // {name: "张三", age: 25, city: "未知"}
// 数组参数默认值
function sum([a = 0, b = 0, c = 0] = []) {
return a + b + c;
}
console.log(sum()); // 0
console.log(sum([1, 2])); // 3
10.3 剩余参数
剩余参数允许我们将不定数量的参数表示为一个数组。
基本剩余参数
// 基本剩余参数
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 结合普通参数
function multiply(multiplier, ...numbers) {
return numbers.map(num => num * multiplier);
}
console.log(multiply(2, 1, 2, 3, 4)); // [2, 4, 6, 8]
剩余参数与arguments的区别
// arguments对象 (ES5方式)
function oldWay() {
// arguments不是真正的数组
let args = Array.prototype.slice.call(arguments);
return args.map(arg => arg * 2);
}
// 剩余参数 (ES6方式)
function newWay(...args) {
// args是真正的数组
return args.map(arg => arg * 2);
}
console.log(oldWay(1, 2, 3)); // [2, 4, 6]
console.log(newWay(1, 2, 3)); // [2, 4, 6]
10.4 扩展运算符
扩展运算符(...)允许一个表达式在期望多个元素(如数组字面量)的位置扩展成多个元素。
数组扩展
// 数组合并
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 数组复制
let original = [1, 2, 3];
let copy = [...original]; // [1, 2, 3]
// 将数组展开作为函数参数
function add(a, b, c) {
return a + b + c;
}
let numbers = [1, 2, 3];
let result = add(...numbers); // 6
对象扩展
// 对象合并 (ES2018)
let obj1 = {a: 1, b: 2};
let obj2 = {c: 3, d: 4};
let combined = {...obj1, ...obj2}; // {a: 1, b: 2, c: 3, d: 4}
// 对象复制
let original = {name: "张三", age: 25};
let copy = {...original}; // {name: "张三", age: 25}
// 对象属性覆盖
let user = {name: "张三", age: 25};
let updatedUser = {...user, age: 26}; // {name: "张三", age: 26}
字符串扩展
// 字符串转数组
let str = "hello";
let chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
// 与数组方法结合
let upperChars = [...str].map(char => char.toUpperCase());
console.log(upperChars); // ['H', 'E', 'L', 'L', 'O']
10.5 参数解构
函数参数可以使用解构语法直接从传入的对象或数组中提取值。
对象参数解构
// 基本对象解构
function createUser({name, age, city = "未知"}) {
return {name, age, city};
}
let userData = {name: "张三", age: 25};
console.log(createUser(userData)); // {name: "张三", age: 25, city: "未知"}
// 嵌套对象解构
function displayUser({name, address: {city, district}}) {
console.log(`${name}住在${city}${district}`);
}
let user = {
name: "李四",
address: {
city: "北京",
district: "朝阳区"
}
};
displayUser(user); // "李四住在北京朝阳区"
数组参数解构
// 基本数组解构
function add([x, y]) {
return x + y;
}
console.log(add([5, 3])); // 8
// 带默认值的数组解构
function multiply([a = 1, b = 1] = []) {
return a * b;
}
console.log(multiply()); // 1
console.log(multiply([5])); // 5
console.log(multiply([3, 4])); // 12
10.6 函数属性和方法的扩展
Name属性
// 函数的name属性
function myFunction() {}
console.log(myFunction.name); // "myFunction"
let anonymousFunc = function() {};
console.log(anonymousFunc.name); // "anonymousFunc"
let arrowFunc = () => {};
console.log(arrowFunc.name); // "arrowFunc"
10.7 尾调用优化
ES6在严格模式下支持尾调用优化,可以减少调用栈的内存消耗。
// 尾调用优化示例
"use strict";
function factorial(n, acc = 1) {
if (n <= 1) {
return acc;
}
return factorial(n - 1, n * acc); // 尾调用
}
console.log(factorial(5)); // 120
10.8 实际应用示例
数据处理函数
// 使用函数扩展处理数据
const processData = (data, {sortBy = 'id', order = 'asc'} = {}) => {
return [...data]
.sort((a, b) => {
if (order === 'asc') {
return a[sortBy] > b[sortBy] ? 1 : -1;
} else {
return a[sortBy] < b[sortBy] ? 1 : -1;
}
});
};
const users = [
{id: 3, name: "张三", age: 25},
{id: 1, name: "李四", age: 30},
{id: 2, name: "王五", age: 20}
];
console.log(processData(users)); // 按id升序排列
console.log(processData(users, {sortBy: 'age', order: 'desc'})); // 按age降序排列
事件处理函数
// 使用箭头函数处理事件
class EventHandler {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
}
emit(event, ...args) {
const callbacks = this.events.get(event) || [];
callbacks.forEach(callback => callback(...args));
}
}
const handler = new EventHandler();
handler.on('click', (x, y) => console.log(`点击位置: ${x}, ${y}`));
handler.emit('click', 100, 200); // "点击位置: 100, 200"
提示:函数扩展特性让JavaScript函数更加灵活和强大。合理使用这些特性可以写出更简洁、更易读的代码。但要注意箭头函数不适用于所有场景,特别是需要动态this绑定的情况。