jQuery 2.0.3 源碼分析之core(一)整體架構(gòu)
拜讀一個開源框架,最想學(xué)到的就是設(shè)計的思想和實現(xiàn)的技巧。
廢話不多說,jquery這么多年了分析都寫爛了,老早以前就拜讀過,
不過這幾年都是做移動端,一直御用zepto, 最近抽出點時間把jquery又給掃一遍
我也不會照本宣科的翻譯源碼,結(jié)合自己的實際經(jīng)驗一起拜讀吧!
github上最新是jquery-master,加入了AMD規(guī)范了,我就以官方最新2.0.3為準(zhǔn)
整體架構(gòu)
jQuery框架的核心就是從HTML文檔中匹配元素并對其執(zhí)行操作、
例如:
$().find().css()
$().hide().html('....').hide().
從上面的寫法上至少可以發(fā)現(xiàn)2個問題
1. jQuery對象的構(gòu)建方式
2 .jQuery方法的調(diào)用方式
分析一:jQuery的無new構(gòu)建
JavaScript是函數(shù)式語言,函數(shù)可以實現(xiàn)類,類就是面向?qū)ο缶幊讨凶罨镜母拍?/P>
var aQuery = function(selector, context) {
//構(gòu)造函數(shù)
}
aQuery.prototype = {
//原型
name:function(){},
age:function(){}
}
var a = new aQuery();
a.name();
這是常規(guī)的使用方法,顯而易見jQuery不是這樣玩的
jQuery沒有使用new運(yùn)行符將jQuery顯示的實例化,還是直接調(diào)用其函數(shù)
按照jQuery的書寫方式
$().ready()
$().noConflict()
要實現(xiàn)這樣,那么jQuery就要看成一個類,那么$()應(yīng)該是返回類的實例才對
所以把代碼改一下:
var aQuery = function(selector, context) {
return new aQuery();
}
aQuery.prototype = {
name:function(){},
age:function(){}
}
通過new aQuery(),雖然返回的是一個實例,但是也能看出很明顯的問題,死循環(huán)了!
那么如何返回一個正確的實例?
在javascript中實例this只跟原型有關(guān)系
那么可以把jQuery類當(dāng)作一個工廠方法來創(chuàng)建實例,把這個方法放到j(luò)Query.prototye原型中
var aQuery = function(selector, context) {
return aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
return this;
}
name:function(){},
age:function(){}
}
當(dāng)執(zhí)行aQuery() 返回的實例:
很明顯aQuery()返回的是aQuery類的實例,那么在init中的this其實也是指向的aQuery類的實例
問題來了init的this指向的是aQuery類,如果把init函數(shù)也當(dāng)作一個構(gòu)造器,那么內(nèi)部的this要如何處理?
var aQuery = function(selector, context) {
return aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
this.age = 18
return this;
},
name: function() {},
age: 20
}
aQuery().age //18
這樣的情況下就出錯了,因為this只是指向aQuery類的,所以需要設(shè)計出獨立的作用域才行
jQuery框架分隔作用域的處理
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
很明顯通過實例init函數(shù),每次都構(gòu)建新的init實例對象,來分隔this,避免交互混淆
那么既然都不是同一個對象那么肯定又出現(xiàn)一個新的問題
例如:
var aQuery = function(selector, context) {
return new aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
this.age = 18
return this;
},
name: function() {},
age: 20
}
//Uncaught TypeError: Object [object Object] has no method 'name'
console.log(aQuery().name())
拋出錯誤,無法找到這個方法,所以很明顯new的init跟jquery類的this分離了
怎么訪問jQuery類原型上的屬性與方法?
做到既能隔離作用域還能使用jQuery原型對象的作用域呢,還能在返回實例中訪問jQuery的原型對象?
實現(xiàn)的關(guān)鍵點
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
通過原型傳遞解決問題,把jQuery的原型傳遞給jQuery.prototype.init.prototype
換句話說jQuery的原型對象覆蓋了init構(gòu)造器的原型對象
因為是引用傳遞所以不需要擔(dān)心這個循環(huán)引用的性能問題
var aQuery = function(selector, context) {
return new aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
return this;
},
name: function() {
return this.age
},
age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
console.log(aQuery().name()) //20
百度借網(wǎng)友的一張圖,方便直接理解:
fn解釋下,其實這個fn沒有什么特殊意思,只是jQuery.prototype的引用
分析二:鏈?zhǔn)秸{(diào)用
DOM鏈?zhǔn)秸{(diào)用的處理:
1.節(jié)約JS代碼.
2.所返回的都是同一個對象,可以提高代碼的效率
通過簡單擴(kuò)展原型方法并通過return this的形式來實現(xiàn)跨瀏覽器的鏈?zhǔn)秸{(diào)用。
利用JS下的簡單工廠模式,來將所有對于同一個DOM對象的操作指定同一個實例。
這個原理就超簡單了
aQuery().init().name()
分解
a = aQuery();
a.init()
a.name()
把代碼分解一下,很明顯實現(xiàn)鏈?zhǔn)降幕緱l件就是實例this的存在,并且是同一個
aQuery.prototype = {
init: function() {
return this;
},
name: function() {
return this
}
}
所以我們在需要鏈?zhǔn)降姆椒ㄔL問this就可以了,因為返回當(dāng)前實例的this,從而又可以訪問自己的原型了
aQuery.init().name()
優(yōu)點:節(jié)省代碼量,提高代碼的效率,代碼看起來更優(yōu)雅
最糟糕的是所有對象的方法返回的都是對象本身,也就是說沒有返回值,這不一定在任何環(huán)境下都適合。
Javascript是無阻塞語言,所以他不是沒阻塞,而是不能阻塞,所以他需要通過事件來驅(qū)動,異步來完成一些本需要阻塞進(jìn)程的操作,這樣處理只是同步鏈?zhǔn)剑惒芥準(zhǔn)絡(luò)query從1.5開始就引入了Promise,jQuery.Deferred后期在討論。
分析三:插件接口
jQuery的主體框架就是這樣,但是根據(jù)一般設(shè)計者的習(xí)慣,如果要為jQuery或者jQuery prototype添加屬性方法,同樣如果要提供給開發(fā)者對方法的擴(kuò)展,從封裝的角度講是不是應(yīng)該提供一個接口才對,字面就能看懂是對函數(shù)擴(kuò)展,而不是看上去直接修改prototype.友好的用戶接口,
jQuery支持自己擴(kuò)展屬性,這個對外提供了一個接口,jQuery.fn.extend()來對對象增加方法
從jQuery的源碼中可以看到,jQuery.extend和jQuery.fn.extend其實是同指向同一方法的不同引用
jQuery.extend = jQuery.fn.extend = function() {
jQuery.extend 對jQuery本身的屬性和方法進(jìn)行了擴(kuò)展
jQuery.fn.extend 對jQuery.fn的屬性和方法進(jìn)行了擴(kuò)展
通過extend()函數(shù)可以方便快速的擴(kuò)展功能,不會破壞jQuery的原型結(jié)構(gòu)
jQuery.extend = jQuery.fn.extend = function(){...}; 這個是連等,也就是2個指向同一個函數(shù),怎么會實現(xiàn)不同的功能呢?這就是this 力量了!
針對fn與jQuery其實是2個不同的對象,在之前有講述:
jQuery.extend 調(diào)用的時候,this是指向jQuery對象的(jQuery是函數(shù),也是對象!),所以這里擴(kuò)展在jQuery上。
而jQuery.fn.extend 調(diào)用的時候,this指向fn對象,jQuery.fn 和jQuery.prototype指向同一對象,擴(kuò)展fn就是擴(kuò)展jQuery.prototype原型對象。
這里增加的是原型方法,也就是對象方法了。所以jQuery的api中提供了以上2中擴(kuò)展函數(shù)。
extend的實現(xiàn)
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {}, // 常見用法 jQuery.extend( obj1, obj2 ),此時,target為arguments[0]
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) { // 如果第一個參數(shù)為true,即 jQuery.extend( true, obj1, obj2 ); 的情況
deep = target; // 此時target是true
target = arguments[1] || {}; // target改為 obj1
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) { // 處理奇怪的情況,比如 jQuery.extend( 'hello' , {nick: 'casper})~~
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) { // 處理這種情況 jQuery.extend(obj),或 jQuery.fn.extend( obj )
target = this; // jQuery.extend時,this指的是jQuery;jQuery.fn.extend時,this指的是jQuery.fn
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) { // 比如 jQuery.extend( obj1, obj2, obj3, ojb4 ),options則為 obj2、obj3...
// 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 ) { // 被拷貝的屬性值是個數(shù)組
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else { 被拷貝的屬性值是個plainObject,比如{ nick: 'casper' }
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 ) { // 淺拷貝,且屬性值不為undefined
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
總結(jié):
通過new jQuery.fn.init() 構(gòu)建一個新的對象,擁有init構(gòu)造器的prototype原型對象的方法
通過改變prorotype指針的指向,讓這個新的對象也指向了jQuery類的原型prototype
所以這樣構(gòu)建出來的對象就繼續(xù)了jQuery.fn原型定義的所有方法了
相關(guān)文章
Jquery之Ajax運(yùn)用 學(xué)習(xí)運(yùn)用篇
JQuery中Ajax的運(yùn)用相信很多人都已熟悉,本文主要是記錄下個人實踐中的應(yīng)用知識,旨在加強(qiáng)記憶。2011-09-09jquery購物車實時結(jié)算特效實現(xiàn)思路
購物車是可以實時結(jié)算,下面為大家解釋下通過jquery是如何實現(xiàn)的,感興趣的朋友可以了解下2013-09-09easyui combotree加載靜態(tài)數(shù)據(jù)問題(選不上)解決方法
這篇文章主要介紹了easyui combotree加載靜態(tài)數(shù)據(jù)問題,選不上)2016-12-12jquery中獲得$.ajax()事件返回的值并添加事件的方法
如果想獲得$.ajax()中返回的值,直接用在success:funciton(){return xx} 是不可以的,要想獲得xx的值,要在script中,使用全局變量。利用全局變量引出xx的值。2010-04-04