JS 基础与高级应用: JS运行机制 & Promise

JS 基础与高级应用: JS运行机制 & Promise

JavaScript 作为一种脚本语言,其运行原理涉及到进程、线程、事件循环等概念。在浏览器环境中,JavaScript 的运行方式和其他语言有所不同,深入了解 JavaScript 的运行原理对于开发者来说至关重要。同时,Promise 作为 JavaScript 中处理异步操作的一种机制,也是 JavaScript 开发中常用的工具之一。本文将从进程、线程、浏览器原理开始介绍 JavaScript 的运行原理,然后深入探讨 Promise 的概念、原理以及手写 Promise 的实现。

JS 运行原理机制

进程与线程

在讨论 JavaScript 的运行原理之前,我们先来了解一下进程和线程的概念。进程是操作系统中资源分配的最小单位,而线程是 CPU 调度的最小单位。在并发与并行的概念中,我们可以理解为并发是指多个任务交替进行,而并行是指多个任务同时进行。进程和线程之间的区别在于进程是独立的资源分配单位,而线程是 CPU 调度的最小单位。

浏览器原理与线程协同方式

在浏览器环境中,JavaScript 是单线程运行的,这意味着它一次只能执行一个任务。但是,浏览器本身是多线程的,它包括了多个线程来处理不同的任务,比如 GUI 渲染线程、JavaScript 引擎线程、定时器线程、异步请求线程、事件触发器等。这些线程需要协同工作,以实现页面的渲染和交互。

  1. JavaScript 引擎线程:
    • 作用: 负责解析和执行 JavaScript 代码。
    • 协作关系: 当 JavaScript 引擎线程执行 JavaScript 代码时,会阻塞 GUI 渲染线程的执行,因此需要尽量减少 JavaScript 代码的执行时间,以避免页面出现卡顿现象。
  2. GUI 渲染线程:
    • 作用: 负责解析 HTML、CSS,构建 DOM 树和 CSSOM,并进行页面的布局和绘制。
    • 协作关系: 当 GUI 渲染线程执行页面的布局和绘制时,会阻塞 JavaScript 引擎线程的执行。因此,为了保持页面的流畅性,需要尽量减少 JavaScript 代码的执行时间。
  3. 定时器线程:
    • 作用: 负责处理定时器任务的执行。
    • 协作关系: 定时器线程会接收 JavaScript 引擎线程分配的定时器任务,并按时执行这些任务。定时器任务的执行不会阻塞其他线程的执行。
  4. 异步请求线程:
    • 作用: 负责处理网络请求等异步操作。
    • 协作关系: 异步请求线程会接收 JavaScript 引擎线程分配的异步网络请求任务,并执行这些任务。异步请求的执行不会阻塞其他线程的执行。
  5. 事件触发线程:
    • 作用: 负责接收和处理事件,并将事件回调插入到任务队列中。
    • 协作关系: 事件触发线程接收到各种事件,如鼠标点击、键盘输入等,然后将相应的事件回调插入到任务队列中,等待 JavaScript 引擎线程的执行。

事件循环 & 任务队列

JavaScript 的事件循环机制是其运行的核心机制之一。事件循环由主调用栈和任务队列组成。在 JavaScript 中,永远只有一个线程在执行代码,而事件循环机制确保了任务的顺序执行。当主调用栈为空时, 事件循环的执行顺序遵循先执行微任务,再执行宏任务的原则。微任务包括 Promise 的 then 方法、MutationObserver 等,而宏任务则包括定时器回调、DOM 事件回调等。

  1. 事件循环(Event Loop):
    • 定义: 是 JavaScript 引擎处理任务和事件的机制,保证代码的顺序执行。
    • 主要组成部分: 主调用栈(Call Stack)和任务队列(Task Queue)。
    • 原理: 当主调用栈为空时,事件循环会从任务队列中取出一个任务执行,然后继续等待下一个任务。
    • 执行顺序: 遵循先执行微任务(Microtask),再执行宏任务(Macrotask)的原则。
    • 示例: 在浏览器中,事件循环负责处理 DOM 事件、定时器回调等任务的执行。
  2. 任务队列(Task Queue):
    • 定义: 是存放待执行任务的队列,包括宏任务和微任务。
    • 种类: 分为宏任务队列(Macrotask Queue)和微任务队列(Microtask Queue)。
    • 特点:
      • 宏任务队列:存放宿主环境触发的任务,如定时器回调、DOM 事件回调等。
      • 微任务队列:存放 JavaScript 引擎发起的任务,如 Promise 的 then 方法、MutationObserver 的回调等。
    • 执行优先级: 微任务的执行优先级高于宏任务,会在宏任务执行之前立即执行。
    • 示例: 微任务队列中的任务通常用于处理异步操作的结果或状态更新,宏任务队列中的任务用于处理宿主环境触发的事件和定时器回调等。

宏任务 & 微任务

  1. 宏任务(Macrotask):
    • 调度方式: 由宿主环境(浏览器或 Node.js)在合适的时机决定执行。
    • 示例: 包括定时器回调、DOM 事件回调、网络请求回调等,这些任务由宿主环境触发并放入任务队列中等待执行。
    • 优先级: 宏任务的优先级较低,通常会在微任务执行完毕后才执行。
  2. 微任务(Microtask):
    • 调度方式: 由 JavaScript 引擎主动发起调度,通常在宏任务执行完毕之后立即执行。
    • 示例: 主要包括 Promise 的 then 方法、MutationObserver 的回调等,这些任务由 JavaScript 引擎在特定时刻触发并放入微任务队列中。
    • 优先级: 微任务的优先级较高,会在宏任务执行之前立即执行,确保了优先级较高的任务能够及时执行。

Tick 的概念

Tick 是事件循环中的一个计时单位,可以看作是一个事件循环的周期。在每个 Tick 中,事件循环会执行一次循环,处理主调用栈中的任务和任务队列中的任务。

事件循环的周期

一个完整的事件循环周期包括以下步骤:

  1. 执行主调用栈中的任务: 在一个 Tick 开始时,首先执行主调用栈中的任务,直到主调用栈为空。
  2. 处理微任务队列中的任务: 当主调用栈为空时,事件循环会检查微任务队列中是否有任务。如果有,会依次执行所有微任务,直到微任务队列为空。
  3. 处理宏任务队列中的任务: 如果微任务队列为空,事件循环会检查宏任务队列中是否有任务。如果有,会从宏任务队列中取出一个任务执行,直到执行完所有的宏任务或者遇到一个长时间运行的任务。
  4. 更新渲染: 在每个 Tick 结束时,浏览器会检查是否需要更新页面的渲染,然后进行页面的重绘和重排。
  5. 等待下一个 Tick: 一次完整的事件循环周期结束后,事件循环会等待下一个 Tick 开始,继续执行下一个周期。

了解事件循环与任务队列的工作原理以及 Tick 的概念对于理解 JavaScript 的异步编程模型至关重要。通过掌握事件循环的周期,我们可以更好地理解 JavaScript 的执行顺序,并优化代码以提高性能和用户体验。

Promise

出现背景

在 Web 开发中,处理异步操作是一项常见的任务,例如发起网络请求、读取文件、处理定时器等。在早期,这些异步操作通常通过回调函数来处理,但随着业务复杂度的增加,回调函数嵌套的问题变得越来越突出,导致代码难以理解和维护,形成了所谓的 "回调地狱"。为了解决这个问题,Promise 应运而生。

Promise 的状态:

  • pending(等待):Promise 对象初始状态,表示异步操作还在进行中,还未完成也未失败。
  • fulfilled(已履行):表示异步操作成功完成,此时会调用 Promise 的 resolve 方法,将结果值传递给 then 方法。
  • rejected(已拒绝):表示异步操作失败,此时会调用 Promise 的 reject 方法,将错误原因传递给 then 方法或 catch 方法。

执行流程:

  1. 创建 Promise 对象:通过 new Promise() 创建一个 Promise 实例,传入执行器函数 executor。
  2. executor 函数执行:executor 函数立即执行,可能是异步操作,也可能是同步操作。
  3. 状态变更
    • 如果 executor 函数调用了 resolve 方法,Promise 的状态变为 fulfilled,并将结果值传递给 then 方法。
    • 如果 executor 函数调用了 reject 方法,Promise 的状态变为 rejected,并将错误原因传递给 then 方法或 catch 方法。
  4. 注册回调函数
    • 使用 then 方法注册回调函数,then 方法接收两个参数:onFulfilled 和 onRejected,分别表示状态变为 fulfilled 和 rejected 时的回调函数。
    • 使用 catch 方法注册错误处理函数,捕获 Promise 链中的错误。
  5. 链式调用
    • then 方法返回一个新的 Promise 对象,可以实现链式调用,即可以在一个 then 方法中继续调用下一个 then 方法。
    • catch 方法也返回一个新的 Promise 对象,可以用于捕获前面 then 方法中的错误。
  6. 状态传递
    • then 方法中的回调函数可以返回一个新的 Promise 对象,以实现状态传递,即将一个 Promise 对象的状态传递给另一个 Promise 对象。
  7. 执行顺序
    • 执行顺序遵循先执行微任务(Promise 的 then 方法、MutationObserver 等),再执行宏任务(定时器回调、DOM 事件回调等)的原则。
    • 在每个任务(微任务或宏任务)执行完毕后,会检查是否有微任务需要执行,如果有,则立即执行微任务队列中的所有微任务,直到微任务队列为空为止。

Promise 的组成部分

Promise 是 JavaScript 中处理异步操作的对象,它代表了一个异步操作的最终完成或失败,并可以返回其结果值或错误原因。

executor 函数

Promise 构造函数接收一个执行器函数(executor),该函数在 Promise 对象被创建时立即执行。executor 函数有两个参数:resolve 和 reject,用于改变 Promise 的状态为成功态(fulfilled)或失败态(rejected)。

实例方法:

  • then 方法:Promise 实例的 then 方法用于注册在 Promise 对象状态发生变化时的回调函数,接收两个参数:onFulfilled 和 onRejected,分别表示状态变为 fulfilled 和 rejected 时的回调函数。then 方法返回一个新的 Promise 对象,可以实现链式调用。
  • catch 方法:Promise 实例的 catch 方法是 then 方法的简化版本,用于注册 Promise 对象状态变为 rejected 时的回调函数。它等价于 then(null, onRejected),用于捕获 Promise 链中的错误。

全局 API:

  • Promise.all:接收一个 Promise 数组作为参数,当所有 Promise 对象都变为 fulfilled 时,返回一个新的 Promise 对象,并将所有 Promise 的结果值以数组形式传递给 then 方法。
  • Promise.race:接收一个 Promise 数组作为参数,返回一个新的 Promise 对象,一旦有任何一个 Promise 对象变为 fulfilled 或 rejected,就立即将该 Promise 对象的结果传递给 then 方法。

手写 Promise

js 复制代码
const PENDING = 'PENDING'; // 定义常量 PENDING,表示 Promise 的初始状态
const FULFILLED = 'FULFILLED'; // 定义常量 FULFILLED,表示 Promise 的成功状态
const REJECTED = 'REJECTED'; // 定义常量 REJECTED,表示 Promise 的失败状态

// 完整版 Promise 类
class myPromise {
    constructor(executor) {
        // 初始化 Promise 的状态、值和原因
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;

        // 存储成功和失败状态下的回调函数
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        // 定义 resolve 函数,用于将状态从 pending 变为 fulfilled,并执行成功状态下的回调函数
        let resolve = value => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        };

        // 定义 reject 函数,用于将状态从 pending 变为 rejected,并执行失败状态下的回调函数
        let reject = reason => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };

        try {
            // 执行执行器函数 executor,并传入 resolve 和 reject 函数
            executor(resolve, reject);
        } catch (error) {
            // 如果执行过程中出现异常,则将状态变为 rejected,并将异常原因传递给 reject 函数
            reject(error);
        }
    }

    // then 方法用于注册成功和失败状态下的回调函数,并返回一个新的 Promise 对象
    then(onFulfilled, onRejected) {
        // 判断回调函数是否为函数类型,如果不是,则返回一个默认的回调函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error };

        let promise2 = new myPromise((resolve, reject) => {
            // 处理状态为 fulfilled 时的逻辑
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            // 处理状态为 rejected 时的逻辑
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            // 处理状态为 pending 时的逻辑
            if (this.status === PENDING) {
                // 将成功和失败状态下的回调函数添加到对应的数组中
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            this.resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            this.resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });

        // 返回新的 Promise 对象
        return promise2;
    }

    // catch 方法用于捕获 Promise 链中的错误,并返回一个新的 Promise 对象
    catch(onRejected) {
        // 调用 then 方法,注册失败状态下的回调函数
        return this.then(null, onRejected);
    }

    // resolvePromise 方法用于处理 Promise 的链式调用
    resolvePromise(promise2, x, resolve, reject) {
        // 如果 promise2 和 x 指向同一个对象,则抛出错误,防止循环引用
        if (promise2 === x) {
            return reject(new TypeError('Chaining cycle detected for promise'));
        }

        // 标识是否已经调用过成功或失败状态的回调函数
        let called = false;

        // 如果 x 是 Promise 对象,则递归地调用 resolvePromise 方法
        if (x instanceof myPromise) {
            x.then(
                y => {
                    this.resolvePromise(promise2, y, resolve, reject);
                },
                error => {
                    reject(error);
                }
            );
        }
        // 如果 x 是对象或函数,则尝试调用其 then 方法
        else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
            try {
                let then = x.then;
                if (typeof then === 'function') {
                    then.call(
                        x,
                        y => {
                            if (called) return;
                            called = true;
                            this.resolvePromise(promise2, y, resolve, reject);
                        },
                        error => {
                            if (called) return;
                            called = true;
                            reject(error);
                        }
                    );
                } else {
                    // 如果 x 是普通对象,则直接将其作为成功状态的返回值
                    resolve(x);
                }
            } catch (error) {
                if (called) return;
                called = true;
                reject(error);
            }
        } else {
            // 如果 x 是基本类型,则直接将其作为成功状态的返回值
            resolve(x);
        }
    }
}

// 测试用例
const asyncOperation = new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('异步逻辑 等待1s');
    }, 1000);
});

asyncOperation
    .then(value => {
        console.log('then:', value);
        return '1. 第一个then的返回';
    })
    .then(value => {
        console.log('Second then:', value);
        return new myPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('2. 第二个then的返回');
            }, 500);
        });
    })
    .then(value => {
        console.log('Third then:', value);
        throw new Error('3. 第三个then会报一个错误');
    })
    .catch(error => {
        console.error('Catch:', error.message);
    });

扩展

栈(Stack)

栈是一种后进先出(Last In First Out,LIFO)的数据结构,类似于我们日常生活中的一摞书或者一堆盘子。在栈中,最后入栈的元素会被最先弹出。

特点:

  • 后进入栈的元素会先出栈,先进入栈的元素会后出栈。
  • 只允许在栈顶进行插入(压栈)和删除(弹栈)操作。
  • 栈的操作主要包括压栈(Push)和弹栈(Pop)。

应用场景:

  • 函数调用栈:函数调用时会将函数调用信息压入栈中,每次函数返回时会从栈顶弹出。
  • 浏览器历史记录:浏览器的后退按钮可以通过栈来实现。
  • 表达式求值:编译器和解释器常常使用栈来对表达式进行求值。

队列(Queue)

队列是一种先进先出(First In First Out,FIFO)的数据结构,类似于我们日常生活中排队的场景。在队列中,先进入队列的元素会先出队列。

特点:

  • 先进入队列的元素会先出队列,后进入队列的元素会后出队列。
  • 队列允许在队尾进行插入(入队)操作,在队首进行删除(出队)操作。
  • 队列的操作主要包括入队(Enqueue)和出队(Dequeue)。

应用场景:

  • 任务调度:操作系统中的任务调度通常使用队列来管理任务的执行顺序。
  • 网络数据传输:网络通信中,数据包通常按照先进先出的顺序进行传输。
  • 缓存:缓存队列可以用于限制系统资源的使用,并控制请求的处理速度。
相关推荐
前端李易安1 小时前
Web常见的攻击方式及防御方法
前端
PythonFun2 小时前
Python技巧:如何避免数据输入类型错误
前端·python
Neituijunsir2 小时前
2024.09.22 校招 实习 内推 面经
大数据·人工智能·算法·面试·自动驾驶·汽车·求职招聘
hakesashou2 小时前
python交互式命令时如何清除
java·前端·python
天涯学馆2 小时前
Next.js与NextAuth:身份验证实践
前端·javascript·next.js
HEX9CF2 小时前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
ConardLi2 小时前
Chrome:新的滚动捕捉事件助你实现更丝滑的动画效果!
前端·javascript·浏览器
ConardLi2 小时前
安全赋值运算符,新的 JavaScript 提案让你告别 trycatch !
前端·javascript
凌云行者2 小时前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
华农第一蒟蒻3 小时前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token