函数扩展

本章将详细介绍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绑定的情况。