jQuery源码研究:为jQ对象扩展的一些工具方法

版权声明:本文为微信公众号前端小二版权所有,如需转载可私信或关注微信公众号留言。 https://blog.csdn.net/qq_34832846/article/details/86297913

上一章,讨论的是jQuery对象及其原型上的extend()方法,在源码中,实现了支持开发者自行扩展新方法的功能,但其实jQuery也通过对extend()传入一个对象参数来添加官方扩展方法,这些工具方法都是扩展在jQuery类对象上的,所以调用时的写法要注意。

下面一起来看下扩展了哪些官方方法。

//总览:
jQuery.extend({
    expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
    isReady: true,
    error: function(){},
    noop: function(){},
    isPlainObject: function(){},
    isEmptyObject: function(){},
    globalEval: function(){},
    each: function(){},
    trim: function(){},
    makeArray: function(){},
    inArray: function(){},
    merge: function(){},
    grep: function(){},
    map: function(){},
    guid: 1,
    support: support
})

1、先来看下expando属性,看它的实现,其实就是提供一个由jq版本号加上随机数字形成的唯一字符串。

error()方法作用是抛出一个错误:

jQuery.extend({
    error: function(msg){
        throw new Error(msg)
    }
})

2、isPlainObject()方法作用是检测参数是否为纯对象,这个方法在上一章中也讲到过,所谓纯对象,就是正常键值对形式的对象。代码解释看注释:

jQuery.extedn({
    isPlaniObject: function(obj){
        var proto, Ctor;

        // 如果参数不是对象 或者 通过call方式对参数调用toString()字符串化的结果不符合要求,则直接方法返回false,就不会再往下走了。
        if( !obj || toString.call( obj ) != "[object Object]" ){
            return false;
        }

        //返回参数对象的原型,在jQ工厂函数的头部,已经定义好getProto变量是Object.getPrototypeOf方法的引用
        proto = getProto( obj ); 

        //如果proto为假,其实就是指参数对象没有原型,由于前面已经对参数是对象进行判定,所以此时对象没有原型指对象属于类型 Object.create(null) 这样的情况,自然参数也是属于 纯对象 
        if(!proto){ 
            return true;
        }

        // 对象如果是被全局对象函数构造出来的,则返回其构造函数本身
        // 这里hasOwn变量是 {}.hasOwnProperty() 方法的引用
        // hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否有指定的值
        Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;    //这里其实就是考的js中的构造函数。
        return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;  //构造函数及其实例 返回false
    }
})

只有键值对形式的js对象,才返回true,才被视为纯对象,即使是构造函数和普通函数等“对象”也不行。

3、isEmptyObject()方法,看命名即知其作用是检测一个对象是否为空对象,即不包含任何可枚举属性。

jQuery.extend({
    isEmptyObject: function(obj){
        var name;
        if(name in obj){
            return false;
        }
        return true;
    }
})

通过for...in...来检测对象是否有可枚举属性来,判断是否为空对象。

这里复习下for...in...作用,其是用来遍历对象的可枚举属性的,包括对象原型上的可枚举属性。而如果只想要遍历对象本身的属性且不想遍历出对象原型上的属性,则需要使用hasOwnProperty()方法:

var obj = {
    a: 1,
    b: 2,
    c: 3
}
function A(x) {
    this.x = x
}
A.prototype = obj;  //将构造函数A的原型指向对象obj
var a = new A(10);

console.log('所有可枚举属性,包含原型上的属性:');
for(var i in a){
    console.log(i);
}

console.log('自有可枚举属性:');
for(var i in a){
    if(a.hasOwnProperty(i)){   
        console.log(i);
    }
}

//打印结果:
/*
所有可枚举属性,包含原型上的属性:
x
a
b
c
自有可枚举属性:
x
*/

4、globalEval(),用于全局性的执行一段代码,其执行代码的作用域是全局作用域,这个方法还是尽量少用,毕竟作用域的使用还是规范的好。

5、each()方法,这个方法大家就非常熟悉了,遍历对象或数组用的,来看看该方法的内部实现:

jQuery.extend({
    each:function(obj, callback){
        var length, i=0;
        if(isArrayLike( obj )){ //如果是类数组
            length = obj.length;
            for( ; i<length; i++ ){
                if( callback.call( obj[i], i, obj[i] ) === false ){ 
                    break;
                }
            }
        }else {     //如果是纯对象
            for( i in obj ){
                if( callback.call( obj[i], i, obj[i] ) === false ){
                    break;
                }
            }
        }
        return obj;
    }
})

看上述代码实现可知,方法内部对参数obj进行了对象和类数组的判别,同时在遍历时,对传入的回调函数也进行了控制:callback.call( obj[i], i, obj[i] ) === false,通过call方法来将回调函数中的this对象指向当前循环到的属性值上,传入回调函数中的参数标识出属性名和属性值的先后顺序,并且还添加false来为回调函数增加了手动停止遍历循环的功能。

// 接上上例的构造函数A的实例对象a来做演示
console.log(jQuery.each( a, function(index, val){
    if(index === 'a'){  //当遍历到的属性为a时,返回false来跳出遍历循环
        return false;
    }else {
        console.log(index + ": " + val);
    }
} ));

时间关系,今天就写这么多吧,更多关于jQuery.extend()扩展出来的工具方法,请看下篇。


接昨天的(上),今天继续。

6、trim方法:删除字符串开始与结尾的空格,来看下方法实现:

jQuery.extend({
    trim: function(text){
        return text == null ?
                "":
                (text + "").replace(rtrim, "");
    }
})

解释:如果参数text值为null或者undefined,则返回空字符串;否则就通过replace()方法传入对应正则进行匹配替换。这里rtrim参数变量是在源码开头就定义好的的一个正则表达式变量rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;

这个方法的return表达式有两个小知识点:

  • undefined == null为真
  • text+""这里是用到js中的字符串转义,确保始终是对字符串在进行replace操作

7、makeArray()方法:将一个类数组转化成真正的数组对象。类数组虽然具有许多数组的属性,比如length[]数组访问运算符等,但是却没有从数组的原型对象上继承下来的内置方法。

jQuery.extend({
    makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArrayLike( Object( arr ) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},
})

解释:方法里的results参数,在代码中有注意:仅供内部使用。意为只有在源码内部调用这个makeArray方法时,才会传入results参数,而在外部调用这个静态方法makeArray时,都只会传入一个参数,即要转为数组的参数arr。当arr参数不为null时,进入方法处理的逻辑判断,内部将参数arr的数据类型分为两种情况:

  • 一种是类数组对象,即有length属性的对象,这里就调用jQuery.merge方法进行处理,这个方法等会在下面详讲;
  • 另一个种情况是对应的没有length属性的有键值对的对象,此时通过call调用原生数组的push()方法来将参数对象arr传入方法开始就创建好的空数组中,生成以对象参数为元素的数组。

8、inArray()方法,作用其实就是检测数组中是否存在某个元素,如存在则返回元素对应的索引,如不存在,则返回-1。这个方法其实就是对js原生数组indexOf方法的使用封装,看源码:

jQuery.extend({
    inArray: function(elem, arr, i){
        return arr == null ? -1 : indexOf.call(arr, elem, i)
    }
})

解释:很简单的一行代码,通过三目运算符,先对传入数组arr进行null判断,如为空直接返回-1,告诉开发者,这货不存在,因为你连数组都没告诉我,我怎么知道你有没存在的,丢你一脸的-1;如果arr源数组传入了,那么就通过call调用早前定义的好的indexOf方法,来对数组arr进行值elem存在检测。这里需要复习下原生的indexOf方法:其可返回某个指定的元素首次出现的位置,方法参数1必传需检索的元素值,参数2选传整数参数,以规定开始检索的位置。

9、merge()方法:合并两个数组内容到第一个数组。这个方法接收两个数组参数firstsecondfirst数组是用于合并的数组,方法最后返回的first数组会包含合并后的第二个数组的内容,而second数组内容在合并后不会被修改。具体实现看源码:

jQuery.extend({
    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;
    }
})

解释:对second数组进行遍历,将该数组中的元素接到first数组的后面,形成新的数组,并手动更新first数组的长度。这里注意,由于数组是引用数据类型,所以first数组内容更新了。

10、grep()方法,使用指定的回调函数来过滤数组中的元素,并返回过滤后的数组。

jQuery.extend({
    grep: function(elems, callback, invert){
        var callbackInverse,
            matches = [],
            i = 0,
            length = elems.length,
            callbackExpect = !invert;

        for(; i<length; i++){
            callbackInverse = !callback( elems[i], i );
            if( callbackInverse !== callbackExpect ){
                matches.push( elems[i] );
            }
        }

        return matches;
    }
})

解释:在方法的for循环中,只有通过检测函数callback筛选的项会被保存下来并存进数组matches中,这不会影响到原数组。看callback函数的传入参数可知,外面指定的筛选函数需要传入两个参数,参数1是数组元素值,参数2是元素索引。另外方法的参数3invert其实是个布尔值,默认为false,其作用在于是否需要反转筛选项。

11、map()方法,遍历数组中的每个元素或者对象中的每个属性,并将处理后的结果push进新的数组并返回。

jQuery.extedn({
    map: function(elems, call, arg){
        var length, value,
            i = 0,
            ret = [];

        if( isArrayLike(elems) ){   //类数组对象
            length = elems.length;
            for( ; i<length; i++ ){
                value = callback( elems[i], i, arg );

                if( value != null ){
                    ret.push( value );
                }
            }
        }else {     //普通对象
            for( i in elems ){
                value = callback( elems[i], i, arg );

                if( value != null ){
                    ret.push( value );
                }
            }
        }

        return concat.apply( [], ret );
    }
})

好了,关于jQuery.extend()扩展出来的一些工具方法,就到这里。在上面讲的方法实现中,关于callapply的使用是异常频繁的,它也是js比较诡异却又神奇的一个特点,需要真正弄清哦。

小预告:明天开始就要读到jQuery的精华:复杂选择器的实现了,那是一座大山呀,要非常耐心的啃呀。

喜欢本文请扫下方二维码,关注微信公众号: 前端小二,查看更多我写的文章哦,多谢支持。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_34832846/article/details/86297913