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函数的多种定义方式
- 深入理解了作用域链和闭包的概念
- 学会了函数参数的高级用法
- 掌握了箭头函数与普通函数的区别
- 理解了函数作为一等公民的特性
- 学会了递归、高阶函数等高级概念
- 了解了函数式编程的基本概念
下一步学习:对象与原型链