前端基础进阶(十三):详细图解jQuery对象,以及如何扩展jQuery插件
jQuery在前端史上有它非常超然的历史地位,许多从中学到的技巧在实践开发中仍然非常好用。简单的了解它有助于我们更加深入的理解JavaScript。如果你能够从中看明白jquery是如何一步步被取代的,那么,我想你的收益远不止学会使用了一个库那么简单。
因此,我的态度是,项目中你可以不用,但是我建议你了解了解jQuery的思想。
这篇文章的主要目的,是从面向对象的角度,跟大家分享jquery对象是如何封装的。
使用jQuery对象时,我们这样写:
1 | // 声明一个jQuery对象 |
在使用之初可能会有许多疑问,比如$
是怎么回事?为什么不用new
就可以直接声明一个对象?等等。了解之后,才知道原来这正是jQuery对象创建的巧妙之处。
先直接用代码展示出来,再用图跟大家解释是怎么回事。
1 | ; |
在上面的代码中,我封装了一个简化版的jQuery对象,给大家简单展示了jQuery的整体骨架。
在代码中可以看到,jQuery自身对于原型的处理使用了一些巧妙的方式,比如jQuery.fn = jQuery.prototype
,jQuery.fn.init.prototype = jQuery.fn
等,这几句正是jQuery对象的关键所在。看图分析。
对象封装分析
在上面的实现中,首先在jQuery构造函数里声明了一个fn属性,并将其指向了原型jQuery.prototype
。然后在原型中添加了init方法。
1 | jQuery.fn = jQuery.prototype = { |
之后又将init的原型,指向了jQuery.prototype
。
1 | jQuery.fn.init.prototype = jQuery.fn; |
而在构造函数jQuery中,返回了init的实例对象。
1 | var jQuery = function (selector) { |
最后对外暴露入口时,将字符$
与jQuery
对等起来。
1 | ROOT.jQuery = ROOT.$ = jQuery; |
因此当我们直接使用$('#test')
创建一个对象时,实际上是创建了一个init的实例,这里的真正构造函数是原型中的init方法。
注意:许多对jQuery内部实现不太了解的盆友,常常会毫无节制使用$(),比如对于同一个元素的不同操作。
1 | var width = parseInt($('#test').css('width')); |
通过上面的一系列分析,我们知道每当执行$()时,就会重新生成一个init的实例对象,因此当我们这样没有节制的使用jQuery是非常不正确的,虽然看上去方便了一些,但是对于内存的消耗非常大。正确的做法是既然是同一个对象,那么就用一个变量保存起来后续使用即可。
1 | var $test = $('#test'); |
扩展方法分析
在上面的代码实现中,简单实现了两个扩展方法。
1 | jQuery.extend = jQuery.fn.extend = function (options) { |
传入的参数options为一个key: value
模式的对象,通过for in
遍历options,将key作为jQuery的新属性,value作为该新属性所对应的新方法,分别添加到jQuery方法和jQuery.fn中。
当我们通过jQuery.extend
扩展jQuery时,方法被添加到了jQuery构造函数中,当通过jQuery.fn.extend
扩展jQuery时,方法被添加到了jQuery原型中。
上面的例子中,简单展示了在jQuery内部,许多方法的实现都是通过这两个扩展方法来完成的。
有一个朋友留言给我,说他被静态方法,工具方法和实例方法这几个概念困扰了很久,到底它们有什么区别?
在封装对象时,属性和方法可以具体放置的三个位置,并且对于这三个位置的不同做了一个详细的解读。
{ % image https://bu.dusays.com/2022/06/27/62b9cdd2affcc.webp % }
在实现jQuery扩展方法时,一部分方法需要扩展到构造函数中,一部分方法需要扩展到原型中,当我们通读jQuery源码时,还发现有一些方法放在了模块作用域中,至于为什么会有这样的区别,建议大家回过头去读读前一篇文章。
这里用一个例子简单区分一下。
1 | // 模块内部 |
如上例中,each就是一个工具方法,或者说静态方法。
工具方法的特性也和工具一词非常贴近,他们与实例的自身属性毫无关联,仅仅只是实现一些通用的功能,我们可以通过$.each
与$('div').each
这2个方法来体会工具方法与实例方法的不同之处。
在实际开发中,我们运用得非常多的一个工具库就是lodash.js
,大家如果时间充裕一定要去学习一下它的使用。
1 | $.ajax() |
放在原型中的方法,在使用时必须创建了一个新的实例对象才能访问,因此这样的方法叫做实例方法。也正是因为这一点,它的使用成本会比工具方法高一些。但是相比构造函数方法,原型方法更节省内存。
1 | $('#test').css() |
这样,那位同学的疑问就被搞定啦。我们在学习的时候,一定不要过分去纠结一些概念,而要明白这些概念背后的具体场景是怎么回事儿,那么学习这件事情就不会在一些奇奇怪怪的地方卡住了。
所以通过$.extend扩展的方法,其实就是对工具方法的扩展,而通过$.fn.extend扩展的方法,就是对于实例方法的扩展。
jQuery插件的实现
在前面我跟大家分享了jQuery如何实现,以及他们的方法如何扩展,并且前一篇文章分享了拖拽对象的具体实现。所以建议大家自己动手尝试将拖拽扩展成为jQuery的一个实例方法,这就是一个jQuery插件了。
具体也没有什么可多说的了,大家看了代码就可以明白一切。
1 | // Drag对象简化代码,完整源码可在上一篇文章中查看 |