jQuery
源码分析(一):Core.js
部分
总体架构
Core
即核心,jQuery
的核心部分主要做了这么几件事
- 通过
require.js
加载了var中的各个模块,获取了关于数组和对象的各种方法,比如slice
,concat
,push
等,这样做使得jQuery对象的各类方法解耦,在模块中提供统一的接口,抹平浏览器之间的差异 - 定义一个jQuery函数并作为模块最后的返回值,这个jQuery函数调用时会调用
jQuery.fn.init
(这个init
函数会在/core/init.js
中实现) - 添加一些数组和对象方法绑定到
jQuery
的prototype
上。其中如map
,forEach
等方法会调用jQuery实例上的一些方法。这些实例上的方法会在第五条通过在后面的代码中调用jQuery.extend
来添加 - 定义
jQuery.extend
, 主要是为了方便外部使用jQuery.extend
来扩展jQuery,至于合并多个对象,这个功能是送的 - 调用
jQuery.extend
,将require.js
加载的数组方法和对象添加到jQuery
的实例上
代码部分
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module
define([
"./var/arr", // 对js原生数组的引用
"./var/document", // 对document对象的引用
"./var/getProto", // 对Object.getPrototypeOf的引用
"./var/slice", // 对Array.prototype.slice的引用
"./var/concat", // 对Array.prototype.concat的引用
"./var/push", // 对Array.prototype.push的引用
"./var/indexOf", // 对Array.prototype.indexOf的引用
"./var/class2type", // 一个对象(字典),里面有类到类名的映射
"./var/toString" // Object.prototype.toString的引用
"./var/hasOwn", // Object.prototype.hasOwnProperty的引用
"./var/fnToString", // Function.prototype.toString的引用
"./var/ObjectFunctionString", // Object构造函数调用toString()的结果。即`Object.toString()`
"./var/support", // 记录浏览器是否支持某种特性的一个对象,jQuery内部没有用到这个变量,但可能会有扩展jQuery的库需要他
"./var/isFunction", // 判断是否是函数(某些浏览器中会出现`typeof document.createElement( "object" ) === "function`)的情况,因此做了兼容
"./var/isWindow", // 判断是否是window对象
"./core/DOMEval", // 这个`eval`的实现是把代码挂载到`<script>`标签中执行后删除`script`标签
"./core/toType" // 获得对象的类型
], function (arr, document, getProto, slice, concat, push, indexOf,
class2type, toString, hasOwn, fnToString, ObjectFunctionString,
support, isFunction, isWindow, DOMEval, toType) {
"use strict";
var
version = "3.3.1",
// Define a local copy of jQuery
// 定义jQuery函数
jQuery = function (selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init(selector, context);
},
// Support: Android <=4.0 only
// Make sure we trim BOM and NBSP
// 移除空格需要的正则表达式
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: version,
// 构造函数
constructor: jQuery,
// The default length of a jQuery object is 0
length: 0,
// 把jQuery选择到的元素转化成数组
toArray: function () {
return slice.call(this);
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
// 按照索引获取`jQuery`选择到的元素
get: function (num) {
// Return all the elements in a clean array
if (num == null) {
return slice.call(this);
}
// Return just the one element from the set
return num < 0 ? this[num + this.length] : this[num];
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
// 返回一个新的元素加入到`jQuery`选择器选出的数组中的jQuery实例,他的`prevObject`指向merge之前的实例
pushStack: function (elems) {
// Build a new jQuery matched element set
// 此处调用了`jQuery.merge`合并两个类数组的对象
var ret = jQuery.merge(this.constructor(), elems);
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
// each方法,会在下面通过`jQuery.extend`被加入到jQuery的实例上
each: function (callback) {
return jQuery.each(this, callback);
},
// 通过调用上面的pushStack方法将map后的数组重新组成一个jQUery的对象
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);
},
// 返回某个index的元素,index为负数就从末尾往前找
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();
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
// 数组方法
push: push,
sort: arr.sort,
splice: arr.splice
};
jQuery.extend = jQuery.fn.extend = function () {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
// 正常情况下从为source `arguments[1]`到`arguments[length - 1]`
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
// Skip the boolean and the target
// 这种情况下从arguments[2]到arguments[length -1]为source
target = arguments[i] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
// 因为有那种类Object的Function,即绑定了字段的Function所以要排除这种情况
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
// Extend jQuery itself if only one argument is passed
// 第一个参数不为boolean
if (i === length) {
target = this;
// 这种情况下为arguments[0]到argument[length - 1]为source,this为target
i--;
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null) {
// Extend the base object
// 如果传入的是数组,name就为Number
for (name in options) {
src = target[name]; // Target的[name]字段
copy = options[name]; // source的[name]字段
// Prevent never-ending loop
// 两者相等直接跳过
if (target === copy) {
continue;
}
// Recurse if we're merging plain objects or arrays
// 如果是深拷贝且这个字段非nullish 且需要拷贝的字段类型是数组或plain object
if (deep && copy && (jQuery.isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) { // 需要拷贝的字段是数组
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else { // 需要拷贝的字段是对象
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
// 递归深拷贝这个数组或plain object
target[name] = jQuery.extend(deep, clone, copy);
// Don't bring in undefined values
} else if (copy !== undefined) { // 浅拷贝,过滤掉undefined的值
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.extend({
// Unique for each copy of jQuery on the page
expando: "jQuery" + (version + Math.random()).replace(/\D/g, ""),
// Assume jQuery is ready without the ready module
// jQuery的所有module是否都加载完毕的标志
isReady: true,
error: function (msg) {
throw new Error(msg);
},
// 空函数
noop: function () { },
// 判断是否是Plain object
isPlainObject: function (obj) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
// 所有的PlainObject调用toString后都会返回`[object, object]`
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}
proto = getProto(obj);
// Objects with no prototype (e.g., `Object.create( null )`) are plain
// `Object.create(null)`返回的对象没有prototype
if (!proto) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
// 通过比较构造函数的toString方法来看obj的原型是否是Object
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
},
// 判断是否为空对象
isEmptyObject: function (obj) {
/* eslint-disable no-unused-vars */
// See https://github.com/eslint/eslint/issues/6125
var name;
for (name in obj) {
return false;
}
return true;
},
// DOMEval的引用拷贝
// Evaluates a script in a global context
globalEval: function (code) {
DOMEval(code);
},
// array.each函数绑定在jQuery对象上,上面的prototype通过调用它并传入this来实现`$('foo').each`
each: function (obj, callback) {
var length, i = 0;
if (isArrayLike(obj)) {// 类数组
length = obj.length;
for (; i < length; i++) {
// jQuery.each的callback的this指向当前循环的对象
if (callback.call(obj[i], i, obj[i]) === false) {
break;
}
}
} else { // 相当于obj.values()
for (i in obj) {
if (callback.call(obj[i], i, obj[i]) === false) {
break;
}
}
}
return obj;
},
// Support: Android <=4.0 only
// 兼容安卓4.0的trim
trim: function (text) {
return text == null ?
"" :
(text + "").replace(rtrim, "");
},
// results is for internal usage only
makeArray: function (arr, results) {
var ret = results || [];
if (arr != null) {
if (isArrayLike(Object(arr))) {
jQuery.merge(ret,
typeof arr === "string" ?
[arr] : arr
);
} else {
push.call(ret, arr);
}
}
return ret;
},
inArray: function (elem, arr, i) {
return arr == null ? -1 : indexOf.call(arr, elem, i);
},
// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
// 合并2个数组
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;
},
// 过滤数组
grep: function (elems, callback, invert) {
var callbackInverse,
matches = [],
i = 0,
length = elems.length,
callbackExpect = !invert;
// Go through the array, only saving the items
// that pass the validator function
for (; i < length; i++) {
callbackInverse = !callback(elems[i], i);
// 若`invert === false,`callbackExcept`true`callback`返回`true`的元素会被加入到数组
// 若`invert === true`callbackExcept`为`false`,`callback`返回`false`的元素会被加入到数组
if (callbackInverse !== callbackExpect) {
matches.push(elems[i]);
}
}
return matches;
},
// arg is for internal usage only
// map方法,`jQuery.prototype.map`通过调用这个来实现map
map: function (elems, callback, arg) {
var length, value,
i = 0,
ret = [];
// Go through the array, translating each of the items to their new values
if (isArrayLike(elems)) {
length = elems.length;
for (; i < length; i++) {
value = callback(elems[i], i, arg);
if (value != null) {
ret.push(value);
}
}
// Go through every key on the object,
// 对象`map`的情况,`i`为对象的k`ey`
} else {
for (i in elems) {
value = callback(elems[i], i, arg);
if (value != null) {
ret.push(value);
}
}
}
// Flatten any nested arrays
return concat.apply([], ret);
},
// A global GUID counter for objects
// 全局`guid`代表`jQuery`对象
guid: 1,
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
});
// 如果有es6的`Symbol`将`Symbol`部署到`jQuery.fn`上。这样就可以使用for和数组扩展等符号
if (typeof Symbol === "function") {
jQuery.fn[Symbol.iterator] = arr[Symbol.iterator];
}
// Populate the class2type map
// 填充class2Type 的字典
jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),
function (i, name) {
// `Object.prototype.toString.call(foo)`会返回`[object ${typeof foo}]`
// toType中通过调用`Object.prototype.toString.call`来获得type的key
class2type["[object " + name + "]"] = name.toLowerCase();
});
// 判断是否是类数组的对象
function isArrayLike(obj) {
// Support: real iOS 8.2 only (not reproducible in simulator)
// `in` check used to prevent JIT error (gh-2145)
// hasOwn isn't used here due to false negatives
// regarding Nodelist length in IE
// 使用`in`检查属性是否存在来避免iOS8.2的JIT错误
// IE下host object(`window`,` Nodelist`, `location`)等没有hasOwnProperty方法
var length = !!obj && "length" in obj && obj.length,
type = toType(obj);
if (isFunction(obj) || isWindow(obj)) {
return false;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
}
return jQuery;
});