JS机制

138 阅读6分钟

1. 事件循环(Event Loop)

事件循环是 JavaScript 的核心执行机制之一,管理着任务的调度,尤其是处理异步任务。

  • 宏任务(Macrotask) :包括 setTimeoutsetIntervalI/O 操作等,它们被放入宏任务队列,等待主线程空闲时执行。
  • 微任务(Microtask) :包括 Promise.thenMutationObserverprocess.nextTick(Node.js)等,它们的优先级高于宏任务,会在当前事件循环结束前立即执行。
示例:
 console.log('Start');
 ​
 setTimeout(() => {
   console.log('setTimeout');
 }, 0);
 ​
 Promise.resolve().then(() => {
   console.log('Promise');
 });
 ​
 console.log('End');

执行顺序

  • console.log('Start')console.log('End') 是同步任务,立即执行。
  • Promise 的回调属于微任务,会在事件循环的本轮结束前执行。
  • setTimeout宏任务,在下一个事件循环中执行。

2. 单线程机制

JavaScript 的单线程机制决定了它一次只能执行一个任务,所有代码共享一个执行上下文。这种设计虽然简化了开发,避免了多线程中的资源争抢、竞态条件等问题,但在处理复杂任务时,可能会出现阻塞,降低性能。

为了应对单线程的缺点,JavaScript 使用异步机制事件循环来处理任务,从而提高应用的性能。

3. 任务队列(Task Queue)

JavaScript 中的任务队列用于管理等待执行的异步任务。任务分为宏任务队列微任务队列

  • 宏任务队列:存放 setTimeoutsetInterval、I/O 操作等任务。
  • 微任务队列:存放 Promise.thenMutationObserverprocess.nextTick(Node.js)等任务。

在每个事件循环的执行中,JavaScript 引擎会优先执行微任务队列中的任务,确保这些任务在宏任务之前执行。

4. 闭包(Closure)

闭包是 JavaScript 中的一种执行机制,它允许内部函数访问外部函数的变量。即使外部函数已经执行完毕,它的作用域仍然被保留在内存中,供内部函数访问。

示例:
 function outer() {
   let counter = 0;
   return function inner() {
     counter++;
     console.log(counter);
   };
 }
 ​
 const increment = outer();
 increment();  // 输出 1
 increment();  // 输出 2

闭包的作用包括:

  • 数据私有化:通过闭包,变量可以在函数内部“私有化”,外部无法直接访问。
  • 记忆功能:闭包可以保持函数执行上下文,记住外部函数中的状态。

5. 原型链机制(Prototype Chain)

JavaScript 中的原型链是实现继承的一种机制。每个对象都有一个内部属性 [[Prototype]],它指向另一个对象(即原型对象)。当访问一个对象的属性时,JavaScript 引擎会先查找该对象本身的属性,如果找不到,则沿着原型链向上查找,直到找到该属性或到达 null(表示原型链的终点)。

示例:
 function Person(name) {
   this.name = name;
 }
 ​
 Person.prototype.greet = function() {
   console.log(`Hello, I'm ${this.name}`);
 };
 ​
 const john = new Person('John');
 john.greet();  // 输出:Hello, I'm John

在这个例子中,john 对象继承了 Person.prototype 上的 greet 方法,这是通过原型链实现的。

6. 作用域链(Scope Chain)

JavaScript 的作用域链是指当前执行上下文中变量的查找机制。JavaScript 中的作用域分为全局作用域和局部作用域,作用域链是通过函数的嵌套关系形成的。当查找变量时,JavaScript 会按照作用域链的顺序查找变量,先从当前作用域开始,逐级向上查找,直到找到变量或到达全局作用域。

示例:
 let a = 10;
 ​
 function outer() {
   let b = 20;
   
   function inner() {
     let c = 30;
     console.log(a, b, c);  // 输出 10, 20, 30
   }
   
   inner();
 }
 ​
 outer();

在这个例子中,inner 函数可以访问到外部作用域的 ab 变量,这是通过作用域链实现的。

7. 执行上下文(Execution Context)

执行上下文是 JavaScript 代码在执行时的环境。每当 JavaScript 代码被执行时,它会创建一个执行上下文。执行上下文包含了变量、函数、对象等信息。JavaScript 在代码执行过程中,依次创建全局执行上下文、函数执行上下文等。

  • 全局执行上下文:在代码加载时被创建,包含全局变量和函数。
  • 函数执行上下文:在函数被调用时被创建,包含函数内部的变量和参数。
  • 块级作用域执行上下文:在 letconst 声明的块作用域内创建。

8. 垃圾回收机制(Garbage Collection)

JavaScript 的垃圾回收机制(Garbage Collection, GC) 是一种自动内存管理机制。JavaScript 引擎通过标记-清除算法来追踪哪些对象和变量仍然在使用,哪些已经不再使用,并释放那些不再使用的内存空间。

  • 标记-清除:在运行过程中,JavaScript 会标记所有当前在使用的对象,当某些对象不再被引用时,它们会被标记为可回收,最后垃圾回收器会清除这些对象。
相关机制:
  • 引用计数:一个对象的引用次数达到 0 时,它会被回收。
  • 标记阶段:从根对象(如全局对象)出发,遍历所有引用,标记可到达的对象。
  • 清除阶段:释放未被标记的对象所占用的内存。

9. 非阻塞 I/O 机制

在 Node.js 环境中,非阻塞 I/O 是一个重要的执行机制。Node.js 使用了事件驱动的非阻塞 I/O 模型,使得它在处理文件读写、网络请求等操作时,可以继续执行其他任务,而不会因为 I/O 操作的等待时间而阻塞线程。

示例:
 const fs = require('fs');
 ​
 fs.readFile('file.txt', 'utf8', (err, data) => {
   if (err) throw err;
   console.log(data);  // 文件读取完成后执行
 });
 ​
 console.log('This line executes immediately');

在这个例子中,文件读取是异步的,Node.js 会继续执行其他任务,而不会等待文件读取完成。

总结

  1. 事件循环(Event Loop) :管理异步任务的执行顺序,协调宏任务与微任务的执行。
  2. 单线程机制:确保 JavaScript 一次只能执行一个任务,避免多线程并发带来的问题。
  3. 任务队列(Task Queue) :管理异步任务,分为宏任务和微任务队列。
  4. 闭包(Closure) :通过内部函数访问外部函数的变量,形成数据私有化机制。
  5. 原型链机制(Prototype Chain) :实现对象继承的机制。
  6. 作用域链(Scope Chain) :处理变量查找的机制,从当前作用域向外部作用域逐级查找。
  7. 执行上下文(Execution Context) :代码执行时的环境,包括全局执行上下文、函数执行上下文等。
  8. 垃圾回收机制(Garbage Collection) :自动内存管理,回收不再使用的对象。
  9. 非阻塞 I/O 机制:Node.js 中的事件驱动机制,使得 I/O 操作不会阻塞主线程。

这些机制共同构成了 JavaScript 的核心执行模型,帮助开发者处理不同类型的任务。