对于操作 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. Pycharm查看源代码的问题

    linear = torch.nn.Linear() # 无法查看Linear的源代码 from torch.nn import Linear linear = Linear() # 可以查看Line ...

  2. PL/SQL中文乱码问题

    本人的使用情况是: 笨既没有安装Oracle,配置了PLSQL,在查询含有中文数据的时候遇到了查询的数据中有中文乱码的问题,中文显示为???,并且软件的某些提示也会出现.   先进行查询确认编码:   ...

  3. tensorflow源码分析——BasicLSTMCell

    BasicLSTMCell 是最简单的LSTMCell,源码位于:/tensorflow/contrib/rnn/python/ops/core_rnn_cell_impl.py.BasicLSTMC ...

  4. rocketmq的linux搭建环境

    3.3. 上传解压[两台机器] # 上传 apache-rocketmq.tar.gz 文件至/usr/local # tar -zxvf apache-rocketmq.tar.gz -C /usr ...

  5. DP&图论 DAY 5 上午

    DP&图论  DAY 5  上午 POJ 1125 Stockbroker Grapevine 有 N 个股票经济人可以互相传递消息,他们之间存在一些单向的通信路径.现在有一个消息要由某个人开 ...

  6. [windows菜鸟]C#中调用Windows API的技术要点说明

    在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的.本文将C#中调用API的要点汇集如下,希 ...

  7. Android Stuido中断点调试和高级调试

    写一个简单的调试程序 import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class M ...

  8. 【巨坑】springmvc 输出json格式数据的几种方式!

    最近公司项目需要发布一些数据服务,从设计到实现两天就弄完了,心中窃喜之. 结果临近部署时突然发现.....  服务输出的JSON 数据中  date 类型数据输出格式要么是时间戳,要么是  {&quo ...

  9. python3.6安装jpype1后引入jpype报“ImportError: numpy.core.multiarray failed to import”问题

    jpype是调用java接口的第三方库,通过该库,python可以运行java程序,从而解决一些调用java的问题,比如:java开发的接口,测试时, 有java的加密算法就不用python写一遍重复 ...

  10. openstack共享组件--rabbitmq消息队列(1)

    一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们. 消息传 ...