深入理解 JavaScript 高级特性

232 阅读3分钟

JavaScript 作为一门动态语言,拥有许多高级特性,如闭包原型链执行上下文事件循环ProxyReflect等。本文将深入探讨这些特性,并结合实战案例,让我们一起更好地理解 JavaScript 的强大之处。

执行上下文与调用栈

JavaScript 采用单线程模型,代码的执行依赖于执行上下文(Execution Context)调用栈(Call Stack)

执行上下文

执行上下文是 JavaScript 代码执行的环境,分为以下几种类型:

  • 全局执行上下文:默认的执行环境,this 指向 window(Node.js 中指向 global)。
  • 函数执行上下文:调用函数时创建,每次调用都会生成新的执行上下文。
  • eval 执行上下文:很少使用,不推荐。

执行上下文生命周期

  1. 创建阶段(变量环境 + 词法环境)

    • 创建变量对象(var 声明的变量会提升)
    • 确定 this 绑定
  2. 执行阶段

    • 代码按顺序执行,变量赋值,函数调用
  3. 销毁阶段

    • 函数执行完毕后,执行上下文出栈,等待垃圾回收
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');

执行顺序:

  1. console.log('Start');
  2. console.log('End')
  3. Promise.then 任务(微任务)
  4. setTimeout 任务(宏任务)

结语

本文深入探讨了 JavaScript 的高级特性,包括执行上下文、闭包、作用域链、原型继承、Proxy/Reflect 和事件循环。在实际项目中,合理运用这些特性,能让我们的代码更加优雅和高效! 🚀