query這麼多年了分析都寫爛了,老早之前就拜讀過, 不過這幾年都是作移動端,一直御用zepto, 最近抽出點時間把jquery又給掃一遍 我也不會照本宣科的翻譯源碼,結合本身的實際經驗一塊兒拜讀吧! github上最新是jquery-master,加入了AMD規範了,我就以官方最新2.0.3爲準javascript
jQuery框架的核心就是從HTML文檔中匹配元素並對其執行操做、 例如:css
$().find().css()
$().hide().html('....').hide().
複製代碼
從上面的寫法上至少能夠發現2個問題html
JavaScript是函數式語言,函數能夠實現類,類就是面向對象編程中最基本的概念java
var aQuery = function(selector, context) {
//構造函數
}
aQuery.prototype = {
//原型
name:function(){},
age:function(){}
}
var a = new aQuery();
a.name();
複製代碼
這是常規的使用方法,顯而易見jQuery不是這樣玩的 jQuery沒有使用new運行符將jQuery顯示的實例化,仍是直接調用其函數 按照jQuery的抒寫方式jquery
$().ready()
$().noConflict()
複製代碼
要實現這樣,那麼jQuery就要當作一個類,那麼$()應該是返回類的實例纔對 因此把代碼改一下:git
var aQuery = function(selector, context) {
returnnew aQuery();
}
aQuery.prototype = {
name:function(){},
age:function(){}
}
複製代碼
經過new aQuery(),雖然返回的是一個實例,可是也能看出很明顯的問題,死循環了!github
在javascript中實例this只跟原型有關係 那麼能夠把jQuery類看成一個工廠方法來建立實例,把這個方法放到jQuery.prototye原型中編程
var aQuery = function(selector, context) {
return aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
returnthis;
}
name:function(){},
age:function(){}
}
複製代碼
當執行aQuery() 返回的實例: api
很明顯aQuery()返回的是aQuery類的實例,那麼在init中的this其實也是指向的aQuery類的實例 問題來了init的this指向的是aQuery類,若是把init函數也看成一個構造器,那麼內部的this要如何處理?var aQuery = function(selector, context) {
return aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
this.age = 18
returnthis;
},
name: function() {},
age: 20
}
aQuery().age //18
複製代碼
這樣的狀況下就出錯了,由於this只是指向aQuery類的,因此須要設計出獨立的做用域才行數組
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'returnnew jQuery.fn.init( selector, context, rootjQuery );
},
複製代碼
很明顯經過實例init函數,每次都構建新的init實例對象,來分隔this,避免交互混淆 那麼既然都不是同一個對象那麼確定又出現一個新的問題 例如:
var aQuery = function(selector, context) {
returnnew aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
this.age = 18
returnthis;
},
name: function() {},
age: 20
}
//Uncaught TypeError: Object [object Object] has no method 'name'
console.log(aQuery().name())
複製代碼
拋出錯誤,沒法找到這個方法,因此很明顯new的init跟jquery類的this分離了
作到既能隔離做用域還能使用jQuery原型對象的做用域呢,還能在返回實例中訪問jQuery的原型對象? 實現的關鍵點
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
複製代碼
經過原型傳遞解決問題,把jQuery的原型傳遞給jQuery.prototype.init.prototype 換句話說jQuery的原型對象覆蓋了init構造器的原型對象 由於是引用傳遞因此不須要擔憂這個循環引用的性能問題
var aQuery = function(selector, context) {
returnnew aQuery.prototype.init();
}
aQuery.prototype = {
init: function() {
returnthis;
},
name: function() {
returnthis.age
},
age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
console.log(aQuery().name()) //20
複製代碼
百度借網友的一張圖,方便直接理解: fn解釋下,其實這個fn沒有什麼特殊意思,只是jQuery.prototype的引用
DOM鏈式調用的處理: 1.節約JS代碼. 2.所返回的都是同一個對象,能夠提升代碼的效率
經過簡單擴展原型方法並經過return this的形式來實現跨瀏覽器的鏈式調用。 利用JS下的簡單工廠模式,來將全部對於同一個DOM對象的操做指定同一個實例。 這個原理就超簡單了
aQuery().init().name()
分解
a = aQuery();
a.init()
a.name()
複製代碼
把代碼分解一下,很明顯實現鏈式的基本條件就是實例this的存在,而且是同一個
aQuery.prototype = {
init: function() {
returnthis;
},
name: function() {
returnthis
}
}
複製代碼
因此咱們在須要鏈式的方法訪問this就能夠了,由於返回當前實例的this,從而又能夠訪問本身的原型了
aQuery.init().name()
複製代碼
優勢:節省代碼量,提升代碼的效率,代碼看起來更優雅 最糟糕的是全部對象的方法返回的都是對象自己,也就是說沒有返回值,這不必定在任何環境下都適合。
Javascript是無阻塞語言,因此他不是沒阻塞,而是不能阻塞,因此他須要經過事件來驅動,異步來完成一些本須要阻塞進程的操做,這樣處理只是同步鏈式,異步鏈式jquery從1.5開始就引入了Promise,jQuery.Deferred後期在討論。
jQuery的主體框架就是這樣,可是根據通常設計者的習慣,若是要爲jQuery或者jQuery prototype添加屬性方法,一樣若是要提供給開發者對方法的擴展,從封裝的角度講是否是應該提供一個接口才對,字面就能看懂是對函數擴展,而不是看上去直接修改prototype.友好的用戶接口,
jQuery支持本身擴展屬性,這個對外提供了一個接口,jQuery.fn.extend()來對對象增長方法 從jQuery的源碼中能夠看到,jQuery.extend和jQuery.fn.extend實際上是同指向同一方法的不一樣引用
jQuery.extend = jQuery.fn.extend = function() { jQuery.extend 對jQuery自己的屬性和方法進行了擴展
jQuery.fn.extend 對jQuery.fn的屬性和方法進行了擴展 經過extend()函數能夠方便快速的擴展功能,不會破壞jQuery的原型結構
jQuery.extend = jQuery.fn.extend = function(){...};
這個是連等,也就是2個指向同一個函數,怎麼會實現不一樣的功能呢?這就是this 力量了!
針對fn與jQuery實際上是2個不一樣的對象,在以前有講述: ● jQuery.extend 調用的時候,this是指向jQuery對象的(jQuery是函數,也是對象!),因此這裏擴展在jQuery上。 ● 而jQuery.fn.extend 調用的時候,this指向fn對象,jQuery.fn 和jQuery.prototype指向同一對象,擴展fn就是擴展jQuery.prototype原型對象。 ● 這裏增長的是原型方法,也就是對象方法了。因此jQuery的api中提供了以上2中擴展函數。 extend的實現
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 situationif ( typeof target === "boolean" ) { // 若是第一個參數爲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 passedif ( 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 valuesif ( (options = arguments[ i ]) != null ) { // 好比 jQuery.extend( obj1, obj2, obj3, ojb4 ),options則爲 obj二、obj3...// Extend the base objectfor ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loopif ( 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 { 被拷貝的屬性值是個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
} elseif ( copy !== undefined ) { // 淺拷貝,且屬性值不爲undefined
target[ name ] = copy;
}
}
}
}
// Return the modified objectreturn target;
複製代碼
總結:
● 經過new jQuery.fn.init() 構建一個新的對象,擁有init構造器的prototype原型對象的方法
● 經過改變prorotype指針的指向,讓這個新的對象也指向了jQuery類的原型prototype
● 因此這樣構建出來的對象就繼續了jQuery.fn原型定義的全部方法了