浅谈前端JavaScript浅拷贝与深拷贝

161 阅读4分钟

基本数据类型和引用数据类型

我们都知道js的数据类型分为基本类型和引用类型

image.png

基本数据类型(7类),number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。

引用数据类型(6类),(Object类){a:1},数组[1,2,3],时间、正则、数学以及函数等。

这两类数据存储分别是这样的:

基本类型--名值存储在栈内存中

引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

image.png

image.png

一般讨论到浅拷贝和深拷贝的都是针对引用类型的,像Object和Array这样的复杂类型。

问题产生:引用类型互相赋值

引用类型直接赋值,对象会指向同一个地址,相互影响

const obj = { name: 'lin' } 
const newObj = obj obj.name = 'xxx' // 改变原来的对象 
console.log('原来的对象', obj) 
console.log('新的对象', newObj) 
console.log('两者指向同一地址', obj == newObj)  // true 两者指向相同的地址

解决方法:深拷贝和浅拷贝

深拷贝和浅拷贝的区别 (值拷贝和引用拷贝)

浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。

深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。

js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是值拷贝.

Talk is cheap. Show me the code!

浅拷贝的实现方式:

1、Object.assign() 注意:当object只有一层的时候,是深拷贝,例如如下:

const obj = { name: 'lin' } 
const newObj = Object.assign({}, obj) 
obj.name = 'xxx' // 改变原来的对象 
console.log(newObj) // { name: 'lin' } 新对象不变 
console.log(obj == newObj) // false 两者指向不同地址

2、数组的slice和concat方法 注意:当arr只有一层的时候,是深拷贝,例如如下:

const arr = ['lin', 'is', 'handsome'] 
const newArr = arr.slice(0) 
arr[2] = 'rich' // 改变原来的数组 
console.log(newArr) // ['lin', 'is', 'handsome'] 
console.log(arr == newArr) // false 两者指向不同地址


const arr = ['lin', 'is', 'handsome'] 
const newArr = [].concat(arr) 
arr[2] = 'rich' // 改变原来的数组 
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变 
console.log(arr == newArr) // false 两者指向不同地址

3、扩展运算符

const arr = ['lin', 'is', 'handsome'] 
const newArr = [...arr] arr[2] = 'rich' // 改变原来的数组 
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变 
console.log(arr == newArr) // false 两者指向不同地址

const obj = { name: 'lin' } 
const newObj = { ...obj } 
obj.name = 'xxx' // 改变原来的对象 
console.log(newObj) // { name: 'lin' } // 新对象不变 
console.log(obj == newObj) // false 两者指向不同地址

浅拷贝底层实现:

function clone (obj) { 
    const cloneObj = {} // 创建一个新的对象 
    for (const key in obj) { // 遍历需克隆的对象 
        cloneObj[key] = obj[key] // 将需要克隆对象的属性依次添加到新对象上 
        } 
       return cloneObj 
    }

深拷贝的实现方式:

1.JSON.parse(JSON.stringify(obj))
2.jQuery.extend()
3.递归
4.使用函数库lodash

第一种方式

用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

const obj = { person: { name: 'lin' } } 
const newObj = JSON.parse(JSON.stringify(obj)) 
obj.person.name = 'xxx' // 改变原来的深层对象 
console.log(newObj) // { person: { name: 'lin' } } 新的深层对象不变

但是这种方式存在弊端,会忽略undefinedsymbol函数和循环引用的问题

const obj = { 
a: undefined, 
b: Symbol('b'), 
c: function () {} } 
const newObj = JSON.parse(JSON.stringify(obj)) 
console.log(newObj) // {}


const obj = { a: 1 } 
obj.obj = obj 
const newObj = JSON.parse(JSON.stringify(obj)) // 报错

第二种方式

var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false

第三种方式

function deepClone (target) { 
    if (target === null) return target // 处理 null 
    if (target instanceof Date) return new Date(target) // 处理日期 
    if (target instanceof RegExp) return new RegExp(target) // 处理正则 
    if (typeof target !== 'object') return target // 处理原始类型

    if (typeof target !== 'object') { return target } // 如果是原始类型
    
     // 如果是引用类型,递归实现每一层的拷贝 
        const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
        for (const key in target) { // 遍历原对象 
            cloneTarget[key] = deepClone(target[key]) // 递归拷贝每一层 
            } 
        return cloneTarget // 返回克隆对象 
       }
       
       
       const obj = { 
               a: [1, 2, 3], 
               b: new Date(), 
               c: /abc/, 
               d: null 
           } 
           const newObj = deepClone(obj) 
           console.log('原来的对象', obj) 
           console.log('新的对象', newObj)

第四种

使用loadsh插件实现浅拷贝和深拷贝

浅拷贝:

import { clone } from 'lodash' 
const obj = { name: 'lin' } 
const newObj = clone(obj) obj.name = 'xxx' // 改变原来的对象 
console.log('原来的对象', obj) 
console.log('新的对象', newObj) 
console.log('两者指向同一地址', obj == newObj)

深拷贝:

import { cloneDeep } from 'lodash' 
const obj = { person: { name: 'lin' } } 
const newObj = cloneDeep(obj) 
obj.person.name = 'xxx' // 改变原来的对象 console.log('原来的对象', obj) 
console.log('新的对象', newObj) 
console.log('更深层的对象指向同一地址', obj.person == newObj.person)

针对loash源码是如何实现深拷贝的,可以参考博客 segmentfault.com/a/119000001…

总结:  以上就是个人结合其他文章对JavaScript浅拷贝和深拷贝方式的整理,其中有不对的地方希望能够指出,谢谢!!!