一、闭包:函数与其词法环境的结合
1.1 闭包的本质
闭包是指函数及其引用的外部环境的组合。当函数可以记住并访问其所在的词法作用域时,即使函数在其词法作用域之外执行,这种现象就称为闭包。
javascript
function createCounter() {
let count = 0; // 私有变量
return function() {
return ++count;
}
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
在上例中,内部函数即使在createCounter
执行完毕后仍能访问count
变量,这就形成了闭包。
1.2 闭包的实际应用
数据封装与私有变量模拟
javascript
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
if (amount > 0) {
balance += amount;
return `存款成功,当前余额: ${balance}`;
}
return "存款金额必须大于0";
},
withdraw: function(amount) {
if (amount <= balance && amount > 0) {
balance -= amount;
return `取款成功,当前余额: ${balance}`;
}
return "余额不足或取款金额无效";
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 存款成功,当前余额: 1500
console.log(account.withdraw(200)); // 取款成功,当前余额: 1300
console.log(account.balance); // undefined(无法直接访问私有变量)
1.3 闭包的内存影响
闭包可能导致内存泄漏,因为被引用的外部变量不会被垃圾回收:
javascript
function createLargeArray() {
const largeArray = new Array(1000000).fill('大数据');
return function() {
console.log(largeArray.length);
};
}
const printArrayLength = createLargeArray(); // largeArray 会一直存在于内存中
解决方案:
javascript
let printArrayLength = createLargeArray();
printArrayLength(); // 使用后
printArrayLength = null; // 解除引用,允许垃圾回收
二、原型链:JavaScript 的继承机制
2.1 原型链的工作原理
JavaScript 中的每个对象都有一个隐藏的 [[Prototype]]
属性,指向其原型对象。当访问一个对象的属性时,如果对象本身没有该属性,JavaScript 会沿着原型链向上查找。
javascript
// 构造函数
function Person(name) {
this.name = name;
}
// 在原型上添加方法
Person.prototype.sayHello = function() {
return `你好,我是${this.name}`;
};
const alice = new Person('Alice');
console.log(alice.sayHello()); // 你好,我是Alice
2.2 原型继承实现
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name}正在进食`;
};
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor
// 添加子类特有方法
Dog.prototype.bark = function() {
return `${this.name}:汪汪!`;
};
const husky = new Dog('雪橇', '哈士奇');
console.log(husky.eat()); // 雪橇正在进食
console.log(husky.bark()); // 雪橇:汪汪!
2.3 ES6 类语法与原型
ES6 的类语法本质上仍是基于原型的语法糖:
javascript
class Animal {
constructor(name) {
this.name = name;
}
eat() {
return `${this.name}正在进食`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
return `${this.name}:汪汪!`;
}
}
const husky = new Dog('雪橇', '哈士奇');
console.log(husky.eat()); // 雪橇正在进食
2.4 原型污染与防御
原型污染是一种安全风险,当攻击者能够修改对象的原型时可能发生:
javascript
// 原型污染示例
Object.prototype.polluted = '被污染的数据';
const normalObj = {};
console.log(normalObj.polluted); // 被污染的数据
防御措施:
javascript
// 使用 Object.create(null) 创建无原型对象
const safeObject = Object.create(null);
safeObject.data = "安全数据";
console.log(safeObject.polluted); // undefined
三、Event Loop:JavaScript的执行机制
3.1 事件循环模型
JavaScript 是单线程的,Event Loop 使其能够非阻塞地处理异步操作。
javascript
console.log('同步任务1');
setTimeout(() => {
console.log('宏任务(Timer)');
}, 0);
Promise.resolve().then(() => {
console.log('微任务1');
}).then(() => {
console.log('微任务2');
});
console.log('同步任务2');
// 输出顺序:
// 同步任务1
// 同步任务2
// 微任务1
// 微任务2
// 宏任务(Timer)
3.2 宏任务与微任务
宏任务(MacroTask):
- setTimeout, setInterval, setImmediate
- I/O, UI rendering, requestAnimationFrame
微任务(MicroTask):
- Promise.then/catch/finally
- process.nextTick (Node.js)
- queueMicrotask
- MutationObserver
javascript
console.log('开始');
setTimeout(() => {
console.log('宏任务1');
Promise.resolve().then(() => {
console.log('宏任务1中的微任务');
});
console.log('宏任务1结束');
}, 0);
Promise.resolve().then(() => {
console.log('微任务1');
setTimeout(() => {
console.log('微任务1中的宏任务');
}, 0);
console.log('微任务1结束');
});
console.log('结束');
// 输出顺序:
// 开始
// 结束
// 微任务1
// 微任务1结束
// 宏任务1
// 宏任务1结束
// 宏任务1中的微任务
// 微任务1中的宏任务
3.3 实际应用案例:防抖函数
javascript
function debounce(fn, delay) {
let timer = null;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
// 使用案例
const efficientSearch = debounce(function(query) {
console.log(`搜索:${query}`);
// 发送API请求
}, 300);
// 用户快速输入时,只有最后一次调用会执行
document.getElementById('search').addEventListener('input', function(e) {
efficientSearch(e.target.value);
});
四、内存管理:垃圾回收与内存优化
4.1 JavaScript的垃圾回收机制
JavaScript 引擎主要使用两种垃圾回收算法:
- 引用计数:计算对象的引用数量,当引用为0时回收
- 标记-清除:从根(全局对象)开始,标记所有可达对象,然后清除未标记的对象
4.2 内存泄漏常见原因
javascript
// 1. 全局变量导致的泄漏
function leakToGlobal() {
leakedVariable = "我不会被回收"; // 缺少var/let/const
}
// 2. 被遗忘的定时器
function startTimer() {
const largeData = new Array(10000000);
setInterval(() => {
console.log(largeData.length);
}, 1000);
// 定时器一直运行,largeData 无法被回收
}
// 3. 闭包引起的泄漏
function createLeakedClosure() {
const largeData = new Array(10000000);
return function() {
console.log(largeData[0]); // 持有对 largeData 的引用
}
}
4.3 内存优化最佳实践
javascript
// 1. 避免全局变量
'use strict'; // 严格模式下未声明变量会报错
// 2. 及时清除不需要的引用
function processData() {
let largeData = fetchLargeData();
const result = process(largeData);
largeData = null; // 手动解除引用
return result;
}
// 3. 正确管理事件监听器
function setupEventListener() {
const button = document.getElementById('myButton');
const handleClick = () => {
console.log('按钮被点击');
// 清理:移除事件监听器
button.removeEventListener('click', handleClick);
};
button.addEventListener('click', handleClick);
}
// 4. WeakMap/WeakSet 存储对象引用
const cache = new WeakMap();
function processObject(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result); // 当obj被垃圾回收时,对应的缓存也会被回收
return result;
}
五、综合案例:模块化组件系统
以下是一个综合以上概念的简单组件系统实现:
javascript
// 组件系统(运用闭包、原型、事件循环和内存管理)
const ComponentSystem = (function() {
// 私有状态 (闭包)
const components = new WeakMap();
let nextId = 1;
// 组件基类
class Component {
constructor(config) {
this.id = nextId++;
this.state = { ...config };
this.eventHandlers = {};
// 存储组件实例的私有数据
components.set(this, {
renderTimer: null,
isMounted: false
});
}
// 原型方法
setState(newState) {
this.state = { ...this.state, ...newState };
this._debouncedRender();
return this;
}
on(event, handler) {
if (!this.eventHandlers[event]) {
this.eventHandlers[event] = [];
}
this.eventHandlers[event].push(handler);
return this;
}
emit(event, ...args) {
const handlers = this.eventHandlers[event] || [];
// 微任务执行事件处理 (事件循环)
handlers.forEach(handler => {
queueMicrotask(() => handler.apply(this, args));
});
return this;
}
// 私有方法
_debouncedRender() {
const privateData = components.get(this);
clearTimeout(privateData.renderTimer);
// 使用宏任务延迟渲染 (事件循环)
privateData.renderTimer = setTimeout(() => {
this._render();
this.emit('afterRender');
}, 0);
}
mount(container) {
const privateData = components.get(this);
if (!privateData.isMounted) {
privateData.isMounted = true;
privateData.container = container;
this._render();
this.emit('mounted');
}
return this;
}
unmount() {
const privateData = components.get(this);
if (privateData.isMounted) {
clearTimeout(privateData.renderTimer); // 避免内存泄漏
privateData.isMounted = false;
privateData.container.innerHTML = '';
this.emit('unmounted');
}
return this;
}
_render() {
const privateData = components.get(this);
if (privateData.isMounted && privateData.container) {
privateData.container.innerHTML = this.render();
}
}
render() {
return `<div>组件ID: ${this.id}</div>`;
}
}
// 继承示例 (原型链)
class Button extends Component {
constructor(config) {
super(config);
}
render() {
return `<button class="btn">${this.state.text || '按钮'}</button>`;
}
}
// 导出公共API
return {
Component,
Button,
create(type, config) {
return new type(config);
}
};
})();
// 使用示例
const btn = ComponentSystem.create(ComponentSystem.Button, { text: '点击我' });
btn.on('mounted', function() {
console.log('按钮已挂载');
});
btn.mount(document.getElementById('app'));
六、结语
JavaScript 的闭包、原型链、事件循环和内存管理是理解高级 JavaScript 开发的关键。通过闭包实现数据封装,通过原型链构建对象继承体系,通过事件循环处理异步任务,以及通过合理的内存管理避免资源泄漏,这些核心概念共同构成了 JavaScript 语言特性和运行时环境的基础。
掌握这些概念不仅有助于编写高质量的代码,还有助于解决复杂的问题,提高应用性能,以及避免常见的陷阱和错误。