详细图解jQuery对象,以及如何扩展jQuery插件

早几年学习前端,大家都非常热衷于研究jQuery源码。我还记得当初从jQuery源码中学到一星半点应用技巧的时候常会有一种发自内心的惊叹,“原来JavaScript居然可以这样用!”

虽然随着前端的发展,另外几种前端框架的崛起,jQuery慢慢变得不再是必须。因此大家对于jQuery的热情低了很多。但是许多从jQuery中学到的技巧用在实际开发中仍然非常好用。简单的了解它也有助于我们更加深入的理解JavaScript。

这篇文章的主要目的就是跟大家分享一下,jquery对象是如何封装的。算是对于大家进一步学习jQuery源码的一个抛砖引玉。

使用jQuery对象时,我们这样写:

// 声明一个jQuery对象
$('.target') // 获取元素的css属性
$('.target').css('width') // 获取元素的位置信息
$('.target').offset()

在使用之初可能会有许多疑问,比如$是怎么回事?为什么不用new就可以直接声明一个对象等等。后来了解之后,才知道原来这正是jQuery对象创建的巧妙之处。

先直接用代码展示出来,再用图跟大家解释是怎么回事。

;
(function(ROOT) { // 构造函数
var jQuery = function(selector) { // 在jQuery中直接返回new过的实例,这里的init是jQuery的真正构造函数
return new jQuery.fn.init(selector)
} jQuery.fn = jQuery.prototype = {
constructor: jQuery, version: '1.0.0', init: function(selector) {
// 在jquery中这里有一个复杂的判断,但是这里我做了简化
var elem, selector;
elem = document.querySelector(selector);
this[0] = elem; // 在jquery中返回一个由所有原型属性方法组成的数组,我们这里简化,直接返回this即可
// return jQuery.makeArray(selector, this);
return this;
}, // 在原型上添加一堆方法
toArray: function() {},
get: function() {},
each: function() {},
ready: function() {},
first: function() {},
slice: function() {}
// ... ...
} jQuery.fn.init.prototype = jQuery.fn; // 实现jQuery的两种扩展方式
jQuery.extend = jQuery.fn.extend = function(options) { // 在jquery源码中会根据参数不同进行很多判断,我们这里就直接走一种方式,所以就不用判断了
var target = this;
var copy; for(name in options) {
copy = options[name];
target[name] = copy;
}
return target;
} // jQuery中利用上面实现的扩展机制,添加了许多方法,其中 // 直接添加在构造函数上,被称为工具方法
jQuery.extend({
isFunction: function() {},
type: function() {},
parseHTML: function() {},
parseJSON: function() {},
ajax: function() {}
// ...
}) // 添加到原型上
jQuery.fn.extend({
queue: function() {},
promise: function() {},
attr: function() {},
prop: function() {},
addClass: function() {},
removeClass: function() {},
val: function() {},
css: function() {}
// ...
}) // $符号的由来,实际上它就是jQuery,一个简化的写法,在这里我们还可以替换成其他可用字符
ROOT.jQuery = ROOT.$ = jQuery; })(window);

在上面的代码中,我封装了一个简化版的jQuery对象。它向大家简单展示了jQuery的整体框架情况。如果了解了整体框架,那么大家去读jQuery源码,就会变得非常轻松。

我们在代码中可以看到,jQuery自身对于原型的处理使用了一些巧妙的语法,比如jQuery.fn = jQuery.prototypejQuery.fn.init.prototype = jQuery.fn;等,这几句正式jQuery对象的关键所在,下面我用图给大家展示一下这中间的逻辑是怎么回事。

jQuery对象核心图

对象封装分析

在上面的实现中,代码首先在jQuery构造函数中声明了一个fn属性,并将其指向了原型jQuery.prototype。并在原型中添加了init方法。

jQuery.fn = jQuery.prototype = {
init: {}
}

之后又将init的原型,指向了jQuery.prototype。

jQuery.fn.init.prototype = jQuery.fn;

而在构造函数jQuery中,返回了init的实例对象。

var jQuery = function(selector) {

    // 在jQuery中直接返回new过的实例,这里的init是jQuery的真正构造函数
return new jQuery.fn.init(selector)
}

最后对外暴露入口时,将字符$jQuery对等起来。

ROOT.jQuery = ROOT.$ = jQuery;

因此当我们直接使用$('#test')创建一个对象时,实际上是创建了一个init的实例,这里的正真构造函数是原型中的init方法。

注意:许多对jQuery内部实现不太了解的盆友,在使用jQuery时常常会毫无节制使用$(),比如对于同一个元素的不同操作:

var width = parseInt($('#test').css('width'));
if(width > 20) {
$('#test').css('backgroundColor', 'red');
}

通过我们上面的一系列分析,我们知道每当我们执行$()时,就会重新生成一个init的实例对象,因此当我们这样没有节制的使用jQuery时是非常不正确的,虽然看上去方便了一些,但是对于内存的消耗是非常大的。正确的做法是既然是同一个对象,那么就用一个变量保存起来后续使用即可。

var $test = $('#test');
var width = parseInt($test.css('width'));
if(width > 20) {
$test.css('backgroundColor', 'red');
}

扩展方法分析

在上面的代码实现中,我还简单实现了两个扩展方法。

jQuery.extend = jQuery.fn.extend = function(options) {

    // 在jquery源码中会根据参数不同进行很多判断,我们这里就直接走一种方式,所以就不用判断了
var target = this;
var copy; for(name in options) {
copy = options[name];
target[name] = copy;
}
return target;
}

要理解它的实现,我们首先要明确的知道this的指向。如果你搞不清楚,可以回头去看看我们之前关于this指向的讲解。传入的参数options对象为一个key: value模式的对象,我通过for in遍历options,将key作为jQuery的新属性,value作为该新属性所对应的新方法,分别添加到jQuery方法和jQuery.fn中。

也就是说,当我们通过jQuery.extend扩展jQuery时,方法被添加到了jQuery构造函数中,而当我们通过jQuery.fn.extend扩展jQuery时,方法被添加到了jQuery原型中。

上面的例子中,我也简单展示了在jQuery内部,许多方法的实现都是通过这两个扩展方法来完成的。

当我们通过上面的知识了解了jQuery的大体框架之后,那么我们对于jQuery的学习就可以具体到诸如css/val/attr等方法是如何实现这样的程度,那么源码学习起来就会轻松很多,也会节约更多的时间。也给一些对于源码敬而远之的朋友提供了一个学习的可能。

有一个朋友留言给我,说她被静态方法,工具方法和实例方法这几个概念困扰了很久,到底他们有什么区别?

其实在上一篇文章中,关于封装一个对象,我跟大家分享了一个非常非常干货,但是却只有少数几个读者老爷get到的知识,那就是在封装对象时,属性和方法可以具体放置的三个位置,并且对于这三个位置的不同做了一个详细的解读。

而在实现jQuery扩展方法的想法中,一部分方法需要扩展到jQuery构造函数中,一部分方法需要扩展到原型中,当我们通读jQuery源码,还发现有一些方法放在了模块作用域中,至于为什么会有这样的区别,建议大家回过头去读读前一篇文章。

而放在构造函数中的方法,因为我们在使用时,不需要声明一个实例对象就可以直接使用,因此这样的方法常常被叫做工具方法,或者所谓的静态方法。工具方法在使用时因为不用创建新的实例,因此相对而言效率会高很多,但是并不节省内存。

而工具方法的特性也和工具一词非常贴近,他们与实例的自身属性毫无关联,仅仅只是实现一些通用的功能,我们可以通过$.each$('div').each这2个方法来体会工具方法与实例方法的不同之处。

在实际开发中,我们运用得非常多的一个工具库就是lodash.js,大家如果时间充裕一定要去学习一下他的使用。

$.ajax()
$.isFunction()
$.each()
... ...

而放在原型中的方法,在使用时必须创建了一个新的实例对象才能访问,因此这样的方法叫做实例方法。也正是由于必须创建了一个实例之后才能访问,所以他的使用成本会比工具方法高很多。但是节省了内存。

$('#test').css()
$('#test').attr()
$('div').each()

这样,那位同学的疑问就很简单的被搞定了。我们在学习的时候,一定不要过分去纠结一些概念,而要明白具体怎么回事儿,那么学习这件事情就不会在一些奇奇怪怪的地方卡住了。

所以通过$.extend扩展的方法,其实就是对工具方法的扩展,而通过$.fn.extend扩展的方法,就是对于实例方法的扩展。那么我们在使用的时候就知道如何准确的去使用自己扩展的方法了。

jQuery插件的实现

我在初级阶段的时候,觉得要自己编写一个jQuery插件是一件高大上的事情,可望不可即。但是通过对于上面的理解,我就知道扩展jQuery插件其实是一件我们自己也可以完成的事情。

在前面我跟大家分享了jQuery如何实现,以及他们的方法如何扩展,并且前一篇文章分享了拖拽对象的具体实现。所以建议大家暂时不要阅读下去,自己动手尝试将拖拽扩展成为jQuery的一个实例方法,那么这就是一个jQuery插件了。

具体也没有什么可多说的了,大家看了代码就可以明白一切。

// Drag对象简化代码,完整源码可在上一篇文章中查看
;
(function() { // 构造
function Drag(selector) {} // 原型
Drag.prototype = {
constructor: Drag, init: function() {
// 初始时需要做些什么事情
this.setDrag();
}, // 稍作改造,仅用于获取当前元素的属性,类似于getName
getStyle: function(property) {}, // 用来获取当前元素的位置信息,注意与之前的不同之处
getPosition: function() {}, // 用来设置当前元素的位置
setPostion: function(pos) {}, // 该方法用来绑定事件
setDrag: function() {}
} // 一种对外暴露的方式
window.Drag = Drag;
})(); // 通过扩展方法将拖拽扩展为jQuery的一个实例方法
(function ($) {
$.fn.extend({
becomeDrag: function () {
new Drag(this[0]);
return this; // 注意:为了保证jQuery所有的方法都能够链式访问,每一个方法的最后都需要返回this,即返回jQuery实例
}
})
})(jQuery);

后续文章内容一个大概预想

去年年末的时候就有了想要将JavaScript基础知识总结一下的这样一个想法,可是JavaScript基础知识确实并非全部是层层递进的关系,有很多碎片化的东西,所以之前一直没有找到一个合适的整理方法。

直到在segmentfault中我在给题主建议如何快速学习一门诸如react/vue这样的流行框架时,找到了一个好一点的思路,于是就有了这样一系列文章,虽然它并不全面,很多知识没有涉及到,但是其实我是围绕最终通过模块化来构建自己代码这样一个思路来总结的,因此这系列文章能够解决大家最核心的问题。

也正因为如此,这系列的文章的终点将会是在ES6环境下掌握react的使用。虽然前面我多多少少都涉及到了模块的一些概念,但是还差一个实践。因此最终我会以ES6的模块跟大家分享如何使用。

详细图解jQuery对象,以及如何扩展jQuery插件的更多相关文章

  1. jQuery对象进行方法扩展

    <!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>01 ...

  2. jquery对象介绍及一些jquery小特效

    一.jquery对象的介绍. 引入jquery库后,通过形如$("#id")的方式得到的对象叫做jquery对象.如var $uu = $("#username" ...

  3. 怎样判断一个jquery对象是否为空jquery对象

    if ( $('#myDiv').length ){} http://stackoverflow.com/questions/47... 也可以直接判断$('#myDiv')[0]===undefin ...

  4. dom对象转成jquery对象时候 变成数组 jquery转成dom时候 取数组第一个

  5. Dom对象和JQuery对象的详细介绍及其区别

    一直搞不清Dom对象和JQuery对象之间的区别,今天好好总结下 1.dom对象(摘抄自百度百科http://baike.baidu.com/link?url=4L8bZ7kW6kE-it4F-1LU ...

  6. 为Jquery类和Jquery对象扩展方法

    转:https://www.cnblogs.com/keyi/p/6089901.html jQuery为开发插件提拱了两个方法,分别是: JavaScript代码 jQuery.fn.extend( ...

  7. 理解jQuery对象$.html

    前面的话 如果要比喻jQuery和原生javascript的关系,我个人认为是自动档和手动档汽车的区别.使用原生javascript,可以知道离合器以及档位的作用:而使用jQuery,则把离合器和手动 ...

  8. DOM对象与jquery对象有什么不同

    jQuery对象和DOM对象使用说明,需要的朋友可以参考下.1.jQuery对象和DOM对象第一次学习jQuery,经常分辨不清哪些是jQuery对象,哪些是 DOM对象,因此需要重点了解jQuery ...

  9. 扩展JQuery和JS的方法

    //JS的扩展方法: 1 定义类静态方法扩展 2 定义类对象方法扩展            var aClass = function(){} //1 定义这个类的静态方法            aC ...

  10. jQuery对象与JS原生dom对象之间的转换

    jQuery就是JS的一个扩展库,工具库,提供很多方便快捷的方法,所以将JS对象转换为jQuery对象后,能更方便地操作这个对象.但是jQuery对象也不是万能的,有一些JS对象有的能,jQuery对 ...

随机推荐

  1. Java >>>运算符 和 >> 运算符

    >>> 在java 表示有符号右移.什么意思呢?就是最高位符号位也会移动. 我们知道,>>表示有符号右移. -1>> 1 = -1 -1>>2 = ...

  2. dubbo之注册管理中心

    一.在dubbo的框架中注册中心是必要的一个环节,这个也是分布式部署的一个必要环节.在dubbo的架构基本图中可以看出,基本上所有的服务都是通过注册中心进行注册,然后在通过注册中心,暴露出接口来. 二 ...

  3. ping telnet 指令

    Ping 一 作用 ping能够辨别网络功能的某些状态,这些状态是日常网络故障诊断的基础.Ping能够识别连接的二进制状态(看是否连通).Ping命令通过过向计算机发送ICMP回应报文并监听回应报文的 ...

  4. nodejs HTTP服务

    nodejs中的HTTP服务   nodejs最重要的方面之一是具有非常迅速的实现HTTP和HTTPS服务器和服务的能力.http服务是相当低层次的,你可能要用到不同的模块,如express来实现完整 ...

  5. Qt-QPalette-调色板学习

    已经很久没有更新博客了,一是因为换了公司,完全是断网开发了,没有时间来写博客,最主要的就是温水煮青蛙,自己在舒适的环境中越来越懒了,最近打算强制自己更新一波.不知道能坚持多久.由于目前没有具体的Qt项 ...

  6. Fiddler - 拦截手机请求

    1. 在电脑上安装Fillder. 安装好之后的Fiddler 打开是这样的: 2. 浏览器访问http://127.0.0.1:8888/fiddler,下载证书并安装 3. 打开抓取https请求 ...

  7. UniMelb Comp30022 IT Project (Capstone) - 1.Android入门

    1. Android入门 Android系统架构 Android系统:四层架构.五块区域 1. Linux内核层 Linux Kernel:为Android设备的硬件提供了底层驱动 2. 系统运行库层 ...

  8. Java应用基础微专业-进阶篇

    第1章--使用对象 1.1 字符类型 char c = 65; // char --> int char c = '\u0041'; // \u: unicode + (Hex 41--> ...

  9. lintcode112 删除排序链表中的重复元素

    删除排序链表中的重复元素   给定一个排序链表,删除所有重复的元素每个元素只留下一个. 您在真实的面试中是否遇到过这个题? Yes 样例 给出 1->1->2->null,返回 1- ...

  10. 孤荷凌寒自学python第八十六天对selenium模块进行较详细的了解

    孤荷凌寒自学python第八十六天对selenium模块进行较详细的了解 (今天由于文中所阐述的原因没有进行屏幕录屏,见谅) 为了能够使用selenium模块进行真正的操作,今天主要大范围搜索资料进行 ...