JavaScript
作为一门动态语言,拥有许多高级特性,如闭包
、原型链
、执行上下文
、事件循环
、Proxy
、Reflect
等。本文将深入探讨这些特性,并结合实战案例,让我们一起更好地理解 JavaScript 的强大之处。
执行上下文与调用栈
JavaScript
采用单线程模型,代码的执行依赖于执行上下文(Execution Context) 和 调用栈(Call Stack) 。
执行上下文
执行上下文是 JavaScript 代码执行的环境,分为以下几种类型:
- 全局执行上下文:默认的执行环境,this 指向 window(Node.js 中指向 global)。
- 函数执行上下文:调用函数时创建,每次调用都会生成新的执行上下文。
- eval 执行上下文:很少使用,不推荐。
执行上下文生命周期
-
创建阶段(变量环境 + 词法环境)
- 创建变量对象(var 声明的变量会提升)
- 确定 this 绑定
-
执行阶段
- 代码按顺序执行,变量赋值,函数调用
-
销毁阶段
- 函数执行完毕后,执行上下文出栈,等待垃圾回收
function foo(a) {
var b = 2;
function bar() {
console.log(a + b);
}
return bar;
}
const fn = foo(3);
fn(); // 5
foo(3) 执行后,bar 仍然持有 a 和 b 的引用,这就是闭包的特性。
闭包与作用域链
闭包(Closure)
闭包是指函数能够访问外部作用域的变量,即使函数在外部作用域执行。这是 JavaScript 强大之处之一,使得私有变量、惰性计算等成为可能。
作用域链
当访问一个变量时,JavaScript 会沿着作用域链查找,直到全局作用域。如果找不到,则抛出 ReferenceError。
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
inner 形成闭包,持续访问 count,但外部无法直接修改 count,形成私有作用域。
实际应用:函数柯里化
闭包常用于函数柯里化,即将一个多参数函数拆成多个单参数函数:
const curry = fn => (...args) => args.length >= fn.length ? fn(...args) : (...next) => curry(fn)(...args, ...next);
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
原型链与 class 继承
原型链
JavaScript 采用原型继承,每个对象都有 proto 指向构造函数的 prototype。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person('Alice');
p1.sayHello(); // Hello, I'm Alice
console.log(p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
访问 p1.sayHello() 时,JavaScript 沿着 p1.proto 查找 sayHello,即原型链的作用。
Class 继承
ES6 提供了 class 语法,使继承更直观。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const dog = new Dog('Buddy');
dog.speak(); // Buddy barks.
Dog 继承 Animal,并重写 speak 方法,super 关键字用于访问父类方法。
Proxy 与 Reflect
Proxy 和 Reflect 让 JavaScript 对象具有更强的拦截和反射能力。
拦截对象操作
const obj = {
name: 'Alice',
age: 25
};
const proxy = new Proxy(obj, {
get(target, prop) {
console.log(`Getting ${prop}`);
return prop in target ? target[prop] : 'Property does not exist';
},
set(target, prop, value) {
console.log(`Setting ${prop} to ${value}`);
target[prop] = value;
return true;
}
});
console.log(proxy.name); // Getting name → Alice
proxy.age = 30; // Setting age to 30
console.log(proxy.unknown); // Getting unknown → Property does not exist
结合 Reflect
Reflect 提供默认行为,避免 Proxy 破坏对象的原始逻辑。
const proxyWithReflect = new Proxy(obj, {
get(target, prop) {
console.log(`Getting ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(target, prop, value);
}
});
事件循环与异步编程
JavaScript 采用事件循环处理异步任务,分为:
- 宏任务(Macro-task) :setTimeout、setInterval、setImmediate(Node.js)、I/O 任务
- 微任务(Micro-task) :Promise.then、MutationObserver、queueMicrotask
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
执行顺序:
- console.log('Start');
- console.log('End')
- Promise.then 任务(微任务)
- setTimeout 任务(宏任务)
结语
本文深入探讨了 JavaScript 的高级特性,包括执行上下文、闭包、作用域链、原型继承、Proxy/Reflect 和事件循环。在实际项目中,合理运用这些特性,能让我们的代码更加优雅和高效! 🚀