$
挂载。 引入jQuery后,在使用jQuery时,一般都采用$([selector])的普通函数执行的方式,但返回的却是一个实例对象,一般new执行构造函数才会返回实例对象,此处jQuery()函数的封装采用了工厂模式制造对象
(function(){
"use strict";
// =============================抽取一些方法,取快捷名字===========================
var arr = [];
var slice = arr.slice;
var push = arr.push;
var indexOf = arr.indexOf;
// =============================jQ构造函数声明,并处理其原型对象===========================
var
version = "3.5.1",
jQuery = function( selector, context ) {
/**
* selector是css选择器,context为限定在哪个DOM容器中查找(非必须)
* $(xx)返回原型上init构造函数的new执行的创造的实例
*/
return new jQuery.fn.init( selector, context );
};
// 取别名,fn是给prototype起的一个别名,并进行原型重定向
jQuery.fn = jQuery.prototype = {...}
// ========================================init构造函数========================================
var
// root默认为rootjQuery
rootjQuery,
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
/**
* && init函数的内部实现
* 1. init函数挂载到jQuery原型上
* 2. 返回init的实例对象也是jq的实例对象。init原型重定向保证$()执行后jq实例对象
* 3. 用户使用方便 $(...)/new $(...),工厂模式使用:普通函数执行返回类,而非用户自己new
* 4. jq实例对象是一个类数组集合,实例私有属性为索引和length。jq[数字]可以获取原生DOM对象
*/
init = jQuery.fn.init = function( selector, context, root ) {
root = root || rootjQuery;
// 不传选择器,返回空jq实例对象
if ( !selector ) {
return this;
}
// *********根据不同类型返回jq,支持三种类型的selector,小型解析器*********
var match,elm;
// selector是string类型
if ( typeof selector === "string" ) {
// 适配$("<xxx>...</xxx>")html字符模板
if ( selector[ 0 ] === "<" &&
selector[ selector.length - 1 ] === ">" &&
selector.length >= 3 ) {
match = [ null, selector, null ];
}
// 适配正常的选择器(eg:'.box','#app' ...)
else {
match = rquickExpr.exec( selector );
// 解析具体string
// 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 ( 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 ( ele
// 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 );
}
}
// selector是DOM对象类型,返回的对象做成类数组形式
else if(selector.nodeType) {
// 将此个原生DOM添加为这个返回的jq对象的一个属性 {0: selector}
this[ 0 ] = selector;
// length属性添加不要忘了
this.length = 1;
// 返回jq对象(new执行显式return的是对象,这个对象是一个类数组)。
// 返回的jq对象上可以找到这个原生DOM,这样就可以使用jq原型方法
return this;
}
// selector是函数类型,返回ready执行的结果
else if(isFunction(selector)) {
// ready函数监听DOMContentLoaded事件,DOM加载完,再执行selector函数
// 执行给ready(fn)传入的回调函数fn 或 直接selector执行
// $(function() {}) --等价于--> $(document).ready(function() {})
return root.ready !== undefined ?
root.ready( selector ) : selector( jQuery );
}
// selector不是以上三种,创建一个jq类数组并返回。eg:nodeList集合 --> jq集合
return jQuery.makeArray(selector, this)
}
// 将init函数的原型重定向为jQuery的原型,这样new init 的实例可以使用jQuery原型上的属性方法
init.prototype = jQuery.fn;
rootjQuery = jQuery( document );
...
// ========================================挂载=========================================
window.jQuery = window.$ = jQuery;
})()
实例方法
通过$('xxx').xxx使用(示例)
jQuery.fn = jQuery.prototype = {
jquery: version,
// 原型重定向补上constructor
constructor : jQuery,
length: 0
// 追加一些数组方法到原型上,jq实例可以用(实例想借用别人类的方法可以将其方法追加到原型对象上)
push: push,
sort: arr.sort,
splice: arr.splice,
// jq.get(传数字) 获取指定索引的原生DOM对象
get: function( num ) {
if ( num == null ) {
// 没指定索引,返回jq类数组上所有的DOM集合的数组
return slice.call( this );
}
// 支持正负索引
return num < 0 ? this[ num + this.length ] : this[ num ];
},
// jq.eq(传数字) 查找jq对象中的某一特定项,并返回新的jq对象。
eq: function( i ) {
var len = this.length,
// 支持负数索引
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
},
pushStack: function( elems ) {
// this.constructor()返回一个空的jq对象,merge使得elems中的所有项追加到空jq中
var ret = jQuery.merge( this.constructor(), elems );
// prevObject记录原始操作,链式调用后会返回新的指向,可以通过它回到原始jq对象
ret.prevObject = this;
// 返回合并好的jq
return ret;
},
// 将second类数组追加到first中,并返回first
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;
},
// ready方法
jQuery.fn.ready = function( fn ) {
// readyList机制类似于promise。监听DOMCOntentLoaded,等待DOM结构加载完成后,执行fn
readyList
.then( fn )
.catch( function( error ) {
jQuery.readyException( error );
});
// 返回$(document)
return this;
};
}
函数对象方法
通过$.xxx()使用(实例)
/**
* & 扩展jQuery方法
* 当用户需要自定义一些个性化方法,或开发jQuery插件完善jq类库,都需要通过$.extend()方法完成。
* 此方法jQuery函数对象或jQuery实例都能调用
*/
// 同时挂载到jQuery和jQuery原型上
jQuery.extend = jQuery.fn.extend = function () {
var
options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
//$.extend({...}):仅传入一个对象 ,target = 这个对象,deep = false
// $.extend([boolean], {...}):传入第一项为布尔,第二项为对象,同上,但deep = true
if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
}
// 保证target是一个对象
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
// 不论传几项参数,最终把target转让为调用extend方法的人,$或$.fn
if (i === length) {
target = this;
i--;
}
// 仅循环操作一次
for (; i < length; i++) {
// 拿到参数传入的对象,若不为空则操作
if ((options = arguments[i]) != null) {
for (name in options) {
// 克隆遍历每一个键值对:{name: copy}
copy = options[name];
// 克隆防止无限递归
if (name === "__proto__" || target === copy) {
continue;
}
// deep为true,(copy存在且是一个普通对象) 或者 (copy是数组)
if (deep && copy && (jQuery.isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
// 获取jQ函数/jQ原型上原始的name对应的属性值
src = target[name];
// 若传入的copy是数组,但原始name的属性值不是数组
if (copyIsArray && !Array.isArray(src)) {
clone = [];
}
// 若传入的copy是不是数组,原始name的属性值不是对象
else if (!copyIsArray && !jQuery.isPlainObject(src)) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
// 深克隆每一项
target[name] = jQuery.extend(deep, clone, copy);
}
// deep不为true,给jQ函数/jQ原型扩展键值对,若重名也直接替换
else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// 返回处理后的jQ函数/jQ原型
return target;
};
/**
* & 遍历方法:类似于数组中的forEach,用于遍历普通对象、类数组、数组
* 采用上面的extend方法将遍历方法扩展到jQ函数对象上
* 两份:一份同extend挂到jQuery对象上,一份挂到原型上
*/
jQuery.fn = jQuery.prototype = {
...
// $([selector]).each(()=>{}) 实例调用
each: function( callback ) {
return jQuery.each( this, callback );
}
}
jQuery.extend({
...
// $.each($([selector]), function(key/index, value){}) 静态调用
each: function (obj, callback) {
var length, i = 0;
typeof callback === 'function' ? callback = Funtion.prototype : null;
// obj是类数组/数组
if ( isArrayLike( obj ) ) {
length = obj.length;
for ( ; i < length; i++ ) {
// 每遍历一项,回调函数执行,this指向当前数组/类数组项
// i -> index,value -> value
// 支持回调函数返回值,一旦返回false,则终止遍历
if ( callback.call( obj[ i ], obj[ i ], i ) === false ) {
break;
}
}
}
// obj是普通对象,同上
else {
// for ( i in obj ) {
// 此处可能会遍历到所属类中自己扩展的属性方法将会被遍历出
// if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
// break;
// }
// }
// 仅获得私有的所有属性
var keys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
]
for(; i <= keys.length-1; i++) {
var key = keys[i],
value = obj[key];
if(callback.call( value, key, value ) === false) {
break;
}
}
}
return obj;
}
})