title | date |
---|---|
03-基础结构 |
2017/02/21 14:47:10 |
在 jQuery
中,我们一般都是用 $('body')
,实际上 new $('body')
也是可以用的。这是如何实现的呢?
要能使用 new
,那 jQuery
一定是一个 function
,又要让直接调用也返回一个实例,那我们就可以考虑返回另外一个全新 function
的实例。
var jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context);
};
通过以上这段代码,不管是用 new jQuery()
还是 jQuery()
返回的都是 init
方法的实例。
在定义 jQuery
这个方法的时候,实际上 fn
和 init
都还不存在。
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,
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
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)
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;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
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();
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: push, // push = [].push
sort: arr.sort, // arr = []
splice: arr.splice // arr = []
};
通过以上方法指定了 jQuery
的原型对象,也可以看到它默认的原型方法。
看到了 jQuery
的构造和 jQuery.prototype
的申明,可能会有这样一个疑惑,在 jQuery()
中返回的明明是 jQuery.fn.init
的实例,为什么可以使用 jQuery.protptype
呢?
我们先看看 init
函数的实现:
var init = jQuery.fn.init = function (selector, context, root) {
var match, elem;
// HANDLE: $(""), $(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") {
if (selector[0] === "<" &&
selector[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];
} 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;
// 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 (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]);
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 (jQuery.isFunction(selector)) {
return root.ready !== undefined ?
root.ready(selector) :
// Execute immediately if ready is not present
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
这个不多说,就是根据选择器查找元素。接下来,关键来了:
// 把jQuery.fn设置为init的原型
init.prototype = jQuery.fn;
// Initialize central reference
// 从document开始查找
rootjQuery = jQuery(document);
有了以上的基础,那么就该扩展 jQuery
的功能了。它的核心就是以下的扩展函数:
jQuery.extend = jQuery.fn.extend = function () {
var options, name, src, copy, copyIsArray, 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;
};
为什么同一个 extend
函数可以扩展 jQuery
,也可以扩展 jQuery.fn
?这就涉及到另外一个JS的知识点了。
函数中this的指向是谁调用谁就是this。
以上就是 jQuery
的核心基础结构,如果我们自己要编写一个 Lib or Framework
,也可以参考这样的方式来实现。
之后所有的功能,都是从这里进行扩展。