摘要:進一步了解類數組對象可以看這篇文章對象的構建和分離構造器然后我們回來看看,讓我們悲傷的代碼。。。然后又通過下面的語句,將兩個獨立的構造器關聯起來了。
背景
不造輪子的程序員不是好程序員,所以我們今天嘗試造一下輪子。今天的主角是 jQuery ,雖然現在市面上已被 React,Angular,Vue 等擠的容不下它的位置,但是它的簡單 API 設計依然優秀,值得學習和體會。
任務
今天造輪子的目標不是實現功能,而是專注在 API 和架構。你需要完成的東西支持以下功能:
1、$(selector) 根據選擇器構造一個jQuery 對象
2、jQuery 對象是一個類數組,需要支持以下方法:
var a = $(selector); a[0] 訪問元素 a.length 元素個數 a.each(function(){ console.log(this)}) 迭代操作
3、鏈式調用
var a = $(selector); a.addClass("hello").click(function(){...});
4、擴展實例方法
$.fn.tabs = function(){ console.log(this); };
之后就可以這樣使用
$(selector).tabs();
好,開始我們的任務。
我在 jQuery 的官網下載的開發版(沒有壓縮)代碼,版本 3.2.1我記的上一次用的時候好像才 1.8左右 ?。只有一個 js 文件,打開一看,我的天,一萬多行代碼。。。
代碼有點多,我們先梳理一下結構,找個入口開始看。
jQuery 的整體架構( function( global, factory ) { //省略... } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); //這里用new,省去了構造函數 jQuery() 前面的運算符new,因此我們可以直接寫 jQuery() }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, ... }; // 通過覆蓋原型的方式,把 jQuery.prototype 覆蓋到 jQuery.fn.init.prototype 上 jQuery.fn.init.prototype = jQuery.fn; //... jQuery.extend = jQuery.fn.extend = function(){ ....// }; jQuery.extend( { isFunction, type, isWindow, ... }) //jQuery.extend()和jQuery.fn.extend() //用于合并多個對象的屬性到第一個對象,類似于 es6 的 Object.assign(),不過還是有區別的 if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } return jQuery; }));源碼分析 立即調用表達式
jQuery 立即調用表達式簡化版
(function(window, factory) { factory(window) }(this, function() { return function() { //jQuery的調用 } }))
一上來,是個 立即調用表達式。 解決命名空間與變量污染的問題,全局變量是魔鬼, 匿名函數可以有效的保證在頁面上寫入 JavaScript,而不會造成全局變量的污染,通過小括號,讓其加載的時候立即初始化,這樣就形成了一個單例模式的效果從而只會執行一次。
jQuery 的這個立即調用表達式的具體講解可以參考這里。
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); } //... window.jQuery = window.$ = jQuery;
jQuery 賦值給了 window.jQuery 和 window.$ 所以我們在使用 jQuery 的時候 $ 和 jQuery 是等價的。
類數組對象但是 jQuery() 返回了 new jQuery.fn.init(),為什么這樣寫?一臉懵逼。。。。
悲傷先放一邊,我們先看一下這個函數 jQuery.fn.init(selector, context)
init = jQuery.fn.init = function( selector, context, root ) { // HANDLE: $(""), $(null), $(undefined), $(false) // Handle HTML strings // HANDLE: $(html) -> $(array) // HANDLE: $(html, props) // HANDLE: $(#id) // HANDLE: $(expr, $(...)) // HANDLE: $(expr, context) // HANDLE: $(DOMElement) // HANDLE: $(function) return jQuery.makeArray( selector, this ); }; init.prototype = jQuery.fn;
這個函數就是對參數 selector 對應的 html、id 和 class 等不同選擇器的處理方式,并返回一個類數組對象。
看到這我們就能實現我們今天任務第一個目標以及第二個目標的 1/2 了。? 上代碼!
var jQuery = function(selector) { return new jQuery.fn.init(selector); } init = jQuery.fn.init = function( selector ) { var elem = document.querySelectorAll(selector); this.length = elem.length; this[0] = elem[0]; for (i = 0; i < elem.length; i++) { this[i] = elem[i]; } this.context = document; this.selector = selector; return this; }
這里有一個 jQuery 的特點 類數組對象結構。
所謂的類數組對象:
擁有一個 length 屬性和若干索引屬性的對象
舉個例子:
var array = ["name", "age", "sex"]; var arrayLike = { 0: "name", 1: "age", 2: "sex", length: 3 }
jQuery 能像數組一樣操作,通過對象 get 方法或者直接通過下標 0 索引就能轉成 DOM 對象。同時還擁有各種自定義方法,自定義屬性,看 jquery 對象的優雅的訪問方式即可知是如此美妙的對象。
進一步了解類數組對象可以看這篇文章
對象的構建和分離構造器然后我們回來看看,讓我們悲傷的代碼。。。
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }
之所以這樣寫,演變過程是這樣的:
1、出于實例化 jQuery 對象性能的考慮 jQuery 采用了原型式的結構構建對象? (jQuery.prototype) ? 2、jQuery 為了初始化對象實例更方便,采用了無 new 化,初始化對象時,可以不寫 new 操作符 (return new jQuery...) ? 3、jQuery 為了避免出現 return jQuery 無限遞歸自己,這種死循環的問題,采取的手段是把原型上的一個 init 方法作為構造器 ? 4、最后,就成了這樣了。 return new jQuery.fn.init()
這樣確實解決了循環遞歸的問題,但是又問題來了,init 是 jQuery 原型上作為構造器的一個方法,那么其 this 就不是 jQuery了,所以 this 就完全引用不到 jQuery 的原型了,所以這里通過 new 把 init 方法與 jQuery 給分離成2個獨立的構造器。
然后 jQuery 又通過下面的語句,將兩個獨立的構造器關聯起來了。
jQuery.fn = jQuery.prototype; jQuery.fn.init.prototype = jQuery.fn;
這樣整個結構就串起來了,不得不佩服作者的設計思路,別具匠心。
上面說的如果沒看懂,可以參考這兩篇文章:
jQuery 源碼解析 - 對象的構建
jQuery 源碼解析 - 分離構造器
靜態與實例方法共享設計我們要實現目標2中的 each 迭代操作,就要說一下 jQuery 的另一個特性 靜態與實例方法共享
$(".box").each() //作為實例方法存在 遍歷一個jQuery對象的,是為jQuery內部服務的 $.each() //作為靜態方法存在 可以迭代任何集合
我們要寫兩個方法嘛?看看 jQuery 怎么做的?
jQuery.prototype = { each: function( callback, args ) { return jQuery.each( this, callback, args ); } }
實例方法取于靜態方法,這里是靜態與實例方法共享設計,靜態方法掛在jQuery構造器上,原型方法經過下面的兩句代碼就掛載到 init 的原型上了,也就是對象的實例方法上了。
jQuery.fn = jQuery.prototype; jQuery.fn.init.prototype = jQuery.fn;
那么剩下的問題就是怎么實現靜態方法 jQuery.each
這個靜態方法是在
jQuery.extend({ each: function( obj, callback ) { var length, i = 0; if ( isArrayLike( obj ) ) { length = obj.length; for ( ; i < length; i++ ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } else { for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } return obj; } })
我們實現 each,代碼如下:
var jQuery = function(selector) { return new jQuery.fn.init(selector); } jQuery.fn = jQuery.prototype = { constructor: jQuery, length:0, get: function( num ) { return this[ num ]; }, each: function( callback ) { return jQuery.each( this, callback ); } } init = jQuery.fn.init = function( selector ) { var elem = document.querySelectorAll(selector); this.length = elem.length; this[0] = elem[0]; for (i = 0; i < elem.length; i++) { this[i] = elem[i]; } this.context = document; this.selector = selector; return this; } init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, copy, target = arguments[0] || {}, i = 1, length = arguments.length; //只有一個參數,就是對jQuery自身的擴展處理 //extend,fn.extend if (i === length) { target = this; //調用的上下文對象jQuery/或者實例 i--; } for (; i < length; i++) { //從i開始取參數,不為空開始遍歷 if ((options = arguments[i]) != null) { for (name in options) { copy = options[name]; //覆蓋拷貝 target[name] = copy; } } } return target; } jQuery.extend( { each: function( obj, callback ) { var length, i = 0; for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } return obj; } });插件接口的設計
既然出現 extend 了,我們就先實現第四個小目標 擴展實例方法 tabs
jQuery 中
jQuery.extend = jQuery.fn.extend = function() { }
雖然指向了同一個函數,但是它們的 this 指向是不同。
fn 與 jQuery 其實是2個不同的對象,在之前有講解:jQuery.extend 調用的時候,this是指向 jQuery 對象的( jQuery 是函數,也是對象!),所以這里擴展在 jQuery 上。而jQuery.fn.extend 調用的時候,this 指向 fn 對象,jQuery.fn 和 jQuery.prototype指向同一對象,擴展 fn 就是擴展 jQuery.prototype 原型對象。這里增加的是原型方法,也就是對象方法了。所以jQuery的API中提供了以上2個擴展函數。
我們這樣擴展實例方法即可。
jQuery.fn.extend({ tabs: function() { console.log("擴展實例方法:tabs"); } });
方法鏈式調用的實現jQuery 抽出了所有可復用的特性,分離出單一模塊,通過組合的用法,不管在設計思路與實現手法上 jQuery 都是非常高明的。因為 jQuery 的設計中最喜歡的做的一件事,就是抽出共同的特性使之模塊化,當然也是更貼近 S.O.L.I.D 五大原則的單一職責SRP了,遵守單一職責的好處是可以讓我們很容易地來維護這個對象,比如,當一個對象封裝了很多職責的時候,一旦一個職責需要修改,勢必會影響該對象的其它職責代碼。通過解耦可以讓每個職責更加有彈性地變化。
通過簡單擴展原型方法并通過 return this 的形式來實現跨瀏覽器的鏈式調用。
所以我們如果需要鏈式的處理,只需要在方法內部返回當前的這個實例對象 this 就可以了,因為返回當前實例的 this,從而又可以訪問自己的原型了,這樣的就節省代碼量,提高代碼的效率,代碼看起來更優雅。
詳細解說請點擊 方法鏈式調用的實現
最終的代碼演示
參考文章的很多內容參考的慕課網的 jQuery源碼解析 系列,感興趣的小伙伴,可以看一下整個系列。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85035.html
摘要:學編程真的不是一件容易的事不管你多喜歡或是多會編程,在學習和解決問題上總會碰到障礙。熟練掌握核心內容,特別是和多線程初步具備面向對象設計和編程的能力掌握基本的優化策略。 學Java編程真的不是一件容易的事,不管你多喜歡或是多會Java編程,在學習和解決問題上總會碰到障礙。工作的時間越久就越能明白這個道理。不過這倒是一個讓人進步的機會,因為你要一直不斷的學習才能很好的解決你面前的難題...
摘要:你是否已經初步掌握了和,但完全不知道從何入手如果是,那么這篇文章一定會對你有所幫助,這里總結了條建議,幫助初學者總結學習方法,提高學習效率。這樣的結果就是,個小時最多只利用了個小時。 你是否已經初步掌握了html和css,但完全不知道從何入手JavaScript?如果是,那么這篇文章一定會對你有所幫助,這里總結了5條建議,幫助JavaScript初學者總結學習方法,提高學習效率。 一、...
摘要:希望在做所有事情之前,操作文檔。不受層級限制子選擇器在給定的父元素下匹配所有子元素。相鄰選擇器匹配所有緊接在元素后的元素。判斷當前對象中的某個元素是否包含指定類名,包含返回,不包含返回下標過濾器精確選出指定下標元素獲取第個元素。 原文鏈接 http://blog.poetries.top/2016... 首先,來了解一下jQuery學習的整體思路 showImg(https://seg...
摘要:一初步了解介紹由創建于年一月的開源項目,憑借著跨平臺的兼容性,簡潔的語法,極大的簡化了人員遍歷文檔,操作,處理事件,執行動畫,和開發的操作。只建立一個名為的對象。對發生在同一個對象上的一組動作,可以直接連寫無需重復獲取對象。 jQuery(一)-- 初步了解 jQuery介紹 由John Resig創建于2006年一月的開源項目,jQuery憑借著跨平臺的兼容性,簡潔的語法,極大的簡...
摘要:多多少少有些不開心的事覺得精力沒有被投入在重點上創業公司遇到問題變成盲人摸象也許正常吧不過最近這段時間因為服務端的策略調整我開始做一些服務端渲染主要的站點是簡聊的登錄頁面整體從切換到了以及做了一些整體項目結構統一的工作或者說一些思考我估計這 多多少少有些不開心的事, 覺得精力沒有被投入在重點上創業公司遇到問題變成盲人摸象也許正常吧不過最近這段時間因為服務端的策略調整, 我開始做一些服務...
閱讀 2077·2023-04-25 19:15
閱讀 2245·2021-11-23 09:51
閱讀 1264·2021-11-17 09:33
閱讀 2165·2021-08-26 14:15
閱讀 2476·2019-08-30 15:54
閱讀 1582·2019-08-30 15:54
閱讀 2167·2019-08-30 12:50
閱讀 1132·2019-08-29 17:08