JQuery源码阅读(一)整体架构

很早就听一些大神说要读源码,直到前一段时间春招受挫才立下决心,先入手一个JQuery吧,以后有机会可以摸一摸Vue。

jQuery,说起都有一种张国荣、陈百强的感觉了,但是还是可以重温的,面试官教育我,不能盲目跟风,人云亦云。
其实看了几天了,一行一行看没有重点,直到看了几个大佬的博文(比如下面这位),觉得可以尝试了。


一位大佬.png

还有一位出了一个系列的,大家在中文社区应该看得到(他也参加过蚂蚁金服17春招,而且通过了,敬佩之心油然而生!)。

从外层入手

( function( global, factory ) {
  "use strict";
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

  if ( !noGlobal ) {
    window.jQuery = window.$ = jQuery;
  }// 在node环境非全局执行时不会暴露为接口
});

首先把映入眼帘的最外层拿出来,就是一个自执行函数,大佬也叫闭包。可以做到‘私有’和暴露到执行环境指定的jQuery接口(noGlobal控制)。
自执行函数里放的啥?

if ( typeof module === "object" && typeof module.exports === "object" ) {
// 自执行函数(模块化)commenJs?(存在document属性?
// 执行函数:暴露包装函数待传入一个含document的对象):执行函数
    module.exports = global.document ?
      factory( global, true ) :
      function( w ) {
        if ( !w.document ) {
          throw new Error( "jQuery requires a window with a document" );
        }
        return factory( w );
      };
  } else {
    factory( global );
  }  // node环境检查是否是全局执行,非node环境直接执行

判断了是不是commonJS(node的模块化标准),传入的执行环境作用域变量是不是有document。

进入正题

现在可以正视那个被严格控制的神秘又长的函数了。JQuery是啥?我找一下,看起来一时半会没有一个全面的答案。

var
    version = "3.3.1", //版本号呗,忽视一下。
    jQuery = function( selector, context ) {
      return new jQuery.fn.init( selector, context );//jQuery ()实际实例化了init
    };
  jQuery.fn = jQuery.prototype = { };

jQuery就是一个起构造作用的函数,实例化了 jQuery.fn.init。fn是一个私有属性,指向jQuery的原型。

init是啥
var rootjQuery,
    rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
    init = jQuery.fn.init = function( selector, context, root ) {
  var match, elem;
  // 规避: $(""), $(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" ) {
    // selector是string
  } else if ( selector.nodeType ) {
    // selector是DOM节点
  } else if ( isFunction( selector ) ) {
    // selector是函数
  }
  return jQuery.makeArray( selector, this );
};

开始有些东西试图阻止我顺藤摸瓜了,先记下来。

rootjQuery、jQuery.makeArray|init私有match,elem

注意到了return this;如果被作为一个构造函数使用,这个return是不言自明的,但是他明说了。猜测,有非new的调用,或者定制无论何种调用的return(返回一个空对象可能想得到其原型?)。
先往下瞄一眼 眼熟吧,一句委托,return的this就是prototype指向jQuery原型的空对象。

init.prototype = jQuery.fn;

有三个流程判断语句,我要捡软的捏!

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

第一个参数如果传入的DOM节点,放入0属性,length属性置1,返回了一个像数组的对象。

selector是函数
// HANDLE: $(function)
// Shortcut for document ready
} else if ( isFunction( selector ) ) {
    return root.ready !== undefined ?
        root.ready( selector ) :
        // Execute immediately if ready is not present
        selector( jQuery );
}

init参数root.ready

在前面jQuery实例化的时候并没有传入root参数,在没有root的情况下执行selector( jQuery ),应该可以在传入的函数里扩充jQuery函数的内容吧。

selector非DOM、function、string
return jQuery.makeArray( selector, this );

那就return 让这个未知的函数执行呗!

selector是string
if ( typeof selector === "string" ) {
  if ( selector[ 0 ] === "<" &&
    selector[ selector.length - 1 ] === ">" &&
    selector.length >= 3 ) { // /^<[\w\W]+>$/
    match = [ null, selector, null ];
  } else {
    match = rquickExpr.exec( selector );
  }
  if ( match && ( match[ 1 ] || !context ) ) {

  } else if ( !context || context.jquery ) {

  } else {

  }
}

这段的结构也被抽离出来了。有必要回看rquickExpr了/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,就是它,匹配到以空格或<[\w\W]+>开头的字符串。
这样match就有两种样子了,[ null, selector, null ]或正则匹配后的(数组第二项为去空格的标签字符串)。

if ( match && ( match[ 1 ] || !context ) ) {
  // HANDLE: $(html) -> $(array)
  if ( match[ 1 ] ) {
    // 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;
  }
}

如果match的第二项空,则按照第三项获取全局的对应id的节点,存入类数组对象返回。

if ( match[ 1 ] ) {
    context = context instanceof jQuery ? context[ 0 ] : context;
    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 ( isFunction( this[ match ] ) ) {
                this[ match ]( context[ match ] );
                // ...and otherwise set as attributes
            } else {
                this.attr( match, context[ match ] );
            }
        }
    }
    return this;
}

要补充一句了

var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );

jQuery.merge jQuery.isPlainObject init的方法attr

其中context.ownerDocument得到页面根节点,而rsingleTag 可以匹配一个无文本的完整标签。
后面的for-in循环大概是可以判断出操作的合法性了后,对初始化的对象拷贝属性,在原基础上重写DOM节点的一些方法。

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 );
}

find()可能就是我们用到的find方法

其他情况则以第二参数构造,再find第一参数。
另外,向下瞄一眼rootjQuery 找到了(init了document)。

// Initialize central reference
rootjQuery = jQuery( document );

整体架构的入手和init函数就完成了,希望能坚持下去吧。

你可能感兴趣的:(JQuery源码阅读(一)整体架构)