微信小程序启动顺序遇到的一个坑

微信小程序启动顺序

  • 启动顺序
  • [App.onLaunch 【异步坑】](#App.onLaunch 【异步坑】)
    • 解决方案
      • [方案一( **Promise 方案**)](#方案一( Promise 方案))
      • [方案二 **回调/事件总线**](#方案二 回调/事件总线)
      • [方案三 **页面内判断**](#方案三 页面内判断)
    • 总结

启动顺序

摘要 :本文梳理了微信小程序的启动顺序,区分冷启动与热启动两种场景:冷启动会完整执行 App.onLaunch → App.onShow → Page.onLoad → Page.onShow → Page.onReady,而热启动仅触发 onShow。同时重点剖析了 App.onLaunch 中的异步坑------在 onLaunch 里获取 token/openId 时,页面 onLoad 往往先于异步请求完成执行,导致数据取不到。最后给出 Promise、回调/事件总线、页面内判断三种解决方案,并附完整代码示例。

如果用户已经打开过小程序,只是切到了后台(如按了 Home 键)又切回来:

不会再次执行 onLaunch 和页面的 onLoad(页面实例还在内存中)。

执行顺序变为:

App.onShow(应用重新展示)

Page.onShow(当前页面重新展示)

冷启动时序图

冷启动指用户首次打开小程序,或小程序被销毁后重新打开,此时会完整执行 App 与 Page 的生命周期:
页面实例 App 实例 用户 页面实例 App 实例 用户 #mermaid-svg-jTFMrtL5tjNzIBnF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jTFMrtL5tjNzIBnF .error-icon{fill:#552222;}#mermaid-svg-jTFMrtL5tjNzIBnF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jTFMrtL5tjNzIBnF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jTFMrtL5tjNzIBnF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jTFMrtL5tjNzIBnF .marker.cross{stroke:#333333;}#mermaid-svg-jTFMrtL5tjNzIBnF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jTFMrtL5tjNzIBnF p{margin:0;}#mermaid-svg-jTFMrtL5tjNzIBnF .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jTFMrtL5tjNzIBnF text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jTFMrtL5tjNzIBnF .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jTFMrtL5tjNzIBnF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jTFMrtL5tjNzIBnF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jTFMrtL5tjNzIBnF .sequenceNumber{fill:white;}#mermaid-svg-jTFMrtL5tjNzIBnF #sequencenumber{fill:#333;}#mermaid-svg-jTFMrtL5tjNzIBnF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jTFMrtL5tjNzIBnF .messageText{fill:#333;stroke:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jTFMrtL5tjNzIBnF .labelText,#mermaid-svg-jTFMrtL5tjNzIBnF .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .loopText,#mermaid-svg-jTFMrtL5tjNzIBnF .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jTFMrtL5tjNzIBnF .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jTFMrtL5tjNzIBnF .noteText,#mermaid-svg-jTFMrtL5tjNzIBnF .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jTFMrtL5tjNzIBnF .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jTFMrtL5tjNzIBnF .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jTFMrtL5tjNzIBnF .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jTFMrtL5tjNzIBnF .actorPopupMenu{position:absolute;}#mermaid-svg-jTFMrtL5tjNzIBnF .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jTFMrtL5tjNzIBnF .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jTFMrtL5tjNzIBnF .actor-man circle,#mermaid-svg-jTFMrtL5tjNzIBnF line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jTFMrtL5tjNzIBnF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 遇到 await 挂起(不阻塞后续) (某个时刻)await 请求完成 冷启动(首次打开) onLaunch 开始执行 onShow(应用展示) 页面开始加载 onLoad onShow onReady 继续执行 onLaunch 后续代码

热启动时序图

热启动指小程序从后台切回前台,页面实例仍驻留内存,不会重新执行 onLaunch 与 onLoad:
页面实例 App 实例 用户 页面实例 App 实例 用户 #mermaid-svg-LF2Etlwf6A4A3Z8g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LF2Etlwf6A4A3Z8g .error-icon{fill:#552222;}#mermaid-svg-LF2Etlwf6A4A3Z8g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LF2Etlwf6A4A3Z8g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LF2Etlwf6A4A3Z8g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LF2Etlwf6A4A3Z8g .marker.cross{stroke:#333333;}#mermaid-svg-LF2Etlwf6A4A3Z8g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LF2Etlwf6A4A3Z8g p{margin:0;}#mermaid-svg-LF2Etlwf6A4A3Z8g .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LF2Etlwf6A4A3Z8g text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LF2Etlwf6A4A3Z8g .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LF2Etlwf6A4A3Z8g .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LF2Etlwf6A4A3Z8g #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LF2Etlwf6A4A3Z8g .sequenceNumber{fill:white;}#mermaid-svg-LF2Etlwf6A4A3Z8g #sequencenumber{fill:#333;}#mermaid-svg-LF2Etlwf6A4A3Z8g #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LF2Etlwf6A4A3Z8g .messageText{fill:#333;stroke:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LF2Etlwf6A4A3Z8g .labelText,#mermaid-svg-LF2Etlwf6A4A3Z8g .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .loopText,#mermaid-svg-LF2Etlwf6A4A3Z8g .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LF2Etlwf6A4A3Z8g .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LF2Etlwf6A4A3Z8g .noteText,#mermaid-svg-LF2Etlwf6A4A3Z8g .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LF2Etlwf6A4A3Z8g .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LF2Etlwf6A4A3Z8g .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LF2Etlwf6A4A3Z8g .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LF2Etlwf6A4A3Z8g .actorPopupMenu{position:absolute;}#mermaid-svg-LF2Etlwf6A4A3Z8g .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LF2Etlwf6A4A3Z8g .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LF2Etlwf6A4A3Z8g .actor-man circle,#mermaid-svg-LF2Etlwf6A4A3Z8g line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LF2Etlwf6A4A3Z8g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不执行 onLaunch 与 onLoad 热启动(后台切回前台) onShow(应用重新展示) onShow(当前页面重新展示)

App.onLaunch 【异步坑】

App.onLaunch 开始执行(遇到 await 挂起,但不阻塞)→ App.onShow 执行 → 页面开始加载:Page.onLoad 执行 → Page.onShow 执行 →(某个时刻)onLaunch 里的 await 请求完成,继续执行后续代码。

如果你在 onLaunch 里获取 token 或 openId,然后在 pages/index.tsonLoad 里立刻去用这个 token,大概率是取不到的,因为 onLoad 跑得比异步请求快。

解决方案

如果页面依赖 onLaunch 的异步数据,通常有以下几种处理方式:

方案一( Promise 方案

在 App 中定义一个 loginReady 的 Promise,页面 onLoadawait 这个 Promise。

App.js

js 复制代码
// app.js
App({
  onLaunch() {
    // 1. onLaunch 开始执行,创建 loginReady Promise
    //    注意:Promise 的 executor 是同步执行的,
    //    所以 loginReady 在 onLaunch 返回前就已经被赋值
    this.loginReady = new Promise((resolve, reject) => {
      // 2. 发起异步请求(如 wx.login 换取 code,再请求后端拿 token/openId)
      wx.login({
        success: (res) => {
          // 3. 模拟请求后端换取 openId
          setTimeout(() => {
            this.globalData.openId = 'mock_open_id_' + res.code;
            // 4. 异步完成,resolve 通知所有等待方
            resolve(this.globalData.openId);
          }, 1000);
        },
        fail: (err) => {
          // 失败也要 resolve/reject,避免页面永久挂起
          reject(err);
        }
      });
    });
  },
  globalData: {
    openId: ''
  }
});

pages/index/index.js

js 复制代码
// pages/index/index.js
const app = getApp();

Page({
  async onLoad() {
    // 1. onLoad 执行时,onLaunch 可能还没完成异步请求
    //    直接读 globalData.openId 大概率是空字符串
    console.log('onLoad 立即读取 openId:', app.globalData.openId); // 输出:''

    // 2. 正确做法:await loginReady,等待 onLaunch 的异步完成
    try {
      const openId = await app.loginReady;
      // 3. 到这里才拿到数据,此时 onLaunch 的异步已 resolve
      console.log('await 后读取 openId:', openId);
      this.setData({ openId });
    } catch (err) {
      console.error('获取 openId 失败:', err);
    }
  }
});

执行顺序说明:onLaunch 先执行并创建 loginReadyonLoad 执行并 await loginReady(挂起,不阻塞页面渲染)→ 异步请求完成后 resolveonLoadawait 之后的代码继续执行。

方案二 回调/事件总线

onLaunch 异步完成后,通过 getApp().globalData 设值,并触发回调通知页面。

复制代码
// app.ts
App({
  // 全局数据
  globalData: {
    token: null as string | null,
  },

  // 存放等待登录完成的页面回调函数(事件总线队列)
  loginReadyCallbacks: [] as Array<(token: string) => void>,

  onLaunch() {
    console.log('[App] onLaunch 开始,发起异步登录');
    this.asyncLogin();
  },

  onShow() {
    console.log('[App] onShow');
  },

  /**
   * 模拟异步登录请求
   */
  asyncLogin() {
    // 模拟 wx.login + wx.request 的异步过程
    setTimeout(() => {
      const mockToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxxx';
      console.log('[App] 异步登录完成,获取到 token');

      // 1. 存入全局数据
      this.globalData.token = mockToken;

      // 2. 通知所有订阅者(触发回调队列)
      this.loginReadyCallbacks.forEach((callback) => {
        try {
          callback(mockToken);
        } catch (error) {
          console.error('[App] 执行回调时出错:', error);
        }
      });

      // 3. 清空队列(一次性事件,避免重复通知)
      this.loginReadyCallbacks = [];
    }, 2000); // 模拟 2 秒的网络延迟
  },

  /**
   * 供页面调用的"订阅"方法
   * @param callback 页面传入的回调函数
   */
  addLoginReadyCallback(callback: (token: string) => void) {
    // 情况 A:如果 token 已经拿到了(比如热启动),直接同步执行回调
    if (this.globalData.token) {
      console.log('[App] token 已存在,直接执行回调');
      callback(this.globalData.token);
      return;
    }

    // 情况 B:token 还没拿到,加入队列等待
    console.log('[App] token 未就绪,页面回调已加入等待队列');
    this.loginReadyCallbacks.push(callback);
  },
  removeLoginReadyCallback(callback: (token: string) => void) {
  const index = this.loginReadyCallbacks.indexOf(callback);
  if (index > -1) {
    this.loginReadyCallbacks.splice(index, 1);
  }
}
});

// pages/index.ts
Page({
  data: {
    token: '',
    pageStatus: '正在等待 token...',
  },

  onLoad() {
    console.log('[Page] onLoad');
    const app = getApp();

    // 订阅 App 的登录完成事件
    app.addLoginReadyCallback((token) => {
      console.log('[Page] 收到 token 通知,开始更新页面');
      
      this.setData({
        token: token,
        pageStatus: 'Token 已就绪,页面可以发起业务请求!',
      });

      // 在这里你可以放心地调用需要 token 的接口
      // this.fetchUserInfo(token);
    });
  },

  onShow() {
    console.log('[Page] onShow');
  },
    onUnload() {
    // 页面销毁时,取消订阅,防止内存泄漏和 setData 警告
    if (this.loginCallback) {
      getApp().removeLoginReadyCallback(this.loginCallback);
    }
  },
});

说明:

  1. 核心机制:利用数组存储回调函数,异步完成后遍历调用。
  2. 优势:逻辑清晰,页面无需关心 App 的生命周期,只需"订阅"数据。
  3. 适用场景:登录态下发、地理位置获取、系统信息初始化等需要在 App 启动阶段完成并通知多个页面的异步任务。

方案三 页面内判断

页面 onShow 时检查全局数据是否就绪,未就绪则显示 loading 或重新获取。

将获取 token 的逻辑抽离成一个公共方法,并做了简单的"防止重复请求"处理。

复制代码
// app.ts
App({
  globalData: {
    token: null as string | null,
  },

  // 标记是否正在请求中,防止页面和App同时调起多个请求
  isFetchingToken: false,

  onLaunch() {
    console.log('[App] onLaunch - 应用启动,开始异步获取 token');
    // App 启动时就去获取
    this.fetchToken();
  },

  onShow() {
    console.log('[App] onShow');
  },

  /**
   * 获取 Token 的公共方法(返回 Promise)
   */
  fetchToken(): Promise<string> {
    return new Promise((resolve) => {
      // 如果已经在请求中,这里简单模拟:直接等一会儿再检查 globalData
      // (实际项目中可以用一个全局 Promise 单例来避免并发)
      if (this.isFetchingToken) {
        console.log('[App] 正在请求中,请稍候...');
        // 简单轮询等待(仅作演示,生产环境建议用 Promise 单例)
        const timer = setInterval(() => {
          if (this.globalData.token) {
            clearInterval(timer);
            resolve(this.globalData.token);
          }
        }, 300);
        return;
      }

      this.isFetchingToken = true;

      // 模拟网络请求(比如调用 wx.login 和后端接口)
      setTimeout(() => {
        const mockToken = 'mock_token_' + Math.random().toString(36).slice(2);
        console.log('[App] 异步请求完成,Token:', mockToken);
        
        this.globalData.token = mockToken;
        this.isFetchingToken = false;
        resolve(mockToken);
      }, 2000);
    });
  },
});

页面在 onShow 生命周期里检查全局数据。如果没有,就显示 loading,并调用 App 的方法去"重新获取"。

复制代码
// pages/index.ts
Page({
  data: {
    token: '' as string | null,
    loading: false,
    pageState: '',
  },

  onLoad() {
    console.log('[Page] onLoad');
  },

  onShow() {
    console.log('[Page] onShow - 检查全局数据');
    this.checkAndLoadData();
  },

  /**
   * 核心逻辑:检查全局数据,未就绪则重新获取
   */
  async checkAndLoadData() {
    const app = getApp();

    // 1. 检查全局数据是否就绪
    if (app.globalData.token) {
      console.log('[Page] Token 已存在,直接渲染页面');
      this.setData({
        token: app.globalData.token,
        loading: false,
        pageState: '数据已通过全局获取,页面渲染完成',
      });
      return;
    }

    // 2. 未就绪:显示 loading
    console.log('[Page] Token 未就绪,显示 Loading 并重新获取');
    this.setData({
      loading: true,
      pageState: '正在获取 Token,请稍候...',
    });

    // 3. 重新获取(调用 App 提供的公共方法)
    try {
      const token = await app.fetchToken();
      this.setData({
        token: token,
        loading: false,
        pageState: 'Token 获取成功,页面渲染完成',
      });
      // 这里可以继续调用依赖 token 的其他接口
      // this.fetchUserProfile(token);
    } catch (error) {
      this.setData({
        loading: false,
        pageState: '获取失败,请重试',
      });
    }
  },

  // 用户手动重试按钮(可选)
  onRetry() {
    this.checkAndLoadData();
  },
});

总结

本文围绕微信小程序的启动顺序与 App.onLaunch 的异步坑展开,核心要点如下:

  1. 启动顺序 :冷启动会完整执行 App.onLaunch → App.onShow → Page.onLoad → Page.onShow → Page.onReady;热启动(后台切回前台)仅触发 App.onShowPage.onShow,不会重新执行 onLaunchonLoad

  2. 异步坑的本质onLaunch 中的异步请求(如获取 token/openId)不会阻塞后续生命周期,页面 onLoad 往往先于异步完成执行,导致直接读取全局数据时取不到值。

  3. 三种解决方案对比

    • Promise 方案 :在 App 中创建 loginReady Promise,页面 onLoadawait 它,代码直观、适合单页面等待;
    • 回调/事件总线方案:App 维护回调队列,异步完成后统一通知所有订阅页面,适合多页面同时等待同一份数据;
    • 页面内判断方案 :页面 onShow 时检查全局数据,未就绪则显示 loading 并重新获取,实现简单、容错性好,但每个页面都要写一遍判断逻辑。
  4. 选型建议:单页面依赖登录态优先用 Promise 方案;多页面共享同一份异步数据(如登录态、地理位置)推荐回调/事件总线;对实时性要求不高、希望页面自洽的场景可用页面内判断。

理解启动顺序与异步时序,是写出稳定、不踩坑的小程序代码的前提。希望本文的时序图与代码示例能帮你理清思路,在实际项目中少走弯路。

相关推荐
用户233376852181 小时前
接口卡死排查实录-缺失return的UB死循环
前端·后端
光影少年1 小时前
如何实现RN 多环境、多渠道打包
前端·react native·react.js
闲坐含香咀翠2 小时前
百万行数据透视表,我是怎么把 Vue 响应式开销砍到零的
前端·vue.js·性能优化
xiaopang2 小时前
小红书小组件(miniwidget)开发实战:单页面viewState切换架构
前端
labixiong2 小时前
告别scroll 监听:CSS 滚动驱动动画,主线程堵死也仍跟手
前端·css·html
云析赢指标公式网42 小时前
文华WH6布林轨道均线强弱共振指标公式
前端·算法
许彰午2 小时前
34-安全复盘96个问题
前端·vue.js·安全
妙码生花2 小时前
PHP 各框架下和 Go 的性能比较
前端·后端·go
Csvn3 小时前
TypeScript 类型性能:让类型体操不再卡死编译器
前端