1. 程式人生 > >jQuery選擇器原始碼分析和easyui核心分析

jQuery選擇器原始碼分析和easyui核心分析

寫在選擇器原始碼分析之前

     這裡指對1.7.2版本的原始碼分析,更高版本添加了更多程式碼。

     整個jQuery的程式碼是寫在一個(function(window, undefined){})(window);這樣一個閉包裡。請思考,為什麼要這樣做?

     將其寫在一個閉包函式裡,並傳入window直接執行的好處有三:

        1,統一名稱空間,防止變數的汙染;

    2,將window作為引數傳入函式,在函式裡呼叫window的時候,就不用再去找外層的物件,可以提高效率 ;

       3undefined並不是javascript的關鍵字,使用引數

undefined是為了防外面定義了全域性的undefined變數而受到汙染。

     jQuery的建構函式,其實返回的是一個jQuery.fn.init的物件。然後通過jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype來將jQuery的原型和jQuery.fn.init的原型聯絡起來。這樣寫看起來有點暈,但是正因為這樣的聯絡,使得new jQuery()jQuery()都能返回一個jQuery物件(其實這裡是jQuery.fn.init物件,但是由於原型都指向同一個物件,所以表面上可以無視這個問題)

    最後,jQuery

對外提供的方法,末了都會將自身返回,以提供鏈式操作。

jQuery選擇器原始碼分析

按選擇器分類逐句分析

   //rootjQuery = jQuery(document);

   init: function (selector, context, rootjQuery) {

                var match, elem, ret, doc;

               // Handle $(""), $(null), or $(undefined) ->返回本身

                if (!selector) {

                    return this;

                }

                // Handle $(DOMElement) -> 如果是element ,就直接封裝成jquery物件

                if (selector.nodeType) {

                    this.context = this[0] = selector;

                    this.length = 1;

                    return this;

                }

                //如果是body ->這裡document.body作為條件,應該是為了相容問題

                //可是今天我在主流瀏覽器裡測試,都能夠同時找到document.body

                //document.documentElement

                if (selector === "body" && !context && document.body) {

                    this.context = document;

                    this[0] = document.body;

                    this.selector = selector;

                    return this;

                }

                if (typeof selector === "string") {

                    //HTML string

                    if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {

                        //跳過檢查

                        match = [null, selector, null];

                    } else {

                        //RegExpObject.exec(string)返回一個數組,其中存放匹配的結果。如      //果未找到匹配,則返回值為 null

     //quickExpr是檢查HTML字串或ID字串的正則

                        match = quickExpr.exec(selector);

                    }

                    //驗證match, match[1](這裡存放是html)為非的時候context也必須為非

                    //(這種情況是#id)

                    if (match && (match[1] || !context)) {

                        // HANDLE: $(html) -> $(array)

                        if (match[1]) {

                            //這裡的context其實可以理解成selectorparentNode或者parent()

                         //context ->DOM物件

                            context = context instanceof jQuery ? context[0] : context;

                            //如果制定了context,就返回context.ownerDocument(這裡是

                            //context當前所屬的document) || context,否則返回document

                            doc = (context ? context.ownerDocument || context : document);

                            //匹配成獨立的標籤(不含有屬性之類,比如<a></a>

                            ret = rsingleTag.exec(selector);

                            if (ret) {

                                //方法jQuery.isPlainObject( object )用於判斷傳入的引數是否      //是“純粹”的物件,即是否是用物件直接量{}new Object()建立                                //的物件,如果是,那麼context就是selector的屬性

                                if (jQuery.isplainObject(context)) {

                                    selector = [document.createElement(ret[1])];

                                    jQuery.fn.attr.call(selector, context, true);

                                } else {

                                    selector = [doc.createElement(ret[1])];

                                }

                            } else {

                                //快取selectorhtml

                                ret = jQuery.buildFragment([match[1]], [doc]);

                                //如果是快取了的,就clone fragment(文件碎片節點在新增到文件樹                      //之後便不能再對其進行操作),否則就直接取fragment

                          //childNodes

                                selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;

                            }

                            //selector合併到this,返回

                            return jQuery.merge(this, selector);

                            // HANDLE: $("#id")

                        } else {

                            elem = document.getElementById(match[2]);

                            if (elem && elem.parentNode) {

                                //處理 IE and Opera 混淆IDNAMEbug

                                if (elem.id !== match[2]) {

                                  //呼叫Sizzle的方法 -- TODO,關於Sizzle.js,有待研究!

                                    return rootjQuery.find(selector);

                                }

                                this.length = 1;

                                this[0] = elem;

                            }

                            this.context = document;

                            this.selector = selector;

                            return this;

                        }

                        // HANDLE: $(expr, $(...)) 

                    } else if (!context || context.jquery) {

                        return (context || rootjQuery).find(selector);

                        // HANDLE: $(expr, context)

                    } else {

                        return this.constructor(context).find(selector);

                    }

                    // HANDLE: $(function)    

                } else if (jQuery.isFunction(selector)) {

                    return rootjQuery.ready(selector);

                }

                //selector本身就是一個jQuery物件的情況

                if (selector.selector !== undefined) {

                    this.selector = selector.selector;

                    this.context = selector.context;

                }

                //合併屬性(jQuery.merge不同的是,這裡的selector可能不是陣列)

                return jQuery.makeArray(selector, this);

            }

easyui核心分析

      Easyui控制元件是在jQuery.fn上擴充套件的,所以使用這些控制元件就如同使用jQuery方法一樣方便(其實就是!)。精妙的地方:

1,建立控制元件和呼叫控制元件的方法,用法都和呼叫jQuery的方法一樣,只在傳參的區別。非常方便。

2,在某dom上建立的控制元件,需要新增或修改一些控制元件的屬性時,和建立控制元件時用法也一樣。

      以combobox為例,

$.fn.combobox = function(options, param){

//如果傳入的第一個引數是string,則認為是呼叫控制元件的方法

if (typeof options == 'string'){

//查詢對應的方法

var method = $.fn.combobox.methods[options];

if (method){

//存在對應的方法,則呼叫

return method(this, param);

} else {

//沒有找到對應的方法,則呼叫$.fn.combobox(easyui

//的控制元件繼承關係的原理)

return this.combo(options, param);

}

}

//傳入的options不是string,則認為是建立combobox控制元件

options = options || {};

return this.each(function(){

//獲取繫結在dom上的combobox對應的資料

var state = $.data(this, 'combobox');

if (state){

//如果dom上有繫結combobox對應的資料,則與傳入的

//options合併,重新建立控制元件

$.extend(state.options, options);

create(this);

} else {

state = $.data(this, 'combobox', {

options: $.extend({}, $.fn.combo.defaults, $.fn.combobox.defaults, parseOptions(this), options)

});

create(this);

//重新繫結用到的其他的控制元件的對應資料到dom

loadData(this, transformData(this));

}

if (state.options.data){

loadData(this, state.options.data);

}

request(this);

});

};

關於$.data

   $.data可以將資料存在dom上,但又相比直接存放在HTMLElement的屬性上要更安全,而且在dom的元素中新增過多的屬性,對瀏覽器來說也是一種負荷。