1.入口结构
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
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 );
}
} )(
typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
//具体代码
}
抽离结构如下:
( function() { })()
涉及到的知识
- js解析函数时的规则
- 函数定义和函数表达式
- js闭包
js解析函数的规则
- js解析器会在遇到function时将其认为是函数定义而非函数表达式
函数定义和函数表达式
- 函数定义:
function test(){ }
- 函数表达式:
let test = function(){ }
js闭包
- 闭包:函数中的函数,本质是指作用域内的作用域
//闭包举例
function f(){
var a = 2;
function g(){
console.log(a)
};
return g;
}
f()();
综合以上的内容,再来看一下刚才抽离出来的代码
(function(){ })()
-
第一个括号有两个作用:
- 让js解析器把后面的function当作函数表达式而不是函数定义
- 形成一个作用域,类似在上面闭包例子中的f函数
-
第二个括号
- 触发函数并传参
2.第二个括号内的参数有哪些?
(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { })
- 第一个参数是判断环境,传入全局对象
- 第二个参数是确定环境后具体执行的代码
3.第一个括号内的函数做了什么?
( function( global, factory ) {
"use strict";
//判断是不是在commonjs环境下,如果是就执行以下代码
if ( typeof module === "object" && typeof module.exports === "object" ) {
//判断是否支持global.document
module.exports = global.document ?
factory( global, true ) :
function( w ) {
//不支持global.document时报错
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
//报错后返回jquery(w)
return factory( w );
};
} else {
//windows环境下执行这个代码
factory( global );
}
} )
4.判断完环境后通过:factory( global );
跳转到第二个括号的第二个参数内执行具体的内容
//整体的结构抽离如下
function( window, noGlobal ) {
"use strict";
//具体的jquery内部代码
if ( !noGlobal ) {
//在window下可以用以下方式调用
window.jQuery = window.$ = jQuery;
}
return jQuery;
}
5.进入函数后,先定义了一些变量,函数和对象(可以跳过先看下面的内容)
//定义了一些变量和方法
var arr = [];
var document = window.document;
var getProto = Object.getPrototypeOf;
//数组方法简写
var slice = arr.slice;
var concat = arr.concat;
var push = arr.push;
var indexOf = arr.indexOf;
//对象方法简写
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call( Object );
var support = {};
//定义函数
var isFunction = function isFunction( obj ) {
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
var isWindow = function isWindow( obj ) {
return obj != null && obj === obj.window;
};
function DOMEval( code, doc, node ) {
doc = doc || document;
var i,
script = doc.createElement( "script" );
script.text = code;
if ( node ) {
for ( i in preservedScriptAttributes ) {
if ( node[ i ] ) {
script[ i ] = node[ i ];
}
}
}
doc.head.appendChild( script ).parentNode.removeChild( script );
}
function toType( obj ) {
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
}
//定义对象
var preservedScriptAttributes = {
type: true,
src: true,
noModule: true
};
6.定义完上面的内容后,jQuery内部进行new对象,使得简化使用操作
var version = "3.3.1",
//在这里jquery通过new新生成了对象简化了使用时的操作
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
},
7.new新对象时,jQuery.fn是什么?
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
length: 0,
toArray: function() {
return slice.call( this );
},
get: function( num ) {
if ( num == null ) {
return slice.call( this );
}
return num < 0 ? this[ num + this.length ] : this[ num ];
},
pushStack: function( elems ) {
var ret = jQuery.merge( this.constructor(), elems );
ret.prevObject = this;
return ret;
},
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();
},
push: push,
sort: arr.sort,
splice: arr.splice
}
总结:看这部分源码可以知道jQuery.fn
就是jquery的原型对象
8.jQuery.fn.init( selector, context )
具体做了什么?
//selector 选择器,可能是DOM对象、html字符串、jQuery对象
//context 选择器选择的范围
//rootjQuery == $(document);
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// 没有传选择器直接返回
if ( !selector ) {
return this;
}
root = root || rootjQuery;
// 选择器传入的是字符串
if ( typeof selector === "string" ) {
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) {
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
if ( match && ( match[ 1 ] || !context ) ) {
// HANDLE: $(html) -> $(array)
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;
// 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 ( isFunction( selector ) ) {
return root.ready !== undefined ?
root.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
return jQuery.makeArray( selector, this );
};
整体看摸不着头脑,抽离结构如下:
init = jQuery.fn.init = function( selector, context, root ) {
if ( typeof selector === "string" ) {
//选择器类型是字符
}else if( selector.nodeType ){
//选择器类型是节点
}else if( jQuery.isFunction( selector ) ){
//简化$(document).ready(function(){});
}
//返回结果。
return jQuery.makeArray( selector, this );
}
抽离完了要想理解这些内容,首先来看看jquery到底支持哪些选择器selector?
1.$(document)
2.$('<div>')
3.$('.div')
4.$('#test')
5.$(function(){})
6.$("input:radio", document.forms[0]);
7.$('input', $('div'))
8.$()
9.$("<div>", { "class": "test" }).appendTo("body");
接着一个一个分支的看,它是如何支持这些选择器的,首先是typeof selector === "string"
if ( typeof selector === "string" ) {
//传入的是标签类型,比如<p>
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) {
// 将html储存入match数组中,并与另一个分支中的正则捕获相对应
match = [ null, selector, null ];
} else {
//放入正则进行匹配,结果类型是:[全匹配, <tag>, #id]
//rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/
//匹配HTML标记和ID表达式
match = rquickExpr.exec( selector );
}
// 如果match不为空,并且match[1]也就是<tag>存在
if ( match && ( match[ 1 ] || !context ) ) {
if ( match[ 1 ] ) {
// 如果context是jQuery对象,则取其中的第一个DOM元素作为context
context = context instanceof jQuery ? context[ 0 ] : context;
// 将通过parseHTML处理生成的DOM对象merge进jQuery对象
jQuery.merge( this, jQuery.parseHTML(
match[ 1 ],
//如果context存在并且是note节点,则context就是的顶级节点或自身,否则content=document
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
if ( isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
//如果是#id的形式,走这个分支进行处理
} else {
//通过getEle方法获得DOM对象 将match[2]传入,是因为#id的形式是在第二个捕获组里面储存的。
elem = document.getElementById( match[ 2 ] );
// 如果该id元素存在
if ( elem ) {
// 将该元素保存进jQuery对象数组当中,并设置其length值为1
this[ 0 ] = elem;
this.length = 1;
}
return this;
}
} else if ( !context || context.jquery ) {
//如果context不存在或者context是jQuery对象
//通过检测是不是有jquery属性
// 进入Sizzle进行处理(复杂的选择器)
return ( context || root ).find( selector );
} else {
//context存在并且context不是jQuery对象的情况 先调用$(context),在进入Sizzle进行处理
return this.constructor( context ).find( selector );
}
}
接着是selector.nodeType
分支
else if ( selector.nodeType ) {
//直接将DOM元素存入jQuery对象并设置context和length
this.context = this[0] = selector;
this.length = 1;
return this;
}
最后是jQuery.isFunction( selector )
分支
else if ( jQuery.isFunction( selector ) ) {
//简化$(document).ready(function(){});
return rootjQuery.ready( selector );
}
分析了以上的分支,把jquery的选择器分别带进去走一下流程,首先是`3.$('div')
首先进入:
if ( typeof selector === "string" ) {}
接着进入下面的if分支:
if ( match && (match[1] || !context) ) {
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
) );
}
}
它进入了一个函数parseHTML()
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 ) {
if ( support.createHTMLDocument ) {
context = document.implementation.createHTMLDocument( "" );
base = context.createElement( "base" );
base.href = document.location.href;
context.head.appendChild( base );
} else {
context = document;
}
}
//var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
//匹配一个独立的标签
parsed = rsingleTag.exec( data );
scripts = !keepScripts && [];
if ( parsed ) {
return [ context.createElement( parsed[ 1 ] ) ];
}
//未通过节点的字符串,则通过创建一个div节点,将字符串置入div的innerHTML
parsed = buildFragment( [ data ], context, scripts );
if ( scripts && scripts.length ) {
jQuery( scripts ).remove();
}
return jQuery.merge( [], parsed.childNodes );
};
最后返回this也就是jQuery
return this;
再看一下4.$("#id")
首先进入:
if ( typeof selector === "string" ) {}
接着进入下面的else分支进行正则匹配:
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
//不满足
}else{
match = rquickExpr.exec( selector );
}
匹配完了再接着进入下面的else分支进行寻找添加:
if ( match[1] ) {
//不满足
}else{
elem = document.getElementById( match[2] );
if ( elem ) {
// 将该元素保存进jQuery对象数组当中,并设置其length值为1
this.length = 1;
this[0] = elem;
}
}
最后返回this也就是jQuery
return this;
今儿先看到这!!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。