对于操作 DOM 来说,jQuery 是非常方便的一个库,虽然如今随着 React, Vue 之类框架的流行,jQuery 用得越来越少了,但是其中很多思想还是非常值得我们学习的,这篇文章将介绍如何从零开始实现一个简化版 jQuery

在这里,我把这个库命名为 Clus(class 的谐音),下面以 $ 符号代替。

首先需要声明一个构造函数并做一些初始化操作:


function $(selector) {
return new $.fn.init(selector);
} $.fn = $.prototype = {
contructor: $,
init,
};

可以看到,该构造函数返回一个 $.fn.init 的实例,这样做的好处就是在使用的时候不要每次都 new 一个构造函数就可以创建一个新的实例了,可以看出来,整个核心都在 init 函数上了:


function init(selector) {
const fragmentRE = /^\s*<(\w+|!)[^>]*>/;
const selectorType = $.type(selector);
const elementTypes = [1, 9, 11]; let dom; if (!selector) {
dom = [],
dom.selector = selector;
} else if (elementTypes.indexOf(selector.nodeType) !== -1 || selector === window) {
dom = [selector],
selector = null;
} else if (selectorType === 'function') {
return $(document).ready(selector);
} else if (selectorType === 'array') {
dom = selector;
} else if (selectorType === 'object') {
dom = [selector],
selector = null;
} else if (selectorType === 'string') {
if (selector[0] === '<' && fragmentRE.test(selector)) {
dom = $.parseHTML(selector),
selector = null;
} else {
dom = [].slice.call(document.querySelectorAll(selector));
}
} dom = dom || [];
$.extend(dom, $.fn);
dom.selector = selector; return dom;
}

可以很清楚的看到,根据传入的参数类型的不同进行一些不同的操作,比如传入的是函数的话,则该函数里的操作的都是 DOM Ready 之后的操作了;再比如传入的是字符串的话,并且如果是标签的话,则会把这段标签字符串解析成 DOM Fragment,如果是普通字符串,则会调用 document.querySelectorAll() 方法来查找 DOM。

相信大家都能很容易的看明白上面的代码,不过有一点值得一提的是 $.extend(dom, $.fn); 这段代码,其含义是把实例上的所有方法都添加到 dom 这个数组对象中,这样做的目的就是为了可以直接链式调用某个实例的方法,比如 $('.clus').addClass('hello'),这个 addClass() 方法就是在 $.fn 上实现的。因此所有在 $.fn 实现的方法都可以通过 $(selector).method() 这种方式来调用了。

至于 extend() 方法我认为是除了 init() 方法以外,整个库中最核心的一个方法了,代码如下:


export default function extend() {
let options, name, clone, copy, source, copyIsArray,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false; if (typeof target === 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
} if (typeof target !== 'object' && $.type(target) !== 'function') {
target = {};
} if (i === length) {
target = this;
i--;
} for (; i < length; i++) {
//
if ((options = arguments[i]) !== null) {
// for in source object
for (name in options) { source = target[name];
copy = options[name]; if (target == copy) {
continue;
} // deep clone
if (deep && copy && ($.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
// if copy is array
if (copyIsArray) {
copyIsArray = false;
// if is not array, set it to array
clone = source && Array.isArray(source) ? source : [];
} else {
// if copy is not a object, set it to object
clone = source && $.isPlainObject(source) ? source : {};
} target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
} return target;
}

可以看到,和 jQuery 的实现一毛一样,没错就是从那儿 copy 过来的当然一样。

下面以 addClass() 方法为例介绍如何操作 DOM 的:


function addClass(cls) {
let classes, clazz, el, cur, curValue, finalValue, j, i = 0; if (typeof cls === 'string' && cls) {
classes = cls.match(rnotwhite) || []; while((el = this[i++])) {
curValue = getClass(el);
cur = (el.nodeType === 1) && ` ${curValue} `.replace(rclass, ' '); if (cur) {
j = 0; while((clazz = classes[j++])) {
// to determine whether the class that to add has already existed
if (cur.indexOf(` ${clazz} `) == -1) {
cur += clazz + ' ';
}
finalValue = $.trim(cur);
if ( curValue !== finalValue ) {
el.setAttribute('class', finalValue);
}
}
}
}
} return this;
} $.fn.addClass = addClass;

值得一提的就是在实例方法中,this 关键字可以访问到根据选择器所查询到的所有元素的集合,在这里是通过 while 循环来对每个元素进行操作。要实现类似 $(selector).addClass().removeClass() 这样的链式操作,只需要在每个实例方法中返回一个 this 即可。要实现其他实例方法比如 hasClass() 之类的也是类似的方法。

其实每个实例方法都是通过 this 关键字来获取查询到的元素,然后遍历这些元素来针对每个元素进行具体的操作,在举一个栗子:


function append(DOMString) {
let el, i = 0,
fregmentCollection = $.parseHTML(DOMString),
fregments = Array.prototype.slice.apply(fregmentCollection); while((el = this[i++])) {
fregments.map(fregment => {
el.appendChild(fregment);
});
} return this;
} $.fn.append = append;

上面是 append() 的实现,首先先解析 DOMStringfregment,然后就是遍历查询到的元素(通过 this 关键字)并针对每个元素去进行 appendChild() 的操作,从而把 DOM 插入到匹配到的所有元素中。

其他实例方法也是通过类似的方式实现的,这里就不一一细说了,想更详细的查看其他方法的实现可以直接到 Clus 中查看源码。

来源:https://segmentfault.com/a/1190000017750847

如何实现一个简化版的 jQuery的更多相关文章

  1. 一个强大的jquery分页插件

    点击这里查看效果 这个分页插件使用方便,引用keleyidivpager.js和keleyidivpager.css文件,然后在htm(或者php,aspx,jsp等)页面中对分页总数,参数名,前缀后 ...

  2. 推荐一个内容滚动jquery插件

    myslider是一个内容滚动jquery插件,版本0.1.2的每次滚动内容是一行内容,可以是文字,可以是一个链接,还可以是图片. 官方网址:http://keleyi.com/jq/myslider ...

  3. WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。

    WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping.请添加一个名为 jquery (区分大小写)的 ScriptRes ...

  4. (转)要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。”的解决办法。

    要“jquery”ScriptResourceMapping.请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping.”的解决办法. 1.先将aspnet.scri ...

  5. 编写一个简单的Jquery插件

    1.实现内容 定义一个简单的jquery插件,alert传递进来的参数 2.插件js文件(jquery.showplugin.js) (function ($) { //定义插件中的方法 var me ...

  6. 一个简单的jQuery插件开发实例

    两年前写的一个简单的jQuery插件开发实例,还是可以看看的: <script type="text/javascript" src="jquery-1.7.2.m ...

  7. 从零实现一个简易的jQuery框架之二—核心思路详解

    如何读源码 jQuery整体框架甚是复杂,也不易读懂.但是若想要在前端的路上走得更远.更好,研究分析前端的框架无疑是进阶路上必经之路.但是庞大的源码往往让我们不知道从何处开始下手.在很长的时间里我也被 ...

  8. WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping 异常详细信息: System.InvalidOperationException: WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的

    WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping.请添加一个名为 jquery (区分大小写)的 ScriptRes ...

  9. 制作一个简洁的jquery插件

    原文:http://mp.weixin.qq.com/s?__biz=MzAxMzgwNDU3Mg==&mid=401571467&idx=1&sn=08cb00963e6ef ...

随机推荐

  1. 大体知道java语法2----------理解面向对象

    我参加过大大小小n场面试,被好几位面试官问到过:能不能谈谈面向对象的几大特征?什么是面向对象?对于这两个问题,我始终觉得一定要理解,其实不只是这种概念题(姑且算它是概念题吧),包括各种语法都应该去理解 ...

  2. 微信支付接口,curl错误代码58

    微信支付接口,curl错误代码58 之前的微信付款到用户零钱都是好好的,今天运营来找我, 我想了了下,就是进行了网站搬家 看了下 微信支付相关的证书配置文件 知道了,在这个 要改下证书的路径 WxPa ...

  3. hdu 1166 线段树 奇兵布阵

    #include<iostream> using namespace std; ; )*];//n个叶子就有2*n-4*n个节点 ]; int n; void getup(int root ...

  4. Java之HSF搭建demo

    1.去阿里云官网下载Demo edas-app-demo.zip 2.下载Ali-Tomcat和Pandora,注意红色下面字体 a)下载 Ali-Tomcat,保存后解压至相应的目录(如:d:\wo ...

  5. 学习前端第二天心得体会(初步了解HTML5的部分API以及画布Canvas)

    一.HTML5部分API 1.选择器querySelector和querySelectorAll 1.1.querySelector:返回文档中匹配指定的CSS选择器的第一元素.  document. ...

  6. 八十:memcached之安装与参数

    Memcached是一个高并发的内存键值对缓存系统,它的主要作用是将数据库查询结果,内容,以及其它一些耗时的计算结果缓存到系统内存中,从而加速Web应用程序的响应速度. 官网:http://memca ...

  7. 【汇总】数据库提权(mysql、mssql)

    日期:2018-04-03 11:46:45 作者:Bay0net 介绍:利用 mssql 的 sa 账号提权.利用 MySQL 的 UDF 提权 0x01.mssql 提权 恢复 xp_cmdshe ...

  8. Python re 正则表达式【一】【转】

    数量词的贪婪模式与非贪婪模式 正则表达式通常用于在文本中查找匹配的字符串.Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符:非贪婪的则相反,总是尝试匹配尽 ...

  9. document.documentElement 和 document.body

    MDN : The Document.documentElement read-only property returns the Element that is the root element o ...

  10. 利用delve(dlv)在Visual Code中进行go程序的远程调试-debug方式

    最近碰到一个问题,如何在Windows的IDE或者文本编辑器上,远程调试Linux服务器上的golang程序. 虽然想说gdb走你,但既然go有dlv这样的类似Java的jdwp的原生方案,而且我用的 ...