函数

本章将详细介绍ECMAScript中的函数,包括函数的定义、调用、参数传递、作用域等核心概念,以及函数作为一等公民的重要特性。

8.1 函数概述

函数是一段可重复使用的代码块,用于执行特定任务。函数可以接收输入参数并返回结果,是ECMAScript中最重要的概念之一。

8.2 函数的定义

ECMAScript提供了多种定义函数的方式。

函数声明

// 函数声明
function greet(name) {
    return `你好, ${name}!`;
}

console.log(greet("张三")); // "你好, 张三!"

函数表达式

// 函数表达式
let greet = function(name) {
    return `你好, ${name}!`;
};

// 命名函数表达式
let greet = function sayHello(name) {
    return `你好, ${name}!`;
};

箭头函数 (ES6)

// 箭头函数
let greet = (name) => {
    return `你好, ${name}!`;
};

// 简化箭头函数
let greet = name => `你好, ${name}!`;

// 无参数箭头函数
let sayHello = () => "你好!";

// 单表达式箭头函数
let add = (a, b) => a + b;

8.3 函数的调用

函数可以通过多种方式调用。

普通调用

function add(a, b) {
    return a + b;
}

let result = add(5, 3); // 8

作为方法调用

let calculator = {
    value: 0,
    add: function(num) {
        this.value += num;
        return this;
    },
    getValue: function() {
        return this.value;
    }
};

calculator.add(5).add(3); // 链式调用
console.log(calculator.getValue()); // 8

构造函数调用

// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

let person = new Person("张三", 25);
console.log(person.name); // "张三"

间接调用

function greet() {
    return `你好, ${this.name}!`;
}

let user = { name: "李四" };
let greeting = greet.call(user); // "你好, 李四!"
let greeting2 = greet.apply(user); // "你好, 李四!"

// bind方法
let boundGreet = greet.bind(user);
let greeting3 = boundGreet(); // "你好, 李四!"

8.4 参数处理

ECMAScript提供了灵活的参数处理机制。

参数默认值 (ES6)

// 参数默认值
function greet(name = "匿名用户", greeting = "你好") {
    return `${greeting}, ${name}!`;
}

console.log(greet()); // "你好, 匿名用户!"
console.log(greet("张三")); // "你好, 张三!"
console.log(greet("李四", "欢迎")); // "欢迎, 李四!"

剩余参数 (ES6)

// 剩余参数
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)); // [2, 4, 6]

参数解构

// 对象参数解构
function createUser({name, age, city = "未知"}) {
    return {
        id: Math.random(),
        name,
        age,
        city
    };
}

let user = createUser({name: "王五", age: 30});
console.log(user);

// 数组参数解构
function add([x, y]) {
    return x + y;
}

console.log(add([5, 3])); // 8

8.5 作用域和闭包

作用域决定了变量的可访问范围,闭包是函数和其词法环境的组合。

作用域

// 全局作用域
let globalVar = "全局变量";

function outer() {
    // 函数作用域
    let outerVar = "外部变量";
    
    function inner() {
        // 嵌套作用域
        let innerVar = "内部变量";
        console.log(globalVar); // 可访问
        console.log(outerVar);  // 可访问
        console.log(innerVar);  // 可访问
    }
    
    inner();
    // console.log(innerVar); // 错误:无法访问
}

outer();
console.log(globalVar); // 可访问
// console.log(outerVar); // 错误:无法访问

块级作用域 (ES6)

// let和const创建块级作用域
if (true) {
    let blockVar = "块级变量";
    const BLOCK_CONST = "块级常量";
    console.log(blockVar); // 可访问
}

// console.log(blockVar); // 错误:无法访问

闭包

// 闭包示例
function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    };
}

let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 私有变量模拟
function createPerson(name) {
    let _name = name; // 私有变量
    
    return {
        getName: function() {
            return _name;
        },
        setName: function(newName) {
            _name = newName;
        }
    };
}

let person = createPerson("张三");
console.log(person.getName()); // "张三"
person.setName("李四");
console.log(person.getName()); // "李四"

8.6 高阶函数

高阶函数是接收函数作为参数或返回函数的函数。

函数作为参数

// 数组方法中的高阶函数
let numbers = [1, 2, 3, 4, 5];

// map
let doubled = numbers.map(x => x * 2); // [2, 4, 6, 8, 10]

// filter
let evens = numbers.filter(x => x % 2 === 0); // [2, 4]

// reduce
let sum = numbers.reduce((acc, x) => acc + x, 0); // 15

// 自定义高阶函数
function repeat(func, times) {
    return function(...args) {
        for (let i = 0; i < times; i++) {
            func(...args);
        }
    };
}

let sayHello = () => console.log("你好!");
let sayHelloThreeTimes = repeat(sayHello, 3);
sayHelloThreeTimes(); // 输出三次"你好!"

函数作为返回值

// 返回函数的函数
function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

let double = createMultiplier(2);
let triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

8.7 立即执行函数表达式 (IIFE)

IIFE是一种定义后立即执行的函数。

// IIFE基本形式
(function() {
    console.log("IIFE执行了");
})();

// 带参数的IIFE
(function(name) {
    console.log(`你好, ${name}!`);
})("张三");

// 返回值的IIFE
let result = (function(a, b) {
    return a + b;
})(5, 3);

console.log(result); // 8

// 创建私有作用域
(function() {
    let privateVar = "私有变量";
    
    // 在这里定义私有函数和变量
    function privateFunction() {
        console.log(privateVar);
    }
    
    // 暴露公共接口
    window.MyModule = {
        publicMethod: function() {
            privateFunction();
        }
    };
})();

// MyModule.publicMethod(); // "私有变量"

8.8 函数的属性和方法

函数在ECMAScript中是对象,具有属性和方法。

函数属性

function myFunction(a, b, c) {
    return a + b + c;
}

console.log(myFunction.length); // 3 (形参个数)
console.log(myFunction.name);   // "myFunction" (函数名)

// 自定义属性
myFunction.description = "这是一个示例函数";
console.log(myFunction.description); // "这是一个示例函数"

call和apply方法

function greet(greeting, punctuation) {
    return `${greeting}, ${this.name}${punctuation}`;
}

let user = { name: "张三" };

// call方法
let result1 = greet.call(user, "你好", "!");
console.log(result1); // "你好, 张三!"

// apply方法
let result2 = greet.apply(user, ["你好", "!"]);
console.log(result2); // "你好, 张三!"

bind方法

function multiply(a, b) {
    return a * b;
}

// 绑定this值
let multiplyByTwo = multiply.bind(null, 2);
console.log(multiplyByTwo(5)); // 10

// 绑定this和部分参数
let obj = {
    multiplier: 3,
    multiply: function(a, b) {
        return a * b * this.multiplier;
    }
};

let boundMultiply = obj.multiply.bind(obj, 2);
console.log(boundMultiply(4)); // 24 (2 * 4 * 3)

提示:函数是ECMAScript中的一等公民,理解函数的各种特性和用法对于编写高质量的JavaScript代码至关重要。掌握闭包、高阶函数等概念可以帮助您写出更优雅和强大的代码。