揭秘 jQuery

woainiqq 7年前發布 | 34K 次閱讀 jQuery Ajax框架

jQuery源碼分析

前言

有時候我在想jQuery為什么可以直接$操作,可以擁有比原生js更便利的DOM操作,而且只要你想就可以直接鏈式操作下去

核心框架

揭開一萬多行代碼的jQuery核心代碼:

(function(window, undefined) {
    function jQuery(selector){
        return new jQuery.fn.init(selector)
    }
    jQuery.fn = jQuery.prototype = {
        init: function () {

    }
}
jQuery.fn.init.prototype = jQuery.fn;
window.jQuery = window.$ = jQuery;

})(window)</code></pre>

  • 閉包結構傳參window
    • 閉包結構傳入實參window,然后里面用形參接收
      • 減少內部每次引用window的查詢時間
      • 方便壓縮代碼
      </li> </ul> </li>
    • 形參undefined
      • 因為ie低版本的瀏覽器可以給undefined賦值成功,所以為了保證undefined的純潔給它一個形參的位置而沒有實參,保證了它一定是undefined
      • </ul> </li>
      • jQuery傳參selector
        • selector可以是一對標簽,可以是id、類、后代、子代等等,可以是jQuery對象,
        • </ul> </li>
        • jQuery原型對象賦值
          • 方便擴展jQuery的原型方法
          • </ul> </li>
          • return 實例化原型方法init
            • 其實就是為了我們每次使用$不用new $();
            • 為什么jQuery要new自己的原型方法呢,因為不new自己的就要new其他的函數返回,那干嘛不自己利用自己
            • </ul> </li>
            • jQuery原型對象賦值給jQuery原型方法init的原型
              • 因為內部給jQuery原型每擴展一個方法init也會有該方法,是不是很酷炫,init有了那么$()出來的jQuery對象是不是也有啦
              • </ul> </li>
              • 給window暴露可利用成員jQuery,$
                • 給window暴露后那么全局都可以直接使用了jQuery和$了
                • 至于為什么有$,因為短啊,當然你也可以每次jQuery()來使用
                • </ul> </li> </ul>

                  御用選擇器-Sizzle

                  • Sizzle也是jQuery的根本,當然了你也單獨使用Sizzle
                  • 上面說過$(selector)的參數selector可以是id、類、后代、子代等等,可以是jQuery對象,那么咱們每次$一下就可以心想事成的得到我們想要的jQuery對象是怎么辦到的呢,沒錯,就是因為Sizzle,Sizzle封裝了獲取各種dom對象的方法,并且會把他們包裝成jQuery對象
                  • 瀏覽器能力測試
                    • Sizzle內部有個support對象,support對象存儲著正則測試瀏覽器能力的結果
                    • 對于有能力問題的選擇器使用通用兼容方案解決(繁瑣的判斷代碼)
                    </li>
                  • 正則
                    • 正則表達式在jQuery中使用的還是比較多的,正則的使用可以很大的提交我們對數據的處理效率
                    • </ul> </li>
                    • 判斷
                      • 判斷是在init內部判斷selector的類型,
                        • 列如可能是個html標簽,那么直接create一個selector標簽的DOM對象包裝成jQuery對象return出去
                        • 列如可能是個id名、類名、標簽名等等,那么直接通過Sizzle獲取到DOM對象包裝成jQuery對象return出去
                        • </ul> </li> </ul> </li>
                        • 包裝
                          • 我已經說了很多次的包裝了,沒錯,jQuery對象其實也是個偽數組,這也是它的設計巧妙之處,因為用數組存儲數據方便我們去進行更多的數據處理,比如 $("div").css("color": "red") ,那么jQuery會自動幫我們隱式迭代、再給頁面上所有div包含的文字顏色設置為red,簡單粗暴的一行代碼搞定簡直是程序猿的最愛
                          • </ul> </li> </ul>

                            對外擴展-extend

                            • jQuery核心的結構處理完畢之后基本上就可以對外使用了,但是我們知道我們是可以基于jQuery來實現插件的,包括jQuery自己可擴展性也必須要求他要對外提供一個接口方便進行二次開發,所以有了extend方法
                            • 簡單的extend就是混入,列子:
                            function extend(obj) {
                                    var k;
                                    for(k in obj) {
                                        this[k] = obj[k];
                                    }
                                }

                            Baiya.extend = extend;
                            Baiya.fn.extend = extend;</code></pre> 
                            

                            對靜態方法的和實例方法的擴展都要有,比如each方法,可以$.each來使用,也可以是$("div").each來使用

                            • 之后jQuery一些方法都是基于extend來擴展的,當然我們自己也可以基于jQuery擴展方法

                            DOM操作

                            • DOM操作也是jQuery的一大特點,因為它太好用了,包含了我們所能想到的所有使用場景,完善了增刪查改常用的方法
                            • jQuery獲取和設置類的方法如html()/css()/val()等等這些傳參是設置值不傳參是獲取值

                            ##鏈式編程

                            • jQuery是支持鏈式編程的,只要你想你就可以一行代碼寫完所有的功能,這是怎么做到的呢
                            • 每一個改變原型鏈的方法都會把當前的this對象保存成他自己的屬性,然后可以調用end方法找到上一級鏈從而方便我們可以進行鏈式操作

                            事件操作

                            • jQuery的事件操作一般可以通過click類(mouseover/mouseleave等等)和on來使用,但是click類的實現是調用on的
                            • on的實現是對原生的onclick類的處理,因為相同的原生的事件在同一個DOM對象上只能被綁定一次,如果再次綁定會覆蓋掉上一次的,所以jQuery幫我們封裝了事件的存儲,把相同的事件分成一個數組存儲在一個對象里面,然后對數組進行遍歷,依次調用數組里存儲的每個方法
                            • on實現之后會把所有的事件處理字符串處理一下用on來改造一下,如下:
                            Baiya.each(("onclick,onmousedown,onmouseenter,onmouseleave," +
                                "onmousemove,onmouseout,onmouseover,onmouseup,onfocus," +
                                "onmousewheel,onkeydown,onkeypress,onkeyup,onblur").split(","),     function (i, v) {
                                    var event = v.slice(2);
                                    Baiya.fn[event] = function (callback) {
                                        return this.on(event, callback);
                                    }
                                });

                            屬性操作

                            • jQuery也提供給了我們方便的屬性操作,底層就是對原生方法的包裝,處理兼容性問題,如果jQuery不對IE瀏覽器的兼容處理的話,那么它的代碼量可能會縮一半,當然鍋不能全部甩給IE,比如innerText方法火狐是不支持的,但是支持textContent方法,所以jQuery會盡可能的處理這種瀏覽器帶來的差異

                            樣式操作

                            • 基本思想如上

                            Ajax操作

                            • Ajax可以說是前端的跨越性進步,毫不夸張的說如果沒有Ajax的發展,那么今天的前端可能不叫前端,可能是美工……

                            • Ajax是什么?

                              • 在我的理解來看Ajax就是一個方法,這個方法遵循著http協議的規范,我們可以使用這個方法來向服務器請求少量的數據,有了數據之后我們就可以操作DOM來達到局部更新網頁的目的,這是一個非常酷的事情
                            • jQuery的Ajax是基于XMLHttpRequest的封裝,當然了他也有兼容性問題,具體的封裝見我之前的文章 簡單的ajax封裝

                            • 具體就是區別get和post請求的區別,get請求的傳參是直接拼接在url結尾,而post請求需要在send()里面傳遞,并且post請求還要設置請求頭setRequestHeader("content-type", "application/x-www-form-urlencoded")

                            • 請求后對json或者text或者xml的數據進行處理就可以渲染到頁面了

                            提到Ajax就不得不提到跨域了

                            • 跨域簡單的來說限制了非同源(ip/域名/端口/協議)的數據交互,當然這肯定是極好的,因為如果不限制那么你的網頁別人也可以操作是不是很恐怖
                            • 但是有些情況下我們需要調用別人的服務器數據,而且別人也愿意怎么辦呢,程序員是很聰明的,html標簽中img,script,link等一些帶有src屬性的標簽是可以請求外部資源的,img和link得到的數據是不可用的,只有script標簽請求的數據我們可以通過函數來接收,函數的參數傳遞可以是任何類型,所以創建一個函數,來接收,參數就是請求到的數據,而對方的數據也要用該函數來調用就可以實現跨域了
                            • 簡單封裝jsonp實現
                            // url是請求的接口
                            // params是傳遞的參數
                            // fn是回調函數
                            function jsonp(url, params, fn){
                                        // cbName實現給url加上哈希,防止同一個地址請求出現緩存
                                        var cbName = `jsonp_${(Math.random() * Math.random()).toString().substr(2)}`;
                                        window[cbName] = function (data) {
                                            fn(data);
                                            // 獲取數據后移除script標簽
                                            window.document.body.removeChild(scriptElement);
                                        };
                            
                                        // 組合最終請求的url地址
                                        var querystring = '';
                                        for (var key in params) {
                                            querystring += `${key}=${params[key]}&`;
                                        }
                                        // 告訴服務端我的回調叫什么
                                        querystring += `callback=${cbName}`;
                            
                                        url = `${url}?${querystring}`;
                            
                                        // 創建一個script標簽,并將src設置為url地址
                                        var scriptElement = window.document.createElement('script');
                                        scriptElement.src = url;
                                        // appendChild(執行)
                                        window.document.body.appendChild(scriptElement);
                                    }

                            Animate

                            • 很抱歉的是jQuery的動畫源碼我并沒有閱讀,但是我自己封裝了一個動畫函數,之后的源碼閱讀會補上的

                            • 封裝的代碼

                            // element設置動畫的DOM對象
                            // attrs設置動畫的屬性 object
                            // fn是回調函數
                            function animate(element, attrs, fn) {
                                    //清除定時器
                                    if(element.timerId) {
                                        clearInterval(element.timerId);
                                    }
                                    element.timerId = setInterval(function () {
                                        //設置開關
                                        var stop = true;
                                        //遍歷attrs對象,獲取所有屬性
                                        for(var k in attrs) {
                                            //獲取樣式屬性 對應的目標值
                                            var target = parseInt(attrs[k]);
                                            var current = 0;
                                            var step = 0;
                                            //判斷是否是要修改透明度的屬性
                                            if(k === "opacity") {
                                                current = parseFloat( getStyle(element, k)) * 100 || 0;
                                                target = target * 100;
                                                step = (target - current) / 10;
                                                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                                                current += step;
                                                element.style[k] = current / 100;
                                                //兼容ie
                                                element.style["filter"] = "alpha(opacity="+  current +")";
                                            }else if(k === "zIndex") {
                                                element.style[k] = target;
                                            } else {
                                                //獲取任意樣式屬性的值,如果轉換數字失敗,返回為0
                                                current = parseInt(getStyle(element, k)) || 0;
                                                step = (target - current) / 10;
                                                console.log("current:" + current + "  step:" + step);
                                                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                                                current += step;
                                                //設置任意樣式屬性的值
                                                element.style[k] = current + "px";
                                            }
                                            if(step !== 0) {
                                                //如果有一個屬性的值沒有到達target  ,設置為false
                                                stop = false;
                                            }
                            
                                        }
                                        //如果所有屬性值都到達target  停止定時器
                                        if(stop) {
                                            clearInterval(element.timerId);
                                            //動畫執行完畢  回調函數
                                            if(fn) {
                                                fn();
                                            }
                                        }
                                    },30);
                                }
                            
                                //獲取計算后的樣式的值
                                function getStyle(element, attr) {
                                    //能力檢測
                                    if(window.getComputedStyle) {
                                        return window.getComputedStyle(element, null)[attr];
                                    }else{
                                        return element.currentStyle[attr];
                                    }
                                }

                             

                            來自:https://github.com/Redshao/dahong/issues/4

                             

 本文由用戶 woainiqq 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!