本文适合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的实现。