“浅”拷贝和“深”拷贝

前言

拷贝是指通过一种手段创建了一个新对象,并且新对象和原对象长得一模一样。拷贝通常只针对引用类型 ,有深拷贝和浅拷贝

浅拷贝 -- 引用类型直接复制引用地址

基于原对象,拷贝得到一个新对象,源对象中的内容修改会影响新对象

常见的浅拷贝方法

  1. Object.create(obj)
  2. Object.assign({}, a)
  3. \].concat(arr)

  4. arr.slice(0)
  5. arr.toReversed().reverse()

实现原理:

  1. 借助for in 遍历原对象,将原对象的属性值增加在新对象中
  2. 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性

深拷贝

基于原对象,拷贝得到一个新对象,源对象中的内容修改不会影响新对象

- JSON.parse(JSON.stringify(obj))

缺陷:

  1. 不能识别BigInt 类型
  2. 不能拷贝 undefined Symbol function 类型的值
  3. 不能处理循环引用

- structuredClone()

实现原理:

  1. 借助for in 遍历原对象,将原对象的属性值增加在新对象中
  2. 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
  3. 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象

示例

ini 复制代码
let a = 1
let b = a
a = 2
console.log(b); 

打印结果

分析

let声明的一个变量a的值为1,声明一个变量b将变量a赋给b,此时b的值为1,将a的值改为2对b没有影响。

ini 复制代码
let obj = {
    age: 18
}
let obj2 = obj
obj.age = 20

console.log(obj2);

打印结果

分析 let 声明了一个对象obj,里面放的值为 age: 18let obj2 = obj这一步是将obj的引用地址赋给了obj2(obj和obj2还是一个对象),因为对象可以无限大,所以js引擎在预编译的时候,对象会被存入堆中,而此时obj的值为一个引用地址,指向该对象,obj.age = 20改变对象里面的属性的值但是并没有改变该引用地址,所以obj2的引用地址也没变,因此,obj2里面的值也会随着obj改变。

Object.create(obj)

用于创建一个新对象,并且让新对象隐式继承原对象里面的属性。

分析

此时b是一个空对象,但是b的隐式原型里继承到了a的属性。但是如果把a的属性改变,b也会变化。这是浅拷贝。

Object.assign(obj, obj2)

用于将两个对象合并,将obj2拼到obj上去。

ini 复制代码
let obj = {
    age: 18
}
let obj2 = {
    name: 'lxp'
}
let c = Object.assign(obj, obj2)
console.log(c, obj);

打印结果

分析

a对象的属性也变了,说明此时a对象和c对象是一个对象。

换一种写法:let c = Object.assign({}, obj)相当于c拷贝了obj

ini 复制代码
let obj = {
    age: 18
}

let c = Object.assign({}, obj)
obj.age = 20
console.log(c, obj);

打印结果

分析 c对象里的age没变,说明是浅拷贝,对吗?

ini 复制代码
let obj = {
    age: 18,
    n: {
        like: 'movie'
    }
}

let c = Object.assign({}, obj)
obj.n.like = 'drink'
console.log(c, obj);

打印结果

分析 c和obj的属性n都改变了,说明属性n拷贝过来的还是n的引用地址。c对象里的属性还是会受原对象obj的影响,所以还是浅拷贝。

[].concat(arr)

将arr的元素和[]中的元素合并,并返回一个新的数组。

ini 复制代码
let arr = [1,2,3, {a: 'movie'}]
let newArr = [].concat(arr)

arr[3].a = 'book'

console.log(newArr);

打印结果

分析

newArr还是会受arr的影响,说明还是浅拷贝。

解构

let newArr = []相当于创建了一个新的空数组, ...arr是数组的解构,将数组的元素解构出来,let newArr = [...arr]也相当于是创建了一个新数组和原数组长得一模一样。

ini 复制代码
let arr = [1,2,3, {a: 'movie'}]
let newArr = [...arr]

arr[3].a = 'book'
console.log(newArr);

打印结果

分析

newArr还是会受arr的影响,说明还是浅拷贝。

arr.slice()

截取数组原数组不受影响,接收两个参数,第一个参数是开始截的数组的下标,第二个参数是停止截取的数组的下标(第二参数不写默认是截到底)。左闭右开。

ini 复制代码
let arr = [1, 2, 3, 4, 5]
let arr2 = arr.slice(1, 3)

console.log(arr2, arr);

打印结果

分析

左闭右开,不包含第二个参数的下标。

ini 复制代码
let arr = [1, 2, 3, 4, {a: 'movie'}]
let arr2 = arr.slice(0)
arr[4].a = 'hello'

console.log(arr2, arr);

打印结果

分析

let arr2 = arr.slice(0)截取arr中所有的元素并返回一个新数组arr2,arr2和arr长得一样,arr2还是会受arr的影响,所以还是浅拷贝。

arr.toReversed().reverse()-- toReversed()是一个比较新的方法如果node版本过低会识别不出来

将一个数组反转再反转,arr.toReversed()会将一个数组反转得到一个新数组,将得到的新数组通过reverse()再反转一次,就得到了与arr长得一样的一个新数组。toReversed()方法会创建一个新数组。注:如果node版本过低可以放到谷歌浏览器上运行。

ini 复制代码
let arr = [1, 2, 3, 4, {a: 'movie'}]
let newArr = arr.toReversed().reverse()

arr[4].a = 'book'
console.log(newArr);

打印结果

分析

newArr还是受arr影响,还是浅拷贝。

手写一个浅拷贝方法shallow

实现原理:

  1. 借助for in 遍历原对象,将原对象的属性值增加在新对象中
  2. 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
vbnet 复制代码
let obj = {
    name: 'lxp',
    like: {
        n: 'coding'
    }
}
function shallow(obj) {
    let newObj = {}
    for(let key in obj){
        newObj[key] = obj[key]
    }
    return newObj
}
console.log(shallow(obj));

打印结果

分析 for循环遍历原对象,newObj[key] = obj[key]这是把obj的属性值赋给newObj相同键名的属性,不能写newObj.key = obj.key因为key是变量。

注:拷贝一般是指拷贝对象上的显示属性,隐式具有的属性是不拷贝的

插播:hasOwnProperty()方法

判断对象中的key是否是隐式具有的,是就返回false,不是就返回true。

javascript 复制代码
Object.prototype.d = 'a'
let obj = {
    a: 'lxp',
    b: {
        n: 'coding'
    }
}

console.log(obj.hasOwnProperty('d'));
console.log(obj.hasOwnProperty('a'));

打印结果

借助hasOwnProperty()方法判断for循环遍历的对象里面是否有隐式具有的属性,有就不赋给newObj。

vbnet 复制代码
let obj = {
    name: 'lxp',
    like: {
        a: 'food'
    }
}

function shallow(obj) {
    let newObj = {}
    for(let key in obj) {
        if(obj.hasOwnProperty(key)){
        newObj[key] = obj[key]
    }}
    return newObj
}
let obj2 = shallow(obj)
obj2.like.a = 'sport'
console.log(shallow(obj));

打印结果

JSON.parse(JSON.stringify(obj))

JSON.stringify(obj)方法是将obj对象转为JSON字符串,再通过JSON.parse()将JSON字符串转为对象。

javascript 复制代码
let obj = {
    name: 'lxp',
    age: 18,
    like: {
        n: 'music'
    },
    a: true,
    b: undefined,
    c: null,
    d: Symbol(1),
    f: function() {},
    e: 123n
}

let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);

打印结果

分析 JSON.parse(JSON.stringify(obj))方法无法识别BigInt类型

javascript 复制代码
let obj = {
    name: 'lxp',
    age: 18,
    like: {
        n: 'music'
    },
    a: true,
    b: undefined,
    c: null,
    d: Symbol(1),
    f: function() {}
}

let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);

打印结果

分析 不能拷贝 undefined Symbol function 类型的值

javascript 复制代码
let obj = {
    name: 'lxp',
    age: 18,
    like: {
        n: 'music'
    },
    a: true,
    b: undefined,
    c: null,
    d: Symbol(1),
    f: function() {},
    e: 123n
}
obj.c = obj.like
obj.like.n = obj.c
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);

打印结果

分析 obj.c = obj.like; obj.like.n = obj.c循环引用,JSON.parse(JSON.stringify(obj))不能处理循环引用。

javascript 复制代码
let obj = {
    name: 'lxp',
    age: 18,
    like: {
        n: 'music'
    },
    a: true,
    c: null
}
let obj2 = JSON.parse(JSON.stringify(obj))

obj.like.n = 'majiang'
console.log(obj2);

打印结果

分析 obj不能影响obj2,实现了深拷贝。

structuredClone()

由js官方打造的实现深拷贝的方法,很新,node版本过低也运行不出来,放到谷歌浏览器运行。

ini 复制代码
const user = {
    name: {
        firstname: 'L',
        lastname: 'xp'
    },
    age: 18
}

const newUser = structuredClone(user)
user.name.firstname = 'Lee'
console.log(newUser)

运行结果

假如面试官让你自己手搓一个深拷贝方法deep()

实现原理:

  1. 借助for in 遍历原对象,将原对象的属性值增加在新对象中
  2. 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
  3. 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象
ini 复制代码
const user = {
    name: {
        firstname: 'L',
        lastname: 'xp'
    },
    age: 18
}

function deep(obj) {
    let newObj = {}
    for(let key in obj) {
        if (obj.hasOwnProperty(key)){
            if(obj[key] instanceof Object) {  // obj[key] == 'object' && obj[key] !== null  obj[key] 是不是对象
                newObj[key] = deep(obj[key]);
            } else{
                newObj[key] = obj[key]
            }
        }
    }
    return newObj
}
const newobj = deep(user)
user.name.firstname = 'zhangsan'
console.log(newobj);

打印结果

结语

内容比较多,自己梳理一下记得更牢。

相关推荐
咬人喵喵13 分钟前
CSS Flexbox:拥有魔法的排版盒子
前端·css
LYFlied13 分钟前
TS-Loader 源码解析与自定义 Webpack Loader 开发指南
前端·webpack·node.js·编译·打包
yzp011215 分钟前
css收集
前端·css
暴富的Tdy15 分钟前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok15 分钟前
Web Worker
前端·javascript·vue.js
风舞红枫18 分钟前
前端可配置权限规则案例
前端
JHC00000026 分钟前
119. 杨辉三角 II
python·算法·面试
zhougl99628 分钟前
前端模块化
前端
暴富暴富暴富啦啦啦44 分钟前
Map 缓存和拿取
前端·javascript·缓存
天问一1 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js