美文网首页
jQuery.extend源码解读

jQuery.extend源码解读

作者: Kevin丶CK | 来源:发表于2019-08-14 15:51 被阅读0次

用法

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

概述

用一个或多个其他对象来扩展一个对象,返回被扩展的对象。
1、如果不指定target,则给jQuery本身进行扩展。这有助于插件作者为jQuery增加新方法和属性。
2、如果第一个参数deep设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象,及把target之后的对象参数深拷贝到target中。
3、如果第一个参数deep设置为false,则jQuery返回一个副本会与原对象共享结构,把target之后的对象参数浅拷贝到target中。
4、未定义的属性将不会被复制,然而从对象的原型继承的属性将会被复制。

代码解读

下面我们将通过源码解析,学习一下优秀的框架的写法。下面的代码是jquery-3.4.1版本。把extend方法单独拿出来学习一下

  function extend() {
        //源对象指的是把自己的值赋给别人的对象;目标对象指的是被源对象赋值的对象
        var options, //指向源对象
          name, //表示某个源对象的某个属性名
          src, //表示目标对象的某个属性的原始值
          copy, //表示某个源对象的某个属性的值
          copyIsArray, //指示变量 copy 是否是数组
          clone, //表示深度复制时原始值的修正值
          target = arguments[0] || {}, //指向目标对象,申明时先临时用第一个参数值
          i = 1, //表示源对象的起始下标,申明时先临时用第二个参数值
          length = arguments.length, //表示参数的个数,用于修正变量 target
          deep = false; //指示是否执行深度复制,默认为 false
        console.log("target:", target);
        // 处理深拷贝的情况
        if (typeof target === "boolean") {
          deep = target; //设置deep变量,确定是深拷贝还是浅拷贝
          //获取深拷贝标记之后的参数(及目标对象)
          target = arguments[i] || {};
          console.log("deep:", target);
          i++; //源对象的起始下标设为2(即从第三个参数开始为源对象)
        }
        //到此,可以总结出:extend三个参数:[深拷贝标记,目标对象,源对象]
        // 处理目标源对象是字符串或者其他类型的情况(可能是深拷贝)
        if (typeof target !== "object" && !isFunction(target)) {
          target = {};
        }

        // 如果只传递一个不是boolean类型的参数,则扩展jQuery本身
        if (i === length) {
          target = this;
          i--;
        }
        // 拷贝的核心部分代码,递归深拷贝
        for (; i < length; i++) {
          // 只处理非null和非未定义的值
          if ((options = arguments[i]) != null) {
            //options就是源对象
            //遍历源对象的属性名
            for (name in options) {
              //获取源对象上,属性名对应的属性值
              copy = options[name];
              //防止对象原型的污染
              //为了避免深度遍历时死循环,因此不会覆盖目标对象的同名属性。防止无限循环
              if (name === "__proto__" || target === copy) {
                continue;
              }
              // 递归合并普通对象或数组
              if (
                deep &&
                copy &&
                (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
              ) {
                //获取目标对象上相同属性的值
                src = target[name];
                if (copyIsArray && !Array.isArray(src)) {
                  // 源对象的属性值是数组,目标对象上相同属性的值不是数组,则创建数组作为该属性的目标数组
                  clone = [];
                } else if (!copyIsArray && !isPlainObject(src)) {
                  // 源对象的属性值是对象,目标对象上相同属性的值不是对象,则创建对象作为该属性的目标对象
                  clone = {};
                } else {
                  //源对象的属性值和目标对象上相同属性的值的类型相同(及都是数组或者都是对象)
                  clone = src;
                }
                copyIsArray = false;
                //递归地拷贝
                target[name] = extend(deep, clone, copy);
              } else if (copy !== undefined) {
                //属性是原始类型,直接把源对象的属性,赋给目标对象
                target[name] = copy;
              }
            }
          }
        }
        // Return the modified object
        return target;
      }

源码里面都加了注释,阅读起来几乎没有难度,先花几分钟好好阅读一下,基本上面的概述中的情况都能理解了。
一些校验的方法这里就不累赘了,自己找了一源码看一下,无非是判断是对象还是函数的。虽然简单,但是JQuery写的还是很不错的,值的参考
现在我们就结合源码看看概述中,我们设置的参数不同,产生的不同结果吧。

情况一:不指定target
      var settings = { mTestValidate: false, mTestLimit: 5, mTestName: "foo" };
      extend(settings);
      console.log(window.mTestValidate, window.mTestLimit, window.mTestName); //false 5 "foo"

源码中,target 赋值为第一个参数deep,然后进行了类型校验,最后判断i=1和参数个数进行比较,发现只有一个参数,所以target赋值为this。(如果在JQuery.js中,this为JQuery)。由于deep =false,所以是浅拷贝。
上面的实例由于是把extend方法独立出来,在window的词法环境下调用,所以thiswindow,所以settings被扩展到了window上。

    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--;
    }
情况二:deep设置为true
      var settings = { mTestValidate: false, mTestLimit: 5, mTestName: "foo" };
      var options = { mTestValidate: true, mTestName: "bar" };
      extend(true, options, settings);
      console.log(options);//{mTestValidate: false, mTestName: "foo", mTestLimit: 5}

源码中,target 赋值为第一个参数deep,然后进行了类型校验,为boolean类型,则 deep = target=true。所以进入递归拷贝对象属性

// 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 ) {
    target[ name ] = copy;
}

修改被扩展对象的属性,不影响原来对象(深拷贝)

      var settings = {
        mTestValidate: false,
        mTestLimit: 5,
        mTestName: "foo",
        ages: [1, 2, 3]
      };
      var options = { mTestValidate: true, mTestName: "bar" };
      options = extend(true, options, settings);
      options.ages[3] = 4;
      console.log(settings);
      console.log(options);
情况三:deep设置为false
      var settings = {
        mTestValidate: false,
        mTestLimit: 5,
        mTestName: "foo",
        ages: [1, 2, 3]
      };
      var options = { mTestValidate: true, mTestName: "bar" };
      options = extend(false, options, settings);
      console.log(settings);
      console.log(options);

源码中,target 赋值为第一个参数deep,然后进行了类型校验,为boolean类型,则 deep =false;target = {}。所以进直接把源对象的属性,赋给目标对象,进行浅拷贝。


修改属性,都改变,浅拷贝。(不理解浅拷贝和深拷贝的自己找资料吧,以后有空写个文章介绍一下)
      options.ages[3] = 4;
      console.log(settings);
      console.log(options);

相关文章

网友评论

      本文标题:jQuery.extend源码解读

      本文链接:https://www.haomeiwen.com/subject/osugjctx.html