#高考加油#每一天的学习就是对自己的投资

javascript系列--实现jQuery的extend的功能

一、前言

上周在学习了深拷贝和浅拷贝的简单实现。我们可以从优秀的库里面学习一些方法,比如这回我们来看看jquery的extend方法的实现。


二、extend的基本用法

extend的方法功能,可以来看一下jquery的官网

Merge the contents of two or more objects together into the first object.

意思就是说:合并两个或者多个对象的内容到第一个对象中。

用法:jQuery.extend( target[ object1 ], [ objectN ] )

说明:第一个参数taarget,表示要扩展的目标,目标对象。

后面的参数,是参数列表,都传入对象,内容都会复制到目标对象中,我们就称它们为待复制对象吧。

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(obj1, obj2, obj3));
// {
//    a: 1,
//    b: { b1: 3, b3: 4 },
//    c: 3,
//    d: 4
// }

我们可以发现,当两个对象的出现相同的字段的时候,后者会覆盖前者的。而不会进行深层次的覆盖。


三、extend的第一个版本-浅拷贝

结合上周写的深浅拷贝的简单实现,我们照着jquery的extend来实现一下。

// 初版
function extendQ(){
    var name,options,copy;
    var length = arguments.length;
    var target = arguments[0];
    for(var i=1;i<length;i++){
        options = arguments[i];
        if(options !== null){
            for(name in options){
                copy = options[name];
                if(copy !==undefined){
                    target[name] = copy;
                }
            }
        }
    }
    return target;
}


四、extend的第二个版本-深拷贝

如何进行深层次的复制呢?jQuery v1.1.4 加入了一个新的用法:

jQuery.extend( [deep], target, object1 [, objectN ] )

函数的第一个参数可以传一个布尔值,如果为 true,我们就会进行深拷贝,false 依然当做浅拷贝,这个时候,target 就往后移动到第二个参数。

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(true, obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b2: 2, b3: 4 },
//    c: 3,
//    d: 4
// }

因为采用了深拷贝,会遍历到更深的层次进行添加和覆盖。

实现第二版本的extend

(1)需要根据第一个参数的类型,确定target要合并的对象的下标起始值。

(2)如果是深拷贝,根据copy的类型递归extend。

// 第二版
function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是才是target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为{}
    if (typeof target !== 'object') {
        target = {}
    }
    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免extend(a,,b)这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];
                if (deep && copy && typeof copy == 'object') {
                    // 递归调用
                    target[name] = extend(deep, src, copy);
                }
                else if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }
    return target;
};

在实现上,核心的部分还是跟上篇实现的深浅拷贝函数一致,如果要复制的对象的属性值是一个对象,就递归调用 extend。不过 extend 的实现中,多了很多细节上的判断,比如第一个参数是否是布尔值,target 是否是一个对象,不传参数时的默认值等。


这时候存在一些问题:

(1)如果target是函数,怎么处理?

(2)类型不一致怎么处理?我们需要对目标属性值和待复制的对象属性值进行判断。

(3)用到了递归,有时候会出现循环引用的问题,我们需要判断要复制的对象是否等于target,如果等就跳过。


五、extend的最终版本

var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
    var proto, Ctor;
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }
    proto = Object.getPrototypeOf(obj);
    if (!proto) {
        return true;
    }
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}


function extend() {
    // 默认不进行深拷贝
    var deep = false;
    var name, options, src, copy, clone, copyIsArray;
    var length = arguments.length;
    // 记录要复制的对象的下标
    var i = 1;
    // 第一个参数不传布尔值的情况下,target 默认是第一个参数
    var target = arguments[0] || {};
    // 如果第一个参数是布尔值,第二个参数是 target
    if (typeof target == 'boolean') {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是对象,我们是无法进行复制的,所以设为 {}
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }
    // 循环遍历要复制的对象们
    for (; i < length; i++) {
        // 获取当前对象
        options = arguments[i];
        // 要求不能为空 避免 extend(a,,b) 这种情况
        if (options != null) {
            for (name in options) {
                // 目标属性值
                src = target[name];
                // 要复制的对象的属性值
                copy = options[name];

                // 解决循环引用
                if (target === copy) {
                    continue;
                }

                // 要递归的对象必须是 plainObject 或者数组
                if (deep && copy && (isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    // 要复制的对象属性值类型需要与目标属性值相同
                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];

                    } else {
                        clone = src && isPlainObject(src) ? src : {};
                    }

                    target[name] = extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }
    return target;
};

我们来检测一下这个函数,例子1:

var a = extend(true, [4, 5, 6, 7, 8, 9], [1, 2, 3]);
console.log(a);      // [1,2,3,7,8,9]

例子2:

var obj1 = {
    value: {
        3: 1
    }
}
var obj2 = {
    value: [5, 6, 7],

}
var b = extend(true, obj1, obj2) // {value: [5,6,7]}
var c = extend(true, obj2, obj1) //  {value: [5,6,7]}

注意:(1)你可能觉得c的值一个个是{value: {3,1}},但是因为执行前面这句的时候,obj1的值已经发生了改变,此时的obj1的值是{value: [5,6,7]}。

(2)要求不能为空 避免 extend(a,,b) 这种情况,不应该是options !=undefined,为啥使用null?

因为 null == null 和 undefined == null 的结果都为 true,所以 options != null ,如果 options 为 null 或者 undefine,结果都为 false,所以无论是 extend(a,,b) 或者你传 extend(a, null, b)都是可以跳过这个参数的。除此之外,在低版本的浏览器中,undefined 的值是可以被更改的


六、参考

(1)jquery文档:https://api.jquery.com/clone/

(2)jquery的extend的实现:https://github.com/jquery/jquery/blob/1472290917f17af05e98007136096784f9051fab/src/core.js#L121


感谢你的阅读,本文由 sau交流学习社区 版权所有。
如若转载,请注明出处:sau交流学习社区-power by saucxs(程新松)(/page/744.html)
交流咨询
    官方QQ群
    群号663940201,欢迎加入!
    sau交流学习社区交流群

图文推荐

saucxs聊天机器人
saucxs
hi ,欢迎来到sau交流学习社区,欢迎与我聊天,问我问题哦!