前言:通过上篇文章已经知道了,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 ;
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()方法是对数组的整合处理,这里倒时也会跟each方法一起解析。
⑫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 );