jQuery 3.0 的 setter和getter 模式詳解
jQuery 的 setter/getter 共用一個(gè)函數(shù),通過(guò)是否傳參來(lái)表明它是何種意義。簡(jiǎn)單說(shuō)傳參它是 setter,不傳它是 getter。
一個(gè)函數(shù)具有多種意義在編程語(yǔ)言中并不罕見,比如函數(shù)重載:一組具有相同函數(shù)名,不同參數(shù)列表的函數(shù),這組函數(shù)被稱為重載函數(shù)。重載的好處是減少了函數(shù)名的數(shù)量,避免了名字空間的污染,對(duì)于程序的可讀性也大有裨益。
函數(shù)重載主要體現(xiàn)的兩個(gè)方面,一是參數(shù)的類型、相同個(gè)數(shù)的參數(shù)類型不同可稱為函數(shù)重載;二是參數(shù)的個(gè)數(shù),個(gè)數(shù)不同也稱為函數(shù)重載。注意,重載與函數(shù)的返回值并無(wú)關(guān)系。
由于 JS 弱類型的特征,想模擬函數(shù)重載就只能通過(guò)第二種方式:參數(shù)的個(gè)數(shù)來(lái)實(shí)現(xiàn)。因此函數(shù)內(nèi)的 arguments 對(duì)象就顯得非常重要。
以下是一個(gè)示例
function doAdd() { var argsLength = arguments.length if (argsLength === 0) { return 0 } else if (argsLength === 1) { return arguments[0] + 10 } else if (argsLength === 2) { return arguments[0] + arguments[1] } } doAdd() // 0 doAdd(5) // 15 doAdd(5, 20) // 25
doAdd 通過(guò)判斷函數(shù)的參數(shù)個(gè)數(shù)重載實(shí)現(xiàn)了三種意義,argsLength 為 0 時(shí),直接返回 0; argsLength 為 1 時(shí),該參數(shù)與 10 相加;argsLength 為 2 時(shí)兩個(gè)參數(shù)相加。
利用函數(shù)重載特性可以實(shí)現(xiàn) setter/getter
function text() { var elem = this.elem var argsLength = arguments.length if (argsLength === 0) { return elem.innerText } else if (argsLength === 1) { elem.innerText = arguments[0] } }
以上簡(jiǎn)單的解釋了函數(shù)重載及利用它實(shí)現(xiàn) setter/getter。即"取值器"與"賦值器"合一。到底是取值還是賦值,由函數(shù)的參數(shù)決定。jQuery 的很多 API 設(shè)計(jì)大量使用了這種模式。
下圖匯總了 jQuery 中采用這種模式的所有 API,共 14 個(gè)函數(shù)
所有這些函數(shù)內(nèi)部都依賴另一個(gè)函數(shù) access, 毫不夸張的說(shuō) access 是所有這些函數(shù)的核心,是實(shí)現(xiàn) setter/getter 的核心。下面是這個(gè)函數(shù)的源碼,它是一個(gè)私有的函數(shù),外部是調(diào)用不到它的。
access 的源碼如下
// Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { var i = 0, len = elems.length, bulk = key == null; // Sets many values if ( jQuery.type( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); } // Sets one value } else if ( value !== undefined ) { chainable = true; if ( !jQuery.isFunction( value ) ) { raw = true; } if ( bulk ) { // Bulk operations run against the entire set if ( raw ) { fn.call( elems, value ); fn = null; // ...except when executing function values } else { bulk = fn; fn = function( elem, key, value ) { return bulk.call( jQuery( elem ), value ); }; } } if ( fn ) { for ( ; i < len; i++ ) { fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); } } } return chainable ? elems : // Gets bulk ? fn.call( elems ) : len ? fn( elems[ 0 ], key ) : emptyGet; };
該函數(shù)的注釋提到:這是一個(gè)多功能的函數(shù),用來(lái)獲取和設(shè)置一個(gè)集合元素的屬性和值。value 可以是一個(gè)可執(zhí)行的函數(shù)。這個(gè)函數(shù)一共不到 60 行代碼。從上往下讀,第一個(gè) if 是設(shè)置多個(gè) value 值,是一個(gè)遞歸調(diào)用。刨去這個(gè)遞歸調(diào)用,設(shè)置單個(gè)值的代碼也就不到 50 行了。寫的非常簡(jiǎn)練、耐讀。
為了理解 access 函數(shù),我畫了兩個(gè)圖
access 內(nèi)部?jī)蓚€(gè)主要分支
access 內(nèi)部的執(zhí)行流程
access 定義的形參有 7 個(gè)
1.elems 元素集合,實(shí)際調(diào)用時(shí)傳的都是 this,這里的 this 是 jQuery 對(duì)象,我們知道 jQuery 對(duì)象本身是一個(gè)集合,具有 length 屬性和索引。必傳。
2.fn 實(shí)現(xiàn) setter/getter 的函數(shù),就是說(shuō)這個(gè)函數(shù)里需要有條件能判斷哪部分是 setter,哪部分是 getter。必傳。
3.key 比如 attr 和 prop 方法要傳,設(shè)置或獲取哪個(gè) key 的值。有的則不用傳,但為了占位用以 null 替代,比如 text、html 方法??蛇x。
4.value 僅當(dāng) setter 時(shí)要傳,即 value 為 undefined 時(shí)是 getter,否則是 setter??蛇x。
5.chainable 當(dāng)為 true 時(shí),進(jìn)入 setter 模式,會(huì)返回 jQuery 對(duì)象。false 則進(jìn)入 getter模式。調(diào)用時(shí)通過(guò) arguments.length 或 arguments.length>1 傳入。
6.emptyGet 當(dāng) jQuery 對(duì)象為空時(shí),返回的結(jié)果,默認(rèn)不傳為 undefined,data 方法調(diào)用時(shí)傳的是 null。
7.raw 當(dāng) value 為函數(shù)類型時(shí) raw 為 false,否則為 true。
上面提到了 access 是 jQuery 所有 setter/getter 函數(shù)的核心,換句話說(shuō)所有 14 個(gè)函數(shù) setter/getter 函數(shù)內(nèi)部都會(huì)調(diào)用 access。這也是為什么 access 有 7 個(gè)參數(shù),里面分支眾多。因?yàn)樗幚淼母鞣N條件就很多呢。但所有這些 setter/getter 有很多類同的代碼,最后還是提取一個(gè)公共函數(shù)。
為了便于理解,我把 access 的調(diào)用分類以下,便于我們理解。
1. 調(diào)用 access 時(shí),第三個(gè)參數(shù) key 傳值為 null,分別是 text/html 方法
text: function( value ) { return access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().each( function() { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.textContent = value; } } ); }, null, value, arguments.length ); }, html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined && elem.nodeType === 1 ) { return elem.innerHTML; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { value = jQuery.htmlPrefilter( value ); try { for ( ; i < l; i++ ) { elem = this[ i ] || {}; // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch ( e ) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); },
圖示這兩個(gè)方法在 access 內(nèi)部執(zhí)行處
為什么 key 傳 null,因?yàn)?DOM API 已經(jīng)提供了。text 方法使用 el.innerText 設(shè)置或獲??;html 方法使用 innerHTML 設(shè)置或獲?。ㄟ@里簡(jiǎn)單說(shuō),實(shí)際還有一些異常處理)。
2. 與第一種情況相反,調(diào)用 access 時(shí) key 值傳了且不為 null。除了 text/html 外的其它 setter 都是如此
attr: function( name, value ) { return access( this, jQuery.attr, name, value, arguments.length > 1 ); }, prop: function( name, value ) { return access( this, jQuery.prop, name, value, arguments.length > 1 ); }, // Create scrollLeft and scrollTop methods jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { var top = "pageYOffset" === prop; jQuery.fn[ method ] = function( val ) { return access( this, function( elem, method, val ) { var win = getWindow( elem ); if ( val === undefined ) { return win ? win[ prop ] : elem[ method ]; } if ( win ) { win.scrollTo( !top ? val : win.pageXOffset, top ? val : win.pageYOffset ); } else { elem[ method ] = val; } }, method, val, arguments.length ); }; } ); css: function( name, value ) { return access( this, function( elem, name, value ) { var styles, len, map = {}, i = 0; if ( jQuery.isArray( name ) ) { styles = getStyles( elem ); len = name.length; for ( ; i < len; i++ ) { map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } return map; } return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); } // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { // Margin is only for outerHeight, outerWidth jQuery.fn[ funcName ] = function( margin, value ) { var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); return access( this, function( elem, type, value ) { var doc; if ( jQuery.isWindow( elem ) ) { // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) return funcName.indexOf( "outer" ) === 0 ? elem[ "inner" + name ] : elem.document.documentElement[ "client" + name ]; } // Get document width or height if ( elem.nodeType === 9 ) { doc = elem.documentElement; // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], // whichever is greatest return Math.max( elem.body[ "scroll" + name ], doc[ "scroll" + name ], elem.body[ "offset" + name ], doc[ "offset" + name ], doc[ "client" + name ] ); } return value === undefined ? // Get width or height on the element, requesting but not forcing parseFloat jQuery.css( elem, type, extra ) : // Set width or height on the element jQuery.style( elem, type, value, extra ); }, type, chainable ? margin : undefined, chainable ); }; } ); } ); data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); },
圖示這些方法在 access 內(nèi)部執(zhí)行處
- 關(guān)于__defineGetter__ 和__defineSetter__的說(shuō)明
- 實(shí)現(xiàn)了一個(gè)PHP5的getter/setter基類的代碼
- JavaScript之Getters和Setters 平臺(tái)支持等詳細(xì)介紹
- javascript中的__defineGetter__和__defineSetter__介紹
- ECMAScript5中的對(duì)象存取器屬性:getter和setter介紹
- JavaScript中setter和getter方法介紹
- 使用node+vue.js實(shí)現(xiàn)SPA應(yīng)用
- 基于Vue.js的表格分頁(yè)組件
- Vue.js每天必學(xué)之組件與組件間的通信
- 談?wù)勔騐ue.js引發(fā)關(guān)于getter和setter的思考
相關(guān)文章
jQuery實(shí)現(xiàn)的簡(jiǎn)單獲取索引功能示例
這篇文章主要介紹了jQuery實(shí)現(xiàn)的簡(jiǎn)單獲取索引功能,涉及jQuery事件響應(yīng)及元素索引獲取相關(guān)操作技巧,需要的朋友可以參考下2018-06-06jQuery 驗(yàn)證插件 Web前端設(shè)計(jì)模式(asp.net)
asp.net下用戶注冊(cè)頁(yè)面的驗(yàn)證代碼,花了點(diǎn)時(shí)間將驗(yàn)證做成一個(gè)jQuery插件,希望對(duì)需要的朋友有所幫助。2010-10-10JQuery實(shí)現(xiàn)簡(jiǎn)單瀑布流布局
這篇文章主要為大家詳細(xì)介紹了JQuery實(shí)現(xiàn)簡(jiǎn)單瀑布流布局,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07來(lái)自國(guó)外的14個(gè)圖片放大編輯的jQuery插件整理
這里就是基于jQuery 的14個(gè)圖片放大編輯插件。需要的朋友可以參考下。2010-10-10使用js dom和jquery分別實(shí)現(xiàn)簡(jiǎn)單增刪改
今天學(xué)了jquery框架的簡(jiǎn)單使用。于是用它實(shí)現(xiàn)簡(jiǎn)單的增刪改,接著也用原始的javascript實(shí)現(xiàn)同樣的功能,兩者對(duì)比可以看出jquery的強(qiáng)大2014-09-09