jQuery 3.0 的 setter/getter 模式
jQuery 的 setter/getter 共用一個(gè)函數(shù),通過是否傳參來表明它是何種意義。簡單說傳參它是 setter,不傳它是 getter。
一個(gè)函數(shù)具有多種意義在編程語言中并不罕見,比如函數(shù)重載:一組具有相同函數(shù)名,不同參數(shù)列表的函數(shù),這組函數(shù)被稱為重載函數(shù)。重載的好處是減少了函數(shù)名的數(shù)量,避免了名字空間的污染,對于程序的可讀性也大有裨益。
函數(shù)重載主要體現(xiàn)的兩個(gè)方面,一是參數(shù)的類型、相同個(gè)數(shù)的參數(shù)類型不同可稱為函數(shù)重載;二是參數(shù)的個(gè)數(shù),個(gè)數(shù)不同也稱為函數(shù)重載。注意,重載與函數(shù)的返回值并無關(guān)系。
由于 JS 弱類型的特征,想模擬函數(shù)重載就只能通過第二種方式:參數(shù)的個(gè)數(shù)來實(shí)現(xiàn)。因此函數(shù)內(nèi)的 arguments 對象就顯得非常重要。
?
以下是一個(gè)示例
function doAdd() {var argsLength = arguments.lengthif (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) // 25doAdd 通過判斷函數(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
?
以上簡單的解釋了函數(shù)重載及利用它實(shí)現(xiàn) setter/getter。即"取值器"與"賦值器"合一。到底是取值還是賦值,由函數(shù)的參數(shù)決定。jQuery 的很多 API 設(shè)計(jì)大量使用了這種模式。
下圖匯總了 jQuery 中采用這種模式的所有 API,共 14 個(gè)函數(shù)
?
所有這些函數(shù)內(nèi)部都依賴另一個(gè)函數(shù) access, 毫不夸張的說 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 valuesif ( 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 setif ( 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 :// Getsbulk ?fn.call( elems ) :len ? fn( elems[ 0 ], key ) : emptyGet; };
該函數(shù)的注釋提到:這是一個(gè)多功能的函數(shù),用來獲取和設(shè)置一個(gè)集合元素的屬性和值。value 可以是一個(gè)可執(zhí)行的函數(shù)。這個(gè)函數(shù)一共不到 60 行代碼。從上往下讀,第一個(gè) if 是設(shè)置多個(gè) value 值,是一個(gè)遞歸調(diào)用。刨去這個(gè)遞歸調(diào)用,設(shè)置單個(gè)值的代碼也就不到 50 行了。寫的非常簡練、耐讀。
?
為了理解 access 函數(shù),我畫了兩個(gè)圖
?
access 內(nèi)部兩個(gè)主要分支
?
access 內(nèi)部的執(zhí)行流程
?
access 定義的形參有 7 個(gè)
?
上面提到了 access 是 jQuery 所有 setter/getter 函數(shù)的核心,換句話說所有 14 個(gè)函數(shù) setter/getter 函數(shù)內(nèi)部都會調(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 innerHTMLif ( 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 leaksif ( 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è)置或獲取(這里簡單說,實(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, outerWidthjQuery.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 heightif ( elem.nodeType === 9 ) {doc = elem.documentElement;// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],// whichever is greatestreturn 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 parseFloatjQuery.css( elem, type, extra ) :// Set width or height on the elementjQuery.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 valuesif ( 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 valuesif ( 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 Datadata = dataUser.get( elem, key );if ( data !== undefined ) {return data;}// Attempt to "discover" the data in// HTML5 custom data-* attrsdata = 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 keydataUser.set( this, key, value );} );}, null, value, arguments.length > 1, null, true ); },?
圖示這些方法在 access 內(nèi)部執(zhí)行處
?
各個(gè)版本的實(shí)現(xiàn)差異
1.1 ~ 1.3 各個(gè) setter/getter 獨(dú)自實(shí)現(xiàn),沒有抽取一個(gè)公共函數(shù)。
1.4 ~ 1.9 抽取了獨(dú)立的 jQuery.access 這個(gè)核心函數(shù)為所有的 setter/getter 服務(wù)。
1.10 ~ 2.24 同上一個(gè)版本區(qū)間,但在內(nèi)部使用了一個(gè)私有的 access 函數(shù),不使用公開的 jQuery.access,即弱化了 jQuery.access。
3.0 ~ 未來 去掉了 jQuery.access ,內(nèi)部直接使用私有的 access 。
?
總結(jié)
以上是生活随笔為你收集整理的jQuery 3.0 的 setter/getter 模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米9电池够用吗(我的小米云服务)
- 下一篇: 迷你世界卡皮肤教程