JavaScript 函数与作用域
章节介绍
函数是 JavaScript 编程的核心概念之一,它让我们能够将代码组织成可重用的块。理解函数的工作原理、作用域机制和闭包概念,对于编写高质量的 JavaScript 代码至关重要。本教程将深入讲解这些重要概念,为后续的 Node.js 学习打下坚实基础。
核心知识点
函数定义
JavaScript 提供了多种定义函数的方式:
函数声明
function greet(name) {
console.log(`你好,${name}!`);
}
greet('张三'); // 输出:你好,张三!函数表达式
const greet = function(name) {
console.log(`你好,${name}!`);
};
greet('李四'); // 输出:你好,李四!箭头函数(ES6)
const greet = (name) => {
console.log(`你好,${name}!`);
};
// 简化形式(单个参数)
const greet2 = name => console.log(`你好,${name}!`);
// 简化形式(单行返回)
const add = (a, b) => a + b;
greet('王五'); // 输出:你好,王五!
console.log(add(5, 3)); // 输出:8函数参数
默认参数
function greet(name = '朋友') {
console.log(`你好,${name}!`);
}
greet(); // 输出:你好,朋友!
greet('张三'); // 输出:你好,张三!剩余参数
function sumAll(...numbers) {
return numbers.reduce((sum, num) => sum + num, 0);
}
console.log(sumAll(1, 2, 3, 4, 5)); // 15
console.log(sumAll(10, 20, 30)); // 60参数解构
function createUser({ name, age, city = '北京' }) {
return {
name,
age,
city,
createdAt: new Date()
};
}
const user = createUser({
name: '张三',
age: 25
});
console.log(user);
// 输出:{ name: '张三', age: 25, city: '北京', createdAt: Date }函数返回值
函数可以返回值,使用 return 语句:
function add(a, b) {
return a + b;
}
const result = add(5, 3);
console.log(result); // 8
// 没有返回值的函数默认返回 undefined
function logMessage(message) {
console.log(message);
// 没有 return 语句
}
const logged = logMessage('Hello');
console.log(logged); // undefined作用域概念
作用域决定了变量和函数的可访问性。JavaScript 有三种作用域:
全局作用域
let globalVar = '全局变量';
function showGlobal() {
console.log(globalVar); // 可以访问全局变量
}
showGlobal(); // 输出:全局变量
console.log(globalVar); // 输出:全局变量函数作用域
function testFunctionScope() {
let localVar = '局部变量';
console.log(localVar); // 可以访问局部变量
console.log(globalVar); // 可以访问全局变量
}
testFunctionScope();
// console.log(localVar); // 错误:无法访问函数局部变量块级作用域
function testBlockScope() {
if (true) {
let blockVar = '块级变量';
console.log(blockVar); // 可以访问块级变量
}
// console.log(blockVar); // 错误:无法访问块级变量
}
testBlockScope();作用域链
作用域链是 JavaScript 查找变量的机制。当访问一个变量时,JavaScript 会从当前作用域开始,逐级向外查找,直到找到变量或到达全局作用域。
作用域链示意图:
全局作用域
├── globalVar = '全局变量'
└── 函数作用域
├── localVar = '局部变量'
└── 块级作用域
└── blockVar = '块级变量'
变量查找顺序:
1. 当前作用域
2. 外层作用域
3. 更外层作用域
4. ...直到全局作用域let globalVar = '全局变量';
function outerFunction() {
let outerVar = '外层变量';
function innerFunction() {
let innerVar = '内层变量';
console.log(innerVar); // 内层变量(当前作用域)
console.log(outerVar); // 外层变量(外层作用域)
console.log(globalVar); // 全局变量(全局作用域)
}
innerFunction();
}
outerFunction();闭包
闭包是指函数能够记住并访问其词法作用域中的变量,即使函数在其词法作用域之外执行。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1(独立的计数器)
console.log(counter1()); // 3闭包的实际应用
// 1. 数据私有化
function createPerson(name) {
let age = 0;
return {
getName: () => name,
getAge: () => age,
setAge: (newAge) => {
if (newAge >= 0) {
age = newAge;
}
},
incrementAge: () => {
age++;
}
};
}
const person = createPerson('张三');
console.log(person.getName()); // 张三
console.log(person.getAge()); // 0
person.setAge(25);
console.log(person.getAge()); // 25
person.incrementAge();
console.log(person.getAge()); // 26
// person.age = -10; // 无法直接访问 age 变量
// 2. 函数工厂
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
// 3. 缓存函数
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log('从缓存获取:', key);
return cache[key];
}
const result = fn(...args);
cache[key] = result;
console.log('计算并缓存:', key);
return result;
};
}
function expensiveCalculation(n) {
console.log('执行复杂计算...');
let result = 0;
for (let i = 0; i < n; i++) {
result += i;
}
return result;
}
const memoizedCalc = memoize(expensiveCalculation);
console.log(memoizedCalc(100)); // 执行计算
console.log(memoizedCalc(100)); // 从缓存获取
console.log(memoizedCalc(100)); // 从缓存获取高阶函数
高阶函数是指接受函数作为参数或返回函数的函数。
// 接受函数作为参数
function calculate(a, b, operation) {
return operation(a, b);
}
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
console.log(calculate(5, 3, add)); // 8
console.log(calculate(5, 3, multiply)); // 15
// 返回函数
function createGreeter(greeting) {
return function(name) {
return `${greeting},${name}!`;
};
}
const sayHello = createGreeter('你好');
const sayGoodbye = createGreeter('再见');
console.log(sayHello('张三')); // 你好,张三!
console.log(sayGoodbye('李四')); // 再见,李四!递归函数
递归函数是指函数调用自身:
// 计算阶乘
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 safeFactorial(n, depth = 0, maxDepth = 1000) {
if (depth > maxDepth) {
throw new Error('递归深度超过限制');
}
if (n <= 1) {
return 1;
}
return n * safeFactorial(n - 1, depth + 1, maxDepth);
}实用案例分析
案例 1:函数式编程工具库
创建一个实用的函数式编程工具库:
// 工具函数库
const utils = {
// 数组工具
map: (array, fn) => array.map(fn),
filter: (array, fn) => array.filter(fn),
reduce: (array, fn, initial) => array.reduce(fn, initial),
find: (array, fn) => array.find(fn),
// 函数工具
compose: (...fns) => (x) => fns.reduceRight((v, f) => f(v), x),
pipe: (...fns) => (x) => fns.reduce((v, f) => f(v), x),
// 类型检查
isString: (value) => typeof value === 'string',
isNumber: (value) => typeof value === 'number',
isArray: (value) => Array.isArray(value),
isObject: (value) => typeof value === 'object' && value !== null,
// 字符串工具
capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
truncate: (str, length) => str.length > length ? str.slice(0, length) + '...' : str,
// 数字工具
clamp: (num, min, max) => Math.min(Math.max(num, min), max),
random: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
};
// 使用示例
const numbers = [1, 2, 3, 4, 5];
// 使用 map
const doubled = utils.map(numbers, n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 使用 filter
const evens = utils.filter(numbers, n => n % 2 === 0);
console.log(evens); // [2, 4]
// 使用 reduce
const sum = utils.reduce(numbers, (acc, n) => acc + n, 0);
console.log(sum); // 15
// 使用 compose
const addOne = n => n + 1;
const multiplyByTwo = n => n * 2;
const composed = utils.compose(multiplyByTwo, addOne);
console.log(composed(5)); // 12 (5 + 1) * 2
// 使用 pipe
const piped = utils.pipe(addOne, multiplyByTwo);
console.log(piped(5)); // 12 (5 + 1) * 2案例 2:事件处理器工厂
创建一个事件处理器工厂,用于处理用户交互:
function createEventHandler() {
let eventCount = 0;
const eventLog = [];
return {
handleClick: (element, callback) => {
element.addEventListener('click', (event) => {
eventCount++;
eventLog.push({
type: 'click',
target: element.tagName,
timestamp: new Date()
});
callback(event);
});
},
handleSubmit: (form, callback) => {
form.addEventListener('submit', (event) => {
event.preventDefault();
eventCount++;
eventLog.push({
type: 'submit',
formData: new FormData(form),
timestamp: new Date()
});
callback(event);
});
},
getStats: () => ({
totalEvents: eventCount,
recentEvents: eventLog.slice(-10)
}),
clearLog: () => {
eventLog.length = 0;
}
};
}
// 使用示例(在浏览器环境中)
const handler = createEventHandler();
// 假设有一个按钮
const button = document.getElementById('myButton');
handler.handleClick(button, (event) => {
console.log('按钮被点击了!');
});
// 假设有一个表单
const form = document.getElementById('myForm');
handler.handleSubmit(form, (event) => {
console.log('表单被提交了!');
});
// 查看统计信息
console.log(handler.getStats());案例 3:数据验证器
创建一个可配置的数据验证器:
function createValidator() {
const validators = {};
return {
addRule: (name, validatorFn, errorMessage) => {
validators[name] = {
validate: validatorFn,
message: errorMessage
};
},
validate: (data, rules) => {
const errors = [];
for (const field in rules) {
const fieldRules = rules[field];
const value = data[field];
for (const rule of fieldRules) {
if (typeof rule === 'string') {
const validator = validators[rule];
if (validator && !validator.validate(value)) {
errors.push({
field,
message: validator.message
});
break;
}
} else if (typeof rule === 'function') {
const result = rule(value);
if (result !== true) {
errors.push({
field,
message: result
});
break;
}
}
}
}
return {
isValid: errors.length === 0,
errors
};
}
};
}
// 创建验证器实例
const validator = createValidator();
// 添加验证规则
validator.addRule('required', (value) => value !== undefined && value !== null && value !== '',
'此字段为必填项');
validator.addRule('email', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
'请输入有效的邮箱地址');
validator.addRule('minLength', (value, min) => value && value.length >= min,
(value, min) => `至少需要 ${min} 个字符`);
validator.addRule('maxLength', (value, max) => value && value.length <= max,
(value, max) => `最多 ${max} 个字符`);
validator.addRule('min', (value, min) => value >= min,
(value, min) => `不能小于 ${min}`);
validator.addRule('max', (value, max) => value <= max,
(value, max) => `不能大于 ${max}`);
// 使用验证器
const userData = {
username: '张三',
email: 'zhangsan@example.com',
age: 25
};
const validationRules = {
username: ['required', (value) => value.length >= 2 || '用户名至少2个字符'],
email: ['required', 'email'],
age: ['required', (value) => value >= 18 || '必须年满18岁', (value) => value <= 120 || '年龄不能超过120岁']
};
const result = validator.validate(userData, validationRules);
if (result.isValid) {
console.log('数据验证通过!');
} else {
console.log('验证失败:');
result.errors.forEach(error => {
console.log(`- ${error.field}: ${error.message}`);
});
}代码示例
示例 1:作用域链演示
let globalVar = '全局变量';
function level1() {
let level1Var = '第一层变量';
function level2() {
let level2Var = '第二层变量';
function level3() {
let level3Var = '第三层变量';
console.log('在 level3 中:');
console.log('level3Var:', level3Var); // 第三层变量
console.log('level2Var:', level2Var); // 第二层变量
console.log('level1Var:', level1Var); // 第一层变量
console.log('globalVar:', globalVar); // 全局变量
}
level3();
console.log('\n在 level2 中:');
console.log('level2Var:', level2Var); // 第二层变量
console.log('level1Var:', level1Var); // 第一层变量
console.log('globalVar:', globalVar); // 全局变量
// console.log(level3Var); // 错误:无法访问
}
level2();
console.log('\n在 level1 中:');
console.log('level1Var:', level1Var); // 第一层变量
console.log('globalVar:', globalVar); // 全局变量
// console.log(level2Var); // 错误:无法访问
// console.log(level3Var); // 错误:无法访问
}
level1();
console.log('\n在全局中:');
console.log('globalVar:', globalVar); // 全局变量
// console.log(level1Var); // 错误:无法访问
// console.log(level2Var); // 错误:无法访问
// console.log(level3Var); // 错误:无法访问示例 2:闭包内存管理
// 不好的闭包使用(可能导致内存泄漏)
function createBadClosure() {
const largeArray = new Array(1000000).fill('data');
return function() {
console.log('函数被调用');
};
}
const badClosure = createBadClosure();
// largeArray 仍然被占用,即使不再使用
// 好的闭包使用
function createGoodClosure() {
const largeArray = new Array(1000000).fill('data');
// 使用大数组
const result = largeArray.slice(0, 10);
// 清除大数组的引用
// largeArray = null; // 在函数结束时自动清除
return function() {
console.log('函数被调用');
console.log('结果:', result);
};
}
const goodClosure = createGoodClosure();
// largeArray 已被垃圾回收
// 使用弱引用避免内存泄漏(在支持的环境中)
function createWeakClosure() {
const weakMap = new WeakMap();
const data = { value: '重要数据' };
weakMap.set(data, '关联数据');
return function() {
const result = weakMap.get(data);
console.log('获取数据:', result);
};
}
const weakClosure = createWeakClosure();示例 3:函数柯里化
// 柯里化:将多参数函数转换为单参数函数序列
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
// 原始函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化后的函数
const curriedAdd = curry(add);
// 逐步调用
console.log(curriedAdd(1)(2)(3)); // 6
// 部分应用
const add1 = curriedAdd(1);
const add1and2 = add1(2);
console.log(add1and2(3)); // 6
// 实际应用
function multiply(a, b) {
return a * b;
}
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
const triple = curriedMultiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 创建特定领域的函数
function calculatePrice(price, tax, discount) {
return price * (1 + tax) * (1 - discount);
}
const curriedCalculate = curry(calculatePrice);
const calculateWithTax = curriedCalculate(0.1); // 10% 税
const calculateWithTaxAndDiscount = calculateWithTax(0.2); // 20% 折扣
console.log(calculateWithTaxAndDiscount(100)); // 100 * 1.1 * 0.8 = 88实现技巧与注意事项
函数设计原则
- 单一职责:每个函数只做一件事
- 纯函数:避免副作用,相同输入总是产生相同输出
- 有意义的命名:函数名应该清楚地描述其功能
- 适当的参数数量:避免参数过多,考虑使用对象参数
闭包使用注意事项
- 内存管理:闭包会保持对外部变量的引用,可能导致内存泄漏
- 性能考虑:频繁创建闭包可能影响性能
- 代码可读性:过度使用闭包可能使代码难以理解
递归使用注意事项
- 基准条件:确保递归有明确的终止条件
- 递归深度:注意递归深度,避免栈溢出
- 尾递归优化:在支持的环境中,使用尾递归可以提高性能
作用域最佳实践
- 最小化作用域:在尽可能小的作用域中声明变量
- 避免全局污染:尽量减少全局变量的使用
- 使用 const 和 let:优先使用 const,需要重新赋值时使用 let
常见问题与解决方案
问题 1:闭包导致的意外行为
// 问题代码
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 输出:5, 5, 5, 5, 5
}, 100);
}
// 解决方案 1:使用 let
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 输出:0, 1, 2, 3, 4
}, 100);
}
// 解决方案 2:使用闭包
for (var i = 0; i < 5; i++) {
(function(index) {
setTimeout(() => {
console.log(index); // 输出:0, 1, 2, 3, 4
}, 100);
})(i);
}问题 2:this 指向问题
// 问题代码
const obj = {
value: 42,
getValue: function() {
return this.value;
}
};
const unboundGetValue = obj.getValue;
console.log(unboundGetValue()); // undefined
// 解决方案 1:使用 bind
const boundGetValue = obj.getValue.bind(obj);
console.log(boundGetValue()); // 42
// 解决方案 2:使用箭头函数
const obj2 = {
value: 42,
getValue: () => this.value
};
console.log(obj2.getValue()); // 42问题 3:递归导致的栈溢出
// 问题代码
function deepRecursion(n) {
if (n <= 0) return 0;
return n + deepRecursion(n - 1);
}
// deepRecursion(100000); // 栈溢出
// 解决方案:使用迭代
function iterativeSum(n) {
let sum = 0;
for (let i = 0; i <= n; i++) {
sum += i;
}
return sum;
}
console.log(iterativeSum(100000)); // 正常执行
// 或使用尾递归优化
function tailRecursiveSum(n, acc = 0) {
if (n <= 0) return acc;
return tailRecursiveSum(n - 1, acc + n);
}问题 4:函数参数过多
// 问题代码
function createUser(name, age, email, phone, address, city, country, postalCode) {
// 参数过多,难以维护
}
// 解决方案:使用对象参数
function createUser(options) {
const {
name,
age,
email,
phone,
address,
city,
country,
postalCode
} = options;
// 更清晰、更易维护
}
// 使用
createUser({
name: '张三',
age: 25,
email: 'zhangsan@example.com',
phone: '1234567890',
address: '北京市朝阳区',
city: '北京',
country: '中国',
postalCode: '100000'
});总结
本教程深入讲解了 JavaScript 的函数与作用域概念,包括函数定义、参数传递、作用域链和闭包等重要内容。这些概念是 JavaScript 编程的基础,对于理解 Node.js 的异步编程和模块系统至关重要。
通过本集的学习,您应该能够:
- 使用多种方式定义函数
- 理解函数参数的传递机制
- 掌握作用域链的工作原理
- 理解闭包的概念和应用场景
- 使用高阶函数和递归解决问题
- 避免常见的函数编程错误
在下一集中,我们将学习 JavaScript 的数组与对象操作,这是数据处理的核心内容。继续加油,您的 JavaScript 基础正在不断巩固!