Day22 浅拷贝与深拷贝那些事

91 阅读3分钟

每日一句

You need to face your fears.

释义:你需要直面你的恐惧。

image.png

前言

  • javascript中数据类型:基本类型和引用类型

栈区(stack):自动分配的内存空间,会被系统自动回收。

堆区(heap):动态分配的内存空间,大小不定,不会被系统回收。

基本类型:String, Number, Boolean, Undefined, Null, Sysmbol 按值传递
一般存在内存的栈区,存取快,存量小

引用类型:Array, Object, Function, Date, RegExp, ... 按引用传递
一般存在内存中的堆区,存取慢,存量大,栈中存放内存地址,指向引用的本身

下图非常清晰的展示了基本类型与引用类型在内存中存放形式。 image.png

const xiaoming = {
  name: '小明',
  age: 30,
  salary: 8000
}

// 简单赋值
const xiaohong = xiaoming
xiaohong.name = '小红'
xiaohong.salary = 10000
console.log(xiaohong)  // { "name": "小红", "age": 30, "salary": 10000 }
console.log(xiaoming) // { "name": "小红", "age": 30, "salary": 10000 }

以上代码实现了浅拷贝,虽然小红可以复制小明的属性,然后只需改变小红的name和salary,这无形之间把小明的也给变了,这哪行呢?

怎样更好的复制对象呢?这里用到了浅拷贝和深拷贝

浅拷贝和深拷贝是相对于引用类型而言的:

浅拷贝: 指两个js对象指向同一个内存地址,其中一个改变会影响另一个;

深拷贝: 指复制后的新对象重新指向一个新的内存地址,两个对象改变互不影响。

浅拷贝

概念与运用

「只拷贝了数据对象的第一层,深层次的数据值与原始数据会互相影响(拷贝后的数据与原始数据还存有关联)」

常见浅拷贝的方法:Object.assign()扩展运算符

const xiaoming = {
  name: '小明',
  age: 30,
  salary: 8000
}

// const xiaohong = Object.assign({}, xiaoming)
const xiaohong = {...xiaoming}
xiaohong.name = '小红'
xiaohong.salary = 10000
console.log(xiaohong)  // { "name": "小红", "age": 30, "salary": 10000 }
console.log(xiaoming) // { "name": "小明", "age": 30, "salary": 8000 }

当然还有通过forconcat, slice, Array.from, Array.of等实现浅拷贝。

浅拷贝的简单实现

function shallowClone(obj) {
  // 此仅考虑数组和对象两种情况
  if (Array.isArray(obj)) {
    let target = []
    for(let i=0; i< obj.length; i++) {
      target.push(i)
    }
    return target
  } else if (typeof obj === 'object') {
    let target = {}
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        target[key] = obj[key]
      }
    }
    return target
  }
}

const xiaoming = {
  name: '小明',
  children: ['Tom', 'Sam']
}

xiaohong.name = '小红'
console.log(xiaohong , xiaoming) // { name: '小红', children: [ 'Tom', 'Sam' ] } { name: '小明', children: [ 'Tom', 'Sam' ] }

浅拷贝的问题

// 如果存在引用类型数据呢
const xiaoming = {
  name: '小明',
  children: ['Tom', 'Sam']
}

const xiaohong = {...xiaoming}
xiaohong.name = '小红'
xiaohong.children[0] = 'steven'
console.log(xiaohong)  // { name: '小红', children: [ 'steven', 'Sam' ] }
console.log(xiaoming) // { name: '小明', children: [ 'steven', 'Sam' ] }
// 拷贝的是地址
console.log(xiaohong.children === xiaoming.children) // true

当拷贝的是引用类型,是拷贝其内存地址,所以改了就会影响原数据,这时候得需要考虑深拷贝了。

深拷贝

概念与运用

「不管数据对象有多少层,改变拷贝后的值都不会影响原始数据的值。(拷贝后的数据与原始数据毫无关系)」

常用的深拷贝方式:JSON.parse(JSON.stringify(obj))

const xiaoming = {
  children: ['Tom', 'Sam']
}

const xiaohong = JSON.parse(JSON.stringify(xiaoming))
xiaohong.children[0] = 'Jacky'
console.log(xiaohong, xiaoming) // { children: [ 'Jacky', 'Sam' ] } { children: [ 'Tom', 'Sam' ] }
console.log(xiaohong === xiaoming) // false

MDN: undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。

此方法用缺陷:无法正确处理函数正则

如果要彻底解决这些需要手写代码,或用开源的库像lodash等。

深拷贝实现

  • 兼容数组和对象
function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};


// 测试
const arr1 = [1,2,3, {a:  {b: 8 }}]
const arr2 = clone(arr1)
arr2[3].a.b = 4
console.log(arr1[3].a , arr2[3].a) // { b: 8 } { b: 4 }
  • 兼容函数

思路:1.首先需要返回一个新的函数 2.新的函数执行结果必须与原函数相同

function deepClone(obj){
  let target = {}
  if(obj instanceof Function){ 
    target = function(){ 
      // 在函数中去执行原来的函数,确保返回的值相同 
      return target.call(this, ...arguments); 
    } 
  }
}
  • 兼容正则表达式
function deepClone(obj){
  let target = {}
  if(target instanceof RegExp){ 
    target = new RegExp(obj.source,obj.flags);
  }
}
  • 兼容日期
function deepClone(obj){
  let target = {}
  if(target instanceof Date){ 
    target = new Date(obj);
  }
}

优化后:

function deepClone(obj){
  if(obj instanceof Object){
      let dist ;
      if(obj instanceof Array){
        // 拷贝数组
        dist = [];
      }else if(obj instanceof Function){
        // 拷贝函数
        dist = function () {
          return obj.call(this, ...arguments);
        };
      }else if(obj instanceof RegExp){
        // 拷贝正则表达式
       dist = new RegExp(obj.source,obj.flags);
      }else if(obj instanceof Date){
          dist = new Date(obj);
      }else{
        // 拷贝普通对象
        dist = {};
      }
      for(let key in obj){
          // 过滤掉原型身上的属性
        if (obj.hasOwnProperty(key)) {
            dist[key] = deepClone(obj[key]);
        }
      }
      return dist;
  }else{
      return obj;
  }
}