首页 前端知识 jQuery的extend方法仅仅是字面意思上的扩展吗?

jQuery的extend方法仅仅是字面意思上的扩展吗?

2024-10-30 21:10:11 前端知识 前端哥 626 718 我要收藏

  jQuery中extend的使用方式大多是这样的:

    jQuery.extend({

        // Unique for each copy of jQuery on the page
        expando: "jQuery" + (version + Math.random()).replace(/\D/g, ""),

        // Assume jQuery is ready without the ready module
        isReady: true,

        error: function (msg) {
            throw new Error(msg);
        },
    })

  jQuery几乎在每一部分模块代码的下方,都会加上这样的一段代码,来添加操作该模块的属性和方法。

  如果去搜索jQuery.extend会发现,这个函数还可以这么用:

 var udataCur = jQuery.extend({}, udataOld);
 var anim = Animation(this, jQuery.extend({}, prop), optall);

  这样的使用方法,可不太像是添加扩展了。一个函数不只能实现一个功能?不如去看看,函数内部是怎么实现的,以及这个函数到底能用来干什么。

.extend源码:

    jQuery.extend = jQuery.fn.extend = function () {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        // Handle a deep copy situation
        if (typeof target === "boolean") {
            deep = target;

            // Skip the boolean and the target
            target = arguments[i] || {};
            i++;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if (typeof target !== "object" && !isFunction(target)) {
            target = {};
        }

        // Extend jQuery itself if only one argument is passed
        if (i === length) {
            target = this;
            i--;
        }

        for (; i < length; i++) {

            // Only deal with non-null/undefined values
            if ((options = arguments[i]) != null) {

                // Extend the base object
                for (name in options) {
                    copy = options[name];

                    // Prevent Object.prototype pollution
                    // Prevent never-ending loop
                    if (name === "__proto__" || target === copy) {
                        debugger
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (jQuery.isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                        src = target[name];

                        // Ensure proper type for the source value
                        if (copyIsArray && !Array.isArray(src)) {
                            clone = [];
                        } else if (!copyIsArray && !jQuery.isPlainObject(src)) {
                            clone = {};
                        } else {
                            // clone = src = 1
                            clone = src;
                        }
                        copyIsArray = false;

                        // Never move original objects, clone them
                        target[name] = jQuery.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    };

  好家伙,72行代码实现了一个函数,看看里面都写了点儿啥。

  首先,这个函数并没有指定入参,而是通过arguments加下标的形式,来动态取参数,从函数内部的注解中也能看出些端倪,注释中有提到 Handle a deep copy situation 即 处理深拷贝的场景,那么目前可知,这个函数,除了能为jQuery本身提供相应模块的扩展, 还可以对对象进行拷贝

   代码最开始部分定义了函数中用到的变量:

        var options, name, src, copy, copyIsArray, clone,
            // 传入的第一个参数,如果不存在,则默认为{}
            target = arguments[0] || {},
            i = 1,
            // 传参个数
            length = arguments.length,
            // 是否为深拷贝操作,默认为false
            deep = false;

   接下来是一些容错和函数应用何种场景的判断

        // 如果首个参数为boolean类型,则代表该函数用于对象的深拷贝
        if (typeof target === "boolean") {
            deep = target;

            // 直接取第二个参数,作为拷贝操作的目标对象
            target = arguments[i] || {};
            // 指针后移一位
            i++;
        }

        // 处理传参不符合要求的情况
        if (typeof target !== "object" && !isFunction(target)) {
            target = {};
        }

        // 如果只传递了一个参数,则视为扩展jQuery
        if (i === length) {
            target = this;
            i--;
        }w

   再往下,就是函数处理拷贝的相关操作了:

        for (; i < length; i++) {

            // 跳过为空的情况
            if ((options = arguments[i]) != null) {

                // Extend the base object
                for (name in options) {
                    copy = options[name];
                    //源码里说是防止污染原型链同时规避调用自身造成的死循环,但是
                    // 打上debugger后,这里并没有被执行过,可能是出于严谨考虑吧
                    if (name === "__proto__" || target === copy) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (jQuery.isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                        src = target[name];

                        // Ensure proper type for the source value
                        if (copyIsArray && !Array.isArray(src)) {
                            clone = [];
                        } else if (!copyIsArray && !jQuery.isPlainObject(src)) {
                            clone = {};
                        } else {
                            clone = src;
                        }
                        copyIsArray = false;

                        // Never move original objects, clone them
                        target[name] = jQuery.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        // 这里说一下,如果是扩展的应用场景,那么,之前已经将
                        // this指向了target,所以,这一步在扩展中就相当于
                        // jQuery[name] = copy
                        target[name] = copy;
                    }
                }
            }
        }

   说一下有关深浅拷贝的概念:
    浅拷贝,只复制对象内部的基本类型数据,如果对象内部还有对象,则新旧对象共享同一个对象引用
    深拷贝,是完全复制一个新对象出来,两个对象间没有任何联系。
     写个例子对比一下:

//浅拷贝
let obj1 = {a: 1, b: {c: 2}};  
let obj2 = Object.assign({}, obj1); 

obj2.a = 'k'
obj2.b.c = 3
console.log(obj1) //{a: 1, b: {c: 3}}

// obj1经由 Object.assign 生成了一个自身的浅拷贝对象 obj2
// 当修改obj2的a属性时,原对象obj1的a属性不会受到影响,但是
// 修改obj2的b.c属性时,obj1的b.c属性也相应的发生了变化。
// 这就是浅拷贝只能复制基础属性,无法复制对象内引用的含义
//深拷贝
    let obj1 = {a: 1, b: {c: 2}};

    let clone = function () {
        let src = arguments[0]
        let target = {}
        for (const name in src) {
            if (typeof src[name] === 'object') {
                src = src[name]
                target[name] = clone(src)
            } else {
                target[name] = src[name]
            }
        }

        return target
    }

    let obj2 = clone(obj1)
    obj2.a = 'k'
    obj2.b.c = 3
    console.log(obj1) //{a: 1, b: {c: 2}}
    
// 最终的运行结果,可以看到,修改深拷贝后的对象基础数据和引用
// 都不会对原始数据产生影响

   了解深浅拷贝的概念后,继续回到源代码,在处理拷贝的开始,有一个for循环,执行条件为 i<length 这样设计,可以确保无论是扩展还是拷贝的哪一种场景,最终的处理逻辑都放在这个循环中去执行,保证代码的逻辑统一,如果后续这个函数的参数继续增加,这样处理也就留出了可扩展的空间。

    // 判断当前是否为深拷贝场景,同时参数需要满足条件:纯对象或数组
    if (deep && copy && (jQuery.isPlainObject(copy) ||
        (copyIsArray = Array.isArray(copy)))) {
        src = target[name];

        // Ensure proper type for the source value
        if (copyIsArray && !Array.isArray(src)) {
            clone = [];;
        } else if (!copyIsArray && !jQuery.isPlainObject(src)) {
            clone = {};
        } else {
            clone = src
        }
        copyIsArray = false;

        // Never move original objects, clone them
        // 递归处理,直到需要拷贝的属性不再是对象或数组
        target[name] = jQuery.extend(deep, clone, copy);

        // Don't bring in undefined values
    } else if (copy !== undefined) {
        target[name] = copy;
    }

   jQuery设计的这个extend函数,通过arguments动态获取参数以及对不同传参类型的判断,来实现了对扩展和拷贝这两种应用场景的重载,其实也不应该算两种情况,因为本身扩展就是一种拷贝行为。

   上面代码中有两个jQuery提供的工具函数,正好也看看这两个函数是如何实现的

isFunction

    var isFunction = function isFunction(obj) {

        // Support: Chrome <=57, Firefox <=52
        // In some browsers, typeof returns "function" for HTML <object> elements
        // (i.e., `typeof document.createElement( "object" ) === "function"`).
        // We don't want to classify *any* DOM node as a function.
        // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
        // Plus for old WebKit, typeof returns "function" for HTML collections
        // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
        return typeof obj === "function" && typeof obj.nodeType !== "number" &&
            typeof obj.item !== "function";
    };

// jQuery判断传参是否为一个function类型的工具函数,在这里框架做了很多的兼容,例如
// 在有些浏览器中 <object> 元素 会被当成一个function
// 老旧版本的WebKit内核会将HTML元素集合当成function

isPlainObject

        isPlainObject: function (obj) {
            var proto, Ctor;

            // Detect obvious negatives
            // Use toString instead of jQuery.type to catch host objects
            if (!obj || toString.call(obj) !== "[object Object]") {
                return false;
            }

            proto = getProto(obj);

            // Objects with no prototype (e.g., `Object.create( null )`) are plain
            if (!proto) {
                return true;
            }

            // Objects with prototype are plain iff they were constructed by a global Object function
            Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
            return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
        },
转载请注明出处或者链接地址:https://www.qianduange.cn//article/19667.html
标签
评论
发布的文章
大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!