jQuery 源码系列(二)init 引见

欢迎来我的专栏检察系列文章。

init 组织器

前面一讲整体架构已引见了 jQuery 的基础状况,这一章主要来引见 jQuery 的进口函数 jQuery.fn.init

《jQuery 源码系列(二)init 引见》

由于这个函数直接和 jQuery() 的参数有关,先来说下能接收什么样的参数。源码中接收 3 个参数:

init: function (selector, context, root) {
  ...
}

jQuery(),空参数,这个会直接返回一个空的 jQuery 对象,return this

jQuery( selector [, context ] ),这是一个规范且经常运用法,selector 示意一个 css 挑选器,这个挑选器通常是一个字符串,#id 或许 .class 等,context 示意挑选局限,即限定作用,可为 DOM,jQuery 对象。

jQuery( element|elements ),用于将一个 DOM 对象或 DOM 数组封装成 jQuery 对象。

jQuery( jQuery object|object ),会把平常的对象或 jQuery 对象包装在 jQuery 对象中。

jQuery( html [, ownerDocument ] ),这个要领用于将 html 字符串先转成 DOM 对象后在天生 jQuery 对象。

jQuery( html, attributes ),和上一个要领一样,不过会将 attributes 中的要领和属性绑定到天生的 html DOM 中,比方 class 等。

jQuery( callback ),此要领接收一个回掉函数,相当于 window.onload 要领,只是相关于。

jQuery.fn.init

引见完进口,就最先来看源码。

init: function (selector, context, root) {
  var match, elem;

  // 处置惩罚: $(""), $(null), $(undefined), $(false)
  if (!selector) {
    return this;
  }
  // rootjQuery = jQuery( document );
  root = root || rootjQuery;

  // 处置惩罚 HTML 字符串状况,包括 $("<div>")、$("#id")、$(".class")
  if (typeof selector === "string") {

  //此部份拆分,留在背面讲

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

  // HANDLE: $(function)
  } 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);
}

上面有几点须要注重,root = root || rootjQuery;,这个参数在前面引见用法的时刻,就没有说起,这个示意 document,默许的话是 rootjQuery,而 rootjQuery = jQuery( document )。

能够看出,关于处置惩罚 $(DOMElement),直接是把 jQuery 看成一个数组,this[0] = DOMElement。实在,这要从 jQuery 的基础组织讲起,我们完成一个 $('div.span') 以后,然后一个 jQuery 对象(this),个中会获得一组(一个)DOM 对象,jQuery 会把这组 DOM 对象看成数组元素增加过来,并给一个 length。背面就像一些链式函数操纵的时刻,若只能对一个 DOM 操纵,比方 width、height,就只对第一个元素操纵,若能够对多个 DOM 操纵,则会对一切 DOM 举行操纵,比方 css()。

jQuery 大题思绪以下,这是一个异常简单点完成:

jQuery.prototype = {
  // 简单点,假定此时 selector 用 querySelectorAll
  init: function(selector){
    var ele = document.querySelectorAll(selector);
    // 把 this 看成数组,每一项都是 DOM 对象
    for(var i = 0; i < ele.length; i++){
      this[i] = ele[i];
    }
    this.length = ele.length;
    return this;
  },
  //css 若只要一个对象,则取其第一个 DOM 对象
  //若 css 有两个参数,则对每个 DOM 对象都设置 css
  css : function(attr,val){
    for(var i = 0; i < this.length; i++){
      if(val == undefined){
        if(typeof attr === 'object'){
          for(var key in attr){
            this.css(key, attr[key]);
          }
        }else if(typeof attr === 'string'){
          return getComputedStyle(this[i])[attr];
        }
      }else{
        this[i].style[attr] = val;
      }
    }
  },
}

所以关于 DOMElement 的处置惩罚,直接将 DOM 赋值给数组后,return this。

jQuery.makeArray 是一个绑定 数组的函数,和上面的道理一样,背面会谈到。

在引见下面的内容之前,先来引见一个 jQuery 中一个辨认 Html 字符串的正则表达式,

var rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;
rquickExpr.exec('<div>') //["<div>", "<div>", undefined]
rquickExpr.exec('<div></div>') //["<div></div>", "<div></div>", undefined]
rquickExpr.exec('#id') //["#id", undefined, "id"]
rquickExpr.exec('.class') //null

上面这一系列的正则表达式 exec,只是为了申明 rquickExpr 这个正则表达式实行后的结果,起首,假如婚配到,结果数组的长度是 3,假如婚配到 <div> 这类 html,数组的第三个元素是 underfined,假如婚配到 #id,数组的第二个元素是 underfined,假如婚配不到,则为 null。

别的另有一个正则表达式:

var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
rsingleTag.test('<div></div>') //true
rsingleTag.test('<div ></div>') //true
rsingleTag.test('<div class="cl"></div>') //false
rsingleTag.test('<div></ddiv>') //false

这个正则表达式主如果对 html 的字符串举行考证,到达不出过失的结果。在这里不多引见 exec 和正则表达式了。

下面来看下重点的处置惩罚 HTMl 字符串的状况:

if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
  // 这个实际上是强行组织了婚配 html 的状况的数组
  match = [null, selector, null];

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

// macth[1] 限定了 html,!context 对 #id 处置惩罚
if (match && (match[1] || !context)) {

  // HANDLE: $(html) -> $(array)
  if (match[1]) {
    //消除 context 是 jQuery 对象状况
    context = context instanceof jQuery ? context[0] : context;

    // jQuery.merge 是特地针对 jQuery 兼并数组的要领
    // jQuery.parseHTML 是针对 html 字符串转换成 DOM 对象
    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) {

        // 此时的 match 非彼时的 match
        if (jQuery.isFunction(this[match])) {
          this[match](context[match]);

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

    return this;

  // 处置惩罚 match(1) 为 underfined 但 !context 的状况
  } else {
    elem = document.getElementById(match[2]);

  if (elem) {

    // this[0] 返回一个规范的 jQuery 对象
    this[0] = elem;
    this.length = 1;
  }
  return this;
}
// 处置惩罚平常的状况,find 实际上上 Sizzle,jQuery 已将其包括进来,下章细致引见
// jQuery.find() 为 jQuery 的挑选器,机能优越
} else if (!context || context.jquery) {
  return (context || root).find(selector);
// 处置惩罚 !context 状况
} else {
  // 这里 constructor 实际上是 指向 jQuery 的
  return this.constructor(context).find(selector);
}

关于 nodeType,这是 DOM 的一个属性,概况 Node.nodeType MDN。nodeType 的值平常是一个数字,比方 1 示意 DOM,3 示意笔墨等,也能够用这个值是不是存在来推断 DOM 元素,比方 context.nodeType。

全部 init 函数等组织逻辑,异常清楚,比方 (selector, context, root) 三个参数,离别示意挑选的内容,能够存在的的限定对象或 Object,而 root 则默许的 jQuery(document)。照旧采纳 jQuery 经常运用的体式格局,对每个变量的处置惩罚都异常的郑重。

假如仔细看上面两部份源代码,我本身也加了诠释,应当能够把全部历程给弄懂。

find 函数实际上是 Sizzle,已零丁出来一个项目,被在 jQuery 中直接运用,将在下章引见 jQuery 中的 Sizzle 挑选器。经由过程源码,能够发明:

jQuery.find = function Sizzle(){...}
jQuery.fn.find = function(selector){
  ...
  //援用 jQuery.find
  jQuery.find()
  ...
}

衍生的函数

init 函数依然挪用了不少 jQuery 或 jQuery.fn 的函数,下面来逐一剖析。

jQuery.merge

这个函数经由过程名字,就晓得它是用来干什么的,兼并。

jQuery.merge = function (first, second) {
  var len = +second.length,
    j = 0,
    i = first.length;

  for (; j < len; j++) {
    first[i++] = second[j];
  }

  first.length = i;

  return first;
}

这模样就可以够对类似于数组且有 length 参数的范例举行兼并,我觉得主要照样为了轻易对 jQuery 对象的兼并,由于 jQuery 对象就是有 length 的。

jQuery.parseHTML

这个函数也异常有意义,就是将一串 HTML 字符串转成 DOM 对象。

起首函数接收三个参数,第一个参数 data 即为 html 字符串,第二个参数是 document 对象,但要斟酌到浏览器的兼容性,第三个参数 keepScripts 是为了删除节点里一切的 script tags,但在 parseHTML 内里没有表现,主要照样给 buildFragment 看成参数。总之返回的对象,是一个 DOM 数组或空数组。

jQuery.parseHTML = function (data, context, keepScripts) {
  if (typeof data !== "string") {
    return [];
  }
  // 平移参数
  if (typeof context === "boolean") {
    keepScripts = context;
    context = false;
  }

  var base, parsed, scripts;

  if (!context) {

    // 下面这段话的意义就是在 context 缺失的状况下,竖立一个 document 对象
    if (support.createHTMLDocument) {
      context = document.implementation.createHTMLDocument("");
      base = context.createElement("base");
      base.href = document.location.href;
      context.head.appendChild(base);
    } else {
      context = document;
    }
  }
  // 用来剖析 parsed,比方对 "<div></div>" 的处置惩罚结果 parsed:["<div></div>", "div"]
  // parsed[1] = "div"
  parsed = rsingleTag.exec(data);
  scripts = !keepScripts && [];

  // Single tag
  if (parsed) {
    return [context.createElement(parsed[1])];
  }
  // 见下方诠释
  parsed = buildFragment([data], context, scripts);

  if (scripts && scripts.length) {
    jQuery(scripts).remove();
  }

  return jQuery.merge([], parsed.childNodes);
}

buildFragment 函数主如果用来竖立一个包括子节点的 fragment 对象,用于频发操纵的增加删除节点。parsed = buildFragment([data], context, scripts);竖立好一个 fragment 对象,用 parsed.childNodes 来猎取这些 data 对应的 HTML。

jQueyr.makeArray

jQuery 内里的函数挪用,真的是一层接一层,虽然有时刻光靠函数名,就可以晓得这函数的作用,但个中思索之逻辑照样挺参考意义的。

jQuery.makeArray = function (arr, results) {
  var ret = results || [];

  if (arr != null) {
    if (isArrayLike(Object(arr))) {
        jQuery.merge(ret, typeof arr === "string" ? [arr] : arr);
    } else {
        push.call(ret, arr);
    }
  }

  return ret;
}

makeArray 把左侧的数组或字符串并入到右侧的数组或一个新数组,个中又间接的援用 jQuery.merge 函数。

接下来是着 isArrayLike 函数,能够须要斟酌多方面的要素,比方兼容浏览器等,就有了下面这一长串:

function isArrayLike(obj) {

  // Support: real iOS 8.2 only (not reproducible in simulator)
  // `in` check used to prevent JIT error (gh-2145)
  // hasOwn isn't used here due to false negatives
  // regarding Nodelist length in IE
  var length = !!obj && "length" in obj && obj.length,
      type = jQuery.type(obj);

  if (type === "function" || jQuery.isWindow(obj)) {
      return false;
  }

  return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}

总结

这篇算是承先启后吧,引见了 jQuery 中比较主要的进口函数,然后预计下章将会解说 Sizzle,jQuery 中的挑选器。

参考

jQuery 2.0.3 源码剖析core – 挑选器
Node.nodeType
jQuery 3.0的buildFragment

本文在 github 上的源码地点,欢迎来 star。

欢迎来我的博客交换。

    原文作者:songjz
    原文地址: https://segmentfault.com/a/1190000008365665
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞