JavaScript高级特性详解
本文深入探讨 JavaScript 的高级特性,包括闭包、作用域链、原型链、事件循环、内存管理、执行上下文等核心概念。这些知识是理解 JavaScript 运行机制、编写高质量代码的关键。
# 一、作用域与作用域链
# 作用域类型
JavaScript 有三种作用域:
// 1. 全局作用域
var globalVar = 'global';
const globalConst = 'global const';
function test() {
// 2. 函数作用域
var functionVar = 'function';
if (true) {
// 3. 块级作用域(let/const)
let blockVar = 'block';
const blockConst = 'block const';
var functionVar2 = 'function2'; // var 无块级作用域
}
console.log(functionVar2); // 'function2'(可访问)
// console.log(blockVar); // 错误:blockVar 未定义
}
# 词法作用域
JavaScript 采用词法作用域(静态作用域),函数的作用域在定义时确定,而非调用时:
let name = 'global';
function outer() {
let name = 'outer';
function inner() {
console.log(name); // 'outer'(词法作用域)
}
return inner;
}
function caller() {
let name = 'caller';
const fn = outer();
fn(); // 'outer'(不是 'caller')
}
caller();
# 作用域链
当访问变量时,JavaScript 引擎会沿着作用域链查找:
let a = 'global a';
function outer() {
let b = 'outer b';
function inner() {
let c = 'inner c';
console.log(a); // 沿作用域链向上查找:inner → outer → global
console.log(b); // 沿作用域链向上查找:inner → outer
console.log(c); // 当前作用域
}
inner();
}
outer();
作用域链:内部作用域 → 外部作用域 → 全局作用域
# 变量遮蔽
内层作用域的变量会遮蔽外层同名变量:
let x = 10;
function test() {
let x = 20; // 遮蔽全局 x
console.log(x); // 20
}
test();
console.log(x); // 10
# 二、闭包(Closure)
# 闭包定义
闭包是指函数能够访问其词法作用域外的变量,即使该函数在其词法作用域之外执行。
function createCounter() {
let count = 0; // 私有变量
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 无法从外部直接访问
// console.log(count); // 错误:count 未定义
闭包的三个条件:
- 函数嵌套(内部函数)
- 内部函数引用外部函数的变量
- 内部函数被返回或传递到外部执行
# 闭包的应用
# 1. 数据封装和私有变量
function createPerson(name) {
let age = 0; // 私有变量
return {
getName() {
return name;
},
getAge() {
return age;
},
setAge(newAge) {
if (newAge >= 0) {
age = newAge;
}
}
};
}
const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
person.setAge(30);
console.log(person.getAge()); // 30
// console.log(person.age); // undefined(无法直接访问)
# 2. 模块模式
const calculator = (function() {
let result = 0; // 私有变量
return {
add(n) {
result += n;
return this;
},
subtract(n) {
result -= n;
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
}
};
})();
calculator.add(10).add(5).subtract(3);
console.log(calculator.getResult()); // 12
# 3. 柯里化(Currying)
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
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. 延迟执行
function debounce(func, delay) {
let timeoutId; // 闭包保存 timeoutId
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
const handleInput = debounce((value) => {
console.log('Search:', value);
}, 500);
handleInput('a');
handleInput('ab');
handleInput('abc'); // 只会执行这次
# 闭包陷阱
# 循环中的闭包
// ❌ 错误:所有函数共享同一个 i
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 5, 5, 5, 5, 5
}, 100);
}
// ✅ 解决方案 1:使用 let(块级作用域)
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2, 3, 4
}, 100);
}
// ✅ 解决方案 2:IIFE(立即执行函数)
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2, 3, 4
}, 100);
})(i);
}
# 内存泄漏
function createHeavyObject() {
const largeData = new Array(1000000).fill('data');
return function() {
// 即使不使用 largeData,闭包也会持有引用
console.log('Function executed');
};
}
const fn = createHeavyObject(); // largeData 无法被垃圾回收
// ✅ 解决方案:手动解除引用
function createHeavyObject() {
let largeData = new Array(1000000).fill('data');
return function() {
const result = largeData.length; // 使用 largeData
largeData = null; // 解除引用
return result;
};
}
# 三、原型与原型链
# 原型基础
每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 访问),指向其原型对象。
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log(`Hello, I'm ${this.name}`);
};
const alice = new Person('Alice');
// 原型关系
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(alice.__proto__.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
# 原型链
当访问对象属性时,JavaScript 引擎会沿着原型链查找:
const obj = { a: 1 };
// 查找顺序:obj → Object.prototype → null
console.log(obj.toString); // 从 Object.prototype 继承
console.log(obj.a); // 自身属性
console.log(obj.b); // undefined(原型链上未找到)
# 继承实现
# 1. 原型链继承
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Rex', 'Labrador');
dog.speak(); // Rex makes a sound
dog.bark(); // Woof!
# 2. ES6 类继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
const dog = new Dog('Rex', 'Labrador');
dog.speak(); // Rex makes a sound
dog.bark(); // Woof!
# 原型方法
// 检查原型关系
Dog.prototype.isPrototypeOf(dog); // true
Object.prototype.isPrototypeOf(dog); // true
// 检查自身属性
dog.hasOwnProperty('name'); // true
dog.hasOwnProperty('speak'); // false
// 获取原型
Object.getPrototypeOf(dog) === Dog.prototype; // true
// 设置原型
const obj = {};
Object.setPrototypeOf(obj, Array.prototype);
obj.push(1); // 现在 obj 可以使用数组方法
# 四、执行上下文与调用栈
# 执行上下文
JavaScript 代码运行在执行上下文中,分为三种:
- 全局执行上下文:代码首次执行时创建
- 函数执行上下文:每次函数调用时创建
- Eval 执行上下文:
eval()函数执行时创建(不推荐使用)
# 执行上下文的组成
每个执行上下文包含三个重要组件:
- 变量对象(Variable Object, VO):存储变量、函数声明、参数
- 作用域链(Scope Chain):用于变量查找
- this 值:当前执行上下文的
this绑定
function test(a, b) {
var c = 10;
function inner() {}
// 执行上下文(简化):
// VO = {
// arguments: { 0: 1, 1: 2, length: 2 },
// a: 1,
// b: 2,
// c: undefined → 10,
// inner: function reference
// }
// Scope Chain = [VO, Global VO]
// this = window / undefined(严格模式)
}
test(1, 2);
# 执行上下文的生命周期
# 1. 创建阶段
function test() {
console.log(a); // undefined(变量提升)
console.log(b); // 错误:b 未定义(let/const 无提升)
var a = 10;
let b = 20;
}
// 创建阶段(简化):
// 1. 创建变量对象:
// - 函数声明:完整提升
// - var 声明:提升并初始化为 undefined
// - let/const 声明:提升但不初始化(暂时性死区)
// 2. 建立作用域链
// 3. 确定 this 值
# 2. 执行阶段
function test() {
var a; // 提升到顶部
console.log(a); // undefined
a = 10;
console.log(a); // 10
}
test();
# 调用栈
JavaScript 使用调用栈(Call Stack) 管理执行上下文:
function first() {
console.log('First function');
second();
console.log('First function end');
}
function second() {
console.log('Second function');
third();
console.log('Second function end');
}
function third() {
console.log('Third function');
}
first();
// 调用栈变化:
// 1. [Global]
// 2. [Global, first]
// 3. [Global, first, second]
// 4. [Global, first, second, third]
// 5. [Global, first, second]
// 6. [Global, first]
// 7. [Global]
# 栈溢出
调用栈有大小限制,递归过深会导致栈溢出:
function recursion() {
recursion(); // 无限递归
}
// recursion(); // 错误:Maximum call stack size exceeded
// ✅ 解决方案:尾递归优化(严格模式 + 支持的引擎)
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // 尾调用
}
console.log(factorial(5)); // 120
# 五、事件循环(Event Loop)
# 单线程模型
JavaScript 是单线程语言,但通过事件循环实现异步非阻塞:
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
// 输出顺序:
// Start
// End
// Promise
// Timeout
# 任务队列
JavaScript 有两种任务队列:
# 1. 宏任务(Macro Task)
setTimeoutsetIntervalsetImmediate(Node.js)- I/O 操作
- UI 渲染
# 2. 微任务(Micro Task)
Promise.then/catch/finallyMutationObserverqueueMicrotask()process.nextTick()(Node.js,优先级最高)
# 事件循环流程
// 执行顺序:
// 1. 执行同步代码
// 2. 执行所有微任务
// 3. 执行一个宏任务
// 4. 执行所有微任务
// 5. 重复步骤 3-4
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
Promise.resolve().then(() => {
console.log('4');
setTimeout(() => console.log('5'), 0);
});
Promise.resolve().then(() => console.log('6'));
console.log('7');
// 输出:1 → 7 → 4 → 6 → 2 → 3 → 5
// 详细流程:
// 同步:1, 7
// 微任务队列:[Promise(4), Promise(6)]
// 执行 Promise(4):输出 4,添加 setTimeout(5) 到宏任务队列
// 执行 Promise(6):输出 6
// 宏任务队列:[setTimeout(2), setTimeout(5)]
// 执行 setTimeout(2):输出 2,添加 Promise(3) 到微任务队列
// 微任务队列:[Promise(3)]
// 执行 Promise(3):输出 3
// 执行 setTimeout(5):输出 5
# async/await 与事件循环
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end'); // 相当于 Promise.then()
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1();
new Promise(resolve => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
# Node.js 事件循环
Node.js 的事件循环有 6 个阶段:
// Node.js 事件循环阶段:
// 1. timers: 执行 setTimeout/setInterval 回调
// 2. pending callbacks: 执行延迟到下一个循环的 I/O 回调
// 3. idle, prepare: 内部使用
// 4. poll: 获取新的 I/O 事件
// 5. check: 执行 setImmediate 回调
// 6. close callbacks: 执行 close 事件回调
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 输出顺序不确定(取决于进入事件循环的时机)
// 在 I/O 回调中,setImmediate 总是先于 setTimeout
# 六、内存管理
# 内存生命周期
- 分配内存:声明变量、创建对象
- 使用内存:读写变量
- 释放内存:垃圾回收(Garbage Collection, GC)
// 1. 分配内存
let obj = { name: 'Alice' };
let arr = new Array(1000);
// 2. 使用内存
console.log(obj.name);
arr.push(1);
// 3. 释放内存(自动)
obj = null; // 解除引用,等待 GC
arr = null;
# 垃圾回收机制
# 1. 引用计数(Reference Counting)
// ❌ 循环引用导致内存泄漏
function problem() {
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1; // 循环引用
}
problem(); // obj1 和 obj2 无法被回收(引用计数不为 0)
# 2. 标记-清除(Mark-and-Sweep)
现代浏览器使用标记-清除算法:
- 从根对象(全局对象)开始,标记所有可达对象
- 清除未标记的对象
function test() {
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1; // 循环引用
}
test(); // obj1 和 obj2 不可达,会被 GC 回收
# 内存泄漏常见场景
# 1. 全局变量
// ❌ 意外创建全局变量
function test() {
name = 'Alice'; // 未声明,变为全局变量
}
test();
console.log(window.name); // 'Alice'
// ✅ 使用严格模式
'use strict';
function test() {
name = 'Alice'; // 错误:name is not defined
}
# 2. 定时器未清除
// ❌ 定时器未清除
function startTimer() {
const largeData = new Array(1000000);
setInterval(() => {
console.log(largeData.length); // largeData 无法被回收
}, 1000);
}
startTimer();
// ✅ 清除定时器
function startTimer() {
const largeData = new Array(1000000);
const timerId = setInterval(() => {
console.log(largeData.length);
}, 1000);
// 适当时机清除
setTimeout(() => clearInterval(timerId), 10000);
}
# 3. 事件监听器未移除
// ❌ 事件监听器未移除
function addListener() {
const element = document.querySelector('#button');
element.addEventListener('click', function handler() {
console.log('Clicked');
});
// element 移除后,handler 仍然存在
element.remove();
}
// ✅ 移除事件监听器
function addListener() {
const element = document.querySelector('#button');
function handler() {
console.log('Clicked');
}
element.addEventListener('click', handler);
// 清理
element.removeEventListener('click', handler);
element.remove();
}
# 4. 闭包引用大对象
// ❌ 闭包持有大对象引用
function createClosure() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log(largeData[0]); // largeData 无法被回收
};
}
const fn = createClosure();
// ✅ 只保存需要的数据
function createClosure() {
const largeData = new Array(1000000).fill('data');
const firstItem = largeData[0]; // 仅保存需要的数据
return function() {
console.log(firstItem);
};
}
# 5. DOM 引用
// ❌ DOM 被移除后仍持有引用
const elements = [];
function storeElements() {
const div = document.querySelector('#myDiv');
elements.push(div);
div.remove(); // DOM 已移除,但 elements 中仍有引用
}
// ✅ 及时清理引用
function storeElements() {
const div = document.querySelector('#myDiv');
elements.push(div);
div.remove();
// 清理引用
const index = elements.indexOf(div);
elements.splice(index, 1);
}
# 内存优化技巧
# 1. 对象池(Object Pool)
class ObjectPool {
constructor(createFn, resetFn, initialSize = 10) {
this.createFn = createFn;
this.resetFn = resetFn;
this.pool = [];
for (let i = 0; i < initialSize; i++) {
this.pool.push(this.createFn());
}
}
acquire() {
return this.pool.length > 0
? this.pool.pop()
: this.createFn();
}
release(obj) {
this.resetFn(obj);
this.pool.push(obj);
}
}
// 使用示例
const bulletPool = new ObjectPool(
() => ({ x: 0, y: 0, active: false }),
(bullet) => { bullet.active = false; },
50
);
const bullet = bulletPool.acquire();
bullet.active = true;
// 使用 bullet...
bulletPool.release(bullet);
# 2. 避免大数组操作
// ❌ 创建大量临时数组
const result = arr
.filter(x => x > 0)
.map(x => x * 2)
.reduce((sum, x) => sum + x, 0);
// ✅ 单次遍历
let result = 0;
for (let x of arr) {
if (x > 0) {
result += x * 2;
}
}
# 3. 使用 WeakMap/WeakSet
// WeakMap:键必须是对象,且弱引用(对象被回收时自动删除)
const cache = new WeakMap();
function process(obj) {
if (!cache.has(obj)) {
const result = expensiveOperation(obj);
cache.set(obj, result);
}
return cache.get(obj);
}
// 当 obj 被回收时,cache 中的条目也会被自动删除
# 七、变量提升(Hoisting)
# var 提升
console.log(x); // undefined
var x = 10;
console.log(x); // 10
// 等价于:
var x;
console.log(x); // undefined
x = 10;
console.log(x); // 10
# 函数声明提升
greet(); // 'Hello'(函数声明完全提升)
function greet() {
console.log('Hello');
}
// 函数表达式不提升
// sayHi(); // 错误:sayHi is not a function
var sayHi = function() {
console.log('Hi');
};
# let/const 暂时性死区
// console.log(x); // 错误:Cannot access 'x' before initialization
let x = 10;
// 暂时性死区(Temporal Dead Zone, TDZ)
function test() {
// TDZ 开始
console.log(a); // 错误
let a = 10;
// TDZ 结束
}
# 八、this 绑定进阶
关于 this 的基础内容,详见关于this关键字的魔幻现实。这里补充一些进阶用法。
# 箭头函数与普通函数的 this 差异
const obj = {
name: 'obj',
regularFn: function() {
console.log(this.name);
},
arrowFn: () => {
console.log(this.name);
}
};
obj.regularFn(); // 'obj'(this 指向 obj)
obj.arrowFn(); // undefined(箭头函数继承外层 this,即 window)
const regular = obj.regularFn;
const arrow = obj.arrowFn;
regular(); // undefined(普通函数 this 丢失)
arrow(); // undefined(箭头函数 this 始终继承外层)
# 显式绑定的优先级
function test() {
console.log(this.value);
}
const obj1 = { value: 1 };
const obj2 = { value: 2 };
const boundFn = test.bind(obj1);
boundFn(); // 1
boundFn.call(obj2); // 1(bind 优先级高于 call)
const newInstance = new boundFn(); // undefined(new 优先级最高)
# 九、模块化进阶
# ES6 模块的静态特性
// ❌ 错误:不能在运行时导入
if (condition) {
import module from './module.js'; // 语法错误
}
// ✅ 动态导入
if (condition) {
import('./module.js').then(module => {
module.doSomething();
});
}
// ✅ async/await
async function loadModule() {
const module = await import('./module.js');
module.doSomething();
}
# 模块循环依赖
// a.js
import { b } from './b.js';
export const a = 'a';
console.log(b); // undefined(b.js 尚未执行完)
// b.js
import { a } from './a.js';
export const b = 'b';
console.log(a); // undefined(a.js 尚未执行完)
// ✅ 解决方案:使用函数延迟访问
// a.js
import { getB } from './b.js';
export const a = 'a';
console.log(getB()); // 'b'
// b.js
import { a } from './a.js';
export const b = 'b';
export function getB() {
return b;
}
# 十、性能优化进阶
# 惰性求值(Lazy Evaluation)
class LazyArray {
constructor(arr) {
this.arr = arr;
this.operations = [];
}
map(fn) {
this.operations.push({ type: 'map', fn });
return this;
}
filter(fn) {
this.operations.push({ type: 'filter', fn });
return this;
}
value() {
return this.arr.reduce((acc, item) => {
let current = item;
for (let op of this.operations) {
if (op.type === 'map') {
current = op.fn(current);
} else if (op.type === 'filter') {
if (!op.fn(current)) return acc;
}
}
acc.push(current);
return acc;
}, []);
}
}
const result = new LazyArray([1, 2, 3, 4, 5])
.map(x => x * 2)
.filter(x => x > 5)
.map(x => x + 1)
.value(); // [7, 9, 11](仅遍历一次)
# 函数记忆化(Memoization)
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;
};
}
// 斐波那契数列
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(40)); // 很快(缓存结果)
# 尾调用优化(TCO)
// ❌ 非尾调用(会导致栈溢出)
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 调用后还有乘法操作
}
// ✅ 尾调用优化(严格模式 + 支持的引擎)
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // 最后一步是函数调用
}
console.log(factorial(100000)); // 不会栈溢出(如果引擎支持 TCO)
# 十一、总结
JavaScript 的高级特性是深入理解语言运行机制的关键:
- 作用域与闭包:理解词法作用域、作用域链、闭包的应用与陷阱
- 原型与继承:掌握原型链、继承模式、ES6 类
- 执行上下文:理解变量提升、调用栈、执行流程
- 事件循环:掌握宏任务、微任务、async/await 的执行顺序
- 内存管理:了解垃圾回收、避免内存泄漏、优化内存使用
- 性能优化:惰性求值、记忆化、尾调用优化
这些概念环环相扣,理解它们能够帮助你编写更高效、更健壮的 JavaScript 代码。
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/07