小总结
看源码的时候要注意:看清楚变量代表的是什么,变量代表的是它里面存储的值,而不是变量本身,这一点不要混淆,别看错了,尤其里面会用一些关键词作为变量名,弄清楚变量里面存的是什么,它代表的就是谁。看到了不知道的变量就往前或者往后在附近找一找,或者直接用查找功能搜一搜,找到它里面的源码到底存的是什么。
jQuery里面用到了很多 js 的思想和原生的方法属性,比如闭包的思想,面向对象的思想,数组的方法,还有一些数据类型转化的小技巧,数据类型参与判断的时候的小技巧等等,还有封装一个方法的时候要考虑的点有很多,一定要严谨,可以在源码里面学到很多自己封装一个方法的时候需要注意的事情,需要考虑的方面有哪些。
下面是部分源码解读
判断执行环境
1、区分浏览器环境 和 node 环境
2、如何判断是否支持CommonJS规范
3、jQuery放在闭包里面,可以利用 return 或者 window. 的方式把里面的方法暴露给外面,来供外面调用里面的这些方法
<script>
(function (global, factory) {
// global--> window factory--> 函数
if (typeof module === "object" && typeof module.exports === "object") {
// 支持Common.js规范,(比如在node.js里运行)
// 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); // 实参变量global的值为 window
}
}(typeof window !== "undefined" ? window : this,function(window, noGlobal){
// window --> window noGlobal -->undefined
}))
</script>
普通函数执行就是创建实例
4、创造的是init这个类的一个实例,代码中让init.prototype=jQuery.prototype:所以最后创造的实例基于____proto____ 查找的时候,找的也是jQuery.prototype原型上的方法,所以我们也可以认为创造的是jQuery这个类的一个实例
目的是: 控制jQuery执行的时候当做普通的函数执行,但是能创造属于自己的一个实例
JQ的选择器: $()其实就是创建了JQ类的一个实例(“JQ对象”)
注意:
-
源码中的jQuery.fn 就是代表的jQuery的原型了
-
jQuery和() 就是jQuery执行,而且相当于构造函数执行,创建了一个jQuery实例对象
-
this是原生js的对象,在jQuery中要使用 $(this) 才是jQuery对象
jQuery充分体现了函数的三种角色:普通函数、构造函数、普通对象
1. 写在原型上的方法是供实例“JQ对象”调用的 $('.box').xxx()
2. 写在jQuery对象上的私有属性方法 $.xxx() =>一般提供一些工具类方法,供项目开发或者JQ内部调用的
下面是源码:
var jQuery = function (selector, context) {
// 这块其实是new 了一个init的实例返回出去,这样写的目的是为了避免死递归
return new jQuery.fn.init(selector, context);
};
jQuery.fn = jQuery.prototype = {
// 给jQuery的原型进行重定向,而且还给jQuery新增一个私有属性,其属性值也是当前这个新的原型
jquery: version,
constructor: jQuery, // 重定向之后的原型没有constructor,所以人家给手动加一个
//此处省略了一些代码,后面详细讲这里的一些方法,比如get、each方法,还有一些原生js的方法
}
var init = jQuery.fn.init = function( selector, context ) {
// 次处的类才是$()运行的真正的类
}
init.prototype = jQuery.fn;
// 把init类的原型指向了jQuery的原型
if (typeof noGlobal === "undefined") {
window.jQuery = window.$ = jQuery;
// 给window增加两个键值对,属性名是jQuery和$,他们的属性值都是当前的jQuery函数
}
}))
可以自己做一些尝试:
// $('#tabList li'); // 他就是jQ的实例
// jQuery(('#tabList li')
// $()但返回值其实是init的实例
// $(this)
// $('<div></div>')
// $('#tabList li').get()
var jQuery = function(){
return new jQuery();
}
$():jQuery选择器、入口函数、对象转化
5、总结:jQuery执行时(创建实例)传的参数
-
参数传选择器的情况
-
参数传元素节点的情况
-
参数传函数的情况 --> 入口函数
-
jQuery元素对象和原生js的元素对象之间可以相互转化,把jQuery对象转化为原生js对象只需要调用get方法或者后面加一个 中括号0 即可
基于$执行所创建出来的东西都是jq的实例,所以可以去调用jq原型上的方法
1、不管传参还是不传参,返回值都是一个对象类型的实例
$() ==>如果jq不穿参,那返回结果是一个空实例
2、$执行时可以传三种不同的格式
1)
$('.box') jq的选择器
$('<div>123</div>')
2)
$(原生的元素):把原生的元素变成jq的实例(只有这样草可以去调用jq原型上的方法)
原生js中的方法jq中的方法不能混着去使用
3) $(函数) 入口函数
$(function(){}); <==> $(document).ready(function(){})
//这俩是等价的
get方法的源码:
// 把jq的实例转成原生的数组集合
get: function (num) {
return num != null ?
// Return just the one element from the set
(num < 0 ? this[num + this.length] : this[num]) :
// Return all the elements in a clean array
slice.call(this);
}
注意: jQuery中的入口函数和原生js中的 window.onload 的区别
window.onload = function(){}
// 等到页面的dom,内容,富媒体全部加载完成之后才会执行
$(function(){}); ==>// $(document).ready(function(){})
// 而ready等到页面的dom加载完就执行
rootjQuery.ready( selector )
console.log( $(function(){}));
下面是$()的源码,有多种情况:
var rootjQuery,
// Use the correct document accordingly with window argument (sandbox)
document = window.document,
// A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
init = jQuery.fn.init = function( selector, context ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
}
// Handle HTML strings
if ( typeof selector === "string" ) {
// $('<div/>123</div>')
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
console.log(match);
} else {
match = rquickExpr.exec( selector );
}
// 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;
console.log(context);
// 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 ( jQuery.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] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] ) {
return rootjQuery.find( selector );
}
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).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.context = this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return typeof rootjQuery.ready !== "undefined" ?
rootjQuery.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
};
// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;
// Initialize central reference
rootjQuery = jQuery( document );
noConflict避免冲突
6、noConflict方法:用来转让 和 jQuery 这两个变量的使用权转让回原来他们代表的东西,然后自己定义一个变量来接收noConflict这个方法执行的返回结果,也就可以让这个变量来代表jQuery的方法了。
比如已经引了一些插件或者类库进来,而这些插件或者类库里面用了或jQuery。
提供一个noConflict方法,防止污染覆盖window中原始的 和 jQuery 还原回到原来代表的东西,比如zepto,可以自己定义一个变量,来代表jQuery,然后使用这个变量来调用jQuery的方法也可以
noConflict源码:
var
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$;
jQuery.noConflict = function (deep) {
if (window.$ === jQuery) {
window.$ = _$;
}
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
return jQuery;
};
// 如果一个页面引用的两个类库,但是都给window赋值一个$属性,人家jq为了避免这样的情况出生,在jq的身上有一个noConflict方法,是专门用来改变$和jQuery的使用权的
// console.log($); // 这是JQuery函数
let res = $.noConflict(true);
// 如果函数执行时不穿参,那仅仅是把$ 的使用权给别人,但是如果传递一个true,那就是把jQuery的使用权也给别人,
// 如果把使用权都给别人的话,那咱们只能使用当前函数的返回值了
console.log($); // jq把$的使用权给别人了
console.log(res);
console.log(jQuery);
可以自己测试一下前面提过的方法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div id="box">5</div>
<script src="./jquery-1.11.3.js"></script>
<script>
// console.log($('div').get());
// console.log($.each());
// var ss = $.noConflict();
// window.$ = 'xxx';
// console.log($());
// console.log($('<div></div>'));
// console.log($(box));
</script>
</body>
</html>
7、类数组转数组的方法:toArray
toArray: function () { // 类数组转数组
return slice.call(this);
}
原生方法不多做描述
push: push,
sort: deletedIds.sort,
splice: deletedIds.splice
// .....
// 这三个方法都是js原生的方法
功能强大的each方法
8、功能强大的each方法:循环遍历,在jQuery本身和在原型上都有each方法
//在jQuery本身 和 在原型上 都有each方法,但是作用不一样
//在原型上的是供jQuery实例调用的
//在本身的是 直接调用 可以遍历数组或者对象
// ==》 遍历dom (遍历jQuery对象)
$("ul>li").each(function(i,item){
console.log(i);
console.log(item);
})
var ary=[4,5,6];
var obj={"name":"lili","age":18}
//===>遍历数据、数组
$.each(ary,function(i,item){
console.log(i);
console.log(item);
})
//===>遍历对象 for in
$.each(obj,function(i,item){
console.log(i); // 属性名
console.log(item); // 属性值
})
与原生js中的each遍历有很多不同
//jQuery中的each方法还可以自己设置中断遍历的位置
//在源码中可以看到 value === false的时候,遍历就中止了
//所以我们在使用each方法的时候,在回调函数中,可以设置一个判断,
//符合某些条件的时候 return false 中止遍历
下面是each的源码:
//jQuery原型上的each源码
each: function (callback, args) {
return jQuery.each(this, callback, args);
}
//jQuery本身身上的each方法源码
each: function( obj, callback, args ) {
var value,
i = 0,
length = obj.length,
isArray = isArraylike( obj );
if ( args ) {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
}
return obj;
}
给jQuery扩展方法
9、extend方法,用于扩展方法
$.extend({
// 注意如果你传的属性名和jq的重名了,一般情况你是可以覆盖人家jq的,但是你不想把人家jq的方法进行覆盖,那就在extend执行的时候收个参数传一个false
queryUrlParams:function(){
},
ajax:function(){
console.log(23);
}
});
console.dir($.ajax())
注意: jQuery.extend 和 jQuery.fn.extend 不一样 一个是给jQuery本身扩展方法 一个是给原型扩展方法
语法:
$.extend( target [, object1 ] [, objectN ] )
指示是否深度合并
$.extend( [deep ], target, object1 [, objectN ] )
下面是extend源码:
jQuery.extend = jQuery.fn.extend = function() { // true {}
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
源码中常见的pushStack
该函数用于创建一个新的jQuery对象,然后将一个DOM元素集合加入到jQuery栈中,最后返回该jQuery对象,有三个参数,如下:
elems Array类型 将要压入 jQuery 栈的数组元素,用于生成一个新的 jQuery 对象
name 可选。 String类型 生成数组元素的 jQuery 方法名
selector 可选。 Array类型 传递给 Query 方法的参数(用于序列化)
参数2和参数3可选的,用于设置返回的新的jQuery对象的selector属性
调用pushStack后之前的jQuery对象内的DOM引用是不会消失的,还保存到新的对象的prevObject里,我们可以通过end()来获取之前的jQuery对象
下面是源码:
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
}
jQuery源码分页注释
盗的图↓↓↓