源碼篇(四):手寫jQuery版mini源碼分析jQuery的優點。附送簡版jQuery源碼(3K字)

適合人羣

本文適合0.5~3年的前端開發人員,以及想了解jQuery是什麼的小夥伴們。css

前言

談談我的對jQuery的見解,無興趣可直接看源碼分析。html

若是你是一個五年以上的開發人員,相信你必定認識瞭解jQuery。這比如你十年前就已經有手機,那你確定認識瞭解諾基亞。前端

當今的jQuery的確是沒落了,遜色於三大框架,他的確落寞了。落寞到什麼層度?vue

落寞到,你面試時你提起他,面試官會以爲你沒有跟上時代;node

落寞到,你寫項目的用上jQuery,別人會以爲你的項目很是的low;react

落寞到,掘金都懶得給他一個專題了(要是十年前確定是個特殊的專區)。jquery

其實否則,jQuery仍是有不少能夠學習且能夠借鑑的地方,這也是筆者考慮再三,把他補上源碼篇之一的緣由。若是你是追求短時間面試高薪,明顯jQuery跟本文都不適合你。若是你注重基礎,不凡瞭解一下本文。webpack

若是你入門前端時,已是三大框架的時代,那能夠簡單的理解爲:jQuery是一個JavaScript函數庫,讓你的項目相比原生的js, 「寫的少,作的多」。是的,document.getelementbyid('id').innerhtml = "你好" 用 $('#id').html("你好") 便可實現,這對比咱們原生js的來寫項目,是帶來多大的方便。他有不少優點,具體的優點,下邊分析完源碼會有彙總。可是不得不先提一下,他的缺陷是什麼?淘汰的根本是什麼?git

筆者的觀點是:虛擬dom的出現是淘汰jQuery的根本。jQuery的簡寫,仍是用法是無腦操做dom。當屢次須要渲染同一個dom時,你直接操做的dom(由於渲染了屢次),他的效率就遠遠比不上"直接操做js,算好結果再同步dom"(由於只渲染了最終的一次)。虛擬dom的出現,讓咱們的前端,直接從mvc結構轉換成mvvm結構,三大框架都是數據驅動,而jQuery依然是操做dom的寫法,不得不給淘汰。這比如諾基亞想要在其餘方面拯救本身,若是不拋棄塞班更換流行系統,將有心無力,最後給無情的拋棄。github

手寫mini_jquery

基本頁面

咱們新建一個html,寫好jQuery的常見寫法:

<html>
    <head>
        <title>zQuery源碼</title>
        <meta name="keywords" content="shopInfo.shopName?if_exists" />
        <meta name="description" content="shopInfo.shopName?if_exists" />
        <meta http-equiv="X-UA-Compatible" content="IE=8">
        </meta>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    </head>
    <body>
        <script src="src/zQuery.js"></script>
    </body>
</html>
複製代碼

再新建zQuery.js引入。

全局註冊

用過jquery的小夥伴們,都知道咱們的$$全局哪裏均可以使用。引入jQuery後,$是在全局上註冊了。咱們看看官方是如何處理:

縮略代碼,即爲上圖。咱們能夠看到,引入jQuery後即調用了,實際上就是利用理解調用函數 (function(){})(),將jQuery暴露在全局中。

那麼有沒有人思考過?爲何截圖中,會有一個module的判斷呢?其實那是後續有了node環境,也有部分人須要在node用到jQuery,node是沒有dom頁面的,添加的判斷。咱們的案例暫時不須要考慮這些。咱們手寫一個註冊:

(function (window) {
    var zQuery = function () {
        return new Object();
    }
    window.$ = zQuery;
})(window);
複製代碼

獲取對象

即完成引入註冊,全局註冊。可是返回的對象究竟是個啥?咱們都知道$("")返回的是個dom對象,能夠根據id獲取,也能夠根據標籤名稱,根據類名等等。

  • 首先他要根據()裏的值,獲取dom對象,
  • 支持id, class, 標籤的獲取。

咱們根據兩個要點,咱們給他定義一個初始化方法,且新建時候當即調用,修改一下他的返回值:

(function (window) {
    var zQuery = function () {
        return new zQuery.fn.init(selector);
    }
    window.$ = zQuery;
    
     zQuery.fn = {
        init: function (selector) {
            this.dom = [];
            const childNodes = document.childNodes;
            var rs = null;
            if (typeof (selector) != 'undefined') {
                if (selector.substr(0, 1) == "#") {//id選擇器
                    rs = document.getElementById(selector.slice(1));
                    this.dom[0] = rs;
                } else if (selector.substr(0, 1) == ".") { //樣式選擇器
                    rs = document.getElementsByClassName(selector.slice(1));
                    for (var i = 0; i < rs.length; i++) {
                        this.dom[i] = rs[i];
                    }
                } else {//標籤選擇器
                    rs = document.getElementssByTagName();
                    for (var i = 0; i < rs.length; i++) {
                        this.dom[i] = rs[i];
                    }
                }
            }
            return this;
        },
    }
})(window);

這樣,便可完成$()的對象獲取。
複製代碼

操做對象

再拿到咱們的dom對象以後,接下來就是咱們如何改變他們的問題。常見的是:

  • html改變文本值
  • css改變顏色
  • hide/show隱藏或者顯示

以上方法,用原生js實現的話,很簡單吧?咱們嵌入zQuery.fn中,來實現代碼:

zQuery.fn = {
        ...,
        html: function (value) {
            if (this.dom.length == 1) {
                this.dom[0].innerHTML = value;
            } else if (this.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style[attr] = value;
                }
            }
        },
        css: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style[attr] = value;
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style[attr] = value;
                }
            }
        },
        show: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style.display = 'block';
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style.display = 'block';
                }
            }
        },
        hide: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style.display = 'none';
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style.display = 'none';
                }
            }
        },
    }
複製代碼

到此,便可完成咱們的dom操做。案例以下:

html:
    <div id="htmlId">點擊我從新賦值</div>
    <div id="cssId" style="color: black;">點擊我變成紅色</div>
    <div id="showId">點擊我變成隱藏</div>
    <div id="changeId">點擊我改變樣式選擇器</div>
    <div class="cssSelector">樣式選擇器</div>
    <div class="cssSelector">樣式選擇器</div>
    <div class="cssSelector">樣式選擇器</div>
    <div class="cssSelector">樣式選擇器</div>
    
js:

btnList.addEventListener('click', function (e) {
    var id = e.target.id;
    switch (id) {
        case 'htmlId':
            $('#htmlId').html("html賦值成功");
            break;
        case 'cssId':
            $('#cssId').css("color", "red");
            break;
        case 'showId':
            $("#showId").hide();
            break;
        case 'changeId':
            $(".cssSelector").css("color", "#0099dd");
            break;
        }
    }
});
複製代碼

完成鏈式

jquery的優點之一,就是方便支持鏈式。那麼咱們如何來實現他呢?

其實也很好理解,咱們只須要將咱們找到的dom對象,接下去再尋找下一層dom對象,直到找到結果爲止。 咱們把第一次的dom對象保存,第二次拿去dom對象,基於第一次的條件去下獲取,而後從新初始化便可。 且這個過程,全部的層次關係,多級的dom,內置方法是一致的,咱們須要完成原型鏈的繼承。

三個步驟:

  • 1.拿到上次的dom

  • 2.基於上次的dom,獲取下一次dom。

  • 3.完成原型鏈繼承

    第一個步驟,咱們能夠新建一個全局的myDocument表示保存的dom結構。
      zQuery.fn = {
              init: function (selector, myDocument) {
                  this.dom = this.dom ? this.dom : [];
                  this.myDocument = myDocument ? myDocument : document;
                  const childNodes = this.myDocument.childNodes;
                  var rs = null;
                  if (typeof (selector) != 'undefined') {
                      if (selector.substr(0, 1) == "#") {//id選擇器
                          rs = this.myDocument.getElementById(selector.slice(1));
                          this.dom[0] = rs;
                          console.log("rs===" + rs.innerText + rs.innerHTML);
                      } else if (selector.substr(0, 1) == ".") { //樣式選擇器
                          rs = this.myDocument.getElementsByClassName(selector.slice(1));
                          for (var i = 0; i < rs.length; i++) {
                              this.dom[i] = rs[i];
                          }
                      }
                  }
                  return this;
              },
              ...
      }
    
      基於上次的dom,獲取下一次dom。
      zQuery.fn = {
          ...
          find: function (selector) {
              if (this.dom.length == 1) {
                  this.init(selector, this.dom[0]);
              } else if (this.dom.length > 1) {
                  for (var i = 0; i < this.dom.length; i++) {
                      this.init(selector, this.dom[i]);
                  }
              }
              return this;
          },
      }
      
      //完成原型鏈繼承
      zQuery.fn.init.prototype = zQuery.fn;
    複製代碼

實現案例測試:

html:
<div id="findId">
    點擊我觸發鏈式變化:<div class="f_div">我是子內容</div>
</div>

js:
$("#findId").find('.f_div').css("color", "red");
複製代碼

網絡請求

最後簡單的實現jQuery的$.ajax神器。

ajax: function ({ url, dataType, success }) {
        var xhr = new XMLHttpRequest();
        xhr.open(dataType, url);
        xhr.send(null);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                success(xhr.responseText);
            };
        }
},


$().ajax({
    url: 'http://....,
    dataType: 'GET',
        success: function (res) {
            alert("接口返回數據:" + res);
        }
})
複製代碼

這樣一個簡版的jQuery以及實現了。看完簡版,咱們來分析一下jq的優點吧。

jQuery的優點

jQuery雖然在dom的處理上,成爲要害。可是他曾經的火熱,說明身上仍是不少優勢的。咱們分析一下:

  • 簡潔的api

不管上在選擇器上,仍是在網絡請求上,jQuery都用了幾個字母,就實現了咱們的長篇代碼。鏈式操做使頁面更加簡潔。

  • dom操做的封裝

jQuery封裝了大量經常使用的DOM操做,使開發者在編寫DOM操做相關程序的時候可以駕輕就熟。jQuery輕鬆地完成各類本來很是複雜的操做,讓JavaScript新手也能寫出出色的程序。jQuery在操做dom上自己是有優點的,只是咱們須要每次都去操做他,不像單頁面架構,只更新本身須要更新的部分

  • 小且不污

jQuery自己的引入很是小,最低差很少15K就解決,並且做者的封裝,值暴露了$,不會污染全局的變量。

  • 瀏覽器兼容性

單頁的兼容,哪一個支持ie10?沒記錯的話,官方都是聲明支持IE11以上吧。

可是jQuery卻不須要考慮這樣的問題,可以支持IE 6.0+、FF 2+、Safari 2.0+和Opera 9.0+下

  • 豐富的插件支持

在筆者的眼中,jQuery目前爲止的生態庫,是比其餘框架要多,且引入即用。特別是一些門戶須要的動畫效果的,遊戲的效果。他的生態圈比較歷史淵源。

  • 自定義插件

jQuery自定義插件,$.fn.extend便可擴展,十分方便。

框架建議

技能上容許時,不要一味的追求框架。三大框架當然是個不錯的框架,可是jQuery仍是有適合本身存在的地方,例如筆者寫門戶時,除了服務端渲染外,剩下的喜歡用簡單的jQuery去實現。不僅僅時候門戶,jQuery的劣勢是什麼?就是dom操做沒法減小。可是咱們自己的需求,就是不須要操做dom,或者少之又少,那就沒有缺陷了。因此沒有太多交互效果的網站中,依然仍是可使用jQuery。

框架源碼

可到github下載完整實例:github.com/zhuangweizh…

(function (window) {
    var zQuery = function (selector) {
        return new zQuery.fn.init(selector);
    }
    zQuery.fn = {
        init: function (selector, myDocument) {
            this.dom = this.dom ? this.dom : [];
            this.myDocument = myDocument ? myDocument : document;
            const childNodes = this.myDocument.childNodes;
            var rs = null;
            if (typeof (selector) != 'undefined') {
                if (selector.substr(0, 1) == "#") {//id選擇器
                    rs = this.myDocument.getElementById(selector.slice(1));
                    this.dom[0] = rs;
                    console.log("rs===" + rs.innerText + rs.innerHTML);
                } else if (selector.substr(0, 1) == ".") { //樣式選擇器
                    rs = this.myDocument.getElementsByClassName(selector.slice(1));
                    for (var i = 0; i < rs.length; i++) {
                        this.dom[i] = rs[i];
                    }
                }
            }
            return this;
        },
        find: function (selector) {
            if (this.dom.length == 1) {
                this.init(selector, this.dom[0]);
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.init(selector, this.dom[i]);
                }
            }
            return this;
        },
        html: function (value) {
            if (this.dom.length == 1) {
                this.dom[0].innerHTML = value;
            } else if (this.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style[attr] = value;
                }
            }
        },
        css: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style[attr] = value;
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style[attr] = value;
                }
            }
        },
        show: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style.display = 'block';
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style.display = 'block';
                }
            }
        },
        hide: function (attr, value) {
            if (this.dom.length == 1) {
                this.dom[0].style.display = 'none';
            } else if (this.dom.length > 1) {
                for (var i = 0; i < this.dom.length; i++) {
                    this.dom[i].style.display = 'none';
                }
            }
        },
        //異常請求
        ajax: function ({ url, dataType, success }) {
            var xhr = new XMLHttpRequest();
            xhr.open(dataType, url);
            xhr.send(null);
            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                    success(xhr.responseText);
                };
            }
        },
    }

    zQuery.fn.init.prototype = zQuery.fn;
    window.$ = zQuery;
})(window);
複製代碼

博客進度

序號 博客主題 相關連接
1 手寫vue_mini源碼解析 juejin.im/post/5f0326…
2 手寫react_mini源碼解析 juejin.im/post/5f154c…
3 手寫webpack_mini源碼解析 juejin.im/post/5f1793…
4 手寫jquery_mini源碼解析(即本文) juejin.im/post/5f1e38…
5 手寫vuex_mini源碼解析 預計下週
6 手寫vue_router源碼解析 預計8月
7 手寫diff算法源碼解析 預計8月
8 手寫promise源碼解析 預計8月
9 手寫原生js源碼解析(手動實現常見api) 預計8月
10 手寫react_redux,fiberd源碼解析等 待定,本計劃先出該文,整理有些難度
11 手寫koa2_mini 預計9月,前端優先

期間除了除了源碼篇,可能會寫一兩篇優化篇,基礎篇等。有興趣的歡迎持續關注。

下一篇將寫vuex的實現。

相關文章
相關標籤/搜索