9012年,当我们讨论js深浅拷贝时我们在说些什么?

栏目: JavaScript · 发布时间: 5年前

内容简介:正文:讨论深浅拷贝,首先要从js的基本数据类型说起: 根据 JavaScript 中的变量类型传递方式,分为值类型和引用类型, 值类型变量包括 Boolean、String、Number、Undefined、Null。引用类型包括了 Object 类的所有, 如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按地址传递。我们来看一下这两则有什么区别:
9012年,当我们讨论js深浅拷贝时我们在说些什么?
前言:
本文主要阅读对象:对深浅拷贝印象模糊对初级前端,想对js深浅拷贝聊一聊的中级前端。
 如果是对这些有完整对认知体系和解决方法对大佬,可以不用再浪费时间了
复制代码

正文:讨论深浅拷贝,首先要从js的基本数据类型说起: 根据 JavaScript 中的变量类型传递方式,分为值类型和引用类型, 值类型变量包括 Boolean、String、Number、Undefined、Null。

引用类型包括了 Object 类的所有, 如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按地址传递。

我们来看一下这两则有什么区别:

//eg1:
// 值类型
var a = 10
var b = a
b = 20
console.log(a)  // 10
console.log(b)  // 20

//解析:上述代码中,a b都是值类型,两者分别修改赋值,相互之间没有任何影响。再看引用类型的例子:

//eg2
// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a)  // {x: 100, y: 200}
console.log(b)  // {x: 100, y: 200}
复制代码

解析: 上述代码中,a b都是引用类型。在执行了b = a之后,修改b的属性值,a的也跟着变化。因为a和b都是引用类型,指向了同一个内存地址,即两者引用的是同一个值,因此b修改属性时,a的值随之改动。

**那到底什么是深浅拷贝呢?**
<br />解析:深浅拷贝是拷贝对象的`深度`来说的:
<br />当你想拷贝的对象只有一级时,就是浅拷贝。
<br />当你拷贝的对象有多级的时候,就是深拷贝。
复制代码

再回到上面的那个例子: 在例子能看出来,如果直接采用“=”赋值,这种类型,当我们改变b的值时候,a也会随之改变的,假如不允许改变a呢? 这时,深拷贝就出场了。

function clone(val){
    if(!val && typeof val !== 'object'){
        return
    }

    const newArr = toString.call(val) === ['object Array'] ? [] : {}

    for (let item in val) {
        if(typeof val[item] ===  'object') {
            newArr[item] = clone(item)
        }else {
            newArr[item] = val[item]
        }
    }
    return newArr
 }

//测试:
    var a = {x: 10, y: 20}
    var b = clone(a)
    b.x = 100
    b.y = 200
    console.log(a)  // {x: 10, y: 20}
    console.log(b)  //{x: 100, y: 200}

复制代码

解析:对于这个深拷贝,大家看到这里应该都可以看明白:主要思路就是浅拷贝 + 递归。

这有几个点需要注意:

1. 判断接收到的参数类型。

2. 使用toString.call()判断更加严谨。

3. 考虑对其他引用类型的数据兼容(这里并没有对每种类型的数据组做判断,引用类型包括了 Object 类的所有,如 Date、Array、Function 等。)

需要注意的是:用递归的话,会有一下几个需要特别注意的点:
    1. 当数据层次很深时,容易爆栈(栈溢出) 解决办法=>
        a. 消除尾递归
        b. 改用循环

    2. “循环引用”,会导致死循环  解决办法 =>
        a. 循环检测
        b. 暴力破解

第一种:数据广度特别大,层次特别深
第二种:类似下面的这种情况
    var a = {}
    a.a = a
    clone(a) //死循环
    

ES6中一行代码实现深拷贝:JSON.parse(JSON.stringify(source))
注释:JSON.stringify()其实利用了“循环检测”机制
复制代码

这里给大家推荐一篇循环解决递归的方法:可以保持拷贝数据以后的引用关系

function cloneForce(x) {
    const uniqueList = []; // 用来去重

    let root = {};

    // 循环数组
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 深度优先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }
        
        // 数据已经存在
        let uniqueData = find(uniqueList, data);
        if (uniqueData) {
            parent[key] = uniqueData.target;
            continue; // 中断本次循环
        }

        // 数据不存在
        // 保存源数据,在拷贝数据中对应的引用
        uniqueList.push({
            source: data,
            target: res,
        });
    
        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

function find(arr, item) {
    for(let i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            return arr[i];
        }
    }

    return null;
}

//eg1:
var a = {
    a1: b,
    a2: b,
}

var b = {};

a.a1 === a.a2 // true

var c = cloneForce(a);
c.a1 === c.a2 // true   引用保持一致

//eg2:
var a = {};
a.a = a;

console.log(cloneForce(a))//还可以破解循环引用
复制代码

主要思路是: 声明一个数组对象(父对象,key,value),其有值时,取其最后一个对象; 判断key有值,代表当前级下有子级,则拷贝到子元素。如果数据已经存在,则中断本次循环。数据不存在则对其拷贝。

有一点需要注意:cloneForce在对象数量很多时会出现很大的问题,如果数据量很大不适合使用cloneForce。

有兴趣的同学,可以前往 深拷贝的终极探索(90%的人都不知道) ,查看更多的细节。

总结:如果觉得对你有帮助,请给作者一点小小的鼓励, 点个赞或者收藏 吧。 有需要沟通的请联系我: 微信( wx9456d )邮箱( allan_liu986@163.com )


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python源码剖析

Python源码剖析

陈儒 / 电子工业出版社 / 2008-6 / 69.80元

作为主流的动态语言,Python不仅简单易学、移植性好,而且拥有强大丰富的库的支持。此外,Python强大的可扩展性,让开发人员既可以非常容易地利用C/C++编写Python的扩展模块,还能将Python嵌入到C/C++程序中,为自己的系统添加动态扩展和动态编程的能力。. 为了更好地利用Python语言,无论是使用Python语言本身,还是将Python与C/C++交互使用,深刻理解Pyth......一起来看看 《Python源码剖析》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试