mysql视频 XShell 设计模式 Java包装类 TCP连接 testing sharepoint path rxjs pmp学习视频 jquery获取dom对象 java数据分析 matlab复数求模 matlab中不等于怎么表示 oracle时间格式化 图片生成链接 mysql删除存储过程 vue与html5 mysql时间戳转时间 python3下载安装 python对象 python的编译器 python函数大全 python可视化编程 java如何配置环境变量 java入门基础 java集合图 java比较字符串 信息系统项目管理师教程 幽城幻剑录五内 vnc客户端 无限弹窗bat js保留两位小数 视频md5修改器 php递归 子节点 小米游戏鼠标 骰子牛牛怎么玩 抠图教程 文字图片制作
当前位置: 首页 > 学习教程  > 编程学习

js中深浅拷贝的实现方式(含图解原理)

2021/1/9 2:04:48 文章标签: 深浅拷贝

关于赋值,浅拷贝,深拷贝 提前熟知: 栈内存(stack):会自动分配的内存空间,它由系统自动释放堆内存(heap):动态分配的内存及大小,不一定会自动释放…

关于赋值,浅拷贝,深拷贝

提前熟知:

  • 栈内存(stack):会自动分配的内存空间,它由系统自动释放
  • 堆内存(heap):动态分配的内存及大小,不一定会自动释放
  • 基本数据类型:String, Number, Boolean, undefined, null, Symbol
  • 引用数据类型:Object, Array, Function
  • 在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中

赋值:赋值是将某一数值或对象赋给某个变量的过程

        var a = 1;
        var b = a;
        console.log(b)  // 1

        a = 2;
        console.log(b)  // 1

        b = 3;
        console.log(a) // 2
  • 基本数据类型:我们发现,对于基本数据类型的数据而言,当进行赋值时,系统会自动对变量b在栈内存中开辟一块新的内存进行存储,变量a与变量b的值互不影响,相互独立
  • 我们可以画个图辅助理解:
    在这里插入图片描述
        let a2 = { a:1, b:'Trist' };
        let b2 = a2;
        console.log(b2) // {a: 1, b: "Trist"}

        b2.a = 2;
        console.log(a2.a) // 2
  • 引用数据类型: 通过对于引用数据类型的实验,我们发现与基本数据类型不同的是,两个变量之间会相互干扰。为什么会出现这种现象呢?其实是因为引用数据类型存储在堆内存中,当进行赋值时,系统为变量b2传入了变量a2指向堆内存中地址的指针,实际上他们俩指向的是同一个对象,所以当第二个变量的值改变的时候,第一个变量的值也会改变。
  • 同样的我们可以画个图辅助理解:
    在这里插入图片描述

注:深浅拷贝只针对于引用数据类型

浅拷贝

        function clone(obj) {
            var cloneObj = {};
            for(var key of Object.keys(obj)) {
                cloneObj[key] = obj[key];
            }
            return cloneObj;
        }
        
        var a3 = {
            a: 1,
            b: 'Trist',
            c: { d: 1 }
        }
        var b3 = clone(a3)
        console.log(b3)  // {a: 1,b: "Trist",c: {d: 1}}

        a3.a = 2;
        console.log(b3.a) // 1

        b3.b = '张三',
        console.log(a3.b) // Trist

        b3.c.d = 2;
        console.log(a3.c.d) // 2
  • 实现原理:创建一个拷贝的方法 clone(),通过遍历传入对象的键名及键值,然后赋值给一个空对象,这样就完成了对原对象的浅拷贝。
  • 实验发现:当我们拿到a3的浅拷贝后,我们发现,对于对象中的基本数据类型而言,他们之间是互不影响的,而对于引用数据而言,他们会相互干扰。
  • 理解浅拷贝:浅拷贝只复制了原对象中最外层的属性,也就是拷贝了其基本类型的数据,而对于引用类型数据而言,它仅复制了其引用,指向的地址还是原对象的地址。
  • 图解:
    在这里插入图片描述

注:通过对浅拷贝的认识,如果我们要实现对更深层级的数据不仅是单纯的引用,则需要对原对象内所有的属性值进行遍历递归,这就是我们的深拷贝了

深拷贝

        function deepClone(obj, cloneObj) {
            var cloneObj = cloneObj || {};
            for(var i in obj) {
                // 通过遍历判断属性是否为引用类型,此处注意null因为历史遗留bug通过typeof输出为object
                if(typeof obj[i] === 'object' && typeof obj[i] !== null) {
                    // 判断引用值是否为数据 obj[i] instanceof Array
                    cloneObj[i] = (obj[i].constructor === Array) ? [] : {};
                    // 进行递归
                    deepClone(obj[i], cloneObj[i]);
                }else {
                    cloneObj[i] = obj[i];
                }
            }
            return cloneObj;
        }

        var b4 = deepClone(a4,b4);
        console.log(b4) 

        a4.a = 2;
        console.log(b4.a) // 1

        b4.b = '张三',
        console.log(a4.b) // Trist

        console.log(a4.c) // 1
        console.log(b4.c) // 1
        b4.c.d = 2;
        console.log(a4.c) // 1
        console.log(b4.c) // 2

        console.log(a4.size) // [1, 2, 3]
        console.log(b5.size) // [1, 2, 3]
        b5.size.push(4,5,6)
        console.log(a4.size) // [1, 2, 3]
        console.log(b5.size) // [1, 2, 3, 4, 5, 6]
  • 实现原理:首先进行遍历,判断原对象内的属性是否还有对象以及空值null,若有则判断对象是否为数组,有则赋值空数组,无则赋值空对象,然后进行递归。
  • 实验发现:不管是原对象内的基本数据类型,还是对象,或者数组,我们在进行属性的修改添加修改时,发现他们都各自独立,互不影响。
  • 理解深拷贝:深拷贝不会拷贝引用类型的引用,而是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题,使得我们可以多次使用同样的数据,而不用担心数据之间会起冲突。
  • 图解:
    在这里插入图片描述

浅拷贝实现方式

原生JS(上文案例)

ES6 assign方法

Object.assign() 拷贝的是属性值,只实现了对第一层的深拷贝,而不能进行更深层的深拷贝,所以归根结底属于浅拷贝

		var assign1 = Object.assign({}, a4);
        console.log(assign1); // {a: 2, b: "Trist", c: {d: 1}, size: (3) [1, 2, 3]}

ES5 属性描述符

了解即可

        function simpleClone(obj) {
            var copy = Object.create(Object.getPrototypeOf(obj));
            Object.getOwnPropertyNames(obj).forEach(key => {
                var desc = Object.getOwnPropertyDescriptor(obj, key);
                Object.defineProperty(copy, key, desc);
            })
            return copy;
        }
        var es5 = simpleClone(a3);
        console.log(es5) // {a: 2, b: "Trist", c: {d: 1}}

深拷贝实现方式

原生JS (上文案例)

下面这种是封装比较好的原生深拷贝写法:

  • 用new obj.constructor ()构造函数新建一个空的对象,而不是使用{}或者[],这样可以保持原形链的继承;
  • 用obj.hasOwnProperty(key)来判断属性是否来自原型链上,因为for…in…也会遍历其原型链上的可枚举属性。
  • 上面的函数用到递归算法,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,需要使用 arguments.callee。
        function deepClone3(obj) {
            if(obj === null ) return null;
            if(typeof obj !== 'object') return obj;
            if(obj.constructor === Date) return new Date(obj);
            var newObj = new obj.constructor();
            for(var key in obj) {
                if(obj.hasOwnProperty(key)) {
                    var val = obj[key];
                    newObj[key] = typeof val === 'object' ? arguments.callee(val) : val;
                }
            }
            return newObj;
        }
        var bbb = deepClone3(a4);
        console.log(bbb); // {a: 2, b: "Trist", c: {d: 1}, size: (3) [1, 2, 3]}

JSON

仅能处理Number, String, Boolean, Array 这种扁平对象,也即是能被JSON直接表示的数据结构类型

        function JSONClone(obj) {
            return JSON.parse(JSON.stringify(obj))
        }
        var json = JSONClone(a4);
        console.log(json) // {a: 2, b: "Trist", c: {d: 1}, size: (3) [1, 2, 3]}

jQuery

注意引入第三方库

 		<script src="jquery-3.3.1.min.js"></script>
 		var jquery1 = $.extend(true, {}, a4);
        console.log(jquery1); // {a: 2, b: "Trist", c: {d: 1}, size: (3) [1, 2, 3]}

Lodash

注意引入第三方库

		<script src="lodash.min.js"></script>
    	var lodash1 = _.cloneDeep(a4);
        console.log(lodash1); // {a: 2, b: "Trist", c: {d: 1}, size: (3) [1, 2, 3]}

本文链接: http://www.dtmao.cc/news_show_1100281.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?