參考資料:饑人谷任務(wù)班任務(wù)42視頻哺眯,jQuery 2.0.3 源碼分析core - 整體架構(gòu)
$
調(diào)用方法的實(shí)現(xiàn)
jQuery 的每一個(gè)實(shí)例并沒有通過 new
來新建,而是使用 $
和 jQuery
來新增實(shí)例并直接進(jìn)行方法的調(diào)用蜕径,如:
$(".btn").on("click", function() {
console.log("do sth.");
});
jQuery.each(function() {
console.log("do sth.");
});
為使每次調(diào)用 $
或 jQuery
能夠創(chuàng)建實(shí)例,在構(gòu)建 jQuery 對(duì)象時(shí),返回值應(yīng)該是對(duì)象的實(shí)例哥倔。
下面嘗試以 rQuery 進(jìn)行重現(xiàn)史煎。
var rQuery = function(selector, context) {
//返回實(shí)例
}
rQuery.prototype = {};
如果直接使用 new
創(chuàng)建實(shí)例番甩,會(huì)產(chǎn)生死循環(huán):
var rQuery = function(selector, context) {
return new rQuery; //沒有盡頭的遞歸
}
rQuery.prototype = {};
因此,要想返回正確的實(shí)例溢豆,必須考慮其他的途徑币绩。由于 new
需要針對(duì)方法來執(zhí)行蜡秽,因此通過返回 rQuery.prototype
來創(chuàng)建實(shí)例也是不可行的。但我們可以在原型中新增一個(gè)方法缆镣,嘗試通過返回這個(gè)方法來創(chuàng)建實(shí)例:
var rQuery = function(selector, context) {
return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
init: function() {
return this; // this 指向?qū)嵗客唬簿褪?rQuery
}
};
不過,這種方法會(huì)帶來一個(gè)新的問題(如圖):所返回的實(shí)例和 rQuery 對(duì)象并沒有對(duì)應(yīng)的關(guān)系(淡粉色線)董瞻,新的實(shí)例不能調(diào)用 rQuery 中的方法寞蚌。
var rQuery = function(selector, context) {
return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
init: function() {
return this;
}
print: function() {
console.log(this);
}
};
rQuery.print(); // 報(bào)錯(cuò)
因此田巴,必須顯性地將 init
方法的原型定義為 rQuery 的原型(紅色線):
var rQuery = function(selector, context) {
return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
init: function() {
return this;
}
};
rQuery.prototype.init.prototype = rQuery.prototype;
這樣,每次使用 rQuery
時(shí)就會(huì)產(chǎn)生新的實(shí)例挟秤,并且可以正常使用 rQuery 原型中定義的方法壹哺。
定義 window.$ = window.rQuery = rQuery
即可使用 $
代替 rQuery
進(jìn)行操作。
鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)
在 jQuery 中艘刚,可以對(duì)方法進(jìn)行鏈?zhǔn)秸{(diào)用管宵。使用鏈?zhǔn)秸{(diào)用節(jié)約代碼、提高效率攀甚,簡(jiǎn)單優(yōu)雅箩朴,可謂 jQuery 的一大魅力。
$(this).removeClass("active").addClass("bye");
要能夠進(jìn)行鏈?zhǔn)秸{(diào)用秋度,則必須在第一個(gè)方法的調(diào)用之后返回對(duì)象本身:
var $tmp = $(this).removeClass("active"); //$tmp === $(this)
$tmp.addClass("bye"); //相當(dāng)于 $(this).addClass("bye")
因此炸庞,在定義可以使用鏈?zhǔn)秸{(diào)用的方法時(shí),需要添加 return this;
以保證鏈?zhǔn)秸{(diào)用能夠?qū)崿F(xiàn):
var rQuery = function(selector, context) {
return new rQuery.prototype.init(); //返回實(shí)例
}
rQuery.prototype = {
init: function() {
return this;
},
setName: function(name) {
this.name = name;
return this;
},
printName: function(name) {
console.log(this.name);
return this;
}
};
rQuery.prototype.init.prototype = rQuery.prototype;
rQuery().setName("Hanna").printName(); //Hanna
插件接口的定義與實(shí)現(xiàn)
同其他庫(kù)一樣荚斯,jQuery 提供可以讓開發(fā)者添加擴(kuò)展的接口埠居。開發(fā)者可以通過 jQuery.fn.extend()
來向 jQuery 中添加新的方法。
jQuery 中事期,同時(shí)存在 jQuery.extend()
和 jQuery.fn.extend()
兩種方法滥壕,從源碼中可以發(fā)現(xiàn),二者實(shí)際上是指向同一個(gè)方法的不同引用:
jQuery.extend = jQuery.fn.extend = function() {
//具體實(shí)現(xiàn)
}
這樣做的用意是:通過 jQuery.extend()
實(shí)現(xiàn)對(duì) jQuery 本身的屬性和方法的擴(kuò)展刑赶;而通過 jQuery.fn.extend()
來實(shí)現(xiàn)對(duì) jQuery.fn捏浊,即 jQuery 的原型的屬性和方法進(jìn)行擴(kuò)展。同時(shí)撞叨,兩種擴(kuò)展都不會(huì)破壞 jQuery 原有的結(jié)構(gòu)金踪。
之所以在使用 jQuery.extend = jQuery.fn.extend
的前提下仍能夠區(qū)別出 jQuery 和 jQuery.fn,是因?yàn)閷?duì) this
的運(yùn)用牵敷。 this
指向的是調(diào)用方法的對(duì)象胡岔。在 jQuery.extend()
中,調(diào)用 extend()
方法的是 jQuery
枷餐,所以 this
指向 jQuery
靶瘸,而在 jQuery.fn.extend()
中,調(diào)用方法的則是 jQuery.fn
毛肋,因此這里 this
指向了 jQuery.fn
怨咪,從而成功地區(qū)分出了兩種不同的擴(kuò)展方式。
之前曾參考《使用jquery實(shí)現(xiàn)簡(jiǎn)單的拖動(dòng)效果》實(shí)現(xiàn)了一個(gè)拖拽效果润匙,拿到這里來試一下 extend()
方法的使用(在線DEMO):
//html
<div class="panel">This div is draggable</div>
//javascript
//drag()
var drag = function() {
$(this).each(function() {
var me = $(this);
var isMoved = false;
var rX, rY;
me.on("mousedown", function(event) {
isMoved = true;
rX = event.pageX - parseInt(me.css("left"));
rY = event.pageY - parseInt(me.css("top"));
me.css("cursor", "move");
return false; //解決拖拽導(dǎo)致的文字選中問題
});
$(document).on("mousemove", function(event) {
if (isMoved) {
var eX = event.pageX;
var eY = event.pageY;
var wX = $(window).width();
var wY = $(window).height();
var thisX = me.width();
var thisY = me.height();
var thisMarginLeft = parseInt(me.css("margin-left"));
var nowX = eX - rX;
var nowY = eY - rY;
var left, top;
var thisLeft = (thisMarginLeft > 0) ? 0 : (-thisMarginLeft);
if (nowX < thisLeft) {
left = thisLeft;
} else if (nowX > wX - thisX + thisLeft) {
left = wX - thisX + thisLeft;
} else {
left = nowX;
}
if (nowY < 0) {
top = 0;
} else if (nowY > wY - thisY) {
top = wY - thisY;
} else {
top = nowY;
}
me.css({
"left": left,
"top": top
});
}
}).on("mouseup", function() {
isMoved = false;
me.css("cursor", "auto");
});
});
};
// 添加到 jQuery 中
jQuery.fn.extend({drag: drag});
// 調(diào)用
$(".panel").drag();
可以看出诗眨,drag()
方法已成功添加到 jQuery 當(dāng)中,可以用 $(".panel").drag()
進(jìn)行調(diào)用孕讳,并實(shí)現(xiàn)拖拽功能匠楚。
以 drag()
為例巍膘,來看 extend()
方法的實(shí)現(xiàn):
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {}, // 傳入的參數(shù),以第一個(gè)參數(shù)為目標(biāo)對(duì)象(這里是 {drag: drag})
i = 1,
length = arguments.length,
deep = false; // 深拷貝的參數(shù)
// Handle a deep copy situation
if ( typeof target === "boolean" ) { // 如果第一個(gè)參數(shù)是 true/false芋簿,通常為 true
deep = target; //deep 為 true/false
target = arguments[1] || {}; // 將第二個(gè)參數(shù)作為目標(biāo)對(duì)象
// skip the boolean and the target
i = 2; // 跳過前兩個(gè)參數(shù)峡懈,從第三個(gè)開始
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) { // 如果作為目標(biāo)對(duì)象的參數(shù)不是對(duì)象類型
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) { // 如果只有一個(gè)參數(shù)
target = this; // 就將 jQuery(.fn) 自身作為目標(biāo)對(duì)象(這里,因?yàn)橹粋魅肓?{drag: drag}与斤,因此目標(biāo)對(duì)象為 jQuery(.fn)肪康,也就實(shí)現(xiàn)了向 jQuery 中添加對(duì)象(方法))
--i; // i=0
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) { // 這里 options = {drag: drag}
// Extend the base object
for ( name in options ) {
src = target[ name ]; // 向目標(biāo)對(duì)象(這里是 jQuery(.fn)) 中添加屬性,并將屬性值賦予變量 src幽告,如果目標(biāo)對(duì)象中沒有該屬性梅鹦,那么此時(shí) src = undefined(這里 src = undefined)
copy = options[ name ]; // copy = drag (drag方法)
// Prevent never-ending loop
if ( target === copy ) { // 防止自引用 (這里防止將 jQuery(.fn) 添加到 jQuery(.fn) 中)
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
// 如果是深拷貝(deep),而且要拷貝的屬性值為對(duì)象(這里deep = false冗锁,不做深拷貝)
if ( copyIsArray ) { // 如果要拷貝的屬性值是數(shù)組
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else { // 要拷貝的屬性值為普通對(duì)象
clone = src && jQuery.isPlainObject(src) ? src : {}; // clone 為目標(biāo)對(duì)象中的屬性值或空對(duì)象
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy ); // 遞歸
// Don't bring in undefined values
} else if ( copy !== undefined ) { // 這里 drag() 非 undefined
target[ name ] = copy; // 將屬性添加到目標(biāo)對(duì)象中(這里, jQuery(.fn)[drag] = drag)
}
}
}
}
// Return the modified object
return target; // 返回目標(biāo)對(duì)象(這里返回的是添加了 drag() 方法的 jQuery(.fn))
};