jQuery 队列实现原理

理解 jQuery 队列机制并手写实现

问题

jQuery 的队列是如何实现的?如何手写一个简化版的队列系统?

解答

jQuery 队列本质是一个函数数组,通过 queue() 添加函数,dequeue() 取出并执行函数。主要用于动画的顺序执行。

jQuery 队列的基本用法

// 向队列添加函数
$('#box').queue('fx', function(next) {
  console.log('第一个任务');
  next(); // 必须调用 next 才会执行下一个
});

$('#box').queue('fx', function(next) {
  console.log('第二个任务');
  next();
});

// 开始执行队列
$('#box').dequeue('fx');

手写实现

class Queue {
  constructor() {
    // 存储不同命名空间的队列
    this.queues = {};
  }

  // 添加函数到队列
  queue(name, fn) {
    // 初始化队列数组
    if (!this.queues[name]) {
      this.queues[name] = [];
    }

    // 如果传入函数,添加到队列
    if (typeof fn === 'function') {
      this.queues[name].push(fn);
    }

    // 返回当前队列(用于查看)
    return this.queues[name];
  }

  // 取出并执行队列中的下一个函数
  dequeue(name) {
    const queue = this.queues[name];

    if (!queue || queue.length === 0) {
      return;
    }

    // 取出第一个函数
    const fn = queue.shift();

    // 创建 next 函数,用于执行下一个
    const next = () => {
      this.dequeue(name);
    };

    // 执行函数,传入 next
    fn(next);
  }

  // 清空队列
  clearQueue(name) {
    if (this.queues[name]) {
      this.queues[name] = [];
    }
  }
}

// 使用示例
const q = new Queue();

// 添加异步任务
q.queue('animate', function(next) {
  console.log('任务1: 开始');
  setTimeout(() => {
    console.log('任务1: 完成');
    next(); // 1秒后执行下一个
  }, 1000);
});

q.queue('animate', function(next) {
  console.log('任务2: 开始');
  setTimeout(() => {
    console.log('任务2: 完成');
    next();
  }, 500);
});

q.queue('animate', function(next) {
  console.log('任务3: 同步完成');
  next();
});

// 开始执行
q.dequeue('animate');

// 输出顺序:
// 任务1: 开始
// (1秒后) 任务1: 完成
// 任务2: 开始
// (0.5秒后) 任务2: 完成
// 任务3: 同步完成

模拟 jQuery 动画队列

class AnimationQueue {
  constructor(element) {
    this.element = element;
    this.queue = [];
    this.running = false;
  }

  // 添加动画到队列
  animate(props, duration) {
    this.queue.push({
      type: 'animate',
      props,
      duration
    });
    this._run();
    return this; // 链式调用
  }

  // 添加延迟
  delay(ms) {
    this.queue.push({
      type: 'delay',
      duration: ms
    });
    this._run();
    return this;
  }

  // 执行队列
  _run() {
    if (this.running || this.queue.length === 0) {
      return;
    }

    this.running = true;
    const task = this.queue.shift();

    if (task.type === 'delay') {
      setTimeout(() => {
        this.running = false;
        this._run();
      }, task.duration);
    } else if (task.type === 'animate') {
      // 简化的动画实现
      console.log(`动画: ${JSON.stringify(task.props)}, 时长: ${task.duration}ms`);
      setTimeout(() => {
        this.running = false;
        this._run();
      }, task.duration);
    }
  }

  // 停止并清空队列
  stop() {
    this.queue = [];
    this.running = false;
    return this;
  }
}

// 使用
const anim = new AnimationQueue('#box');
anim
  .animate({ left: 100 }, 500)
  .delay(200)
  .animate({ top: 100 }, 500)
  .animate({ opacity: 0 }, 300);

关键点

  • 队列是一个函数数组,遵循先进先出(FIFO)原则
  • queue() 向数组末尾添加函数,dequeue() 从数组头部取出并执行
  • 每个函数接收 next 参数,必须调用它才能继续执行下一个任务
  • 支持命名空间,jQuery 默认使用 fx 作为动画队列名
  • 异步任务通过 next() 回调控制执行顺序,实现串行执行