目录

第四章 函数

4.1 函数概述

函数是JavaScript中最重要的概念之一。函数是可重复使用的代码块,用于执行特定任务。JavaScript中的函数是一等公民(First-class Citizen),这意味着函数可以:

4.2 函数定义方式

4.2.1 函数声明(Function Declaration)

// 函数声明语法
function greet(name) {
    return "Hello, " + name + "!";
}
 
// 调用函数
console.log(greet("Alice"));  // "Hello, Alice!"
 
// 函数声明会被提升(hoisting)
console.log(sayHi());  // "Hi!"(可以在声明前调用)
 
function sayHi() {
    return "Hi!";
}

函数声明的特点

4.2.2 函数表达式(Function Expression)

// 匿名函数表达式
const greet = function(name) {
    return "Hello, " + name + "!";
};
 
// 命名函数表达式(函数名在函数内部可用)
const factorial = function fact(n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);  // 在内部可以用fact调用自己
};
 
// 函数表达式不会被提升
// console.log(greet2());  // TypeError: greet2 is not a function
const greet2 = function(name) {
    return "Hello!";
};

4.2.3 箭头函数(Arrow Function,ES6)

// 基本语法
const greet = (name) => {
    return "Hello, " + name + "!";
};
 
// 单参数可以省略括号
const double = x => {
    return x * 2;
};
 
// 单表达式可以省略花括号和return(隐式返回)
const triple = x => x * 3;
 
// 多参数
const add = (a, b) => a + b;
 
// 返回对象需要括号
const createUser = (name, age) => ({ name, age });
 
// 无参数
const getTime = () => new Date().toLocaleTimeString();

箭头函数的特点

4.2.4 Function构造函数

// 使用Function构造函数(不推荐)
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3));  // 5
 
// 等同于
const add2 = function(a, b) {
    return a + b;
};

不推荐使用的理由:

4.3 函数参数

4.3.1 形参与实参

// 形参(形式参数):函数定义时的参数
function greet(name, greeting) {
    return `${greeting}, ${name}!`;
}
 
// 实参(实际参数):函数调用时传入的参数
greet("Alice", "Hello");  // "Hello, Alice!"
 
// 实参多于形参
function sum(a, b) {
    console.log(arguments);  // [3, 5, 7, 9]
    return a + b;            // 只使用前两个参数
}
sum(3, 5, 7, 9);  // 8
 
// 实参少于形参
function greet(name, greeting) {
    return `${greeting || "Hello"}, ${name}!`;
}
greet("Bob");  // "Hello, Bob!"(greeting是undefined)

4.3.2 默认参数(ES6)

// 基本默认参数
function greet(name, greeting = "Hello") {
    return `${greeting}, ${name}!`;
}
greet("Alice");           // "Hello, Alice!"
greet("Bob", "Hi");       // "Hi, Bob!"
 
// 默认参数可以是表达式
function getTimestamp() {
    return Date.now();
}
function log(message, timestamp = getTimestamp()) {
    console.log(`[${timestamp}] ${message}`);
}
 
// 默认参数可以引用前面的参数
function createUser(name, role = "user", fullName = `${role}: ${name}`) {
    return { name, role, fullName };
}
 
// 默认参数与解构结合
function createPoint({ x = 0, y = 0 } = {}) {
    return { x, y };
}
createPoint();            // { x: 0, y: 0 }
createPoint({ x: 5 });    // { x: 5, y: 0 }

4.3.3 剩余参数(Rest Parameters,ES6)

// 使用剩余参数收集所有额外参数
function sum(first, ...numbers) {
    console.log(first);      // 第一个参数
    console.log(numbers);    // 其余参数组成的数组
    return numbers.reduce((a, b) => a + b, first);
}
sum(1, 2, 3, 4, 5);  // 15
 
// 剩余参数必须是最后一个参数
function process(a, ...rest, b) { }  // SyntaxError
 
// 与展开运算符的区别
function multiply(multiplier, ...numbers) {
    return numbers.map(n => n * multiplier);
}
multiply(2, 1, 2, 3);  // [2, 4, 6]

4.3.4 arguments对象

// 传统方式:使用arguments对象
function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}
sum(1, 2, 3, 4, 5);  // 15
 
// arguments是类数组对象
function test() {
    console.log(arguments.length);  // 3
    console.log(arguments[0]);      // "a"
    console.log(typeof arguments);  // "object"
    console.log(Array.isArray(arguments));  // false
}
test("a", "b", "c");
 
// 将arguments转为数组
function oldStyle() {
    const args = Array.prototype.slice.call(arguments);
    // 或使用ES6方式
    const args2 = [...arguments];
    // 或使用Array.from
    const args3 = Array.from(arguments);
}

箭头函数与arguments

// 箭头函数没有自己的arguments
const arrow = () => {
    console.log(arguments);  // 引用外部函数的arguments
};
 
// 在箭头函数中获取参数
const arrow2 = (...args) => {
    console.log(args);  // 真正的数组
};

4.3.5 参数解构

// 对象解构
function createUser({ name, age, email = "N/A" }) {
    return { name, age, email };
}
createUser({ name: "Alice", age: 25 });  // { name: "Alice", age: 25, email: "N/A" }
 
// 数组解构
function getCoordinates([x, y, z = 0]) {
    return { x, y, z };
}
getCoordinates([10, 20]);        // { x: 10, y: 20, z: 0 }
getCoordinates([10, 20, 30]);    // { x: 10, y: 20, z: 30 }
 
// 嵌套解构
function displayPerson({ name, address: { city, country } }) {
    console.log(`${name} lives in ${city}, ${country}`);
}
displayPerson({
    name: "Bob",
    address: { city: "北京", country: "中国" }
});

4.4 函数返回值

4.4.1 return语句

// 返回单个值
function add(a, b) {
    return a + b;
}
 
// 无返回值(返回undefined)
function log(message) {
    console.log(message);
    // 隐式返回undefined
}
 
// 显式返回undefined
function doNothing() {
    return;
}
 
// 提前返回
function divide(a, b) {
    if (b === 0) {
        return null;  // 提前返回
    }
    return a / b;
}

4.4.2 返回多个值

// 使用数组返回多个值
function getMinMax(arr) {
    return [Math.min(...arr), Math.max(...arr)];
}
const [min, max] = getMinMax([3, 1, 4, 1, 5]);
 
// 使用对象返回多个值(推荐,更清晰)
function getUserStats(user) {
    return {
        name: user.name,
        age: calculateAge(user.birthDate),
        membershipDays: getMembershipDays(user.joinDate)
    };
}
const { name, age } = getUserStats(user);

4.5 箭头函数详解

4.5.1 箭头函数与普通函数的区别

特性 普通函数 箭头函数
—————-———-
this 动态绑定 词法绑定(继承外层)
arguments 有自己的arguments 没有,使用…args
构造函数 可以 不可以
prototype 没有
简写语法 支持

4.5.2 this的词法绑定

// 普通函数的this问题
const obj = {
    name: "Alice",
    friends: ["Bob", "Carol"],
    greetFriends: function() {
        this.friends.forEach(function(friend) {
            // 这里的this不是obj!
            // console.log(this.name + " says hi to " + friend);  // undefined
        });
    }
};
 
// 传统解决方案:保存this
const obj2 = {
    name: "Alice",
    friends: ["Bob", "Carol"],
    greetFriends: function() {
        const self = this;
        this.friends.forEach(function(friend) {
            console.log(self.name + " says hi to " + friend);
        });
    }
};
 
// 使用bind
const obj3 = {
    name: "Alice",
    friends: ["Bob", "Carol"],
    greetFriends: function() {
        this.friends.forEach(function(friend) {
            console.log(this.name + " says hi to " + friend);
        }.bind(this));
    }
};
 
// 箭头函数解决方案(推荐)
const obj4 = {
    name: "Alice",
    friends: ["Bob", "Carol"],
    greetFriends: function() {
        this.friends.forEach(friend => {
            console.log(this.name + " says hi to " + friend);
        });
    }
};

4.5.3 箭头函数的适用场景

// 1. 回调函数
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((a, b) => a + b, 0);
 
// 2. 链式调用
const result = users
    .filter(u => u.active)
    .map(u => u.name)
    .sort((a, b) => a.localeCompare(b));
 
// 3. Promise回调
fetch('/api/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));
 
// 4. 对象字面量中的方法(注意this问题)
const counter = {
    count: 0,
    // 箭头函数在这里不合适,因为this不会指向counter
    increment: () => {
        this.count++;  // 错误!this不是counter
    },
    // 应该使用普通函数或方法简写
    incrementCorrect() {
        this.count++;
    }
};

4.6 高阶函数

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

4.6.1 回调函数

// 异步回调
setTimeout(() => {
    console.log("1秒后执行");
}, 1000);
 
// 数组方法回调
[1, 2, 3].forEach(item => console.log(item));
 
// 自定义高阶函数
function processData(data, callback) {
    const result = data.map(item => item * 2);
    callback(result);
}
 
processData([1, 2, 3], result => {
    console.log(result);  // [2, 4, 6]
});

4.6.2 函数作为返回值(闭包)

// 创建计数器
function createCounter() {
    let count = 0;
    return function() {
        return ++count;
    };
}
 
const counter1 = createCounter();
const counter2 = createCounter();
 
console.log(counter1());  // 1
console.log(counter1());  // 2
console.log(counter2());  // 1(独立的计数器)
 
// 创建乘法函数
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}
 
const double = createMultiplier(2);
const triple = createMultiplier(3);
 
console.log(double(5));   // 10
console.log(triple(5));   // 15

4.6.3 函数组合

// 简单组合
const compose = (f, g) => x => f(g(x));
 
const add1 = x => x + 1;
const double = x => x * 2;
 
const add1ThenDouble = compose(double, add1);
console.log(add1ThenDouble(5));  // 12((5 + 1) * 2)
 
// 多函数组合
const composeMany = (...fns) => x => 
    fns.reduceRight((v, f) => f(v), x);
 
const pipeline = composeMany(
    x => x + 1,
    x => x * 2,
    x => x - 3
);
console.log(pipeline(5));  // 5(((5 - 3) * 2) + 1)
 
// 管道(从左到右)
const pipe = (...fns) => x => 
    fns.reduce((v, f) => f(v), x);

4.6.4 偏函数和柯里化

// 偏函数:固定部分参数
function partial(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        return fn(...fixedArgs, ...remainingArgs);
    };
}
 
function greet(greeting, name) {
    return `${greeting}, ${name}!`;
}
 
const sayHello = partial(greet, "Hello");
console.log(sayHello("Alice"));  // "Hello, Alice!"
 
// 柯里化:将多参数函数转为单参数链
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));
            };
        }
    };
}
 
function sum(a, b, c) {
    return a + b + c;
}
 
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3));      // 6
console.log(curriedSum(1, 2)(3));      // 6
console.log(curriedSum(1)(2, 3));      // 6

4.7 递归函数

函数调用自身的编程技巧。

// 阶乘
function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
 
// 斐波那契数列
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
 
// 数组求和
function sumArray(arr) {
    if (arr.length === 0) return 0;
    return arr[0] + sumArray(arr.slice(1));
}
 
// 深拷贝
function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
 
    if (obj instanceof Date) {
        return new Date(obj);
    }
 
    if (Array.isArray(obj)) {
        return obj.map(item => deepClone(item));
    }
 
    const cloned = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloned[key] = deepClone(obj[key]);
        }
    }
    return cloned;
}
 
// 尾递归优化(ES6支持,但浏览器实现不一)
function factorialTail(n, acc = 1) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);
}

4.8 立即执行函数(IIFE)

// 基本IIFE
(function() {
    console.log("立即执行");
})();
 
// 带参数的IIFE
(function(name) {
    console.log("Hello, " + name);
})("World");
 
// 箭头函数版本
(() => {
    console.log("IIFE with arrow");
})();
 
// IIFE的使用场景:创建私有作用域
const counter = (function() {
    let count = 0;
 
    return {
        increment() {
            return ++count;
        },
        decrement() {
            return --count;
        },
        getCount() {
            return count;
        }
    };
})();
 
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getCount());   // 2
// count变量无法从外部直接访问
 
// 模块化模式(ES6模块之前)
var myModule = (function() {
    var privateVar = "private";
 
    function privateMethod() {
        return privateVar;
    }
 
    return {
        publicMethod: function() {
            return privateMethod();
        }
    };
})();

4.9 函数式编程基础

4.9.1 纯函数

// 纯函数:相同输入始终产生相同输出,无副作用
function add(a, b) {
    return a + b;
}
 
// 非纯函数:依赖外部状态
let count = 0;
function increment() {
    return ++count;  // 修改外部状态
}
 
// 非纯函数:产生副作用
function logMessage(msg) {
    console.log(msg);  // IO操作
}
 
// 非纯函数:依赖外部可变数据
const users = [];
function addUser(user) {
    users.push(user);  // 修改外部数据
    return users;
}

4.9.2 不可变性

// 可变操作(不推荐)
const arr = [1, 2, 3];
arr.push(4);  // 修改原数组
 
// 不可变操作(推荐)
const newArr = [...arr, 4];
 
// 对象不可变更新
const user = { name: "Alice", age: 25 };
const updatedUser = { ...user, age: 26 };
 
// 数组方法对比
const numbers = [1, 2, 3];
 
// 可变
numbers.push(4);        // 修改原数组
numbers.splice(1, 1);   // 修改原数组
 
// 不可变
const newNumbers = [...numbers, 4];
const filtered = numbers.filter(n => n !== 2);
const mapped = numbers.map(n => n * 2);

4.10 练习题

练习1:函数定义

编写以下函数的不同定义方式:

// 1. 使用函数声明定义一个计算圆面积的函数
 
// 2. 使用函数表达式定义一个检查字符串是否为回文的函数
 
// 3. 使用箭头函数定义一个计算数组平均值的函数
 
// 4. 将上述函数改写为使用默认参数和剩余参数

练习2:高阶函数

// 1. 实现一个map函数(模拟Array.prototype.map)
function myMap(array, callback) {
    // 你的代码
}
 
// 2. 实现一个filter函数
function myFilter(array, callback) {
    // 你的代码
}
 
// 3. 实现一个once函数,确保函数只执行一次
function once(fn) {
    // 你的代码
}
 
// 4. 实现一个memoize函数,缓存函数结果
function memoize(fn) {
    // 你的代码
}

练习3:递归

// 1. 使用递归实现数组扁平化
function flatten(arr) {
    // 你的代码
}
// flatten([1, [2, [3, 4]], 5]) => [1, 2, 3, 4, 5]
 
// 2. 使用递归实现对象属性路径获取
function get(obj, path) {
    // 你的代码
}
// get({ a: { b: { c: 1 } } }, 'a.b.c') => 1
 
// 3. 实现快速排序
function quickSort(arr) {
    // 你的代码
}

练习4:综合应用

实现一个函数组合管道,能够:

// 1. 创建一个管道函数,可以链式组合多个函数
const pipeline = createPipeline(
    x => x + 1,
    x => x * 2,
    x => x.toString()
);
pipeline(5);  // "12"
 
// 2. 实现一个节流(throttle)函数
function throttle(fn, delay) {
    // 你的代码
}
 
// 3. 实现一个防抖(debounce)函数
function debounce(fn, delay) {
    // 你的代码
}

参考答案

练习1答案

// 1. 函数声明
function circleArea(radius) {
    return Math.PI * radius ** 2;
}
 
// 2. 函数表达式
const isPalindrome = function(str) {
    const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
    return cleaned === cleaned.split('').reverse().join('');
};
 
// 3. 箭头函数
const average = arr => arr.reduce((a, b) => a + b, 0) / arr.length;
 
// 4. 使用默认参数和剩余参数
const circleArea2 = (radius = 1) => Math.PI * radius ** 2;
 
const sumAll = (first = 0, ...numbers) => 
    numbers.reduce((a, b) => a + b, first);

练习2答案

// 1. myMap
function myMap(array, callback) {
    const result = [];
    for (let i = 0; i < array.length; i++) {
        result.push(callback(array[i], i, array));
    }
    return result;
}
 
// 2. myFilter
function myFilter(array, callback) {
    const result = [];
    for (let i = 0; i < array.length; i++) {
        if (callback(array[i], i, array)) {
            result.push(array[i]);
        }
    }
    return result;
}
 
// 3. once
function once(fn) {
    let called = false;
    let result;
    return function(...args) {
        if (!called) {
            called = true;
            result = fn.apply(this, args);
        }
        return result;
    };
}
 
// 4. memoize
function memoize(fn) {
    const cache = new Map();
    return function(...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = fn.apply(this, args);
        cache.set(key, result);
        return result;
    };
}

练习3答案

// 1. flatten
function flatten(arr) {
    return arr.reduce((flat, item) => {
        return flat.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}
 
// 迭代版本
function flattenIterative(arr) {
    const result = [];
    const stack = [...arr];
    while (stack.length) {
        const item = stack.pop();
        if (Array.isArray(item)) {
            stack.push(...item);
        } else {
            result.unshift(item);
        }
    }
    return result;
}
 
// 2. get
function get(obj, path) {
    const keys = path.split('.');
    let result = obj;
    for (const key of keys) {
        if (result === null || result === undefined) {
            return undefined;
        }
        result = result[key];
    }
    return result;
}
 
// 递归版本
function getRecursive(obj, path) {
    const keys = path.split('.');
    if (keys.length === 1) {
        return obj?.[keys[0]];
    }
    return getRecursive(obj?.[keys[0]], keys.slice(1).join('.'));
}
 
// 3. quickSort
function quickSort(arr) {
    if (arr.length <= 1) return arr;
 
    const pivot = arr[Math.floor(arr.length / 2)];
    const left = arr.filter(x => x < pivot);
    const middle = arr.filter(x => x === pivot);
    const right = arr.filter(x => x > pivot);
 
    return [...quickSort(left), ...middle, ...quickSort(right)];
}

练习4答案

// 1. createPipeline
function createPipeline(...fns) {
    return function(input) {
        return fns.reduce((result, fn) => fn(result), input);
    };
}
 
// 使用
const pipeline = createPipeline(
    x => x + 1,
    x => x * 2,
    x => x.toString()
);
console.log(pipeline(5));  // "12"
 
// 2. throttle
function throttle(fn, delay) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= delay) {
            lastTime = now;
            return fn.apply(this, args);
        }
    };
}
 
// 3. debounce
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

本章小结

本章深入学习了JavaScript函数:

函数是JavaScript的核心,掌握函数的各种特性和模式对于编写高质量代码至关重要。

下一章将学习对象,这是JavaScript中最重要的数据类型。