jQuery 实现原理

手写一个简易版 jQuery,理解其设计思想

问题

jQuery 是如何实现的?它的链式调用、选择器、插件机制是怎么工作的?

解答

基本结构

(function(window) {
  // jQuery 构造函数
  function jQuery(selector) {
    // 无需 new 即可创建实例
    return new jQuery.fn.init(selector);
  }

  // 原型对象
  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    length: 0,

    // 真正的构造函数
    init: function(selector) {
      // 处理空选择器
      if (!selector) {
        return this;
      }

      // 处理字符串选择器
      if (typeof selector === 'string') {
        const elements = document.querySelectorAll(selector);
        for (let i = 0; i < elements.length; i++) {
          this[i] = elements[i];
        }
        this.length = elements.length;
      }

      // 处理 DOM 元素
      if (selector.nodeType) {
        this[0] = selector;
        this.length = 1;
      }

      return this;
    }
  };

  // 关键:让 init 的实例能访问 jQuery.fn 上的方法
  jQuery.fn.init.prototype = jQuery.fn;

  // 暴露到全局
  window.$ = window.jQuery = jQuery;
})(window);

链式调用

jQuery.fn.css = function(property, value) {
  for (let i = 0; i < this.length; i++) {
    this[i].style[property] = value;
  }
  // 返回 this 实现链式调用
  return this;
};

jQuery.fn.addClass = function(className) {
  for (let i = 0; i < this.length; i++) {
    this[i].classList.add(className);
  }
  return this;
};

jQuery.fn.on = function(event, callback) {
  for (let i = 0; i < this.length; i++) {
    this[i].addEventListener(event, callback);
  }
  return this;
};

// 使用
$('.box').css('color', 'red').addClass('active').on('click', fn);

插件机制

// 实例方法扩展
jQuery.fn.extend = function(obj) {
  for (let key in obj) {
    this[key] = obj[key];
  }
};

// 静态方法扩展
jQuery.extend = function(obj) {
  for (let key in obj) {
    this[key] = obj[key];
  }
};

// 添加插件
$.fn.extend({
  fadeIn: function(duration) {
    // 淡入逻辑
    return this;
  }
});

// 添加工具方法
$.extend({
  ajax: function(options) {
    // ajax 逻辑
  }
});

完整示例

(function(window) {
  function jQuery(selector) {
    return new jQuery.fn.init(selector);
  }

  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    length: 0,

    init: function(selector) {
      if (!selector) return this;

      if (typeof selector === 'string') {
        // 处理 HTML 字符串
        if (selector[0] === '<') {
          const div = document.createElement('div');
          div.innerHTML = selector;
          this[0] = div.firstChild;
          this.length = 1;
        } else {
          // 处理选择器
          const elements = document.querySelectorAll(selector);
          for (let i = 0; i < elements.length; i++) {
            this[i] = elements[i];
          }
          this.length = elements.length;
        }
      } else if (selector.nodeType) {
        this[0] = selector;
        this.length = 1;
      } else if (typeof selector === 'function') {
        // $(function) 等同于 DOMContentLoaded
        document.addEventListener('DOMContentLoaded', selector);
      }

      return this;
    },

    // 遍历方法
    each: function(callback) {
      for (let i = 0; i < this.length; i++) {
        callback.call(this[i], i, this[i]);
      }
      return this;
    },

    // 获取/设置属性
    attr: function(name, value) {
      if (value === undefined) {
        return this[0]?.getAttribute(name);
      }
      return this.each(function() {
        this.setAttribute(name, value);
      });
    },

    // 获取/设置内容
    html: function(content) {
      if (content === undefined) {
        return this[0]?.innerHTML;
      }
      return this.each(function() {
        this.innerHTML = content;
      });
    }
  };

  jQuery.fn.init.prototype = jQuery.fn;
  window.$ = window.jQuery = jQuery;
})(window);

// 测试
$('.btn').attr('disabled', 'true').html('Loading...');

关键点

  • 无 new 构造:通过 new jQuery.fn.init() 内部创建实例,外部无需 new
  • 原型共享jQuery.fn.init.prototype = jQuery.fn 让 init 实例能访问所有方法
  • 链式调用:每个方法返回 this,支持连续调用
  • 类数组对象:用数字索引存储 DOM 元素,配合 length 属性
  • 插件扩展:通过 $.fn.extend 添加实例方法,$.extend 添加静态方法