【jquery源碼四】jQuery對象原型下的方法

前言:通過上篇文章已經知道了,jQuery實例對象中的大量方法很多都是通過$.fn.extend()去進行擴展出來的,但是jQuery下還是有些方法寫在上面的,這些方法的作用是相對重要的,而且不會經常需要修改、優化,或者刪除的方法。現在來探究下這些方法的奧義。

【jquery源碼】目錄。

 

一、模擬封裝jQuery對象的

<!DOCTYPE >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>模擬jQ對象</title>
<script>
(function(window,undefined){  
	var jQ = function(selector){  
		return new jQ.fn.init(selector);  
	};  
	jQ.fn = jQ.prototype = {  
		jquery:'2.0.3',     //jquery版本號信息
		constructor: jQ,    //添加構造器屬性
		length:0,			//初始length屬性
		selector:'',		//初始selector屬性
		init: function(selector){   
			var match, elem;
			if ( !selector ) {	
				return this;
			}
			elem = document.getElementsByTagName(selector);
			for(var i =0,len=elem.length; i<len; i++){
				this[i] = elem[i];
			}
			this.context = document;
			this.length = elem.length;
			this.selector = selector;
		},
		toArray: function(){},
		get: function(num){},
		pushStack: function(){},
		each: function(){},
		ready: function(){},
		slice: function(){},
		first: function(){},
		last: function(){},
		eq: function(){},
		map: function(){},
		end: function(){},
		push: function(){},
		sort: function(){},
		splice: function(){} 
	}; 
	jQ.fn.init.prototype = jQ.fn;  
	  
	window.$$ = jQ;    	
})( window ); 
</script>
</head>

<body>
<div>div1</div>
<div>div2</div>
<div>div3</div>
<div>div4</div>
<script>
	console.log($$('div'));
</script>
</body>
</html>

運行結果

這是我之前寫的模擬jQuery對象,如有看不明白的可以查看我之前的文章。現在主要看原型下的方法。

原型下的方法並不多,除了init()初始化jQuery對象外,只有14個方法。

 

①、toArray();

就是將jQuery實例對象轉換成數組類型。

toArray: function(){
			return [].slice.call(this);	
		}
<script>
console.log($$('div').toArray());
</script>

運行代碼:

這裏其實就是調用了數組下的slice方法,通過call()的機制,給$$對象調用。

 

②、get();

get()方法相信大家也用過很多了,在將jQuery對象轉換成DOM對象的時候要用到。

get: function(num){
    return num == null ?
	   this.toArray() :
	   ( num < 0 ? this[ this.length + num ] : this[ num ] );
},
<script>
console.log($$('div').get());
$$('div').get(0).style.color = 'red';
$$('div').get(-3).style.color = 'blue';
</script>

運行結果

$$('div').get(); 當get()裏面並沒有參數的時候,其實調用的是之前創建的toArray方法。

$$('div').get(0),其實就是$$('div')[0],因爲我們之前封裝jQ對象的時候,是把他的鍵值設置爲數字的,這樣也就可以獲取到第一個div DOM節點對象了。

$$('div').get(-3),當參數爲負數時,我們將負數與$$('div')的length屬性的值進行了相加,獲取到了1,相當於$$('div').get(1)

 

③pushStack || ⑨eq();  || ⑪end();

因爲第三個的pushStack方法相對特殊一點,跟eq(),end()方法來一起使用會比較好理解。

$('div').eq(0).css('color','red').end().css('borderBottom','1px solid #ccc');

運行結果(我們先改變了第一個div的顏色,然後有返回原來的$('div')中,把所有div添加了一個border-bottom)

在jQuery中使用end(),就需要用到pushStack()方法的功效了。我們知道end() 方法會結束當前鏈條中的最近的篩選操作,並將匹配元素集還原爲之前的狀態。這是怎麼樣去實現的呢?

在模擬源碼中我們想給jQ對象添加一個方法

jQ.merge = function( first, second ) {
	var l = second.length,
		i = first.length,
		j = 0;

	if ( typeof l === "number" ) {
		for ( ; j < l; j++ ) {
			first[ i++ ] = second[ j ];
		}
	} else {
		while ( second[j] !== undefined ) {
			first[ i++ ] = second[ j++ ];
		}
	}

	first.length = i;
		
	return first;
}

jQ.merge是用於數組的合併或者是對象與自變量的合併。

例如

var arr4 = {
	0:'c',
	1:'d',
	length: 2,
	sayHi: function(){
		console.log('Hi');	
	}	
}
var arr5 = ['a','b'];

console.log($$.merge(arr4,arr5));

輸出結果:

添加pushStack, eq, end方法(freddySay方法用於稍後的測試)

pushStack: function(elems){
	var ret = jQ.merge( this.constructor(), elems );
	ret.prevObject = this;
	ret.context = this.context;
	return ret;
},
eq: function(i){
	var len = this.length,
	    j = +i + ( i < 0 ? len : 0 );
	return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );	
},
end: function(){
	return this.prevObject || this.constructor(null);	
},
freddySay: function(){
	console.log(this.length);
	return this;	
}
<body>
<div>div1</div>
<div>div2</div>
<div>div3</div>
<div>div4</div>
<script>
$$('div').eq(0).freddySay().end().freddySay();
console.log($$('div').eq(0));
</script>
</body>

運行結果

這時候效果已經實現了。

pushStack: function(elems){
	var ret = jQ.merge( this.constructor(), elems );
	ret.prevObject = this;
	ret.context = this.context;
	return ret;
},
eq: function(i){
	var len = this.length,
	j = +i + ( i < 0 ? len : 0 );
	return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );	
}

$$('div').eq(0),

可以知道var len = this.length; 這裏len是爲4,即j = 0 + 0 ,

然後return this.pushStack( this[0] ),也就是相當於 return this.pushStack($$('div')[0]);

在pushStack方法中:

var ret = jQ.merge( this.constructor(), $$('div')[0] );    //這裏的this.constructor就是jQ對象

      ret.prevObject = this;     

      ret.context = this.context;

      return ret ;

其實就是把$$('div')[0]與jQ對象,合併成一個對象,相當於實例了一個新的jQ對象,且把原先的$$('div')保存到了,prevObect屬性下了。

 

 

 

end: function(){
	return this.prevObject || this.constructor(null);	
},

最後end()方法 就是返回一個prevObject中的jQ對象 或者一個空的構造對象。

 

④、each()

each就是遍歷jQ實例對象的方法了。

each: function(){
	return jQ.each( this, callback, args );	
}

其實這裏調用jQ中封裝的的jQ.each()方法,對jQ.each()有很明白的可以看

【jquery源碼】工具方法彙總①。

 

⑤、ready()

ready: function(){
	jQuery.ready.promise().done( fn );

	return this;
}

ready()方法是在瀏覽器中DOM加載完畢之後觸發的方法,涉及到的知識面太廣,蠻點也會單獨分出一篇文章來說它。

 

⑥、slice()

slice: function(){
	return this.pushStack( [].slice.apply( this, arguments ) );
}

slice()方法在數組中是對數組進行分割,而這裏也是將jQ對象進行分割,並通過this.pushStack方法跟eq()那裏一樣,進行創建新的jQ對象,並添加prevObject

<body>
<div>div1</div>
<div>div2</div>
<div>div3</div>
<div>div4</div>
<script>
$$('div').slice(0,2).freddySay().end().freddySay();
console.log($$('div').slice(0,2));
</script>
</body>

 

⑦first()  || ⑧last()

瞭解eq是怎麼實現之後,first(),last()方法也就很好解決了,直接調用eq也就行了。

first: function(){
	return this.eq( 0 );
},
last: function(){
	return this.eq( -1 );	
}

 

⑩map()

map是對jQuery對象數組的處理

map: function( callback ) {
    return this.pushStack( jQuery.map(this, function( elem, i ) {
        return callback.call( elem, i, elem );
    }));
},

關於jQuery.punshStack方法跟jQuery.map方法,看下以下文章

【jquery源碼五】工具方法彙總②。

 

push() || ⑬sort() ⑭splice() 在開發中並沒有用到,並且jQuery的註釋中也說明了,這三個方法一般用於jQuery內部處理一些東西的時候使用。

push: [].push,
sort: [].sort,
splice: [].splice

很簡單的直接引用數組的方法就對了。

 

彙總:這就是綜上合併後的模擬jQuery的代碼

(function(window,undefined){  
	var jQ = function(selector){  
		return new jQ.fn.init(selector);  
	};  
	jQ.fn = jQ.prototype = {  
		jquery:'2.0.3',     //jquery版本號信息
		constructor: jQ,    //添加構造器屬性
		length:0,			//初始length屬性
		selector:'',		//初始selector屬性
		init: function(selector){   
			var match, elem;
			if ( !selector ) {	
				return this;
			}
			elem = document.getElementsByTagName(selector);
			for(var i =0,len=elem.length; i<len; i++){
				this[i] = elem[i];
			}
			this.context = document;
			this.length = elem.length;
			this.selector = selector;
		},
		toArray: function(){
			return [].slice.call(this);	
		},
		get: function(num){
			return num == null ?

			this.toArray() :

			( num < 0 ? this[ this.length + num ] : this[ num ] );
		},
		pushStack: function(elems){
			var ret = jQ.merge( this.constructor(), elems );
			ret.prevObject = this;
			ret.context = this.context;
			return ret;
		},
		each: function(){
			//return jQuery.each( this, callback, args );	
		},
		ready: function(){
			//jQuery.ready.promise().done( fn );

			//return this;
		},
		slice: function(){
			return this.pushStack( [].slice.apply( this, arguments ) );
		},
		first: function(){
			return this.eq( 0 );
		},
		last: function(){
			return this.eq( -1 );	
		},
		eq: function(i){
			var len = this.length,
				j = +i + ( i < 0 ? len : 0 );
			return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );	
		},
		map: function(){
			/*return this.pushStack( jQuery.map(this, function( elem, i ) {
				return callback.call( elem, i, elem );
			}));*/
		},
		end: function(){
			return this.prevObject || this.constructor(null);	
		},
		push: [].push,
		sort: [].sort,
		splice: [].splice,
		freddySay: function(){
			console.log(this.length);
			return this;	
		}
	}; 
	jQ.fn.init.prototype = jQ.fn;  
	
	jQ.merge = function( first, second ) {
		var l = second.length,
			i = first.length,
			j = 0;

		if ( typeof l === "number" ) {
			for ( ; j < l; j++ ) {
				first[ i++ ] = second[ j ];
			}
		} else {
			while ( second[j] !== undefined ) {
				first[ i++ ] = second[ j++ ];
			}
		}

		first.length = i;
		
		return first;
	}
	  
	window.$$ = jQ;    	
})( window ); 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章