本文適合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
咱們新建一個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獲取,也能夠根據標籤名稱,根據類名等等。
咱們根據兩個要點,咱們給他定義一個初始化方法,且新建時候當即調用,修改一下他的返回值:
(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對象以後,接下來就是咱們如何改變他們的問題。常見的是:
以上方法,用原生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雖然在dom的處理上,成爲要害。可是他曾經的火熱,說明身上仍是不少優勢的。咱們分析一下:
不管上在選擇器上,仍是在網絡請求上,jQuery都用了幾個字母,就實現了咱們的長篇代碼。鏈式操做使頁面更加簡潔。
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的實現。