从本篇开始会陪大家一起从零开始走一遍 jQuery 的奇妙旅途,在整个系列的实践中,我们会把 jQuery 的主要功能模块都了解和实现一遍。

这会是一段很长的历程,但也会很有意思 —— 作为前端领域的经典之作,jQuery 里有着太多奇思妙想,如果能够深入理解它,对于我们稳固js基础、提升前端大法技能来说大有裨益。

另外,本系列的相关代码均可以从 我的github 上获取到。

1. 免 new 实现

我们在使用很多插件的时候,都需要使用 new XXX() 的写法来实例化一个引用:

var list = new Slip(document.getElementById('slip'), {
//options
});

jQuery 同样作为一个面向对象的工具库,在我们创建一个实例时却无需使用 new 语法,节省了一些代码量:

var $div = $('div');
//不需要如下写法:
//var $div = new $('div');

这种便捷的形式依赖了工厂模式,其实现非常简单,把 new 封装在库内即可,让每次调用 jQuery() 时自行在内部进行一次实例化:

(function() {
var _jQuery = window.jQuery,
_$ = window.$; var version = "0.0.1",
jQuery = function(selector) {
console.log(document.querySelector(selector))
}; jQuery.prototype = {
jquery: version,
constructor: jQuery
}; window.$ = window.jQuery = function(selector) {
return new jQuery(selector); //notice here~
};
})();

留意这里我们走的 IIFE 形式,让 jQuery 代码库形成自己的作用域,避免污染外部变量。

于是乎以上就是咱写的第一个 JQ 雏形,简单跑一下:

<div></div>
<script>
var $div = $('div'); //<div></div>
console.log($div.jquery); //0.0.1
</script>

别忘了后续我们还希望能通过 $.extend / $.fn.extend 来扩展 JQ 的静态方法和原型方法,我们把出口方法抽出来增加这个 extend 的API:

    function Factory(selector){  //抽出构造函数
return new jQuery(selector);
} Factory.fn = jQuery.prototype; Factory.extend = Factory.fn.extend = function(){
console.log(this)
}; window.$ = window.jQuery = Factory;

这样我们也能直接通过 $.fn.jquery 来获取当前 JQ 版本号了。

如果希望可以通过 $.prototype 直接访问 jQuery 的原型对象,再修改下这句代码即可:

Factory.prototype = Factory.fn = jQuery.prototype;

2. 写法优化

事实上我们不太喜欢再写多一个冗余的 Factory 构造函数来作为 window.jQuery 的引用,也不喜欢(在模块内部)使用 Factory.extend() 来扩展 JQ,它听起来和 JQ 没有半毛钱关系。

如果可以,直接把 jQuery 方法作为接口输出,且在模块内部能以 jQuery.extend()  的形式来调用扩展接口,这样的形式更佳。

也就是说我们希望代码应该是这样写的:

    jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
};
window.jQuery = window.$ = jQuery;

“直接把 jQuery 方法作为接口输出”意味着我们要把工厂模式挪入 jQuery 方法中,显然我们不能这样改:

    var version = "0.0.1",
jQuery = function (selector) {
return new jQuery(selector);
};

这样死循环了,调用栈会直接爆掉~

于是我们可以抽出一个 init 方法来做初始化处理(比如简单地注入检索到的元素到JQ对象中),把 jQuery 方法中的内容更改为 return new init(selector) 就行了。

保证两个前提:

1. this 指向 jQuery 上下文

2. 其原型指向 jQuery 的原型

第一点很好理解,方便我们直接在 init 方法中通过对 this 的操作来处理 JQ 实例上下文,如:

  //注入元素到 JQ 实例对象中
this[0] = elem;
this.length = 1;

针对这点,我们不妨把 init 作为 jQuery.prototype 的属性方法来实现:

    var version = "0.0.1",
jQuery = function(selector) {
return new jQuery.fn.init() //修改点1
}; //方便我们使用 jQuery.fn 来引用 jQuery 原型对象
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery
}; //修改点2 —— init 作为原型方法,确保 this 指向正确
jQuery.fn.init = function( selector ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
}; jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
}; window.$ = window.jQuery = jQuery;

然而这时候存在一个问题 —— JQ实例对象无法访问原型属性/方法:

    var $div = $('div');
console.log($div.jquery); //undefined

原因很简单——我们还未实现上述提及的第二个前提——“init 原型指向 jQuery 的原型”

在 js 中,实例的内部原型(__proto__)总是指向其构造函数的原型(prototype),而经过我们这番修改,JQ实例的构造函数已经变成了 jQuery.fn.init ,而其原型并非指向 jQuery 的原型,这导致 JQ 实例无法顺其原型链爬取到 jQuery.prototype。

要实现这个条件,只需要做小小改动——把 jQuery.fn.init 的原型指向 jQuery 的原型(jQuery.prototype / jQuery.fn)即可:

    var init = jQuery.fn.init = function( selector ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
}; init.prototype = jQuery.fn; //修改点

这里贴下完整代码:

    var version = "0.0.1",
jQuery = function(selector) {
return new jQuery.fn.init(selector)
}; jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery
}; var init = jQuery.fn.init = function( selector, context, root ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
}; init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
}; window.$ = window.jQuery = jQuery;

3. 链式写法实现

JQ 里一个很大的亮点是,它支持链式写法,调用起来非常方便:

$('div').removeClass('hide').css('width', '100px')

其实现其实非常简单 —— 确保每个调用的方法尾部均返回自身即可,这里我们新增两个实例方法做示例:

    jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
setBackground: function(){
this[0].style.background = 'yellow';
return this //返回自身引用
},
setColor: function(){
this[0].style.color = 'blue';
return this //返回自身引用
}
}; var init = jQuery.fn.init = function( selector ) {
if ( !selector ) {
return this;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
return this;
}
};

链式调用:

<div>hello world</div>

<script>
var $div = $('div');
$div.setBackground().setColor();
</script>

效果如下,杠杠的:

4. 冲突处理

存在某些情况,用户可能并不想拿 window.$ 甚至 window.jQuery 来引用 JQ 接口,或者已经有其它库使用了 window.$ 这个变量,如果我们粗暴地改变其引用肯定是不合理的。

so 我们来实现 JQ 中冲突处理的静态接口 jQuery.noConflict,这意味着在代码段开始时,就得先保存下当前 window.$ 和 window.jQuery 两个变量:

(function(){
var _jQuery = window.jQuery,
_$ = window.$; //var version = "0.0.1"......
})()

然后是实现 noConflict 方法,退耕还林,把保存的变量吐回去即可:

(function(){
var _jQuery = window.jQuery,
_$ = window.$; //var version = "0.0.1"...... jQuery.noConflict = function( deep ) {
//确保window.$没有再次被改写
if ( window.$ === jQuery ) {
window.$ = _$;
} //确保window.jQuery没有再次被改写
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
} return jQuery; //返回 jQuery 接口引用
}; window.jQuery = window.$ = jQuery;
})();

deep 参数类型为 Boolean,若为真,表示要求连window.jQuery 变量都需要吐回去。

留意在尾部我们返回了 jQuery 的接口引用,这意味着我们可以以

var $$$ = jQuery.noConflict()

的形式来把它赋予新的变量。

接着在外部运行如下代码:

<head>
<meta charset="UTF-8">
<title>DIY A JQ</title>
<script>
$ = 'old $';
jQuery = 'old JQ'
</script>
<script src="jQuery.js"></script>
</head>
<body> <div>hello world</div> <script>
var $div = $('div');
$div.setBackground().setColor(); var $$$ = $.noConflict(true);
console.log($);
console.log(jQuery);
console.log($$$);
</script>

输出如下:

第一篇就写到这里,相关的代码可以从 我的github 上下载到。

下次我们会试着实现模块化的写法,并与时俱进,改用 ES6解构赋值语法 + Rollup 来进行打包以减少可能存在的冗余代码段。

共勉~

从零开始,DIY一个jQuery(1)的更多相关文章

  1. 从零开始,DIY一个jQuery(3)

    在前两章,为了方便调试,我们写了一个非常简单的 jQuery.fn.init 方法: jQuery.fn.init = function (selector, context, root) { if ...

  2. 从零开始,DIY一个jQuery(2)

    在上篇文章我们简单实现了一个 jQuery 的基础结构,不过为了顺应潮流,这次咱把它改为模块化的写法,此举得以有效提升项目的可维护性,因此在后续也将以模块化形式进行持续开发. 模块化开发和编译需要用上 ...

  3. 自己diy一个jquery分页插件

    js基础学习过程中,期间经历换工作的各种面试,很多面试官问过:有没有写过jquery插件?等类似问题. 就个人而言,关于jquery插件的文章确实看过不少,但是一直没有动手写一个,一是不想在目前学习j ...

  4. 从零开始构建一个的asp.net Core 项目

    最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"He ...

  5. 一起学习造轮子(二):从零开始写一个Redux

    本文是一起学习造轮子系列的第二篇,本篇我们将从零开始写一个小巧完整的Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Red ...

  6. 一起学习造轮子(一):从零开始写一个符合Promises/A+规范的promise

    本文是一起学习造轮子系列的第一篇,本篇我们将从零开始写一个符合Promises/A+规范的promise,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Pr ...

  7. 一起学习造轮子(三):从零开始写一个React-Redux

    本文是一起学习造轮子系列的第三篇,本篇我们将从零开始写一个React-Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Re ...

  8. 从零开始构建一个的asp.net Core 项目(一)

    最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到“Hello W ...

  9. Vue.js 入门:从零开始做一个极简 To-Do 应用

    Vue.js 入门:从零开始做一个极简 To-Do 应用 写作时间:2019-12-10版本信息:Vue.js 2.6.10官网文档:https://cn.vuejs.org/ 前言  学习 Vue ...

随机推荐

  1. RxJS + Redux + React = Amazing!(译二)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...

  2. JavaScript String对象

    本编主要介绍String 字符串对象. 目录 1. 介绍:阐述 String 对象的说明以及定义方式. 2. 实例属性:介绍 String 对象的实例属性: length. 3. 实例方法:介绍 St ...

  3. favicon.ioc使用以及注意事项

    1.效果 2.使用引入方法 2.1 注意事项:(把图标命名为favicon.ico,并且放在根目录下,同时使用Link标签,多重保险) 浏览器默认使用根目录下的favicon.ico 图标(如果你并没 ...

  4. python黑魔法 -- 内置方法使用

    很多pythonic的代码都会用到内置方法,根据自己的经验,罗列一下自己知道的内置方法. __getitem__ __setitem__ __delitem__ 这三个方法是字典类的内置方法,分别对应 ...

  5. 旺财速啃H5框架之Bootstrap(一)

    接下来的时间里,我将和大家一起对当前非常流行的前端框架Bootstrap进行速度的学习,以案例的形式.对刚开始想学习Bootstrap的同学而找不着边的就很有帮助了.如果你想详细的学习Bootstra ...

  6. Java 8 的 Nashorn 脚本引擎教程

    本文为了解所有关于 Nashorn JavaScript 引擎易于理解的代码例子. Nashorn JavaScript 引擎是Java SE 8的一部分,它与其它像Google V8 (它是Goog ...

  7. JavaScript自定义媒体播放器

    使用<audio>和<video>元素的play()和pause()方法,可以手工控制媒体文件的播放.组合使用属性.事件和这两个方法,很容易创建一个自定义的媒体播放器,如下面的 ...

  8. 多线程爬坑之路-Thread和Runable源码解析

    多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...

  9. SignalR系列目录

    [置顶]用SignalR 2.0开发客服系统[系列1:实现群发通讯] [置顶]用SignalR 2.0开发客服系统[系列2:实现聊天室] [置顶]用SignalR 2.0开发客服系统[系列3:实现点对 ...

  10. springmvc+bootstrap+jquerymobile完整搭建案例(提供下载地址)

    用一张简单的截图说明下,然后提供一个下载地址. bootstrap的大部分样式官方都是写好的,所以只需要class="官方样式即可",具体可以看官方的案例,下面来个地址 http: ...