摘要:而在構(gòu)造函數(shù)中,返回了的實(shí)例對(duì)象。在中直接返回過的實(shí)例,這里的是的真正構(gòu)造函數(shù)最后對(duì)外暴露入口時(shí),將字符與對(duì)等起來。因此當(dāng)我們直接使用創(chuàng)建一個(gè)對(duì)象時(shí),實(shí)際上是創(chuàng)建了一個(gè)的實(shí)例,這里的正真構(gòu)造函數(shù)是原型中的方法。
早幾年學(xué)習(xí)前端,大家都非常熱衷于研究jQuery源碼。我還記得當(dāng)初從jQuery源碼中學(xué)到一星半點(diǎn)應(yīng)用技巧的時(shí)候常會(huì)有一種發(fā)自內(nèi)心的驚嘆,“原來JavaScript居然可以這樣用!”
雖然隨著前端的發(fā)展,另外幾種前端框架的崛起,jQuery慢慢變得不再是必須。因此大家對(duì)于jQuery的熱情低了很多。但是許多從jQuery中學(xué)到的技巧用在實(shí)際開發(fā)中仍然非常好用。簡(jiǎn)單的了解它也有助于我們更加深入的理解JavaScript。
這篇文章的主要目的就是跟大家分享一下,jquery對(duì)象是如何封裝的。算是對(duì)于大家進(jìn)一步學(xué)習(xí)jQuery源碼的一個(gè)拋磚引玉。
使用jQuery對(duì)象時(shí),我們這樣寫:
// 聲明一個(gè)jQuery對(duì)象 $(".target") // 獲取元素的css屬性 $(".target").css("width") // 獲取元素的位置信息 $(".target").offset()
在使用之初可能會(huì)有許多疑問,比如$是怎么回事?為什么不用new就可以直接聲明一個(gè)對(duì)象等等。后來了解之后,才知道原來這正是jQuery對(duì)象創(chuàng)建的巧妙之處。
先直接用代碼展示出來,再用圖跟大家解釋是怎么回事。
; (function(ROOT) { // 構(gòu)造函數(shù) var jQuery = function(selector) { // 在jQuery中直接返回new過的實(shí)例,這里的init是jQuery的真正構(gòu)造函數(shù) return new jQuery.fn.init(selector) } jQuery.fn = jQuery.prototype = { constructor: jQuery, version: "1.0.0", init: function(selector) { // 在jquery中這里有一個(gè)復(fù)雜的判斷,但是這里我做了簡(jiǎn)化 var elem, selector; elem = document.querySelector(selector); this[0] = elem; // 在jquery中返回一個(gè)由所有原型屬性方法組成的數(shù)組,我們這里簡(jiǎn)化,直接返回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; // 實(shí)現(xiàn)jQuery的兩種擴(kuò)展方式 jQuery.extend = jQuery.fn.extend = function(options) { // 在jquery源碼中會(huì)根據(jù)參數(shù)不同進(jìn)行很多判斷,我們這里就直接走一種方式,所以就不用判斷了 var target = this; var copy; for(name in options) { copy = options[name]; target[name] = copy; } return target; } // jQuery中利用上面實(shí)現(xiàn)的擴(kuò)展機(jī)制,添加了許多方法,其中 // 直接添加在構(gòu)造函數(shù)上,被稱為工具方法 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() {} // ... }) // $符號(hào)的由來,實(shí)際上它就是jQuery,一個(gè)簡(jiǎn)化的寫法,在這里我們還可以替換成其他可用字符 ROOT.jQuery = ROOT.$ = jQuery; })(window);
在上面的代碼中,我封裝了一個(gè)簡(jiǎn)化版的jQuery對(duì)象。它向大家簡(jiǎn)單展示了jQuery的整體框架情況。如果了解了整體框架,那么大家去讀jQuery源碼,就會(huì)變得非常輕松。
我們?cè)诖a中可以看到,jQuery自身對(duì)于原型的處理使用了一些巧妙的語法,比如jQuery.fn = jQuery.prototype,jQuery.fn.init.prototype = jQuery.fn;等,這幾句正式j(luò)Query對(duì)象的關(guān)鍵所在,下面我用圖給大家展示一下這中間的邏輯是怎么回事。
對(duì)象封裝分析
在上面的實(shí)現(xiàn)中,代碼首先在jQuery構(gòu)造函數(shù)中聲明了一個(gè)fn屬性,并將其指向了原型jQuery.prototype。并在原型中添加了init方法。
jQuery.fn = jQuery.prototype = { init: {} }
之后又將init的原型,指向了jQuery.prototype。
jQuery.fn.init.prototype = jQuery.fn;
而在構(gòu)造函數(shù)jQuery中,返回了init的實(shí)例對(duì)象。
var jQuery = function(selector) { // 在jQuery中直接返回new過的實(shí)例,這里的init是jQuery的真正構(gòu)造函數(shù) return new jQuery.fn.init(selector) }
最后對(duì)外暴露入口時(shí),將字符$與jQuery對(duì)等起來。
ROOT.jQuery = ROOT.$ = jQuery;
因此當(dāng)我們直接使用$("#test")創(chuàng)建一個(gè)對(duì)象時(shí),實(shí)際上是創(chuàng)建了一個(gè)init的實(shí)例,這里的正真構(gòu)造函數(shù)是原型中的init方法。
注意:許多對(duì)jQuery內(nèi)部實(shí)現(xiàn)不太了解的盆友,在使用jQuery時(shí)常常會(huì)毫無節(jié)制使用$(),比如對(duì)于同一個(gè)元素的不同操作:
var width = parseInt($("#test").css("width")); if(width > 20) { $("#test").css("backgroundColor", "red"); }
通過我們上面的一系列分析,我們知道每當(dāng)我們執(zhí)行$()時(shí),就會(huì)重新生成一個(gè)init的實(shí)例對(duì)象,因此當(dāng)我們這樣沒有節(jié)制的使用jQuery時(shí)是非常不正確的,雖然看上去方便了一些,但是對(duì)于內(nèi)存的消耗是非常大的。正確的做法是既然是同一個(gè)對(duì)象,那么就用一個(gè)變量保存起來后續(xù)使用即可。
var $test = $("#test"); var width = parseInt($test.css("width")); if(width > 20) { $test.css("backgroundColor", "red"); }
擴(kuò)展方法分析
在上面的代碼實(shí)現(xiàn)中,我還簡(jiǎn)單實(shí)現(xiàn)了兩個(gè)擴(kuò)展方法。
jQuery.extend = jQuery.fn.extend = function(options) { // 在jquery源碼中會(huì)根據(jù)參數(shù)不同進(jìn)行很多判斷,我們這里就直接走一種方式,所以就不用判斷了 var target = this; var copy; for(name in options) { copy = options[name]; target[name] = copy; } return target; }
要理解它的實(shí)現(xiàn),我們首先要明確的知道this的指向。如果你搞不清楚,可以回頭去看看我們之前關(guān)于this指向的講解。傳入的參數(shù)options對(duì)象為一個(gè)key: value模式的對(duì)象,我通過for in遍歷options,將key作為jQuery的新屬性,value作為該新屬性所對(duì)應(yīng)的新方法,分別添加到j(luò)Query方法和jQuery.fn中。
也就是說,當(dāng)我們通過jQuery.extend擴(kuò)展jQuery時(shí),方法被添加到了jQuery構(gòu)造函數(shù)中,而當(dāng)我們通過jQuery.fn.extend擴(kuò)展jQuery時(shí),方法被添加到了jQuery原型中。
上面的例子中,我也簡(jiǎn)單展示了在jQuery內(nèi)部,許多方法的實(shí)現(xiàn)都是通過這兩個(gè)擴(kuò)展方法來完成的。
當(dāng)我們通過上面的知識(shí)了解了jQuery的大體框架之后,那么我們對(duì)于jQuery的學(xué)習(xí)就可以具體到諸如css/val/attr等方法是如何實(shí)現(xiàn)這樣的程度,那么源碼學(xué)習(xí)起來就會(huì)輕松很多,也會(huì)節(jié)約更多的時(shí)間。也給一些對(duì)于源碼敬而遠(yuǎn)之的朋友提供了一個(gè)學(xué)習(xí)的可能。
有一個(gè)朋友留言給我,說她被靜態(tài)方法,工具方法和實(shí)例方法這幾個(gè)概念困擾了很久,到底他們有什么區(qū)別?
其實(shí)在上一篇文章中,關(guān)于封裝一個(gè)對(duì)象,我跟大家分享了一個(gè)非常非常干貨,但是卻只有少數(shù)幾個(gè)讀者老爺get到的知識(shí),那就是在封裝對(duì)象時(shí),屬性和方法可以具體放置的三個(gè)位置,并且對(duì)于這三個(gè)位置的不同做了一個(gè)詳細(xì)的解讀。
而在實(shí)現(xiàn)jQuery擴(kuò)展方法的想法中,一部分方法需要擴(kuò)展到j(luò)Query構(gòu)造函數(shù)中,一部分方法需要擴(kuò)展到原型中,當(dāng)我們通讀jQuery源碼,還發(fā)現(xiàn)有一些方法放在了模塊作用域中,至于為什么會(huì)有這樣的區(qū)別,建議大家回過頭去讀讀前一篇文章。
而放在構(gòu)造函數(shù)中的方法,因?yàn)槲覀冊(cè)谑褂脮r(shí),不需要聲明一個(gè)實(shí)例對(duì)象就可以直接使用,因此這樣的方法常常被叫做工具方法,或者所謂的靜態(tài)方法。工具方法在使用時(shí)因?yàn)椴挥脛?chuàng)建新的實(shí)例,因此相對(duì)而言效率會(huì)高很多,但是并不節(jié)省內(nèi)存。
而工具方法的特性也和工具一詞非常貼近,他們與實(shí)例的自身屬性毫無關(guān)聯(lián),僅僅只是實(shí)現(xiàn)一些通用的功能,我們可以通過$.each與$("div").each這2個(gè)方法來體會(huì)工具方法與實(shí)例方法的不同之處。
在實(shí)際開發(fā)中,我們運(yùn)用得非常多的一個(gè)工具庫(kù)就是lodash.js,大家如果時(shí)間充裕一定要去學(xué)習(xí)一下他的使用。
$.ajax() $.isFunction() $.each() ... ...
而放在原型中的方法,在使用時(shí)必須創(chuàng)建了一個(gè)新的實(shí)例對(duì)象才能訪問,因此這樣的方法叫做實(shí)例方法。也正是由于必須創(chuàng)建了一個(gè)實(shí)例之后才能訪問,所以他的使用成本會(huì)比工具方法高很多。但是節(jié)省了內(nèi)存。
$("#test").css() $("#test").attr() $("div").each()
這樣,那位同學(xué)的疑問就很簡(jiǎn)單的被搞定了。我們?cè)趯W(xué)習(xí)的時(shí)候,一定不要過分去糾結(jié)一些概念,而要明白具體怎么回事兒,那么學(xué)習(xí)這件事情就不會(huì)在一些奇奇怪怪的地方卡住了。
所以通過$.extend擴(kuò)展的方法,其實(shí)就是對(duì)工具方法的擴(kuò)展,而通過$.fn.extend擴(kuò)展的方法,就是對(duì)于實(shí)例方法的擴(kuò)展。那么我們?cè)谑褂玫臅r(shí)候就知道如何準(zhǔn)確的去使用自己擴(kuò)展的方法了。
jQuery插件的實(shí)現(xiàn)
我在初級(jí)階段的時(shí)候,覺得要自己編寫一個(gè)jQuery插件是一件高大上的事情,可望不可即。但是通過對(duì)于上面的理解,我就知道擴(kuò)展jQuery插件其實(shí)是一件我們自己也可以完成的事情。
在前面我跟大家分享了jQuery如何實(shí)現(xiàn),以及他們的方法如何擴(kuò)展,并且前一篇文章分享了拖拽對(duì)象的具體實(shí)現(xiàn)。所以建議大家暫時(shí)不要閱讀下去,自己動(dòng)手嘗試將拖拽擴(kuò)展成為jQuery的一個(gè)實(shí)例方法,那么這就是一個(gè)jQuery插件了。
具體也沒有什么可多說的了,大家看了代碼就可以明白一切。
// Drag對(duì)象簡(jiǎn)化代碼,完整源碼可在上一篇文章中查看 ; (function() { // 構(gòu)造 function Drag(selector) {} // 原型 Drag.prototype = { constructor: Drag, init: function() { // 初始時(shí)需要做些什么事情 this.setDrag(); }, // 稍作改造,僅用于獲取當(dāng)前元素的屬性,類似于getName getStyle: function(property) {}, // 用來獲取當(dāng)前元素的位置信息,注意與之前的不同之處 getPosition: function() {}, // 用來設(shè)置當(dāng)前元素的位置 setPostion: function(pos) {}, // 該方法用來綁定事件 setDrag: function() {} } // 一種對(duì)外暴露的方式 window.Drag = Drag; })(); // 通過擴(kuò)展方法將拖拽擴(kuò)展為jQuery的一個(gè)實(shí)例方法 (function ($) { $.fn.extend({ becomeDrag: function () { new Drag(this[0]); return this; // 注意:為了保證jQuery所有的方法都能夠鏈?zhǔn)皆L問,每一個(gè)方法的最后都需要返回this,即返回jQuery實(shí)例 } }) })(jQuery);
后續(xù)文章內(nèi)容一個(gè)大概預(yù)想
去年年末的時(shí)候就有了想要將JavaScript基礎(chǔ)知識(shí)總結(jié)一下的這樣一個(gè)想法,可是JavaScript基礎(chǔ)知識(shí)確實(shí)并非全部是層層遞進(jìn)的關(guān)系,有很多碎片化的東西,所以之前一直沒有找到一個(gè)合適的整理方法。
直到在segmentfault中我在給題主建議如何快速學(xué)習(xí)一門諸如react/vue這樣的流行框架時(shí),找到了一個(gè)好一點(diǎn)的思路,于是就有了這樣一系列文章,雖然它并不全面,很多知識(shí)沒有涉及到,但是其實(shí)我是圍繞最終通過模塊化來構(gòu)建自己代碼這樣一個(gè)思路來總結(jié)的,因此這系列文章能夠解決大家最核心的問題。
這系列文章,算是對(duì)于大家學(xué)習(xí)方向的一個(gè)具體的,切實(shí)可行的一個(gè)指引,而非簡(jiǎn)單的通過雞湯的方式告訴你應(yīng)該如何學(xué)習(xí)。所以,想要學(xué)習(xí)這些知識(shí)的朋友,趕緊來公眾號(hào)關(guān)注我吧!!!!
前端基礎(chǔ)進(jìn)階系列目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/90545.html
摘要:不過其實(shí)簡(jiǎn)書文章評(píng)論里有很多大家的問題以及解答,對(duì)于進(jìn)一步理解文中知識(shí)幫助很大的,算是有點(diǎn)可惜吧。不過也希望能夠?qū)φ趯W(xué)習(xí)前端的你有一些小幫助。如果在閱讀中發(fā)現(xiàn)了一些錯(cuò)誤,請(qǐng)?jiān)谠u(píng)論里告訴我,我會(huì)及時(shí)更改。 前端基礎(chǔ)進(jìn)階(一):內(nèi)存空間詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(二):執(zhí)行上下文詳細(xì)圖解 前端基礎(chǔ)進(jìn)階(三):變量對(duì)象詳解 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包 前端基礎(chǔ)進(jìn)階(五):全方位...
摘要:一團(tuán)隊(duì)組織網(wǎng)站說明騰訊團(tuán)隊(duì)騰訊前端團(tuán)隊(duì),代表作品,致力于前端技術(shù)的研究騰訊社交用戶體驗(yàn)設(shè)計(jì),簡(jiǎn)稱,騰訊設(shè)計(jì)團(tuán)隊(duì)網(wǎng)站騰訊用戶研究與體驗(yàn)設(shè)計(jì)部百度前端研發(fā)部出品淘寶前端團(tuán)隊(duì)用技術(shù)為體驗(yàn)提供無限可能凹凸實(shí)驗(yàn)室京東用戶體驗(yàn)設(shè)計(jì)部出品奇舞團(tuán)奇虎旗下前 一、團(tuán)隊(duì)組織 網(wǎng)站 說明 騰訊 AlloyTeam 團(tuán)隊(duì) 騰訊Web前端團(tuán)隊(duì),代表作品WebQQ,致力于前端技術(shù)的研究 ISUX 騰...
摘要:一團(tuán)隊(duì)組織網(wǎng)站說明騰訊團(tuán)隊(duì)騰訊前端團(tuán)隊(duì),代表作品,致力于前端技術(shù)的研究騰訊社交用戶體驗(yàn)設(shè)計(jì),簡(jiǎn)稱,騰訊設(shè)計(jì)團(tuán)隊(duì)網(wǎng)站騰訊用戶研究與體驗(yàn)設(shè)計(jì)部百度前端研發(fā)部出品淘寶前端團(tuán)隊(duì)用技術(shù)為體驗(yàn)提供無限可能凹凸實(shí)驗(yàn)室京東用戶體驗(yàn)設(shè)計(jì)部出品奇舞團(tuán)奇虎旗下前 一、團(tuán)隊(duì)組織 網(wǎng)站 說明 騰訊 AlloyTeam 團(tuán)隊(duì) 騰訊Web前端團(tuán)隊(duì),代表作品WebQQ,致力于前端技術(shù)的研究 ISUX 騰...
摘要:一團(tuán)隊(duì)組織網(wǎng)站說明騰訊團(tuán)隊(duì)騰訊前端團(tuán)隊(duì),代表作品,致力于前端技術(shù)的研究騰訊社交用戶體驗(yàn)設(shè)計(jì),簡(jiǎn)稱,騰訊設(shè)計(jì)團(tuán)隊(duì)網(wǎng)站騰訊用戶研究與體驗(yàn)設(shè)計(jì)部百度前端研發(fā)部出品淘寶前端團(tuán)隊(duì)用技術(shù)為體驗(yàn)提供無限可能凹凸實(shí)驗(yàn)室京東用戶體驗(yàn)設(shè)計(jì)部出品奇舞團(tuán)奇虎旗下前 一、團(tuán)隊(duì)組織 網(wǎng)站 說明 騰訊 AlloyTeam 團(tuán)隊(duì) 騰訊Web前端團(tuán)隊(duì),代表作品WebQQ,致力于前端技術(shù)的研究 ISUX 騰...
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
閱讀 3259·2021-11-15 11:37
閱讀 1065·2021-11-02 14:45
閱讀 3893·2021-09-04 16:48
閱讀 3569·2019-08-30 15:55
閱讀 748·2019-08-23 17:53
閱讀 994·2019-08-23 17:03
閱讀 2020·2019-08-23 16:43
閱讀 2183·2019-08-23 16:22