jQuery 封装详解

我们来实现一个简化版的 jQuery 库

一个库就是一个单独的模块,因此应使用自执行函数的方式模拟一个模块。

(function() {
  // do something
});

既然能够在全局直接调用 jQuery,则说明 jQuery 被挂载在了全局对象上。 块中对外提供接口时,可以采取 window.jQuery 的方式。

var jQuery = function() {};
// ...
window.jQuery = jQuery;

我们在使用过程中,使用了 $,其实只是多加了一个赋值操作。

window.$ = window.jQuery = jQuery;

在使用过程中直接使用 $,其实相当于直接调用构造函数 jQuery 创建了一个实例,而没有用 new。 我们知道创建实例 new 关键字是必不可少的,由此说明 new 的操作被放在了 jQuery 方法中来实现,而 jQuery 并不是真正的构造函数。

(function(ROOT) {
  // 构造函数
  var jQuery = function(selector) {
    // 在该方法中直接返回new创建的实例,
    // 因此这里的 init 才是真正的构造函数
    return new jQuery.fn.init(selector);
  };

  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    version: '1.0.0',
    init: function(selector) {
      var elem, selector;
      elem = document.querySelector(selector);
      this[0] = elem;

      // 在jQuery中返回的是一个由所有原型属性方法组成的数组,
      // 这里做了简化,直接返回this即可
      return this;
    },

    // 在原型上添加一堆方法
    toArray: function() {},
    get: function() {},
    each: function() {},
    ready: function() {},
    first: function() {},
    slice: function() {}
    // ...more
  };

  // 让 init 方法的原型指向jQuery的原型
  jQuery.fn.init.prototype = jQuery.fn;

  ROOT.jQuery = ROOT.$ = jQuery;
})(window);

在上面的实现中,首先在 jQuery 构造函数中声明了一个 fn 属性,并将其指向了原型 jQuery.prototype。 随后在原型对象中添加了 init 方法。

jQuery.fn = jQuery.prototype = {
  init: function() {}
};

之后又将 init 的原型指向了 jQuery.prototype

jQuery.fn.init.prototype = jQuery.fn;

而在构造函数 jQuery 中 ,则返回了 init 的实例对象。

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

最后对外暴露接口时,将字符 $ 与方法 jQuery 对等起来。

ROOT.jQuery = ROOT.$ = jQuery;

因此当使用 $('#test') 创建一个 jQuery 实例时,实际上调用的是 jQuery('#test') 创建的一个 init 实例。这里正在构造函数的是原型中的 init 方法。

扩展方法

jQuery 提供了两个扩展接口来帮助自定义 jQuery 的方法,通常称自定义的 jQuery 方法为 jQuery 桶件。

(function(ROOT) {
  // 构造函数
  var jQuery = function(selector) {
    // 在该方法中直接返回new创建的实例,
    // 因此这里的 init 才是真正的构造函数
    return new jQuery.fn.init(selector);
  };

  jQuery.fn = jQuery.prototype = {
    constructor: jQuery,
    version: '1.0.0',
    init: function(selector) {
      var elem, selector;
      elem = document.querySelector(selector);
      this[0] = elem;

      // 在jQuery中返回的是一个由所有原型属性方法组成的数组,
      // 这里做了简化,直接返回this即可
      return this;
    },

    // 在原型上添加一堆方法
    toArray: function() {},
    get: function() {},
    each: function() {},
    ready: function() {},
    first: function() {},
    slice: function() {}
    // ...more
  };

  // 让 init 方法的原型指向jQuery的原型
  jQuery.fn.init.prototype = jQuery.fn;

  // 实现 jQuery 的两种扩展方法
  jQuery.extend = jQuery.fn.extend = function(options) {
    // 在jQuery源码中根据参数不同进行不同的判断,这里假设只有一种方式
    var target = this;
    var copy;

    for (name in options) {
      copy = options[name];
      target[name] = copy;
    }
    return target;
  };

  // jQuery利用上面实现的扩展机制,添加了许多方法

  // 添加静态扩展方法,即工具方法
  jQuery.extend({
    isFunction: function() {},
    type: function() {},
    parseHTML: function() {},
    parseJSON: function() {},
    ajax: function() {}
    // ...more
  });

  // 添加原型方法
  jQuery.fn.extend({
    queue: function() {},
    promise: function() {},
    attr: function() {},
    prop: function() {},
    addClass: function() {},
    removeClass: function() {},
    val: function() {},
    css: function() {}
    // ...more
  });

  ROOT.jQuery = ROOT.$ = jQuery;
})(window);

在上面的代码中,我们通过下面的方式简单实现了两个扩展方法。

jQuery.extend = jQuery.fn.extend = function(options) {
  // 在jQuery源码中根据参数不同进行不同的判断
  // 而这里直接用了一种方式 ,所以就不用判断了
  var target = this;
  var copy;

  for (name in options) {
    copy = options[name];
    target[name] = copy;
  }
  return target;
};

要理解它的实现,首先要明确知道内部 this 的指向。传人的参数 options 对象是一个 key-value 模式的对象。我们可以通过 for in 遍历 options,将 key 作为新的属性,value作为该属性对应的新方法,分别添加到 jQueryjQuery.fn 中。

也就是说,当通过 $.extend 扩展 jQuery 时,方法被添加到了静态方法中;而通过 $.fn.extend 扩展 jQuery 时,方法被添加到了原型对象中。静态方法可以直接调用,因此也被称为工具方法。

$.ajax();
$.isFunction();
$.each();

原型方法必须通过声明的实例才能调用

$('#test').css();
$('#test').attr();