JavaScript函数

掌握JavaScript函数的定义、参数、作用域、闭包和高级函数概念

函数概述

函数是JavaScript中的一等公民,用于封装可重用的代码块。在JavaScript中,函数不仅是执行特定任务的代码块,更是构建复杂程序的基础。

函数的重要性

  • 代码复用 - 避免重复代码,提高开发效率
  • 模块化 - 将复杂问题分解为小问题,便于维护
  • 抽象 - 隐藏实现细节,提供清晰的接口
  • 作用域管理 - 创建局部作用域,避免变量污染
  • 数据封装 - 通过闭包实现私有变量
  • 行为抽象 - 将操作封装为可复用的单元

函数的特点

  • 函数是一等公民,可以作为参数传递或作为返回值
  • 函数可以拥有属性和方法
  • 函数可以动态创建和调用
  • 支持函数式编程范式

函数定义

JavaScript提供了多种定义函数的方式,每种方式都有其特点和适用场景:

1. 函数声明

函数声明
// 函数声明 (会被提升)
function greet(name) {
    return "Hello, " + name + "!";
}

let message = greet("张三");
console.log(message);  // "Hello, 张三!"

// 函数声明可以在定义前调用 (函数提升)
console.log(square(5));  // 25

function square(x) {
    return x * x;
}
特点: 函数声明会被提升到当前作用域的顶部,可以在声明前调用。

2. 函数表达式

函数表达式
// 函数表达式 (不会被提升)
const multiply = function(a, b) {
    return a * b;
};

console.log(multiply(4, 5));  // 20

// 命名函数表达式
const factorial = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
};

console.log(factorial(5));  // 120
特点: 函数表达式不会被提升,必须在定义后才能调用。命名函数表达式在调试时更有帮助。

3. 箭头函数 (ES6)

箭头函数
// 箭头函数语法
const add = (a, b) => {
    return a + b;
};

// 简写形式 (单行返回)
const square = x => x * x;

// 无参数箭头函数
const sayHello = () => "Hello!";

// 返回对象字面量
const createUser = (name, age) => ({ name, age });

console.log(add(2, 3));        // 5
console.log(square(4));        // 16
console.log(sayHello());       // "Hello!"
console.log(createUser("李四", 30));  // {name: "李四", age: 30}
箭头函数特点:
  • 没有自己的this,继承自父作用域
  • 没有arguments对象
  • 不能用作构造函数
  • 没有prototype属性

4. 构造函数

构造函数
// 使用Function构造函数创建函数
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 3));  // 5

// 动态创建复杂函数
const operation = new Function('x', 'y', `
    if (x > y) {
        return x - y;
    } else {
        return y - x;
    }
`);
console.log(operation(5, 10));  // 5
注意: 使用Function构造函数创建函数会带来安全风险,因为它会从字符串解析代码,应谨慎使用。

函数参数

JavaScript函数的参数处理非常灵活,支持多种参数传递方式:

参数传递

参数传递
// 基本参数
function introduce(name, age, city) {
    console.log(`我叫${name},今年${age}岁,来自${city}`);
}

introduce("王五", 25, "上海");

// 默认参数 (ES6)
function createOrder(product, quantity = 1, price = 10) {
    return {
        product,
        quantity,
        price,
        total: quantity * price
    };
}

console.log(createOrder("书本"));          // quantity=1, price=10
console.log(createOrder("钢笔", 2));       // quantity=2, price=10
console.log(createOrder("电脑", 1, 5000));  // quantity=1, price=5000

剩余参数 (ES6)

剩余参数
// 剩余参数 (...)
function sum(...numbers) {
    let total = 0;
    for (let num of numbers) {
        total += num;
    }
    return total;
}

console.log(sum(1, 2, 3));        // 6
console.log(sum(1, 2, 3, 4, 5));  // 15

// 与普通参数结合使用
function join(separator, ...strings) {
    return strings.join(separator);
}

console.log(join("-", "a", "b", "c"));  // "a-b-c"

参数解构

参数解构
// 对象参数解构
function displayUser({ name, age, email = "未知" }) {
    console.log(`姓名: ${name}, 年龄: ${age}, 邮箱: ${email}`);
}

const user = { name: "张三", age: 25 };
displayUser(user);  // 姓名: 张三, 年龄: 25, 邮箱: 未知

// 数组参数解构
function getFirstTwo([first, second]) {
    return { first, second };
}

const numbers = [1, 2, 3, 4];
console.log(getFirstTwo(numbers));  // {first: 1, second: 2}

arguments 对象

arguments 对象
// arguments 对象 (类数组对象)
function showArgs() {
    console.log("参数个数: " + arguments.length);
    for (let i = 0; i < arguments.length; i++) {
        console.log(`参数 ${i}: ${arguments[i]}`);
    }
}

showArgs("a", "b", "c");
// 参数个数: 3
// 参数 0: a
// 参数 1: b
// 参数 2: c
注意: 箭头函数没有自己的arguments对象,建议使用剩余参数代替。

返回值

函数使用return语句返回值,如果没有return语句或return后没有值,函数返回undefined

返回值
// 显式返回值
function add(a, b) {
    return a + b;
}

// 无返回值 (返回 undefined)
function logMessage(message) {
    console.log(message);
    // 没有 return 语句
}

// 提前返回
function getGrade(score) {
    if (score < 0 || score > 100) {
        return "无效分数";
    }
    
    if (score >= 90) return "A";
    if (score >= 80) return "B";
    if (score >= 70) return "C";
    if (score >= 60) return "D";
    return "F";
}

console.log(add(2, 3));          // 5
console.log(logMessage("Hi"));    // undefined
console.log(getGrade(85));       // "B"

返回多个值

返回多个值
// 返回数组
function getMinMax(...numbers) {
    return [Math.min(...numbers), Math.max(...numbers)];
}

const [min, max] = getMinMax(1, 5, 3, 9, 2);
console.log(min, max);  // 1 9

// 返回对象
function analyzeArray(arr) {
    return {
        sum: arr.reduce((a, b) => a + b, 0),
        average: arr.reduce((a, b) => a + b, 0) / arr.length,
        min: Math.min(...arr),
        max: Math.max(...arr)
    };
}

const analysis = analyzeArray([1, 2, 3, 4, 5]);
console.log(analysis);  // {sum: 15, average: 3, min: 1, max: 5}

作用域和闭包

理解作用域和闭包是掌握JavaScript函数的关键:

函数作用域

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

function outerFunction() {
    let outerVar = "外部变量";
    
    function innerFunction() {
        let innerVar = "内部变量";
        console.log(innerVar);   // 可以访问
        console.log(outerVar);   // 可以访问
        console.log(globalVar);  // 可以访问
    }
    
    innerFunction();
    // console.log(innerVar);  // 错误: innerVar 未定义
}

outerFunction();

闭包

闭包示例
// 创建计数器
function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    };
}

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

// 创建私有变量
function createPerson(name) {
    let age = 0;
    
    return {
        getName: function() { return name; },
        getAge: function() { return age; },
        setAge: function(newAge) { 
            if (newAge > 0) age = newAge; 
        },
        birthday: function() { age++; }
    };
}

const person = createPerson("张三");
person.setAge(25);
person.birthday();
console.log(person.getName());  // "张三"
console.log(person.getAge());   // 26

闭包的实际应用

闭包的实际应用
// 模块模式
const calculator = (function() {
    let memory = 0;
    
    return {
        add: function(a, b) {
            return a + b;
        },
        subtract: function(a, b) {
            return a - b;
        },
        store: function(value) {
            memory = value;
        },
        recall: function() {
            return memory;
        },
        clear: function() {
            memory = 0;
        }
    };
})();

console.log(calculator.add(5, 3));  // 8
calculator.store(10);
console.log(calculator.recall());  // 10

高阶函数

高阶函数是接受函数作为参数或返回函数的函数,是函数式编程的核心概念:

高阶函数
// 接受函数作为参数
function operate(a, b, operation) {
    return operation(a, b);
}

const result1 = operate(5, 3, (x, y) => x + y);
const result2 = operate(5, 3, (x, y) => x * y);

console.log(result1);  // 8
console.log(result2);  // 15

// 返回函数
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

// 数组高阶函数
const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(num => num * 2);
const evens = numbers.filter(num => num % 2 === 0);
const sum = numbers.reduce((total, num) => total + num, 0);

console.log(doubled);  // [2, 4, 6, 8, 10]
console.log(evens);    // [2, 4]
console.log(sum);      // 15

函数组合

函数组合
// 函数组合工具
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

// 示例函数
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;

// 组合函数 (从右到左执行)
const composed = compose(subtract2, multiply3, add5);
console.log(composed(10));  // (10 + 5) * 3 - 2 = 43

// 管道函数 (从左到右执行)
const piped = pipe(add5, multiply3, subtract2);
console.log(piped(10));  // (10 + 5) * 3 - 2 = 43

递归函数

递归函数是调用自身的函数,适用于解决可以分解为相似子问题的问题:

递归函数
// 阶乘函数
function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

console.log(factorial(5));  // 120

// 斐波那契数列 (带缓存优化)
const fibonacci = (function() {
    const cache = {};
    
    return function fib(n) {
        if (n <= 1) return n;
        if (cache[n]) return cache[n];
        
        cache[n] = fib(n - 1) + fib(n - 2);
        return cache[n];
    };
})();

console.log(fibonacci(10));  // 55

// 遍历嵌套对象
function findValue(obj, targetKey) {
    for (let key in obj) {
        if (key === targetKey) {
            return obj[key];
        }
        if (typeof obj[key] === 'object' && obj[key] !== null) {
            const result = findValue(obj[key], targetKey);
            if (result !== undefined) {
                return result;
            }
        }
    }
    return undefined;
}

const nestedObj = {
    a: 1,
    b: {
        c: 2,
        d: {
            e: 3,
            f: 4
        }
    }
};

console.log(findValue(nestedObj, 'e'));  // 3

立即执行函数 (IIFE)

立即执行函数在定义后立即执行,常用于创建独立作用域:

立即执行函数
// 基本 IIFE
(function() {
    console.log("这个函数会立即执行");
})();

// 带参数的 IIFE
(function(name) {
    console.log("Hello, " + name);
})("张三");

// 箭头函数 IIFE
(() => {
    console.log("箭头函数 IIFE");
})();

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

console.log(result);  // 8

// 模块模式 IIFE
const myModule = (function() {
    let privateVar = "私有变量";
    
    function privateFunction() {
        console.log("私有函数");
    }
    
    return {
        publicMethod: function() {
            console.log("公有方法可以访问: " + privateVar);
            privateFunction();
        },
        getPrivateVar: function() {
            return privateVar;
        }
    };
})();

myModule.publicMethod();  // 公有方法可以访问: 私有变量
console.log(myModule.getPrivateVar());  // "私有变量"

生成器函数

生成器函数可以暂停和恢复执行,用于创建迭代器:

生成器函数
// 基本生成器函数
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const generator = numberGenerator();
console.log(generator.next().value);  // 1
console.log(generator.next().value);  // 2
console.log(generator.next().value);  // 3
console.log(generator.next().done);   // true

// 无限序列生成器
function* infiniteSequence() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const infinite = infiniteSequence();
console.log(infinite.next().value);  // 0
console.log(infinite.next().value);  // 1
console.log(infinite.next().value);  // 2

// 异步生成器
async function* asyncGenerator() {
    yield await Promise.resolve(1);
    yield await Promise.resolve(2);
    yield await Promise.resolve(3);
}

(async function() {
    for await (let value of asyncGenerator()) {
        console.log(value);  // 1, 2, 3
    }
})();

实践练习

👆 请点击上方按钮进行演示操作

选择不同的演示按钮来探索JavaScript的各种功能和用法

练习代码
// 练习1: 计算阶乘
function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

console.log(factorial(5));  // 120

// 练习2: 斐波那契数列
const fibonacci = (function() {
    const cache = {};
    
    return function fib(n) {
        if (n <= 1) return n;
        if (cache[n]) return cache[n];
        
        cache[n] = fib(n - 1) + fib(n - 2);
        return cache[n];
    };
})();

console.log(fibonacci(10));  // 55

// 练习3: 函数组合
const compose = (...functions) => 
    input => functions.reduceRight((acc, fn) => fn(acc), input);

const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;

const composed = compose(subtract2, multiply3, add5);
console.log(composed(10));  // 43

// 练习4: 柯里化函数
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            };
        }
    };
}

const curriedAdd = curry((a, b, c) => a + b + c);
console.log(curriedAdd(1)(2)(3));  // 6
console.log(curriedAdd(1, 2)(3));    // 6
console.log(curriedAdd(1, 2, 3));    // 6

函数性能优化

了解如何优化函数性能对于编写高效JavaScript代码至关重要:

内存管理

  • 避免不必要的闭包,及时释放不再使用的引用
  • 使用尾调用优化(在支持的环境中)
  • 避免在循环中创建函数

执行优化

  • 使用记忆化(缓存函数结果)
  • 避免深度递归,考虑使用迭代替代
  • 使用节流和防抖处理高频函数调用
记忆化示例
// 记忆化函数
function memoize(fn) {
    const cache = {};
    return function(...args) {
        const key = JSON.stringify(args);
        if (cache[key]) {
            return cache[key];
        }
        const result = fn.apply(this, args);
        cache[key] = result;
        return result;
    };
}

// 使用记忆化优化斐波那契数列
const memoizedFibonacci = memoize(function(n) {
    if (n <= 1) return n;
    return memoizedFibonacci(n - 1) + memoizedFibonacci(n - 2);
});

console.log(memoizedFibonacci(40));  // 快速计算

最佳实践

  • 使用有意义的函数名,清晰表达函数意图
  • 函数应该只做一件事(单一职责原则)
  • 避免过长的函数,考虑拆分成小函数
  • 优先使用纯函数(相同输入总是产生相同输出)
  • 使用默认参数而不是在函数内检查参数
  • 优先使用箭头函数,特别是作为回调时
  • 避免在函数内修改外部状态(减少副作用)
  • 合理使用函数注释,说明参数和返回值
  • 考虑函数的可测试性,设计易于测试的函数

函数设计原则

  • 单一职责原则 - 每个函数只负责一个明确的任务
  • 开闭原则 - 函数应该对扩展开放,对修改关闭
  • 依赖倒置原则 - 高层函数不应该依赖低层函数,两者都应该依赖抽象
  • 接口隔离原则 - 使用多个专门的函数而不是一个通用的函数

下一步学习

掌握了函数后,接下来可以学习: