很早就听一些大神说要读源码,直到前一段时间春招受挫才立下决心,先入手一个JQuery吧,以后有机会可以摸一摸Vue。
jQuery,说起都有一种张国荣、陈百强的感觉了,但是还是可以重温的,面试官教育我,不能盲目跟风,人云亦云。
其实看了几天了,一行一行看没有重点,直到看了几个大佬的博文(比如下面这位),觉得可以尝试了。
还有一位出了一个系列的,大家在中文社区应该看得到(他也参加过蚂蚁金服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函数就完成了,希望能坚持下去吧。