详细图解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. 我错了的N个学习

    其实在面对自己的失误的时候,勇敢的说我错了,就是我错了,不找借口,不找理由,然后就开始分析错误的原因,分析,总结,学习,提高,成为自己成长的垫脚石,这个才是正确的做法,做人要拿出精神头,拼死至休的劲头 ...

  2. MySQL高级-索引优化

    索引失效 1. 2.最佳左前缀法则 4. 8. 使用覆盖索引解决这个问题. 二.索引优化 1.ORDER BY 子句,尽量使用Index方式排序,避免使用FileSort方式排序 MySQL支持两种方 ...

  3. Spring缓存穿透问题修复

    本文来自网易云社区. 本剧情纯属真实,犹如雷同实乃缘分. 发生 事情的发生在某天早上,天气怎样反正是忘了,只记得当时监控平台大量的数据库错误报警. 作为后端开发,当看到日志中大量的db连接获取失败,心 ...

  4. JavaWeb(二)——Tomcat服务器(一)

    一.Tomcat服务器端口的配置 Tomcat的所有配置都放在conf文件夹之中,里面的server.xml文件是配置的核心文件. 如果想修改Tomcat服务器的启动端口,则可以在server.xml ...

  5. Python全栈 Web(边框、盒模型、背景)

    原文地址 https://yq.aliyun.com/articles/634926 ......................................................... ...

  6. Java并发基础--Lock的学习

    一.Lock的出现 Lock的主要作用实现线程之间的同步互斥,与synchronized关键字的效果是一样的,synchronized是Java语言内置的特性,那么为什么又出现了Lock呢?原因是sy ...

  7. 使用maven构建web项目(简易版)

    在eclipse中使用maven开发一个web项目 第一步:安装maven:在Windows上安装Maven 中间省略很多步骤....(包括关于eclipse中配置maven) 第二步:不用懂任何ma ...

  8. jQuery用unbind方法去掉hover事件及其他方法介绍

    近日项目开发十分的繁忙,其中一个需求是实现响应式导航.(响应式的问题我们在css相关的博客中再交流) 大家都知道导航是需要下来菜单效果的,必然就会用到 jQuery的 hover() 方法.若是导航放 ...

  9. django request bug

    bug描述:django请求request接收数据时,如果参数中包含分号时,会导致分号后面的消息丢失. 比如前台js调用代码 $.post('/get_params', { "A" ...

  10. 贵州省未来二十年的投资机会的探讨2>

    房产投资 升值最快的 在教育资源丰富  生活方便的 地方 价格和地段取其中之一. 其次 车位 再其次墓地等 公寓住房. 还有商标 和网站注册 公司注册 除了以上的这些  还有茅台生效酒 收藏