返回首页

02-函数与作用域

分类:01-JavaScript基础核心
发布于:
阅读时间:98 分钟

函数与作用域

📋 学习目标

  • 掌握JavaScript函数的多种定义方式
  • 深入理解作用域链和闭包的概念
  • 学会函数参数的高级用法
  • 掌握箭头函数与普通函数的区别
  • 理解函数作为一等公民的特性

🎯 函数基础

1. 函数定义方式

// 1. 函数声明(Function Declaration)
function add(a, b) {
    return a + b;
}

// 函数声明会被提升(hoisting)
console.log(subtract(10, 5)); // 5 (可以提前调用)
function subtract(a, b) {
    return a - b;
}

// 2. 函数表达式(Function Expression)
const multiply = function(a, b) {
    return a * b;
};

// 函数表达式不会被提升
// console.log(divide(10, 2)); // ReferenceError
const divide = function(a, b) {
    return a / b;
};

// 3. 箭头函数(Arrow Function)
const square = x => x * x;
const addWithDefault = (a, b = 1) => a + b;
const complex = (a, b) => {
    const result = a + b;
    return result * 2;
};

// 4. 构造函数方式(不推荐)
const newFunction = new Function('a', 'b', 'return a + b');
console.log(newFunction(2, 3)); // 5

// 5. 立即执行函数(IIFE)
(function() {
    console.log('立即执行');
})();

// 带参数的IIFE
const result = (function(x, y) {
    return x + y;
})(10, 20);
console.log(result); // 30

2. 函数参数

// 基础参数
function greet(name, age) {
    return `Hello ${name}, you are ${age} years old`;
}

// 默认参数(ES6)
function greetWithDefault(name = 'Guest', age = 18) {
    return `Hello ${name}, you are ${age} years old`;
}

console.log(greetWithDefault()); // Hello Guest, you are 18 years old
console.log(greetWithDefault('Alice')); // Hello Alice, you are 18 years old

// 剩余参数(Rest Parameters)
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

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

// 与命名参数结合
function createPerson(name, ...hobbies) {
    return {
        name,
        hobbies,
        hobbyCount: hobbies.length
    };
}

// 参数解构
function processUser({ name, age, city = 'Unknown' }) {
    console.log(`${name} is ${age} years old and lives in ${city}`);
}

processUser({ name: 'Alice', age: 25, city: 'New York' });
processUser({ name: 'Bob', age: 30 }); // city默认为Unknown

// 参数验证
function validateParams(required, ...optional) {
    if (required === undefined) {
        throw new Error('Required parameter is missing');
    }
    console.log('Required:', required);
    console.log('Optional:', optional);
}

3. 函数返回值

// 单个返回值
function calculateArea(radius) {
    return Math.PI * radius * radius;
}

// 多个返回值(返回对象)
function getPersonInfo(id) {
    const person = {
        id,
        name: `Person ${id}`,
        age: Math.floor(Math.random() * 50) + 18
    };

    return {
        person,
        isAdult: person.age >= 18,
        status: 'active'
    };
}

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

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

// 早期返回模式
function calculatePrice(quantity, price, isMember) {
    if (quantity <= 0) return 0;

    let total = quantity * price;

    if (isMember) {
        total *= 0.9; // 会员9折
    }

    return total;
}

// 返回函数(高阶函数)
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

🔄 作用域与作用域链

1. 作用域类型

// 全局作用域
var globalVar = 'I am global';

function outerFunction() {
    // 函数作用域
    var outerVar = 'I am outer';

    function innerFunction() {
        // 嵌套函数作用域
        var innerVar = 'I am inner';

        console.log(globalVar); // 可以访问全局变量
        console.log(outerVar);  // 可以访问外层函数变量
        console.log(innerVar);  // 可以访问自己作用域的变量
    }

    innerFunction();
    // console.log(innerVar); // ReferenceError: 无法访问内层函数变量
}

outerFunction();
// console.log(outerVar); // ReferenceError: 无法访问函数内部变量

// 块级作用域(ES6)
function blockScopeDemo() {
    if (true) {
        let blockVar = 'I am block scoped';
        const blockConst = 'I am also block scoped';

        console.log(blockVar); // 可以访问
    }

    // console.log(blockVar); // ReferenceError: 无法访问块级变量
}

blockScopeDemo();

2. 作用域链

// 作用域链示例
var globalVar = 'global';

function level1() {
    var level1Var = 'level1';

    function level2() {
        var level2Var = 'level2';

        function level3() {
            var level3Var = 'level3';

            // 作用域链查找过程
            console.log(level3Var); // 在level3作用域找到
            console.log(level2Var);  // 在level2作用域找到
            console.log(level1Var);  // 在level1作用域找到
            console.log(globalVar);   // 在全局作用域找到
        }

        level3();
    }

    level2();
}

level1();

// 变量遮蔽
var name = 'Global name';

function showName() {
    var name = 'Local name';
    console.log(name); // 'Local name' (局部变量遮蔽了全局变量)
}

showName();
console.log(name); // 'Global name'

3. 作用域与this

// this在不同作用域中的指向
const obj = {
    name: 'Object',
    outerMethod: function() {
        console.log(this.name); // 'Object' (方法调用)

        function innerMethod() {
            console.log(this.name); // undefined (函数独立调用,this指向全局对象)
        }

        innerMethod();

        // 解决方案1:保存this引用
        const self = this;
        function innerMethod2() {
            console.log(self.name); // 'Object'
        }
        innerMethod2();

        // 解决方案2:使用箭头函数
        const innerMethod3 = () => {
            console.log(this.name); // 'Object' (箭头函数继承外层this)
        };
        innerMethod3();
    }
};

obj.outerMethod();

🔐 闭包(Closure)

1. 闭包的基本概念

// 闭包的定义:函数能够访问其外部作用域的变量,即使外部函数已经执行完毕
function outerFunction(x) {
    // 外部函数的变量
    return function innerFunction(y) {
        // 内部函数可以访问外部函数的变量x
        return x + y;
    };
}

const addFive = outerFunction(5);
console.log(addFive(3)); // 8 (x仍然是5)

const addTen = outerFunction(10);
console.log(addTen(3)); // 13 (x是10,与上面的闭包独立)

// 闭包保存状态
function createCounter() {
    let count = 0;

    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.getCount());  // 2

const counter2 = createCounter();
console.log(counter2.increment()); // 1 (独立的闭包)

2. 闭包的实际应用

// 1. 私有变量模拟
function createPerson(name) {
    // 私有变量
    let _name = name;
    let _age = 0;

    return {
        getName: function() {
            return _name;
        },

        getAge: function() {
            return _age;
        },

        setAge: function(newAge) {
            if (newAge > 0 && newAge < 150) {
                _age = newAge;
            }
        },

        celebrateBirthday: function() {
            _age++;
            return `Happy ${_age}th birthday, ${_name}!`;
        }
    };
}

const person = createPerson('Alice');
console.log(person.getName()); // Alice
console.log(person.celebrateBirthday()); // Happy 1st birthday, Alice!
// 无法直接访问 _name 和 _age

// 2. 函数工厂
function createCalculator(operation) {
    return function(a, b) {
        switch(operation) {
            case 'add':
                return a + b;
            case 'subtract':
                return a - b;
            case 'multiply':
                return a * b;
            case 'divide':
                return a / b;
            default:
                return 0;
        }
    };
}

const adder = createCalculator('add');
const multiplier = createCalculator('multiply');

console.log(adder(5, 3));     // 8
console.log(multiplier(5, 3)); // 15

// 3. 事件处理器中的闭包
function setupButtons() {
    const buttons = document.querySelectorAll('button');

    buttons.forEach((button, index) => {
        button.addEventListener('click', function() {
            // 闭包保存了index的值
            console.log(`Button ${index} clicked`);
        });
    });
}

// 4. 防抖和节流
function debounce(func, delay) {
    let timeoutId;

    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

const debouncedSearch = debounce((query) => {
    console.log('Searching for:', query);
}, 300);

3. 闭包的陷阱

// 陷阱1:循环中的闭包
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i); // 3, 3, 3 (因为var是函数作用域,循环结束后i为3)
    }, 100);
}

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

// 解决方案2:使用let(块级作用域)
for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i); // 0, 1, 2
    }, 100);
}

// 陷阱2:内存泄漏
function createLeakyClosure() {
    const largeObject = { data: new Array(1000000).fill('data') };

    return function() {
        // 这个闭包引用了largeObject,导致largeObject无法被垃圾回收
        return largeObject.data[0];
    };
}

const leakyFunction = createLeakyClosure();
// largeObject仍然存在于内存中,直到leakyFunction被释放

// 解决方案:只保留需要的数据
function createOptimizedClosure() {
    const largeObject = { data: new Array(1000000).fill('data') };
    const neededData = largeObject.data[0];

    return function() {
        return neededData; // 只引用需要的数据
    };
}

➡️ 箭头函数

1. 箭头函数语法

// 基础语法
const add = (a, b) => a + b;
const square = x => x * x;
const greet = () => 'Hello World!';

// 多行箭头函数
const calculateArea = (radius) => {
    const pi = Math.PI;
    return pi * radius * radius;
};

// 对象字面量返回
const createUser = (name, age) => ({ name, age });
// 注意:返回对象时需要用括号包裹,否则会被当作函数体

// 箭头函数与普通函数的区别
const obj = {
    value: 42,

    // 普通函数
    regularMethod: function() {
        console.log(this.value); // 42
        return function() {
            console.log(this.value); // undefined (this指向全局对象)
        };
    },

    // 箭头函数
    arrowMethod: function() {
        console.log(this.value); // 42
        return () => {
            console.log(this.value); // 42 (继承外层this)
        };
    }
};

const regularInner = obj.regularMethod();
const arrowInner = obj.arrowMethod();

regularInner(); // undefined
arrowInner();  // 42

2. 箭头函数的特点

// 1. 没有自己的this
const button = document.querySelector('button');
const app = {
    name: 'My App',

    init() {
        // ❌ 错误:this指向button
        button.addEventListener('click', function() {
            console.log(this.name); // undefined
        });

        // ✅ 正确:使用箭头函数
        button.addEventListener('click', () => {
            console.log(this.name); // 'My App'
        });
    }
};

// 2. 没有自己的arguments对象
function regularFunction() {
    console.log(arguments); // Arguments对象
}

const arrowFunction = () => {
    // console.log(arguments); // ReferenceError: arguments is not defined
};

// 可以使用剩余参数替代
const arrowWithRest = (...args) => {
    console.log(args); // 数组形式
};

// 3. 不能作为构造函数
const Person = (name) => {
    this.name = name;
};

// const person = new Person('Alice'); // TypeError: Person is not a constructor

// 4. 没有prototype属性
console.log(regularFunction.prototype); // {}
console.log(arrowFunction.prototype); // undefined

3. 箭头函数的实际应用

// 1. 数组方法
const numbers = [1, 2, 3, 4, 5];

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

// 2. Promise链
fetch('/api/data')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error(error));

// 3. 事件处理器
class Component {
    constructor() {
        this.name = 'Component';

        // 使用箭头函数绑定this
        this.handleClick = () => {
            console.log(this.name);
        };
    }

    render() {
        return `<button onclick="${this.handleClick}">Click me</button>`;
    }
}

// 4. 高阶函数
const withLogging = (fn) => (...args) => {
    console.log('Calling function with args:', args);
    const result = fn(...args);
    console.log('Function returned:', result);
    return result;
};

const addWithLogging = withLogging((a, b) => a + b);
console.log(addWithLogging(2, 3)); // 显示调用和返回信息

🎯 高阶函数

1. 函数作为参数

// 数组方法接收函数作为参数
const numbers = [1, 2, 3, 4, 5];

// map: 转换数组
const doubled = numbers.map(x => x * 2);

// filter: 过滤数组
const evens = numbers.filter(x => x % 2 === 0);

// reduce: 累积计算
const sum = numbers.reduce((acc, x) => acc + x, 0);

// forEach: 遍历数组
numbers.forEach(x => console.log(x));

// 自定义高阶函数
function withRetry(fn, maxRetries = 3) {
    return async function(...args) {
        let lastError;

        for (let i = 0; i < maxRetries; i++) {
            try {
                return await fn(...args);
            } catch (error) {
                lastError = error;
                if (i < maxRetries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
                }
            }
        }

        throw lastError;
    };
}

const fetchWithRetry = withRetry(async (url) => {
    const response = await fetch(url);
    if (!response.ok) throw new Error('Request failed');
    return response.json();
});

2. 函数作为返回值

// 函数工厂
function createValidator(rules) {
    return function(data) {
        const errors = [];

        for (const [field, rule] of Object.entries(rules)) {
            if (!rule.test(data[field])) {
                errors.push(`${field}: ${rule.message}`);
            }
        }

        return {
            isValid: errors.length === 0,
            errors
        };
    };
}

const userValidator = createValidator({
    name: {
        test: name => name && name.length >= 2,
        message: 'Name must be at least 2 characters'
    },
    email: {
        test: email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
        message: 'Invalid email format'
    }
});

const userData = { name: 'A', email: 'invalid-email' };
const result = userValidator(userData);
console.log(result); // { isValid: false, errors: ['Name must be at least 2 characters', 'Invalid email format'] }

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

const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6

🔄 递归函数

1. 基础递归

// 阶乘计算
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 sumArray(arr) {
    if (arr.length === 0) return 0;

    return arr[0] + sumArray(arr.slice(1));
}

console.log(sumArray([1, 2, 3, 4, 5])); // 15

2. 尾递归优化

// 普通递归(可能导致栈溢出)
function factorialNormal(n) {
    if (n <= 1) return 1;
    return n * factorialNormal(n - 1);
}

// 尾递归优化
function factorialTail(n, accumulator = 1) {
    if (n <= 1) return accumulator;
    return factorialTail(n - 1, n * accumulator);
}

console.log(factorialTail(5)); // 120
console.log(factorialTail(100)); // 不会栈溢出

// 注意:JavaScript引擎不一定支持尾递归优化
// 但尾递归的写法更清晰且更易于优化

3. 递归的实际应用

// 深度克隆对象
function deepClone(obj) {
    if (obj =<mark> null || typeof obj !</mark> 'object') return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof Array) return obj.map(item => deepClone(item));
    if (typeof obj === 'object') {
        const cloned = {};
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                cloned[key] = deepClone(obj[key]);
            }
        }
        return cloned;
    }
}

// 树形结构遍历
function traverseTree(node, callback) {
    if (!node) return;

    callback(node);

    if (node.children) {
        node.children.forEach(child => traverseTree(child, callback));
    }
}

// 文件系统遍历
function traverseFileSystem(dir, callback) {
    const files = readDirectory(dir);

    files.forEach(file => {
        const fullPath = path.join(dir, file);

        if (isDirectory(fullPath)) {
            traverseFileSystem(fullPath, callback);
        } else {
            callback(fullPath);
        }
    });
}

⚠️ 常见陷阱与最佳实践

1. this指向陷阱

// ❌ 错误:丢失this
const obj = {
    name: 'Object',
    methods: {
        showName: function() {
            console.log(this.name);
        }
    }
};

const showName = obj.methods.showName;
showName(); // undefined

// ✅ 解决方案1:bind
const boundShowName = obj.methods.showName.bind(obj);
boundShowName(); // 'Object'

// ✅ 解决方案2:箭头函数
const arrowShowName = () => obj.methods.showName();
arrowShowName(); // 'Object'

2. 内存泄漏

// ❌ 错误:闭包导致内存泄漏
function createLeak() {
    const largeData = new Array(1000000).fill('data');

    return function() {
        // 闭包引用了largeData,无法被垃圾回收
        return largeData.length;
    };
}

// ✅ 解决方案:只保留必要的数据
function createOptimized() {
    const largeData = new Array(1000000).fill('data');
    const necessaryInfo = largeData.length;

    return function() {
        return necessaryInfo;
    };
}

3. 最佳实践

// 1. 使用默认参数而不是arguments对象
function good(name = 'Guest', age = 18) {
    return `${name} is ${age} years old`;
}

// 2. 使用解构参数
function processUser({ name, age, city = 'Unknown' }) {
    // 参数清晰明确
}

// 3. 使用箭头函数保持this
class Component {
    constructor() {
        this.handleClick = () => {
            console.log(this.name);
        };
    }
}

// 4. 避免在循环中创建函数
// ❌ 错误
for (let i = 0; i < 10; i++) {
    elements[i].onclick = function() {
        console.log(i); // 每个都会输出9
    };
}

// ✅ 正确
for (let i = 0; i < 10; i++) {
    elements[i].onclick = () => console.log(i);
}

// 5. 使用纯函数
function pureAdd(a, b) {
    return a + b; // 无副作用,相同输入总是产生相同输出
}

📝 函数式编程概念

1. 纯函数

// 纯函数:相同输入总是产生相同输出,无副作用
function add(a, b) {
    return a + b;
}

// 非纯函数:有副作用
let counter = 0;
function increment() {
    counter++;
    return counter;
}

// 纯函数的优势
// 1. 可预测性
// 2. 可测试性
// 3. 可缓存性
// 4. 并发安全

2. 不可变性

// 不可变操作
const numbers = [1, 2, 3, 4, 5];

// ❌ 可变操作
numbers.push(6); // 修改原数组

// ✅ 不可变操作
const newNumbers = [...numbers, 6]; // 创建新数组

const updatedNumbers = numbers.map(x => x === 3 ? 33 : x); // 创建新数组

// 对象不可变操作
const user = { name: 'Alice', age: 25 };

// ❌ 可变操作
user.age = 26;

// ✅ 不可变操作
const updatedUser = { ...user, age: 26 };

3. 函数组合

// 函数组合
const compose = (f, g) => (x) => f(g(x));

const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;

const addOneThenMultiply = compose(multiplyByTwo, addOne);
console.log(addOneThenMultiply(5)); // 12 ( (5 + 1) * 2 )

// 多个函数组合
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);

const processNumber = pipe(
    x => x + 1,
    x => x * 2,
    x => x - 3
);

console.log(processNumber(5)); // 9 ( ((5 + 1) * 2) - 3 )

🎯 小结

  • 掌握了JavaScript函数的多种定义方式
  • 深入理解了作用域链和闭包的概念
  • 学会了函数参数的高级用法
  • 掌握了箭头函数与普通函数的区别
  • 理解了函数作为一等公民的特性
  • 学会了递归、高阶函数等高级概念
  • 了解了函数式编程的基本概念

下一步学习对象与原型链