深拷贝与浅拷贝问题-程序员宅基地

技术标签: ViewUI  json  javascript  

JavaScript的变量类型

五种基本变量类型Null, Undefined, Boolean, Number, String,变量都是按值存放的,存放在栈内存中的简单数据段,可以直接访问。对于引用类型,是存放在栈中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获取该对象的地址指针,然后再从堆内存中取得所需的数据。JavaScript存储对象都是存地址的,所以浅拷贝会导致obj1和obj2只想同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和原对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和原对象各自的操作各不影响。

eg1:

//数组拷贝
var arr1 = [1,2,3];
var arr2 = arr1;
arr1[0] = "e";
console.log(arr1);//"e,2,3"
console.log(arr2);//"e,2,3"

浅拷贝的实现

  1. 引用复制
function shallowClone(copyObj) {
    var obj = {};
    for(var i in copyObj) {
        obj[i] = copyObj[i];
    }
}
var x = {
    a:1,
    b:{d:{e:5}},
    c:[1,2,3]
};
var y = shallowClone(x);
console.log(y.b.d===x.b.d);//true;

  2. Object.assign()

  Object.assign方法用于对象的合并,并将源对象(source)的所有可枚举属性,复制到目标对象(target)。当目标对象和源对象有同名属性时,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

const target = {a: 1,b: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target, source1, source2);
console.log(target) //{a: 1, b: 2, c: 3}

Object.assign()拷贝属性是有限制的,只拷贝原对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)

Object.assign({b: 'c'},
    Object.defineProperty({}, 'invisible', {
        enumerable: false,
        value: 'hello'
    })
)
//{b: 'c'}

Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

const obj1 = {
     a: {b: 5}};
const obj2 = Object.assign({}, x);
obj1.a.b=2;
console.log(obj2.a.b) //2

上面代码中,源对象obj1a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

//采用这种方法可以克隆它继承的值
function clone(origin){
    let originProto = Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto), origin);
}

 

深拷贝的实现

   1.Array的slice和concat方法

Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里是因为它表现得像个深拷贝实际上是浅拷贝。原数组的元素会按照下述规则拷贝:

  •  如果该元素是个对象引用,slice会拷贝这个对象引用到新的数组中。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  • 对于字符串、数字、布尔值来说,slice会拷贝这些值到新的数组中,在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
var arr = [1, 2, 3];
var arr_shallow = arr;
var arr_concat = arr.concat();
var arr_slice = arr.slice(0);
console.log(arr===arr_shallow);//true;
console.log(arr==arr_slice);//false;
console.log(arr===arr_concat);//false;

可以看到,concat和slice返回不同的数组实例,这与直接引用复制是不同的。而从另一个例子可以看出Array的concat和slice并不是真正的深拷贝,数组中的对象元素(Object, Array)只是复制了引用。如下:

var arr = [1, [1,2,3], {name: "knight"}];
var arr_concat = arr.concat();
var arr_slice = arr.slice(0);
arr_concat[1][0] = 5;//改变arr_concat中数组元素的值
console.log(arr[1]);//[5,2,3];
console.log(arr_slice[1]);//[5,2,3];
arr_slice[2].name = "knightboy";//改变arr_slice中对象元素的值
console.log(arr[2].name);//knightboy
console.log(arr_concat[2].name);//knightboy

    1.1 递归实现对象的深拷贝

function deepCopy(obj) {
    var target = Array.isArray(obj) ? [] : {};
    for(var key in obj){
        if(typeof(obj[key]) == 'object' || Array.isArray(obj[key]))
            target[key] = deepCopy(obj[key]);
        else
            target[key] = obj[key];
    }
    return target;
}

  2. JSON对象的parse和stringify

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


//eg1
var source = {name: "source", child: {name: "child"}};
var target = JSON.parse(JSON.stringify(source));
target.name = "target";//改变target的name属性
console.log(source.name); //source
console.log(target.name);//target
target.child.name = "target child";//改变target的child
console.log(source.child.name);//child
console.log(target.child.name);//target child


//eg2
var source = {name: function(){console.log(1);}, child:{name: "child"}};
var target = JSON.parse(JSON.stringify(source));
console.log(target.name);//undefined;


//eg3
var source = {name: function() {console.log(1);}, child:new RegExp("e")};
var target = JSON.parse(JSON.stringify(source));
console.log(target.name);//undefined
console.log(target.child);//Object {}

这种方法使用较为简单,可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝(而且会直接丢失相应的值)。还有一点不好的地方是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理。

  3. jQuery.extend()方法源码实现

/** 
*$.extend([deep], clone, copy)
*[options]用来缓存arguments[i]
*[name]用来接收将要被扩展对象的key
*[src]表示改变之前,target对象上每个key所对应的value,即target[name]
*[copy]表示传入对象上每个key所对应的value, 即options[name]
*[copyIsArray]判定copy是否为一个数组
*[clone]深拷贝中用来临时存储对象或数组的src
*/
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 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) {
                src = target[name];
                copy = options[name];

                //Prevent never-ending loop such as var i = {}; i.a = i; $.extend(true, {}, i);
                if(target === copy) {
                    continue;
                }

                //Recurse if we're merging plain objects or arrays
                if(deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
                    if(copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];
                    else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }
                    //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;//if find name, cover it,or make it;
                }
            }
        }
    }
    //Return the modified object
    return target;
};

这个方法到目前还是不能处理原对象内部的循环引用,

var a = {name: "knight"},
    b = {name: "knightboy"};
a.child = b;
b.parent = a;
$.extend(true, {}, a);
//Uncaught RangeError: Maximum call stack size exceeded

  4. 自定义实现深拷贝

    // 偏函数
   //判断类型 boolean
var $ = (function() { var $={}; 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(obj) { return type.call(obj) === self; }; })(types[i]); } return $; })(); function copy(obj, key, deep) { var index = 0; if (obj === null || typeof obj !== "object") { return obj; } var name, value, target = $.isArray(obj) ? [] : {}; for (name in obj) { value = obj[name]; if (value === obj) { continue; } if(index===0) value.copykey = 1; if (deep && ($.isArray(value) || $.isObject(value))) { if (!value.copykey && key!==name) { target[name] = copy(value, name, deep); delete target[name].copykey; delete value.copykey; } else { target[name] = value; delete target[name].copykey; delete value.copykey; } } else { target[name] = value; } index=1; } return target; } //嵌套 const obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2; }, a: Symbol("foo") }; const obj2 = copy(obj1,{},true); obj2.x.m = 2; //原型链 function SupType(){ this.SupProperty = "knight"; } function SubType(){ this.SubProperty = 19; } SupType.prototype.getSupValue = function(){ return this.SupProperty; }; SubType.prototype = new SupType(); var instance = new SubType(); let origin = instance.getSupValue(); //循环引用test1 var a = {name: "knight"}, b = {name: "knightboy"}; a.child = b; b.parent = a; //循环引用test2 function P(obj){ } P.prototype.test = function(){console.log(1)}; var p =new RegExp('/[.]/'); var obj3,obj4; //var obj5=new P(); obj3={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}}; obj4={a:1,b:2,c:3,d:Date.now(),e:p}; obj3.a = obj4; obj4.a = obj3; obj3.f = obj4; obj4.f = obj3; var res = copy(obj3,{},true); console.log("源对象(copy):\n",obj3); console.log("目标对象(clone):\n",res); res.b=5; console.log(res.b=obj3.b); console.log("二次环引用测试():\n",obj4);

 

//数据类型判断还有一种更加简洁的方法:
var Type = {};
for(var i=0,type;type=['String','Array','Number','Object','Boolean','Undefined','Null','Symbol','Date','RegExp','Function'][i++];){
    (function(type){
       Type['is'+ type] = function(obj){
          return Object.prototype.toString.call(obj) === '[object ' + type + ']';
       }
    })(type);
}
Type.isArray([]);//true

 

转载于:https://www.cnblogs.com/princess-knight/p/9539446.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30467087/article/details/95305544

智能推荐

【小李木耳】出品:一直舍不得丢掉的东西:“一(个)白云、三(个)兔子...” 和“...早日发账”-程序员宅基地

文章浏览阅读841次。 什么是虚拟化3.0时代? 微软公司的云计算服务有哪些? 云计算IDC服务都包括什么? 什么是云计算? 向私有云过渡的步骤有哪些?一直舍不得丢掉的东西:“一(个)白云、三(个)兔子...” 和“...早日发账”(写于2011年12月10日00:17 北京)今天这个话题比较有感觉,就晒一晒我舍不得的两件东西:“一(个)白云、三(个)兔子...” 和“

Unity_用鼠标控制相机旋转、拖拽、视角缩放_unity 相机 平移 拖拽 缩放-程序员宅基地

文章浏览阅读2w次,点赞17次,收藏97次。一、滚轮控制视角缩放 /// &amp;amp;lt;summary&amp;amp;gt; /// 滚轮控制相机视角缩放 /// &amp;amp;lt;/summary&amp;amp;gt; public void CameraFOV() { //获取鼠标滚轮的滑动量 float wheel = Input.GetAxis(&amp;quot;Mouse ScrollWheel&am_unity 相机 平移 拖拽 缩放

精选力扣500题 第22题 LeetCode 88. 合并两个有序数组【c++详细题解】_力扣22 c++-程序员宅基地

文章浏览阅读280次,点赞6次,收藏4次。目录1、题目2、思路13、代码14、思路25、代码21、题目给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1中,使 nums1 成为一个有序数组。初始化 nums1 和 nums2的元素数量分别为 m 和n。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2的元素。示例 1:输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3输出:[1,2,2,3,5,6]_力扣22 c++

php文本框怎么设置好看,一个很不错的CSS改写的大表单文本框和搜索按钮组-程序员宅基地

文章浏览阅读139次。《一个很不错的CSS改写的大表单文本框和搜索按钮组》要点:本文介绍了一个很不错的CSS改写的大表单文本框和搜索按钮组,希望对您有用。如果有疑问,可以联系我们。先看效果图:HTML代码部分:开始爬取CSS部分:/*大搜索框*/.searchInputBox{height:62px;width:810px;margin:0auto;border-radius:6px;background-..._css 好看 文本框

select下拉框启用和禁止-程序员宅基地

文章浏览阅读3.4k次。描述:通过你好来判断hello是否启用和禁止html:<li> <span class="xin">*</span><label>你好</label> <select id="dimAcc"> <option >类似</option> ..._select下拉框启用和禁止

【前端面试--JS】=>关于async/await、promise和setTimeout的执行顺序_js执行顺序console.log('script start') async function a-程序员宅基地

文章浏览阅读988次,点赞29次,收藏21次。前言之前面试遇到这样一个题目。关于async/await、promise和setTimeout的执行顺序,当时没做对。后来查了查是非常经典的题目。也给大家解疑答惑一下,说出自己的理解。题目是看代码写结果。async function async1() { console.log('async1 start'); await async2(); console.log('asnyc1 end');}async function async2() { console.log('async2_js执行顺序console.log('script start') async function async1() { await async2

随便推点

Vue笔记整理,12.项目-完成tabbar的小图标设置_tab-bar设置图标-程序员宅基地

文章浏览阅读912次。一、前言上一节我们介绍了:vs code工具的一些特殊使用,使用vs code默认集成的Git工具快速提交代码,详细可参考博文:原创Vue笔记整理,11.项目-使用vs code默认集成的Git工具快速提交代码这篇我们将介绍项目-完成tabbar的小图标设置二、完成tabbar的小图标设置更新中。。。..._tab-bar设置图标

D4 - Makefile_d4文件-程序员宅基地

文章浏览阅读109次。第一节 Make与MakefileMake简介Make将只编译改动的代码文件,而不用完全编译Make使用Makefile1:Make只能读取Makefile文件2:Makefile功能包含’由谁生成’可执行文件,‘怎么生成可执行文件’.‘生成什么可执行文件’Makefile格式生成什么:由谁生成 <table> commandMakefile隐含规则第二节 创建与使用变量Make变量(变量必须大写)去""的字符串Make变量为了便_d4文件

Android MVVM(使用经验篇)-程序员宅基地

文章浏览阅读702次。MVVM的大名相信做手机开发的肯定不会陌生,我第一次听到它是从做IOS开发的同学那里听到的,我们的项目之前应用了MVP,要说服大家从MVP到MVVM,肯定得说说为啥,他优秀在那里? 首先我们看看正常MVP的依赖关系图: 这是个经典的MVP依赖关系,View 层和Presenter,Presenter和Model层彼此依..._android mvvm image src

postgresql 日志配置_pg修改log_connections-程序员宅基地

文章浏览阅读4.8k次。PostgreSQL有3种日志,分别是pg_log(数据库运行日志)、pg_xlog(WAL 日志,即重做日志)、pg_clog(事务提交日志,记录的是事务的元数据)pg_log默认是关闭的,需要设置参数启用此日志。pg_xlog和pg_clog都是强制打开的,无法关闭。1.启用pg_log并配置日志参数log_destination = 'csvlog'logging_collector = o..._pg修改log_connections

php逆波兰表达式,我就给一个PHP逆波兰表达式的算法吧---工资计算专用-程序员宅基地

文章浏览阅读62次。有个网友写信给我谈到关于PHP计算工资问题。我以前一篇文章中谈到过一种计算工资的方法,不过是偷巧,利用现有的表达式的工具,现在既然有人想要,我就给出一个逆波兰的算法。 我们的目标是实现如下的计算公式: 假设有一个计算公式如下: $expression = "(F1*F12+10.34)"; 其中的变量值如下: $expression_value = Array('F1'=>10, 'F12'..._php 逆波兰算法 函数

模板类之间的友元关系实现Blob和BlobPtr-程序员宅基地

文章浏览阅读57次。16.12编写你自己版本的Blob和BlobPtr模板,包含书中未定义的多个const成员。Blob.h(注意,成员函数的声明和定义要放在一个头文件中)/*记住,模板的头文件中通常既包括声明也包括定义。函数模板和类模板成员函数的定义通常放在头文件中,不能分开放。。。。谨记*/#ifndef BLOB_H#define BLOB_H#include<iostream&g..._blobptr