1-js对象/数组的深拷贝和浅拷贝

JavaScript有两种数据类型

基础数据类型(Undefined、Null、Boolean、Number 和 String). 变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。

引用数据类型(Array、Object),变量保存的是一个指针。当需要访问引用类型时,先获得地址指针,在从内存中取得数据。

JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。


浅复制:

浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响。(对于直接赋值是不会影响的)

如图:

1基础数据类型的浅复制

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

对于深度克隆,我们需要使用其他替代方法,因为Object.assign()复制属性值。如果源值是对象的引用,则它仅复制该引用值。  

2 object.assign()

深复制:

Array的slice和concat方法

不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。 类似于Object.assign()

JSON对象的parse和stringify

JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。


3、JSON.parse / JSON.stringify 

但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。还有一点不好的地方是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理。


4、jQuery.extend()方法源码实现

jQuery的源码 - src/core.js #L121源码及分析如下:

jQuery.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法

  var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},

  i = 1,

  length = arguments.length,

  deep = false;

  //以上其中的变量:options是一个缓存变量,用来缓存arguments[i],name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的value。

  //copy传入对象上每个key对应的value,copyIsArray判定copy是否为一个数组,clone深拷贝中用来临时存对象或数组的src。

  // 处理深拷贝的情况

  if (typeof target === "boolean") {

    deep = target;

    target = arguments[1] || {};

    //跳过布尔值和目标

    i++;

  }

  // 控制当target不是object或者function的情况

  if (typeof target !== "object" && !jQuery.isFunction(target)) {

    target = {};

  }

  // 当参数列表长度等于i的时候,扩展jQuery对象自身。

  if (length === i) {

    target = this; --i;

  }

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

    if ((options = arguments[i]) != null) {

      // 扩展基础对象

      for (name in options) {

        src = target[name];

        copy = options[name];

        // 防止永无止境的循环,这里举个例子,

            // 如 var a = {name : b};

            // var b = {name : a}

            // var c = $.extend(a, b);

            // console.log(c);

            // 如果没有这个判断变成可以无限展开的对象

            // 加上这句判断结果是 {name: undefined}

        if (target === copy) {

          continue;

        }

        if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {

          if (copyIsArray) {

            copyIsArray = false;

            clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是数组的话就让clone副本等于src否则等于空数组。

          } else {

            clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是对象的话就让clone副本等于src否则等于空数组。

          }

          // 递归拷贝

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

        } else if (copy !== undefined) {

          target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。

        }

      }

    }

  }

  // 返回修改的对象

  return target;

};

jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用,例如:

var a = {"name":"aaa"};

var b = {"name":"bbb"};

a.child = b;

b.parent = a;

$.extend(true,{},a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded

5、自己动手实现一个拷贝方法

(function ($) {

    'use strict';

    var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');

function type () {

  return Object.prototype.toString.call(this).slice(8, -1);

}

for (var i = types.length; i--;) {

    $['is' + types[i]] = (function (self) {

        return function (elem) {

          return type.call(elem) === self;

        };

    })(types[i]);

}

    return $;

})(window.$ || (window.$ = {}));//类型判断

function copy (obj,deep) {

    if ($.isFunction(obj)) {

    return new Function("return " + obj.toString())();

    } else if (obj === null || (typeof obj !== "object")) {

        return obj;

    } else {

        var name, target = $.isArray(obj) ? [] : {}, value;

        for (name in obj) {

            value = obj[name];

            if (value === obj) {

            continue;

            }

            if (deep) {

                if ($.isArray(value) || $.isObject(value)) {

                    target[name] = copy(value,deep);

                } else if ($.isFunction(value)) {

                    target[name] = new Function("return " + value.toString())();

                } else {

                target[name] = value;

                }

            } else {

            target[name] = value;

            }

        }

        return target;

    }        

}

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • /*! jQuery JavaScript Library v1.4.2 http://jquery.com/ C...
    ssttIsme阅读 1,972评论 2 0
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,720评论 1 45
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,053评论 0 2
  • 今天坐高铁,竟然有15块的套餐,要了一份,吃起来味道还不赖,跟飞机餐很像。以前读书时,舍不得坐卧铺,一路80个小时...
    青三1219阅读 587评论 0 51