jQuery 3.0 的 setter/getter 模式

来源:http://www.cnblogs.com/snandy/archive/2016/07/11/5644994.html
-Advertisement-
Play Games

jQuery 的 setter/getter 共用一個函數,通過是否傳參來表明它是何種意義。簡單說傳參它是 setter,不傳它是 getter。 一個函數具有多種意義在編程語言中並不罕見,比如函數重載:一組具有相同函數名,不同參數列表的函數,這組函數被稱為重載函數。重載的好處是減少了函數名的數量, ...


jQuery 的 setter/getter 共用一個函數,通過是否傳參來表明它是何種意義。簡單說傳參它是 setter,不傳它是 getter。

一個函數具有多種意義在編程語言中並不罕見,比如函數重載:一組具有相同函數名,不同參數列表的函數,這組函數被稱為重載函數。重載的好處是減少了函數名的數量,避免了名字空間的污染,對於程式的可讀性也大有裨益。

函數重載主要體現的兩個方面,一是參數的類型、相同個數的參數類型不同可稱為函數重載;二是參數的個數,個數不同也稱為函數重載。註意,重載與函數的返回值並無關係。

由於 JS 弱類型的特征,想模擬函數重載就只能通過第二種方式:參數的個數來實現。因此函數內的 arguments 對象就顯得非常重要。

 

以下是一個示例

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 通過判斷函數的參數個數重載實現了三種意義,argsLength 為 0 時,直接返回 0; argsLength 為 1 時,該參數與 10 相加;argsLength 為 2 時兩個參數相加。


利用函數重載特性可以實現 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]
	}
}

 

以上簡單的解釋了函數重載及利用它實現 setter/getter。即"取值器"與"賦值器"合一。到底是取值還是賦值,由函數的參數決定。jQuery 的很多 API 設計大量使用了這種模式。

下圖彙總了 jQuery 中採用這種模式的所有 API,共 14 個函數

 

所有這些函數內部都依賴另一個函數 access, 毫不誇張的說 access 是所有這些函數的核心,是實現 setter/getter 的核心。下麵是這個函數的源碼,它是一個私有的函數,外部是調用不到它的。

 

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;
};

  

該函數的註釋提到:這是一個多功能的函數,用來獲取和設置一個集合元素的屬性和值。value 可以是一個可執行的函數。這個函數一共不到 60 行代碼。從上往下讀,第一個 if 是設置多個 value 值,是一個遞歸調用。刨去這個遞歸調用,設置單個值的代碼也就不到 50 行了。寫的非常簡練、耐讀。

 

為了理解 access 函數,我畫了兩個圖

 

access 內部兩個主要分支

 

access 內部的執行流程

 

access 定義的形參有 7 個

  1. elems 元素集合,實際調用時傳的都是 this,這裡的 this 是 jQuery 對象,我們知道 jQuery 對象本身是一個集合,具有 length 屬性和索引。必傳。
  2. fn 實現 setter/getter 的函數,就是說這個函數里需要有條件能判斷哪部分是 setter,哪部分是 getter。必傳。
  3. key 比如 attr 和 prop 方法要傳,設置或獲取哪個 key 的值。有的則不用傳,但為了占位用以 null 替代,比如 text、html 方法。可選。
  4. value 僅當 setter 時要傳,即 value 為 undefined 時是 getter,否則是 setter。可選。
  5. chainable 當為 true 時,進入 setter 模式,會返回 jQuery 對象。false 則進入 getter模式。調用時通過 arguments.length 或 arguments.length>1 傳入。
  6. emptyGet 當 jQuery 對象為空時,返回的結果,預設不傳為 undefined,data 方法調用時傳的是 null。
  7. raw 當 value 為函數類型時 raw 為 false,否則為 true。

 

上面提到了 access 是 jQuery 所有 setter/getter 函數的核心,換句話說所有 14 個函數 setter/getter 函數內部都會調用 access。這也是為什麼 access 有 7 個參數,裡面分支眾多。因為它要處理的各種條件就很多呢。但所有這些 setter/getter 有很多類同的代碼,最後還是提取一個公共函數。

 

為了便於理解,我把 access 的調用分類以下,便於我們理解。

 

1. 調用 access 時,第三個參數 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 );
},

 

圖示這兩個方法在 access 內部執行處

 

為什麼 key 傳 null,因為 DOM API 已經提供了。text 方法使用 el.innerText 設置或獲取;html 方法使用 innerHTML 設置或獲取(這裡簡單說,實際還有一些異常處理)。

 

2. 與第一種情況相反,調用 access 時 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 內部執行處

 

各個版本的實現差異

1.1 ~ 1.3 各個 setter/getter 獨自實現,沒有抽取一個公共函數。
1.4 ~ 1.9 抽取了獨立的 jQuery.access 這個核心函數為所有的 setter/getter 服務。
1.10 ~ 2.24 同上一個版本區間,但在內部使用了一個私有的 access 函數,不使用公開的 jQuery.access,即弱化了 jQuery.access。
3.0 ~ 未來 去掉了 jQuery.access ,內部直接使用私有的 access 。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近打算用C#實現一個基於文件的EventStore。 什麼是EventStore 關於什麼是EventStore,如果還不清楚的朋友可以去瞭解下CQRS/Event Sourcing這種架構,我博客中也有大量介紹。EventStore是在Event Sourcing(下麵簡稱ES)模式中,用於存儲 ...
  • ...
  • 認識觀察者模式 首先來看看報紙訂閱的過程 1.報社的業務就是出版報紙 2.向某家報社訂閱報紙,只要他們有新報紙出版,就會送過來,只要你是他們的訂戶 3.當你不想再看報紙的時候,取消訂閱,他們就不會再送新報紙 4.只要報社還在運營,就會一直有人來訂閱或取消訂閱報紙 觀察者模式和報紙訂閱流程是一樣的,只 ...
  • 在上一篇《html+ccs3太陽系行星運轉動畫》中實現了太陽系八大行星的基本運轉動畫。 太陽系又何止這些內容,為豐富一下動畫,接下來增加“土星環”和“月球”來充盈太陽系動畫。 下麵是充盈後的動畫效果靜態圖。 一、土星環 修改原來土星的div,在外面放一個包裹層div,class設成saturn-co ...
  • Functionde 對象的實例可以創建構造函數 但是Object對象就不能,當完成一個object對象的實例化後,不能再基於新實例使用new 創建一個實例 添加公有方法 要在構造函數的新實例中添加公有方法,使用點號想它的原型屬性添加即可。 但是不能直接在 Myfunc 上運用 如:Myfunc.n ...
  • ...
  • 含義:滾動條高度 作用:滾動載入(ajax),滾動導航固定定位,滾動彈框定位等等. 展示滾動導航和側邊欄滾動固定定位的效果:查看效果 1、chrome瀏覽器 2、各瀏覽器下 scrollTop的差異 IE6/7/8/9/10: 對於沒有doctype聲明的頁面里可以使用 document.body. ...
  • 關於前端對話框、消息框的優秀插件多不勝數。造輪子是為了更好的使用輪子,並不是說自己造的輪子肯定好。所以,這個博客系統基本上都是自己實現的,包括日誌記錄、響應式佈局等等一些本可以使用插件的。好了,廢話不多時。我們來實現自己的對話框和消息框。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...