前言:通過上篇文章已經知道了,jQuery實例對象中的大量方法很多都是通過$.fn.extend()去進行擴展出來的,但是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 );