Skip to content

函数

函数是 JavaScript 中最重要的概念之一,它是一段可重复使用的代码块,可以接收参数并返回值。本章将详细介绍函数的定义、参数、作用域和闭包等概念。

函数定义

函数声明

javascript
// 函数声明
function greet(name) {
    return 'Hello, ' + name + '!';
}

// 调用函数
console.log(greet('张三'));  // Hello, 张三!

// 函数声明会被提升(可以在声明前调用)
console.log(add(1, 2));  // 3

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

// 函数声明语法
// function 函数名(参数1, 参数2, ...) {
//     函数体
//     return 返回值;
// }

函数表达式

javascript
// 函数表达式:将函数赋值给变量
let greet = function(name) {
    return 'Hello, ' + name + '!';
};

console.log(greet('李四'));  // Hello, 李四!

// 函数表达式不会提升
// console.log(subtract(5, 3));  // 错误:subtract 未定义

let subtract = function(a, b) {
    return a - b;
};

// 命名函数表达式
let factorial = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);  // 内部可以使用函数名递归
};

console.log(factorial(5));  // 120
// console.log(fact(5));  // 错误:fact 在外部不可用

// 函数表达式作为回调
let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(function(num) {
    return num * 2;
});
console.log(doubled);  // [2, 4, 6, 8, 10]

箭头函数(ES6)

javascript
// 箭头函数:更简洁的函数语法
let greet = (name) => {
    return 'Hello, ' + name + '!';
};

// 简写:单个参数可省略括号
let greet2 = name => 'Hello, ' + name + '!';

// 简写:单个表达式可省略花括号和 return
let double = num => num * 2;
console.log(double(5));  // 10

// 多个参数需要括号
let add = (a, b) => a + b;
console.log(add(3, 4));  // 7

// 无参数需要空括号
let sayHi = () => console.log('Hi!');
sayHi();  // Hi!

// 返回对象需要用括号包裹
let createUser = (name, age) => ({ name, age });
console.log(createUser('张三', 25));  // { name: '张三', age: 25 }

// 多行函数体需要花括号和 return
let calculate = (a, b, operation) => {
    switch (operation) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return a / b;
        default: return 0;
    }
};

// 箭头函数没有自己的 this
let obj = {
    name: '张三',
    // 普通函数:this 指向调用者
    greetRegular: function() {
        console.log('Hello, ' + this.name);
    },
    // 箭头函数:this 继承自外层作用域
    greetArrow: () => {
        console.log('Hello, ' + this.name);  // this.name 是 undefined
    }
};

obj.greetRegular();  // Hello, 张三
obj.greetArrow();    // Hello, undefined

// 箭头函数适合回调函数
let numbers = [1, 2, 3, 4, 5];
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens);  // [2, 4]

Function 构造函数(不推荐)

javascript
// 使用 Function 构造函数创建函数(不推荐)
let add = new Function('a', 'b', 'return a + b');
console.log(add(1, 2));  // 3

// 等价于
let add2 = function(a, b) {
    return a + b;
};

// 不推荐使用 Function 构造函数:
// 1. 每次调用都会创建新的函数对象
// 2. 代码在全局作用域执行,存在安全风险
// 3. 难以阅读和调试

函数参数

基本参数

javascript
// 函数参数
function greet(name, greeting) {
    return greeting + ', ' + name + '!';
}

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

// 参数数量可以不匹配
function add(a, b) {
    return a + b;
}

console.log(add(1, 2));     // 3
console.log(add(1));        // NaN(b 是 undefined)
console.log(add(1, 2, 3));  // 3(多余的参数被忽略)

// 检查参数
function greet2(name) {
    if (name === undefined) {
        name = '游客';
    }
    return 'Hello, ' + name + '!';
}

console.log(greet2());      // Hello, 游客!
console.log(greet2('张三')); // Hello, 张三!

默认参数(ES6)

javascript
// 默认参数
function greet(name = '游客', greeting = 'Hello') {
    return greeting + ', ' + name + '!';
}

console.log(greet());             // Hello, 游客!
console.log(greet('张三'));        // Hello, 张三!
console.log(greet('张三', '你好')); // 你好, 张三!

// 默认值可以是表达式
function add(a, b = a * 2) {
    return a + b;
}
console.log(add(5));  // 15(5 + 5*2)

// 默认值可以是函数调用
function getDefault() {
    return '默认值';
}

function test(value = getDefault()) {
    console.log(value);
}

test();          // 默认值
test('自定义');  // 自定义

// 默认参数对 arguments 的影响
function testArgs(a = 1, b = 2) {
    console.log(a, b);           // 3 undefined
    console.log(arguments[0], arguments[1]);  // 3 undefined
}
testArgs(3);

剩余参数(ES6)

javascript
// 剩余参数:收集多余的参数
function sum(...numbers) {
    let total = 0;
    for (let num of numbers) {
        total += num;
    }
    return total;
}

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

// 与普通参数结合
function greetAll(greeting, ...names) {
    names.forEach(name => {
        console.log(greeting + ', ' + name + '!');
    });
}

greetAll('你好', '张三', '李四', '王五');
// 你好, 张三!
// 你好, 李四!
// 你好, 王五!

// 剩余参数是真正的数组
function logArgs(...args) {
    console.log(Array.isArray(args));  // true
    args.forEach(arg => console.log(arg));
}

// 剩余参数必须是最后一个参数
// function wrong(...rest, last) {}  // 错误

// 剩余参数 vs arguments
function compare(a, b, ...rest) {
    console.log('a:', a);           // 1
    console.log('b:', b);           // 2
    console.log('rest:', rest);     // [3, 4, 5]
    console.log('arguments:', arguments);  // Arguments(5) [1, 2, 3, 4, 5]
}

compare(1, 2, 3, 4, 5);

参数解构(ES6)

javascript
// 解构对象参数
function createUser({ name, age, city = '北京' }) {
    return { name, age, city };
}

let user = createUser({ name: '张三', age: 25 });
console.log(user);  // { name: '张三', age: 25, city: '北京' }

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

console.log(getFirstTwo([1, 2, 3, 4]));  // [1, 2]

// 混合使用
function process({ name, scores: [first, second] }) {
    return `${name}: ${first + second}`;
}

console.log(process({ name: '张三', scores: [80, 90] }));  // 张三: 170

// 带默认值的解构
function setup({ host = 'localhost', port = 3000 } = {}) {
    console.log(`Server running at ${host}:${port}`);
}

setup();  // Server running at localhost:3000
setup({ port: 8080 });  // Server running at localhost:8080

返回值

return 语句

javascript
// return 返回函数结果
function add(a, b) {
    return a + b;  // 返回计算结果
}

let result = add(1, 2);
console.log(result);  // 3

// return 后面的代码不会执行
function test() {
    console.log('开始');
    return '结果';
    console.log('不会执行');  // 不会执行
}

console.log(test());  // 开始,然后输出 结果

// 没有 return 或 return 后面没有值,返回 undefined
function noReturn() {
    console.log('执行');
}

console.log(noReturn());  // 执行,然后输出 undefined

function emptyReturn() {
    return;  // 相当于 return undefined
}

console.log(emptyReturn());  // undefined

// return 只能返回一个值
function getCoords() {
    return 10, 20;  // 只返回 20(逗号运算符)
}

console.log(getCoords());  // 20

// 返回多个值:使用数组或对象
function getCoords2() {
    return [10, 20];  // 返回数组
}

function getCoords3() {
    return { x: 10, y: 20 };  // 返回对象
}

let [x, y] = getCoords2();
let { x: x2, y: y2 } = getCoords3();

提前返回

javascript
// 使用 return 提前结束函数
function divide(a, b) {
    if (b === 0) {
        return '除数不能为 0';  // 提前返回
    }
    return a / b;
}

console.log(divide(10, 2));  // 5
console.log(divide(10, 0));  // 除数不能为 0

// 验证参数
function processUser(user) {
    if (!user) {
        return '用户不存在';
    }
    if (!user.name) {
        return '用户名不能为空';
    }
    if (!user.email) {
        return '邮箱不能为空';
    }
    // 处理用户
    return `处理用户:${user.name}`;
}

// 多个返回点
function findElement(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return i;  // 找到后立即返回
        }
    }
    return -1;  // 未找到
}

console.log(findElement([1, 2, 3, 4, 5], 3));  // 2
console.log(findElement([1, 2, 3, 4, 5], 6));  // -1

作用域

全局作用域

javascript
// 全局变量:在任何地方都可以访问
var globalVar = '全局变量';

function test() {
    console.log(globalVar);  // 可以访问
}

test();
console.log(globalVar);  // 可以访问

// 全局变量会成为全局对象的属性(浏览器中是 window)
console.log(window.globalVar);  // 全局变量

// let 和 const 声明的全局变量不会成为 window 的属性
let globalLet = 'let 全局变量';
console.log(window.globalLet);  // undefined

函数作用域

javascript
// 函数作用域:变量只在函数内部有效
function test() {
    var localVar = '局部变量';
    console.log(localVar);  // 可以访问
}

test();
// console.log(localVar);  // 错误:localVar 未定义

// 函数内部声明的变量会覆盖全局变量
var name = '全局';

function showName() {
    var name = '局部';
    console.log(name);  // 局部
}

showName();
console.log(name);  // 全局

// 函数参数也是局部变量
function greet(name) {
    console.log('Hello, ' + name);
}

greet('张三');
// console.log(name);  // 错误:name 未定义

块级作用域(ES6)

javascript
// let 和 const 有块级作用域
if (true) {
    let blockLet = '块级变量';
    const blockConst = '块级常量';
    var blockVar = '函数作用域变量';
    
    console.log(blockLet);    // 可以访问
    console.log(blockConst);  // 可以访问
}

// console.log(blockLet);    // 错误:未定义
// console.log(blockConst);  // 错误:未定义
console.log(blockVar);  // 可以访问

// 循环中的块级作用域
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2

for (var j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100);
}
// 输出:3, 3, 3

// 块级作用域避免变量污染
function test() {
    let x = 1;
    if (true) {
        let x = 2;  // 不同的变量
        console.log(x);  // 2
    }
    console.log(x);  // 1
}

作用域链

javascript
// 作用域链:内部函数可以访问外部函数的变量
let global = '全局';

function outer() {
    let outerVar = '外部';
    
    function inner() {
        let innerVar = '内部';
        
        // 可以访问所有外层作用域的变量
        console.log(innerVar);   // 内部
        console.log(outerVar);   // 外部
        console.log(global);     // 全局
    }
    
    inner();
    // console.log(innerVar);  // 错误:无法访问内部变量
}

outer();

// 变量查找规则:从内到外
let x = 1;

function a() {
    let x = 2;
    
    function b() {
        let x = 3;
        console.log(x);  // 3(找到最近的 x)
    }
    
    b();
}

a();

闭包

什么是闭包

javascript
// 闭包:函数可以访问其外部函数的变量,即使外部函数已经执行完毕
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

// count 变量被闭包"记住"了,不会被销毁

// 另一个计数器是独立的
let counter2 = createCounter();
console.log(counter2());  // 1

闭包的应用

javascript
// 1. 数据私有化
function createWallet(initialBalance) {
    let balance = initialBalance;  // 私有变量
    
    return {
        deposit: function(amount) {
            balance += amount;
            console.log(`存入 ${amount},余额 ${balance}`);
        },
        withdraw: function(amount) {
            if (amount > balance) {
                console.log('余额不足');
                return;
            }
            balance -= amount;
            console.log(`取出 ${amount},余额 ${balance}`);
        },
        getBalance: function() {
            return balance;
        }
    };
}

let wallet = createWallet(100);
wallet.deposit(50);   // 存入 50,余额 150
wallet.withdraw(30);  // 取出 30,余额 120
console.log(wallet.getBalance());  // 120
// console.log(balance);  // 错误:无法直接访问 balance

// 2. 函数工厂
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

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

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

// 3. 柯里化
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        return function(...moreArgs) {
            return curried.apply(this, args.concat(moreArgs));
        };
    };
}

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

let curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3));  // 6
console.log(curriedAdd(1, 2)(3));  // 6
console.log(curriedAdd(1)(2, 3));  // 6

// 4. 模块模式
let module = (function() {
    let privateVar = '私有变量';
    
    function privateMethod() {
        console.log('私有方法');
    }
    
    return {
        publicVar: '公共变量',
        publicMethod: function() {
            console.log(privateVar);
            privateMethod();
        }
    };
})();

module.publicMethod();  // 可以访问私有成员
// module.privateMethod();  // 错误:无法访问

闭包的注意事项

javascript
// 闭包导致的循环问题(经典问题)
for (var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);  // 输出 5 次 6
    }, 100);
}

// 解决方案 1:使用 let
for (let i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);  // 输出 1, 2, 3, 4, 5
    }, 100);
}

// 解决方案 2:使用 IIFE
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j);  // 输出 1, 2, 3, 4, 5
        }, 100);
    })(i);
}

// 解决方案 3:使用闭包
for (var i = 1; i <= 5; i++) {
    setTimeout((function(j) {
        return function() {
            console.log(j);
        };
    })(i), 100);
}

// 闭包会占用内存
function createClosures() {
    let closures = [];
    for (let i = 0; i < 1000; i++) {
        closures.push(function() {
            console.log(i);  // 每个闭包都持有 i 的引用
        });
    }
    return closures;
}

// 不再使用的闭包应该解除引用
let closures = createClosures();
// 使用完后
closures = null;  // 允许垃圾回收

高阶函数

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

函数作为参数

javascript
// 回调函数
function processArray(arr, callback) {
    let result = [];
    for (let item of arr) {
        result.push(callback(item));
    }
    return result;
}

let numbers = [1, 2, 3, 4, 5];

// 传入不同的回调函数实现不同功能
let doubled = processArray(numbers, n => n * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

let squared = processArray(numbers, n => n * n);
console.log(squared);  // [1, 4, 9, 16, 25]

// 数组内置的高阶函数
let arr = [1, 2, 3, 4, 5];

// map:映射
let mapped = arr.map(n => n * 2);
console.log(mapped);  // [2, 4, 6, 8, 10]

// filter:过滤
let filtered = arr.filter(n => n % 2 === 0);
console.log(filtered);  // [2, 4]

// reduce:归约
let sum = arr.reduce((acc, n) => acc + n, 0);
console.log(sum);  // 15

// find:查找
let found = arr.find(n => n > 3);
console.log(found);  // 4

// some:是否存在满足条件的元素
let hasEven = arr.some(n => n % 2 === 0);
console.log(hasEven);  // true

// every:是否所有元素都满足条件
let allPositive = arr.every(n => n > 0);
console.log(allPositive);  // true

函数作为返回值

javascript
// 返回函数
function createLogger(prefix) {
    return function(message) {
        console.log(`[${prefix}] ${message}`);
    };
}

let infoLogger = createLogger('INFO');
let errorLogger = createLogger('ERROR');

infoLogger('操作成功');   // [INFO] 操作成功
errorLogger('发生错误');  // [ERROR] 发生错误

// 防抖函数
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

// 节流函数
function throttle(fn, interval) {
    let lastTime = 0;
    return function(...args) {
        let now = Date.now();
        if (now - lastTime >= interval) {
            lastTime = now;
            fn.apply(this, args);
        }
    };
}

// 使用示例
let handleScroll = debounce(function() {
    console.log('滚动事件处理');
}, 300);

window.addEventListener('scroll', handleScroll);

函数组合

javascript
// 函数组合:将多个函数组合成一个
function compose(...fns) {
    return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x);
    };
}

let addOne = x => x + 1;
let double = x => x * 2;
let square = x => x * x;

let composed = compose(addOne, double, square);
console.log(composed(3));  // ((3^2) * 2) + 1 = 19

// 管道:从左到右执行
function pipe(...fns) {
    return function(x) {
        return fns.reduce((acc, fn) => fn(acc), x);
    };
}

let piped = pipe(square, double, addOne);
console.log(piped(3));  // ((3^2) * 2) + 1 = 19

立即执行函数(IIFE)

javascript
// IIFE:Immediately Invoked Function Expression
// 定义后立即执行的函数

// 基本语法
(function() {
    console.log('立即执行');
})();

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

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

// 返回值
let result = (function(a, b) {
    return a + b;
})(1, 2);
console.log(result);  // 3

// 创建独立作用域
let counter = (function() {
    let count = 0;
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        }
    };
})();

console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.decrement());  // 1

// 避免全局变量污染
(function() {
    var localVar = '局部变量';
    // 这里的变量不会污染全局
})();

// console.log(localVar);  // 错误:未定义

递归函数

javascript
// 递归:函数调用自身

// 阶乘
function factorial(n) {
    // 基准情况
    if (n <= 1) return 1;
    // 递归调用
    return n * factorial(n - 1);
}

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

// 斐波那契数列
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

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

// 优化:使用缓存
function fibonacciMemo() {
    let cache = {};
    return function fib(n) {
        if (n in cache) return cache[n];
        if (n <= 1) return n;
        cache[n] = fib(n - 1) + fib(n - 2);
        return cache[n];
    };
}

let fib = fibonacciMemo();
console.log(fib(50));  // 12586269025

// 遍历树结构
let tree = {
    name: 'root',
    children: [
        {
            name: 'child1',
            children: [
                { name: 'grandchild1', children: [] },
                { name: 'grandchild2', children: [] }
            ]
        },
        {
            name: 'child2',
            children: []
        }
    ]
};

function traverseTree(node, level = 0) {
    console.log('  '.repeat(level) + node.name);
    for (let child of node.children) {
        traverseTree(child, level + 1);
    }
}

traverseTree(tree);
// root
//   child1
//     grandchild1
//     grandchild2
//   child2

// 尾递归优化
// 尾递归:递归调用是函数的最后一步操作
function factorialTail(n, acc = 1) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);  // 尾递归
}

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

小结

本章学习了 JavaScript 函数的核心概念:

  • 函数定义:函数声明、函数表达式、箭头函数
  • 函数参数:默认参数、剩余参数、参数解构
  • 返回值:return 语句、提前返回
  • 作用域:全局作用域、函数作用域、块级作用域、作用域链
  • 闭包:闭包的原理和应用
  • 高阶函数:函数作为参数、函数作为返回值
  • IIFE:立即执行函数
  • 递归:递归函数的原理和优化

下一章我们将学习 数组,了解数组的创建和操作方法。