乐趣区

关于javascript:浅谈深浅拷贝|手摸手带你入坑

前言

再次谈及深拷贝,曾经过了两三年了!花有重开日人无再少年啊,从当初的懵懵懂懂到当初的清淡大叔,害

根本类型 与 援用类型

在这里咱们先阐明 根本类型 援用类型 的区别

根本数据类型 :间接存储在 栈 (stack) 中的数据

String, Number, Boolean, Null, Undefined,Symbol

let a = 1
let b = a
b = 2
console.log(a,b)
// 1 , 2

ab 变量 都是根本类型,咱们间接批改 b,a 是不会被影响到的

援用数据类型 :存储的是该对象在栈中援用,实在的数据存储在 堆(heap)

Object、Array、Function、Data

题来!!

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = {
  name: '严家辉',
  age: 18
}
console.log(obj === obj1)

输入什么?

置信有一部分同学曾经晓得了是 false, 那是为什么呢,假相就是援用地址不同因为是援用类型

援用类型的变量,== 和 === 只会判断援用的地址是否雷同,而不会判断对象具体里属性以及值是否雷同

如何比拟一个对象是否相等?

1.JSON.stringify

console.log(JSON.stringify(obj) === JSON.stringify(obj1))
// true

咱们当初是将obj 转成了 string 类型,不会比照援用地址

'{"name":" 严家辉 ","age":18}' === '{"age":18,"name":" 严家辉 "}'

然而这样有一个问题

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = {
  age: 18,
  name: '严家辉'
}
console.log(JSON.stringify(obj) === JSON.stringify(obj1))

obj1 的程序批改了一遍之后?当初这两个对象是否相等?还是这样转为字符串,必定返回 false

那么咱们须要如何判断两个对象(不定程序的 kv)是否相等呢?

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = {
  age: 18,
  name: '严家辉'
}
const isSame = (obj1, obj2) => {var obj1keys = Object.keys(obj1);
    var obj2keys = Object.keys(obj2);
    if (obj2keys.length !== obj1keys.length)return false
    for (let i = 0; i <= obj1keys.length - 1; i++) {let key = obj1keys[i]
        if (!obj2keys.includes(key)) return false
        if (obj2[key] !== obj1[key]) return false
    }
    return true
}
console.log(isSame(obj,obj1)) // true

这样对象值为根本数据类型的不论是程序是否统一都能够比照了

对于援用地址

好了,咱们回过头来看看这个

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = obj
console.log(obj === obj1)

咱们在之前说过援用类型的 ==、=== 只是判断它的援用地址是否雷同

那么它在这里的援用地址必定是一样的,故打印 true

那么我当初须要批改 obj1age 为 24

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = obj
obj1.age = 24
console.log('obj',obj)
console.log('obj1',obj1)

输入什么呢?

为啥 obj 也会扭转呢

当咱们应用 = 将这些变量赋值到另外的变量,实际上是将对应的值拷贝了一份,而后赋值给新的变量。

对象是通过援用传递,而不是值传递。也就是说,变量赋值只会将地址传递过来。

故咱们批改 obj1 其实也是批改的 obj 自身

那么问题来了,如果咱们在我的项目下面有个这样的需要

也就是咱们指标对象 data,a 函数先执行,而后执行 b 函数,然而 b 函数的要用到 data.name 为严家辉

let data = {
    name: '严家辉',
    age: 18
}
const a = () => data.name = '老严'
const b = () => console.log('b 函数',data.name) // 老严
a()
b()

在这里咱们就须要理解一下深拷贝了

什么是深拷贝?

在图中咱们能够看到咱们在内存中新开了一个堆用来拷贝 obj 的数据,然而批改 obj1 不会影响 obj

建一个新的对象或数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过去,是“数据”而不是“援用地址”咱们心愿在扭转新的对象的时候,不影响原对象

怎么实现呢?

1、JSON 序列化

let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {
    // 核心内容
    let data1 = JSON.parse(JSON.stringify(data))
    data1.name = '老严'
    data1.other.gender = '女'
    console.log('a 函数',data1.name)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

先通过 JSON.stringify 将对象转为字符串再从新序列化为对象。

这个应该是最简略的深拷贝了

毛病是如果对象中蕴含函数会失落

let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"},
      // + 个 test 函数
    test: function() {}
}
const a = () => {let data1 = JSON.parse(JSON.stringify(data))
    data1.name = '老严'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

看看 test 凉了没

2、for in 遍历

const deepCopy = obj => {
    // 判断是数组还是对象
    let result = typeof obj.splice === "function" ? [] : {};
    if (obj && typeof obj === 'object') {for (let key in obj) {if (obj[key] && typeof obj[key] === 'object') {
                // 如果对象的属性值为 object 的时候,递归调用 deepClone, 即在吧某个值对象复制一份到新的对象的对应值中。result[key] = deepCopy(obj[key]);
            } else {
                // 如果对象的属性值不为 object 的时候,间接复制参数对象的每一个键值到新的对象对应的键值对中。result[key] = obj[key];
            }
        }
          // 返回拷贝实现后数据
        return result;
    }
    return obj;
}
// 应用
let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {let data1 = deepCopy(data)
    data1.name = '隔壁小花'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

通过遍历数据返回一个拷贝后船新的数据

毛病:据说如果数据深度 > 1000+ 会爆栈

3、lodash 函数库

应用 lodash 函数库来进行深拷贝

html

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

js

let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {
    // 外围代码
    let data1 = _.cloneDeep(data)
    data1.name = '隔壁小花'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

4、$.extend

html

<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

js

let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {
    // 外围代码
    let data1 = $.extend(true,{},data);
    data1.name = '隔壁老王'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

小结一下

举荐应用第一种或者第二种,前面两种须要通过引入内部库才行

其实还有其余的办法能够实现深拷贝,比方 Proxy、树遍历等

那浅拷贝又是什么?

万物皆有对立面,有深便有浅

刚刚咱们说深拷贝是将原对象的各项属性的“值”(数组的所有元素)拷贝过去,不是援用地址 ,那么浅拷贝就是 拷贝原对象的援用地址

对于浅拷贝而言,就是只拷贝对象的援用,而不深层次的拷贝对象的值,多个对象指向堆内存中的同一对象,任何一个批改都会使得所有对象的值批改,因为它们专用一条数据

找到后面的例子和图,这就是一个最典型的浅拷贝

let obj = {
  name: '严家辉',
  age: 18
}
let obj1 = obj
obj1.age = 24

这下明确了吗?

啥,还不明确,那咱们再写俩栗子吧

浅拷贝的实现

1、for in

const deepCopy = obj => {let result = typeof obj.splice === "function" ? [] : {};
    if (obj && typeof obj === 'object') {for (let key in obj) {
               // 咱们间接去掉递归,让第二层的数据间接赋值(援用地址)result[key] = obj[key];
        }
        return result;
    }
    return obj;
}
let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {let data1 = deepCopy(data);
    data1.name = '隔壁老王'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

咱们间接应用深拷贝的第二个实现形式删除递归,让它间接赋值(援用地址),这就实现了一个浅拷贝

2、Object.assign

let data = {
    name: '严家辉',
    age: 18,
    other: {gender: "男"}
}
const a = () => {
    // 外围代码
    let data1 = Object.assign({},data);
    data1.name = '城中村村花'
    data1.other.gender = '女'
    console.log('a 函数',data1)
}
const b = () => console.log('b 函数',data) // 老严
a()
b()

小结一下

对于第一层的数据来说的确是拷贝胜利了,然而在对象的属性是援用类型数据时咱们还是拷贝的援用地址

总结

深浅拷贝的区别就是前者将原数据从新复制了一份而后新开了一个地址,后者则是将原数据的援用地址拷贝了一份而已

如有谬误,望各位不吝赐教。

参考文档

https://www.jianshu.com/p/f43…

https://segmentfault.com/a/11…

退出移动版