(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数组方法。

原文地址:https://www.cnblogs.com/winderby/p/4151252.html