详解JS变量存储深拷贝和浅拷贝

变量类型与存储空间

栈内存和堆内存

基本数据类型

string、number、null、undefined、boolean、symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值
基本数据类型不存在拷贝,好比如说你无法修改数值1的值

引用类型

Object Function RegExp Math Date 值为对象,存放在堆内存中
在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。
当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据

图解存储空间

let a1 = 0; // 栈内存
let a2 = "this is string" // 栈内存
let a3 = null; // 栈内存
let b = { x: 10 }; // 变量b存在于栈中,{ x: 10 }作为对象存在于堆中
let c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3]作为对象存在于堆中

引用类型的赋值

let a = { x: 10, y: 20 }
let b = a;
b.x = 5;
console.log(a.x); // 5

深拷贝和浅拷贝

深拷贝

将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址

对象的赋值

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

三者对比

浅拷贝的常用的五种方法

Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝

Object.assign 会从左往右遍历源对象(sources)的所有属性,然后用 = 赋值到目标对象(target)

var obj = { a: {a: "kobe", b: 39},b:1 };
        var initalObj = Object.assign({}, obj);
        initalObj.a.a = "wade";
        initalObj.b = 2;
        console.log(obj.a.a); //wade
        console.log(obj.b); //1

扩展运算符

let obj = {a:1,b:{c:1}}
let obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

Array.prototype.slice

slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组的基本类型不会被改变,引用类型会被改变。

let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[0]=0;
arr3[2].username = 'wade'
console.log(arr);

Array.prototype.concat()

let arr = [1, 3, {
    username: 'kobe'
    }];
let arr2=arr.concat();
arr3[0]=0;
arr2[2].username = 'wade';
console.log(arr);

手写浅拷贝

function shallowCopy(src) {
    var dst = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            dst[prop] = src[prop];
        }
    }
    return dst;
}

深拷贝的常用方法

jsON.parse(jsON.stringify())

通过JSON.stringify实现深拷贝有几点要注意

拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失

无法拷贝不可枚举的属性,无法拷贝对象的原型链

拷贝Date引用类型会变成字符串

拷贝RegExp引用类型会变成空对象

对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null

无法拷贝对象的循环应用(即obj[key] = obj)

let arr = [1, 3, {
    username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)

手写乞丐版深拷贝

首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型

这里只是针对Object引用类型的值做的循环迭代,而对于Array,Date,RegExp,Error,Function引用类型无法正确拷贝

对象成环,即循环引用 (例如:obj1.a = obj)

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

皇帝版深拷贝

该实例来自ConardLi大佬的github,源地址:https://github.com/ConardLi/

const mapTag = "[object Map]";
    const setTag = "[object Set]";
    const arrayTag = "[object Array]";
    const objectTag = "[object Object]";
    const argsTag = "[object Arguments]";

    const boolTag = "[object Boolean]";
    const dateTag = "[object Date]";
    const numberTag = "[object Number]";
    const stringTag = "[object String]";
    const symbolTag = "[object Symbol]";
    const errorTag = "[object Error]";
    const regexpTag = "[object RegExp]";
    const funcTag = "[object Function]";

    const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];

    function forEach(array, iteratee) {
      let index = -1;
      const length = array.length;
      while (++index < length) {
        iteratee(array[index], index);
      }
      return array;
    }

    function isObject(target) {
      const type = typeof target;
      return target !== null && (type === "object" || type === "function");
    }

    function getType(target) {
      return Object.prototype.toString.call(target);
    }

    function getInit(target) {
      const Ctor = target.constructor;
      return new Ctor();
    }

    function cloneSymbol(targe) {
      return Object(Symbol.prototype.valueOf.call(targe));
    }

    function cloneReg(targe) {
      const reFlags = /\w*$/;
      const result = new targe.constructor(targe.source, reFlags.exec(targe));
      result.lastIndex = targe.lastIndex;
      return result;
    }

    function cloneFunction(func) {
      const bodyReg = /(?<={)(.|\n)+(?=})/m;
      const paramReg = /(?<=\().+(?=\)\s+{)/;
      const funcString = func.toString();
      if (func.prototype) {
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
          if (param) {
            const paramArr = param[0].split(",");
            return new Function(...paramArr, body[0]);
          } else {
            return new Function(body[0]);
          }
        } else {
          return null;
        }
      } else {
        return eval(funcString);
      }
    }

    function cloneOtherType(targe, type) {
      const Ctor = targe.constructor;
      switch (type) {
        case boolTag:
        case numberTag:
        case stringTag:
        case errorTag:
        case dateTag:
          return new Ctor(targe);
        case regexpTag:
          return cloneReg(targe);
        case symbolTag:
          return cloneSymbol(targe);
        case funcTag:
          return cloneFunction(targe);
        default:
          return null;
      }
    }

    function clone(target, map = new WeakMap()) {
      // 克隆原始类型
      if (!isObject(target)) {
        return target;
      }

      // 初始化
      const type = getType(target);
      let cloneTarget;
      if (deepTag.includes(type)) {
        cloneTarget = getInit(target, type);
      } else {
        return cloneOtherType(target, type);
      }

      // 防止循环引用
      if (map.get(target)) {
        return map.get(target);
      }
      map.set(target, cloneTarget);

      // 克隆set
      if (type === setTag) {
        target.forEach(value => {
          cloneTarget.add(clone(value, map));
        });
        return cloneTarget;
      }

      // 克隆map
      if (type === mapTag) {
        target.forEach((value, key) => {
          cloneTarget.set(key, clone(value, map));
        });
        return cloneTarget;
      }

      // 克隆对象和数组
      const keys = type === arrayTag ? undefined : Object.keys(target);
      forEach(keys || target, (value, key) => {
        if (keys) {
          key = value;
        }
        cloneTarget[key] = clone(target[key], map);
      });

      return cloneTarget;
    }

    const map = new Map();
    map.set("key", "value");
    map.set("ConardLi", "code秘密花园");

    const set = new Set();
    set.add("ConardLi");
    set.add("code秘密花园");

    const target = {
      field1: 1,
      field2: undefined,
      field3: {
        child: "child"
      },
      field4: [2, 4, 8],
      empty: null,
      map,
      set,
      bool: new Boolean(true),
      num: new Number(2),
      str: new String(2),
      symbol: Object(Symbol(1)),
      date: new Date(),
      reg: /\d+/,
      error: new Error(),
      func1: () => {
        console.log("code秘密花园");
      },
      func2: function(a, b) {
        return a + b;
      }
    };

    const result = clone(target);

    console.log(target);
    console.log(result);

以上就是详解JS变量存储深拷贝和浅拷贝的详细内容,更多关于JS变量存储深拷贝和浅拷贝的资料请关注我们其它相关文章!

(0)

相关推荐

  • javascript深拷贝、浅拷贝和循环引用深入理解

    一.为什么有深拷贝和浅拷贝? 这个要从js中的数据类型说起,js中数据类型分为基本数据类型和引用数据类型. 基本类型值指的是那些保存在栈内存中的简单数据段,即这种值是完全保存在内存中的一个位置.包含Number,String,Boolean,Null,Undefined ,Symbol. 引用类型值指的是那些保存在堆内存中的对象,所以引用类型的值保存的是一个指针,这个指针指向存储在堆中的一个对象.除了上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型.细分的话,有:O

  • 浅谈JavaScript中面向对象的的深拷贝和浅拷贝

    理解深拷贝和浅拷贝之前需要弄懂一些基础概念,内存中存储的变量类型分为值类型和引用类型. 1.值类型赋值的存储特点, 将变量内的数据全部拷贝一份, 存储给新的变量. 例如:var num = 123 :var num1=num; 表示变量中存储的数字是 123.然后将数据拷贝一份,就是将 123 拷贝一份. 那么内存中有 2 个 数组;将拷贝数据赋值给 num2,其特点是在内存中有两个数据副本.这可以理解为浅拷贝. 2.引用类型的赋值. var o={name:'张三'}: var obj=o;

  • JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)

    前言 说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章JavaScript基础心法--数据类型说的很清楚了,这里就不多说了. 需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型. 对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的. 浅拷贝 浅拷贝的意思就是只复制引用,而未复制真正的值. const originArray = [1,2,3,4,5]; const originObj = {a:'a'

  • JS对象复制(深拷贝和浅拷贝)

    一.浅拷贝 1.Object.assign(target,source,source...) a.可支持多个对象复制 b.如果source和target属性相同 source会复制target的属性 c.target只能为Object对象 var obj = {a:1,b:2} undefined Object.assign({c:3},obj) {c: 3, a: 1, b: 2} obj {a: 1, b: 2} 兼容性写法if(Object.assign){//兼容}else{//不兼容}

  • js对象浅拷贝和深拷贝详解

    本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷贝: var Chinese = { nation:'中国' } var Doctor = { career:'医生' } function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } c.uber = p; return c; } 使用的时候,这样写

  • javascript深拷贝和浅拷贝详解

    一.数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生. 这是为什么呢? 因为如果只是简单的赋值,它只是进行了地址的引用,所以改变一个另一个也会跟着变. var arr = ["One","Two","Three"]; var arrto = arr; arrto[1] = "te

  • 浅析javaScript中的浅拷贝和深拷贝

    1.javaScript的变量类型 (1)基本类型: 5种基本数据类型Undefined.Null.Boolean.Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问. (2)引用类型: 存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置.当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据. JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一

  • javascript对浅拷贝和深拷贝的详解

    下面小编就为大家带来一篇浅谈JavaScript中面向对象的的深拷贝和浅拷贝.小编觉得挺不错的,现在就分享给大家,也给大家做个参考. 1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据. 2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制. 这里画一个简单的图来加深理解: 一.数组的深浅拷贝 在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个

  • JavaScript数组深拷贝和浅拷贝的两种方法

    例如这个例子: 复制代码 代码如下: var arr = ["One","Two","Three"]; var arrto = arr;arrto[1] = "test";document.writeln("数组的原始值:" + arr + "<br />");//Export:数组的原始值:One,test,Threedocument.writeln("数组的新值

  • 详解JS变量存储深拷贝和浅拷贝

    变量类型与存储空间 栈内存和堆内存 基本数据类型 string.number.null.undefined.boolean.symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值 基本数据类型不存在拷贝,好比如说你无法修改数值1的值 引用类型 Object Function RegExp Math Date 值为对象,存放在堆内存中 在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址. 当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据

  • 详解Python直接赋值,深拷贝和浅拷贝

    直接赋值: 对象的引用,也就是给对象起别名 浅拷贝: 拷贝父对象,但是不会拷贝对象的内部的子对象. 深拷贝: 拷贝父对象. 以及其内部的子对象 在之前的文章中,提到可变对象和不可变对象,接下来也是以这两者的区别进行展开 直接赋值 对于可变对象和不可变对象,将一个变量直接赋值给另外一个变量,两者 id 值一致,其实本质上是将变量量绑定到对象的过程. >>> a=1 >>> b=a >>> id(a) == id(b) True >>>

  • 详解java中的深拷贝和浅拷贝(clone()方法的重写、使用序列化实现真正的深拷贝)

    1.序列化实现 public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T object){ T cloneObj = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new Objec

  • 详解JS深拷贝与浅拷贝

    一.预备知识 1.1.JS数据类型 基本数据类型:Boolean.String.Number.null.undefined 引用数据类型:Object.Array.Function.RegExp.Date等 1.2.数据类型的复制 基本数据类型的复制,是按值传递的 var a = 1; var b = a; b = 2; console.log(a); // 1 console.lob(b); // 2 引用数据类型的复制,是按引用传值 var obj1 = { a: 1; b: 2; }; v

  • 详解JS ES6变量的解构赋值

    1.什么是解构? ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构.它在语法上比ES5所提供的更加简洁.紧凑.清晰.它不仅能减少你的代码量,还能从根本上改变你的编码方式. 2.数组解构 以前,为变量赋值,我们只能直接指定值,比如 let a = 1; let b = 2; let c = 3; 现在可以用数组解构的方式来进行赋值 let [a, b, c] = [1, 2, 3]; console.log(a, b, c); // 1, 2, 3 这是数组解构最基本类型

  • 详解JS内存空间

    概述 变量对象与堆内存 var a = 20; var b = 'abc'; var c = true; var d = { m: 20 } 在很长一段时间里认为内存空间的概念在JS的学习中并不是那么重要.可是后我当我回过头来重新整理JS基础时,发现由于对它们的模糊认知,导致了很多东西我都理解得并不明白.比如最基本的引用数据类型和引用传递到底是怎么回事儿?比如浅复制与深复制有什么不同?还有闭包,原型等等. 因此后来我才渐渐明白,想要对JS的理解更加深刻,就必须对内存空间有一个清晰的认知. 一.栈

  • 详解JS预解析原理

    目录 预解析的的不同机制 var的预解析机制 function 关键字的预解析步骤 预解析机制 面试题 预解析的的不同机制 预解析也叫预声明,是提前解析声明的意思:预解析是针对变量和函数来说的:但是变量和function的的预解析是两套不同的机制: 当浏览器加载我们的HTML页面的时候,首先会提供一个供JS代码执行的环境->全局作用域global(浏览器中的全局作用域,也叫顶级作用域是window) JS中的内存空间分为两种:栈内存.堆内存 栈内存:提供JS代码执行的环境,存储基本数据类型的值:

  • js中区分深拷贝与浅拷贝的实战过程

    目录 一.自我理解 二.数据存储形式 (1)基本数据类型存储于栈中 (2)引用数据类型存储与堆中 三.怎样实现深拷贝? (1)借助JSON对象的parse和stringify (2)手写递归 (3)JQuery中extend方法 总结/注意 总结 一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属性,而在浅拷贝中,它们具有相同的属性. 举个栗子:存

  • Qiankun原理详解JS沙箱是如何做隔离

    目录 前言 复习一下沙箱 SanpshotSandbox LegacySandbox ProxySandbox 隔离原理 XXX is undefined 总结 前言 相信大家也知道 qiankun 有 SnapshotSandbox, LegacySandbox 和 ProxySandbox 这些沙箱,而它们又可以分为单例和多例两种模式,网上也有很多文章对其进行介绍. 但这些文章的关注点都是沙箱的环境恢复做的事,那 JS 的隔离到底是怎么做到的呢? 换个问法,当我写 window.a = 1

  • 详解JS数值Number类型

    Number 问题 下面的问题你都能回答对了吗? 0.1 + 0.2 == 0.3 成立吗? .e-5 表示多少? 怎么表示8进制? 怎么转换进制? 如何将字符串转换成数值或整数?反过来呢?十六进制又怎么处理? parseInt(0x12, 16) 的返回值是多少?是0x12吗? Number.MAX_VALUE 为最大数值,(new Number(12)).MAX_VALUE 是多少? JavaScript中怎么进行四舍五入?如果保留3位小数的精度? 如何获取一个随机数?如何取整?如何向上取整

随机推荐