Skip to content

Latest commit

 

History

History
277 lines (234 loc) · 8.21 KB

03-基础结构.md

File metadata and controls

277 lines (234 loc) · 8.21 KB
title date
03-基础结构
2017/02/21 14:47:10

0、jQuery() 与 new jQuery();

jQuery 中,我们一般都是用 $('body'),实际上 new $('body') 也是可以用的。这是如何实现的呢?

要能使用 new,那 jQuery 一定是一个 function,又要让直接调用也返回一个实例,那我们就可以考虑返回另外一个全新 function 的实例。

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

通过以上这段代码,不管是用 new jQuery() 还是 jQuery() 返回的都是 init 方法的实例。

在定义 jQuery 这个方法的时候,实际上 fninit 都还不存在。

1、jQuery.fn = jQuery.prototype

jQuery.fn = jQuery.prototype = {
  // The current version of jQuery being used
  jquery: version,
  constructor: jQuery,
  // The default length of a jQuery object is 0
  length: 0,
  toArray: function () {
    return slice.call(this);
  },
  // Get the Nth element in the matched element set OR
  // Get the whole matched element set as a clean array
  get: function (num) {
    // Return all the elements in a clean array
    if (num == null) {
      return slice.call(this);
    }
    // Return just the one element from the set
    return num < 0 ? this[num + this.length] : this[num];
  },
  // Take an array of elements and push it onto the stack
  // (returning the new matched element set)
  pushStack: function (elems) {
    // Build a new jQuery matched element set
    var ret = jQuery.merge(this.constructor(), elems);
    // Add the old object onto the stack (as a reference)
    ret.prevObject = this;
    // Return the newly-formed element set
    return ret;
  },
  // Execute a callback for every element in the matched set.
  each: function (callback) {
    return jQuery.each(this, callback);
  },
  map: function (callback) {
    return this.pushStack(jQuery.map(this, function (elem, i) {
      return callback.call(elem, i, elem);
    }));
  },
  slice: function () {
    return this.pushStack(slice.apply(this, arguments));
  },
  first: function () {
    return this.eq(0);
  },
  last: function () {
    return this.eq(-1);
  },
  eq: function (i) {
    var len = this.length,
      j = +i + (i < 0 ? len : 0);
    return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
  },
  end: function () {
    return this.prevObject || this.constructor();
  },
  // For internal use only.
  // Behaves like an Array's method, not like a jQuery method.
  push: push, // push = [].push
  sort: arr.sort, // arr = []
  splice: arr.splice // arr = []
};

通过以上方法指定了 jQuery 的原型对象,也可以看到它默认的原型方法。

2、jQuery.fn.init.prototype?

看到了 jQuery 的构造和 jQuery.prototype 的申明,可能会有这样一个疑惑,在 jQuery() 中返回的明明是 jQuery.fn.init 的实例,为什么可以使用 jQuery.protptype 呢?

我们先看看 init 函数的实现:

var init = jQuery.fn.init = function (selector, context, root) {
  var match, elem;
  // HANDLE: $(""), $(null), $(undefined), $(false)
  // 没有给选择器还玩个毛啊
  if (!selector) {
    return this;
  }
  // Method init() accepts an alternate rootjQuery
  // so migrate can support jQuery.sub (gh-2101)
  root = root || rootjQuery;

  // Handle HTML strings
  if (typeof selector === "string") {
    if (selector[0] === "<" &&
      selector[selector.length - 1] === ">" &&
      selector.length >= 3) {

      // Assume that strings that start and end with <> are HTML and skip the regex check
      match = [null, selector, null];

    } else {
      match = rquickExpr.exec(selector);
    }

    // Match html or make sure no context is specified for #id
    if (match && (match[1] || !context)) {

      // HANDLE: $(html) -> $(array)
      if (match[1]) {
        context = context instanceof jQuery ? context[0] : context;

        // Option to run scripts is true for back-compat
        // Intentionally let the error be thrown if parseHTML is not present
        jQuery.merge(this, jQuery.parseHTML(
          match[1],
          context && context.nodeType ? context.ownerDocument || context : document,
          true
        ));

        // HANDLE: $(html, props)
        if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
          for (match in context) {

            // Properties of context are called as methods if possible
            if (jQuery.isFunction(this[match])) {
              this[match](context[match]);

              // ...and otherwise set as attributes
            } else {
              this.attr(match, context[match]);
            }
          }
        }

        return this;

        // HANDLE: $(#id)
      } else {
        elem = document.getElementById(match[2]);

        if (elem) {

          // Inject the element directly into the jQuery object
          this[0] = elem;
          this.length = 1;
        }
        return this;
      }

      // HANDLE: $(expr, $(...))
    } else if (!context || context.jquery) {
      return (context || root).find(selector);

      // HANDLE: $(expr, context)
      // (which is just equivalent to: $(context).find(expr)
    } else {
      return this.constructor(context).find(selector);
    }

    // HANDLE: $(DOMElement)
  } else if (selector.nodeType) {
    this[0] = selector;
    this.length = 1;
    return this;

    // HANDLE: $(function)
    // Shortcut for document ready
  } else if (jQuery.isFunction(selector)) {
    return root.ready !== undefined ?
      root.ready(selector) :

      // Execute immediately if ready is not present
      selector(jQuery);
  }

  return jQuery.makeArray(selector, this);
};

这个不多说,就是根据选择器查找元素。接下来,关键来了:

// 把jQuery.fn设置为init的原型
init.prototype = jQuery.fn;

// Initialize central reference
// 从document开始查找
rootjQuery = jQuery(document);

3、jQuery.extend = jQuery.fn.extend

有了以上的基础,那么就该扩展 jQuery 的功能了。它的核心就是以下的扩展函数:

jQuery.extend = jQuery.fn.extend = function () {
  var options, name, src, copy, copyIsArray, clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;
  // Handle a deep copy situation
  if (typeof target === "boolean") {
    deep = target;
    // Skip the boolean and the target
    target = arguments[i] || {};
    i++;
  }
  // Handle case when target is a string or something (possible in deep copy)
  if (typeof target !== "object" && !jQuery.isFunction(target)) {
    target = {};
  }
  // Extend jQuery itself if only one argument is passed
  if (i === length) {
    target = this;
    i--;
  }
  // 将属性扩展到指定的对象上。
  for (; i < length; i++) {
    // Only deal with non-null/undefined values
    if ((options = arguments[i]) != null) {
      // Extend the base object
      for (name in options) {
        src = target[name];
        copy = options[name];
        // Prevent never-ending loop
        if (target === copy) {
          continue;
        }
        // Recurse if we're merging plain objects or arrays
        if (deep && copy && (jQuery.isPlainObject(copy) ||
          (copyIsArray = jQuery.isArray(copy)))) {
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && jQuery.isArray(src) ? src : [];
          } else {
            clone = src && jQuery.isPlainObject(src) ? src : {};
          }
          // Never move original objects, clone them
          target[name] = jQuery.extend(deep, clone, copy);
          // Don't bring in undefined values
        } else if (copy !== undefined) {
          target[name] = copy;
        }
      }
    }
  }
  // Return the modified object
  return target;
};

为什么同一个 extend 函数可以扩展 jQuery,也可以扩展 jQuery.fn?这就涉及到另外一个JS的知识点了。

函数中this的指向是谁调用谁就是this

4、小结

以上就是 jQuery 的核心基础结构,如果我们自己要编写一个 Lib or Framework,也可以参考这样的方式来实现。

之后所有的功能,都是从这里进行扩展。