(92-176)
原型定义
jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // Start with an empty selector selector: "", // The default length of a jQuery object is 0 length: 0, toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num != null ? // Return just the one element from the set ( num < 0 ? this[ num + this.length ] : this[ num ] ) : // Return all the elements in a clean array slice.call( this ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, 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] ] : [] ); }, end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: arr.sort, splice: arr.splice };
1. jquery: version 版本信息, constructor: jQuery 原型的构造函数属性修改为函数jQuery,selector: "", 属性存放选择器,length存放jQuery对象的长度,将jQuery对象模仿成数组对象,使得可以像处理数组那样处理jQuery对象。
2.将类数组转化为数组
//模仿出一个类数组对象 var a = { 0: 2, 1: 4, 2: 6,length:3} console.log(Array.prototype.slice.call(a));//[ 2, 4, 6 ]
jQuery对象是一个类数组对象,我们可以用slice将其转化为数组。
3.get方法
var arr = [], slice = arr.slice; //提取出get,这个操作会覆盖原代码中的get jQuery.fn.get = function( num ) { return num != null ? // Return just the one element from the set ( num < 0 ? this[ num + this.length ] : this[ num ] ) : // Return all the elements in a clean array slice.call( this ); } var $div = $('div') //正数和0 //这个情况下与[]访问符效果一样 console.log($div.get(0) === $div[0]) //负数位置 console.log($div.get(-1)) //为空时转化为将对象数组, console.log($div.get())
这里先讲一下三目运算符[con]?[code] :[code];三目预算符等价于If(){}else(){}。
if( a != null ){ if( a<0 ){ this[ num + this.length ] }else{ this[ num ] } }else{ slice.call( this ) } //等价于 a != null ?( num < 0 ? this[ num + this.length ] : this[ num ] ) :slice.call( this );
相比较,功能一样,代码的量缩小,可读性变差。自己取舍。
在负数情况下,length+index = 元素的位置。
4.jQuery对象栈。
var $ = {0:1,prev: Object.prototype} console.log($[0]) $ = {0:2,prev:$} console.log($[0]) $ = {0:3,prev:$} console.log($[0]) //回溯 $ = $.prev console.log($[0])
在这个例子中,我们通过在新对象中存储对上一对象的引用,可以实现栈式的回溯访问。
jQuery对象在遍历功能中运用了这一原理,在遍历操作后返回的是一个新的jQuery对象,这个新的对象存在对上一对象的引用,以实现链式的回溯访问。
下面来看jQuery中的实现
var slice = [].slice; jQuery.fn.pushStack = function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; } jQuery.fn.slice = function() { return this.pushStack( slice.apply( this, arguments ) ); } jQuery.merge = function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; } jQuery.fn.end = function() { return this.prevObject || this.constructor(null); } console.log($('li').slice(2,4).css('background-color','red').end());
这里pushStack扮演的角色就是设置一个对上个对象引用的属性,因为它不是初始化的jQuery对象,我们还要设置一下它的上下文context, merge 的作用是合并对象设置length。
end 的作用是返回上个对象,当没有这个属性就构造一个空jQuery对象(because constructor: jQuery, line 96)。
总得来说,遍历是为了对当前已存在jQuery对象筛选,采用栈的原因是方便我们在链式调用中回溯,这要比重新构造jQuery对象来得快的多。
5.从代码中可以很清楚的看到jQuery.fn.each实际就是jQuery.each方法第一个参数传入this,因为这个方法在很早很频繁的使用,所以在这里就先来分析。
//simple function isArraylike(obj){ return obj.length - 1 in obj; } // args is for internal usage only jQuery.each = function( obj, callback, args ) { var value, i = 0, length = obj.length, isArray = isArraylike( obj ); if ( args ) { if ( isArray ) { for ( ; i < length; i++ ) { value = callback.apply( obj[ i ], args ); if ( value === false ) { break; } } } else { for ( i in obj ) { value = callback.apply( obj[ i ], args ); if ( value === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isArray ) { for ( ; i < length; i++ ) { value = callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } } else { for ( i in obj ) { value = callback.call( obj[ i ], i, obj[ i ] ); if ( value === false ) { break; } } } } return obj; }; var a1 = [1,2,3], a2 = {0:1,1:2,2:3,length:3}, a3 = {a:1,b:2,c:3}; jQuery.each(a1,function(i,el){ console.log(i+':'+el) }) jQuery.each(a2,function(i,el){ console.log(i+':'+el) }) jQuery.each(a3,function(i,el){ console.log(i+':'+el) })
情况分析:
~带参数情况
~数组及类数组情况
~对象情况
~如果返回false则终止遍历
效率分析:
~for in 的效率比for(var i =0;i<arr.length;i++)低很多
~forEach不适用?
这里的 isArray 实际指的是数组和类数组(这也是类数组对象的优点)。一个大的if..else将代码分成两个模块,代码基本一样,作者注释到带参数仅内部使用。先来讨论不带参数的情况。
进行分支,然后当数组类数组时,用for循环遍历, callback.call( obj[ i ], i, obj[ i ] ) ,利用call方法,指定this为当前对象并调用回调函数,后面的参数依次是i位置,objec[i]当前对象。因为有一个false终止遍历功能,用判断然后break;。对象的情况一样,只是循环时采用for in。
回到最初,含有参数时,与不带参数的情况基本一样,但关键处, callback.apply( obj[ i ], args ) 回调函数调用的参数完全不同,这里用的是apply方法。
6.map和each很相似,但map的侧重点是对返回值处理,each是对每个对象执行一个函数。
//因为我们将方法提取出来,很多辅助的方法访问不到,我们重新定义。 var arr = []; concat = arr.concat; function isArraylike( obj ) { var length = obj.length, type = jQuery.type( obj ); if ( type === "function" || jQuery.isWindow( obj ) ) { return false; } if ( obj.nodeType === 1 && length ) { return true; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } jQuery.fn.map = function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }; // arg is for internal usage only jQuery.map = function( elems, callback, arg ) { var value, i = 0, length = elems.length, isArray = isArraylike( elems ), ret = []; // Go through the array, translating each of the items to their new values if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return concat.apply( [], ret ); }; console.log($(':checkbox').map(function() { return this.id; }).get().join())
情况分析:
~数组及类数组情况
~对象情况
~value不存在
效率分析:
~for in 的效率比for(var i =0;i<arr.length;i++)低很多
情况和each基本一样,关键地方, if ( value != null ) 判断值不存在的情况, this.pushStack 加入堆栈。
这里有一个技巧,因为apply的第二个参数为数组,利用它可以将先前收集的数据数组(方便收集数据)当作数据处理,
var arr = []; concat = arr.concat; var ret = [[1,2],[3,4],5]; console.log([].concat(1,[2,3])) console.log([].concat(ret)) //不能深入合并 console.log(concat.apply([],ret)) //可以
7.slice先前讲过,下面主要来讲eq
eq和get方法类同,区别有两个地方,一个是对不传入参数时及溢出的处理方式,一个是eq的结果将进行pushStack处理
jQuery.fn.eq = function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); } console.log($('div').eq(0))
因为两个方法判断部分相似,如果不考虑不传入参数及溢出的情况。可以直接调用get方法。
jQuery.fn.eq = function(i){ return this.pushStack([this.get(i)]) } console.log($('div').eq(1))
最后作者存储push,sort,splice数组方法。