首頁  >  文章  >  web前端  >  jQuery 2.0.3 源碼分析之core(一)整體架構_jquery

jQuery 2.0.3 源碼分析之core(一)整體架構_jquery

WBOY
WBOY原創
2016-05-16 16:46:401074瀏覽

拜讀一個開源框架,最想學到的就是設計的想法和實現的技巧。

廢話不多說,jquery這麼多年了分析都寫爛了,老早以前就拜讀過,

不過這幾年都是做行動端,一直御用zepto, 最近抽出點時間把jquery又給掃一遍

我也不會照本宣科的翻譯源碼,結合自己的實際經驗一起拜讀吧!

github上最新是jquery-master,加入了AMD規範了,我就以官方最新2.0.3為準

整體架構

jQuery框架的核心就是從HTML文件中匹配元素並對其執行操作、

例如:

複製程式碼 程式碼如下:

$().find().css()
$().hide().html('....').hide().

從上面的寫法上至少可以發現2個問題

1. jQuery物件的建構方式

2 .jQuery方法的呼叫方式

分析一:jQuery的無new建置

JavaScript是函數式語言,函數可以實現類,類別就是物件導向程式設計中最基本的概念

複製程式碼 程式碼如下:

var aQuery = function(selfpector, conpunction(self, context) {        //建構子
}
aQuery.prototype = {
    //原型
    name:function(){}, >
var a = new aQuery();

a.name();

這是常規的使用方法,顯而易見jQuery不是這樣玩的

jQuery沒有使用new運行符將jQuery顯示的實例化,還是直接呼叫其函數

依照jQuery的書寫方式

複製程式碼

程式碼如下:$().ready()
$().ready()
$(>
$().ready()
$(>
$()。 ).noConflict()

要實現這樣,那麼jQuery就要看成一個類,那麼$()應該是返回類的實例才對

所以把程式碼改一下:

複製程式碼
程式碼如下:


var aQuery = function(selfpector, conpunction(self, context) {       return new aQuery();
}
aQuery.prototype = {
    name:function(){},
 
透過new aQuery(),雖然傳回的是一個實例,但是也能看出很明顯的問題,死循環了!

那麼如何回傳一個正確的實例?

在javascript中實例this只跟原型有關係

那麼可以把jQuery類別當作一個工廠方法來創建實例,把這個方法放到jQuery.prototye原型中

複製程式碼 程式碼如下:
var aQuery = function(selfpector, conpunction(self, context) {       return  aQuery.prototype.init();
}
aQuery.prototype = {
    init:function( name:function(){ },
    age:function(){}
}



當執行aQuery() 傳回的實例:

 
很明顯aQuery()回傳的是aQuery類別的實例,那麼在init中的this其實也是指向的aQuery類別的實例

問題來了init的this指向的是aQuery類,如果把init函數也當作一個建構器,那麼內部的this要如何處理?

複製程式碼

  this;
    },
    name: function() {},
    age: 20
}

aQuery().age  //18

這樣的情況下就出錯了,因為this只是指向aQuery類的,所以需要設計出獨立的作用域才行

jQuery框架分隔作用域的處理

複製程式碼 程式碼如下:

jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );很明顯地透過實例init函數,每次都建構新的init實例對象,來分隔this,避免交互混淆

那麼既然都不是同一個物件那麼肯定又出現一個新的問題
例如:

複製程式碼

    init: function() { 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的原型物件?

實現的關鍵點

複製程式碼

程式碼如下:

// Give the init function the jQuery prototype for later jQuery.fn.init.prototype = jQuery.fn; 透過原型傳遞解決問題,把jQuery的原型傳遞給jQuery.prototype.init.prototype
換句話說jQuery的原型物件覆蓋了init建構器的原型物件

因為是引用傳遞所以不需要擔心這個循環引用的效能問題

複製程式碼

程式碼如下:

var aQuery = function(selfpector, conpunction(self, context) {       return  new aQuery.prototype.init();}aQuery.prototype = {    init: function( 🠎>   name: function( ) {        return this.age    },
    age: 20
}
aQuery.prototype.inin.prototype = aQuery. name()) //20



百度借網友的一張圖,方便直接理解:

fn解釋下,其實這個fn沒有什麼特殊意思,只是jQuery.prototype的引用

 

分析二:鍊式呼叫

DOM鍊式呼叫的處理:

1.節約JS代碼.
2.所回傳的都是同一個對象,可以提高程式碼的效率

透過簡單地擴展原型方法並透過return this的形式來實現跨瀏覽器的鍊式呼叫。

利用JS下的簡單工廠模式,來將所有對於同一個DOM物件的操作指定同一個實例。

這個原理就超簡單了

複製程式碼

程式碼如下:

aQuery().init().name()

分解

a = aQuery();

a.init()



複製程式碼


程式碼如下:
aQuery.prototype = {

aQuery.prototype = {

in ) {        return this;    },    name: function() {   

所以我們在需要鍊式的方法存取this就可以了,因為傳回目前實例的this,從而又可以存取自己的原型了

複製程式碼 程式碼如下:

aQuery.init().name(>
aQuery.init().name(>

aQuery.init().name(>

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.extendunction( ) {

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 situation
    if ( 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 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 ) {    // 被拷贝的属性值是个数组
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {    被拷贝的属性值是个plainObject,比如{ nick: 'casper' }
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
   deep, clone, copy );  // 遞迴~

                // Don't bring in undefined values
        defined
                    target[ name ] = copy;
}
            }
        }
    }

    // Return the modified object
    return target;

總結:

    透過new jQuery.fn.init() 建構一個新的對象,擁有init建構器的prototype原型物件的方法
    透過改變prorotype指標的指向,讓這個新的物件也指向了jQuery類別的原型prototype
    所以這樣建構出來的物件就延續了jQuery.fn原型定義的所有方法了

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn