浅拷贝、深拷贝、赋值

news/2024/7/11 0:20:03 标签: javascript, html5, html, es6
htmledit_views">

一、数据类型存储

前面文章我们讲到,JavaScript中存在两大数据类型

  • 基本类型  

字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。 (注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。 )

  • 引用类型

对象(Object)、数组(Array)、函数(Function)。

基本类型数据保存在在栈内存

引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中

深拷贝与浅拷贝的区别

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝;如果B没变,那就是深拷贝。

二、浅拷贝

浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

即浅拷贝是拷贝一层,深层次的引用类型则是仅仅拷贝内存地址,共享内存地址

下面简单实现一个浅拷贝:

// 引用类型赋值为浅拷贝
let obj={name:'ww'}
let obj2=obj;
obj.name='eee'
console.log(obj2)//{ name: 'eee' }

 let arr=[1,2,3]
 let arr2=arr;
 arr[1]=0;
 console.log(arr2)//[ 1, 0, 3 ]

JavaScript中,存在浅拷贝的现象有:

  • Object.assign(),( 如果对象的属性值为简单类型(如string, number),通过Object.assign({},srcObj);得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。)   

  • Array.prototype.slice(),

  • Array.prototype.concat()

  • 使用拓展运算符...实现的复制

Object.assign  (ES6对象的拓展,合并,返回值:目标对象)

// 第一步
let a = {
  name: "advanced",
  age: 18
}
let b = {
  name: "saucxs",
  book: {
      title: "You Don't Know JS",
      price: "45"
  }
}
let c = Object.assign(a, b);
console.log(a)
console.log(c);
// {
// 	name: "saucxs",
//  age: 18,
// 	book: {title: "You Don't Know JS", price: "45"}
// }
console.log(a === c);//true
// 第二步
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// }

// 第三步
console.log(a);
// {
// 	name: "saucxs",
//  age: 18,
// 	book: {title: "You Don't Know JS", price: "55"}
//}

第二步,修改源对象b的基本类型值(name)和引用类型值(book)。

第三步中,浅拷贝之后目标对象a的基本类型值没有改变,但是引用类型值发生了改变,因为Object.assign()拷贝的是属性值。加入源对象的属性值是一个指向对象的引用,只拷贝那个引用地址。

slice()  提取,可以取负值,返回数组

const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.slice(0)
fxArr[1] = "love";//拷贝的是地址,可以修改
console.log(fxArr) // ["One", "love", "Three"]  
console.log(fxArrs) // ["One", "Two", "Three"]  发现新的值没有变,很多人就以为是深拷贝了,其实这是对于一层数组的结果

var a = [[1,2,3],4,5];
var b = a.slice();
console.log(a === b);
a[0][0] = 6;
console.log(a);//[ [ 6, 2, 3 ], 4, 5 ]
console.log(b);//[ [ 6, 2, 3 ], 4, 5 ] 所以为浅拷贝

concat()  合并,返回数组    (浅拷贝原理与slice一样)

const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArr[1] = "love";//拷贝的是地址,可以修改
console.log(fxArr) // ["One", "love", "Three"]  
console.log(fxArrs) // ["One", "Two", "Three"]  发现新的值没有变,很多人就以为是深拷贝了,其实这是对于一层数组的结果

var a = [[1,2,3],4,5];
var b = a.concat();
console.log(a === b);
a[0][0] = 6;
console.log(a);//[ [ 6, 2, 3 ], 4, 5 ]
console.log(b);//[ [ 6, 2, 3 ], 4, 5 ] 所以为浅拷贝
//与contan不同,assign返回的是对象

拓展运算符

let aa = {
	age: 18,
	name: 'aaa',
	address: {
		city: 'shanghai'
	}
}

let bb = {...aa};
bb.age=14
bb.address.city = 'shenzhen';

console.log(aa.age)//18  没变  相当于深拷贝
console.log(aa.address.city);  // shenzhen  浅拷贝

浅拷贝总结 :

当对数组或对象进行拷贝时,如果只是一层数组或是对象,其元素只是简单类型的元素,那么属于深拷贝(就是一层拷贝,暂时就理解为深拷贝吧!!!!),如果数组或对象中的元素是引用类型的元素(比如对层数组嵌套,对象属性有多层等),那么就是浅拷贝。

三、深拷贝

深拷贝开辟一个新的栈,两个对象属性完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

  • _.cloneDeep()          (lodash库实现深拷贝)

  • jQuery.extend()

  • 通过json对象实现深拷贝 (JSON.stringify/parse)

  • 手写循环递归

_.cloneDeep()

const _ = require('lodash');//引用lodash库
const obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
const obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

jQuery.extend()

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

JSON.stringify()

const obj2=JSON.parse(JSON.stringify(obj1));

但是这种方式存在弊端,会忽略undefinedsymbol函数

const obj = {
    name: 'A',
    name1: undefined,
    name3: function() {},
    name4:  Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}

循环递归

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

四、深浅拷贝区别

浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

// 浅拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj3=shallowClone(obj1) // 一个浅拷贝方法
obj3.name = "update";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
​
console.log('obj1',obj1) // obj1 { name: 'init',  arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

// 深拷贝
const obj1 = {
    name : 'init',
    arr : [1,[2,3],4],
};
const obj4=deepClone(obj1) // 一个深拷贝方法
obj4.name = "update";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
​
console.log('obj1',obj1) // obj1 { name: 'init', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: 'update', arr: [ 1, [ 5, 6, 7 ], 4 ] }

小结

前提为拷贝类型为引用类型的情况下:

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址

  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

赋值(Copy)

赋值是将某一数值或对象赋给某个变量的过程,分为:

1、基本数据类型:赋值,赋值之后两个变量互不影响

2、引用数据类型:赋**址**,两个变量具有相同的引用,指向同一个对象,相互之间有影响

对基本类型进行赋值操作,两个变量互不影响。

// saucxs
let a = "saucxs";
let b = a;
console.log(b);  // saucxs

a = "change";
console.log(a);   // change

console.log(b);    // saucxs

对引用类型进行赋**址**操作,两个变量指向同一个对象,改变变量 a 之后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。

区别

赋值不会创建新的空间,指向同一对象。


http://www.niftyadmin.cn/n/553163.html

相关文章

使用XPath对象解析xml文件

使用XPath对象解析xml文件 1.DocumentBuilderFactory类 工厂API,使应用程序能从XML文档获取生成DOM对象树的解析器 其构造方法受保护,用newInstance()实例化 2.创建解析器 DocumentBuilder 使用这个类,应用程序员可以从XML获得一个Document。…

vue的特点及核心思想

一、声明式渲染 命令式渲染 :需要具体代码表达在哪里,做什么,关心如何实践。声明式渲染 :只需要声明在哪里,做什么(what),而无需关心如何实现(how)。 例子: 声明式编码…

Java 并发类

java.util.concurrent包里 提供了一批线程安全的类 一. java.util.concurrent.atomic java.util.concurrent.atomic包里的原子处理类。AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference。主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理. 转载于:http…

计算属性computed、监听器watch、区别

计算属性 computed 定义:可以理解为能够在里面写一些计算逻辑的属性。 原理:底层借助了Object.defineProperty⽅法提供的getter和setter get函数什么时候执⾏? 1)初次读取时会执⾏⼀次 2)当依赖的数据发⽣改变的时…

操作系统实验一:处理器调度算法的实现

一、实验目的 (1)加深对处理机调度的作用和工作原理的理解。 (2)进一步认识并发执行的实质。 二、实验要求: 本实验要求用高级语言,模拟在单处理器情况下,采用多个调度算法,对N个进程…

引用Vue.js组件的使用

组件的使用 传统开发存在的问题: 依赖关系混乱代码复⽤率不⾼ 在html中引用Vue.js来使⽤组件的三⼤步骤 1. 定义组件 2. 注册组件 3. 使⽤组件 1. 定义组件(创建组件) 使⽤Vue.extend(options)创建,其中options和new Vue&…

MyBatis Generator 命令行和maven运行方式及乱码解决

mybatis运行方式有很多种,这里记录下自己使用过的两种方式,一种是命令行方式,另一种是使用maven插件的方式。关于mabatis geneartor的运行方式,请参考官方文档: Running MyBatis Generator。 1. 命令行方式&#xff1a…

vue中组件注册及使用

全局注册 1、Vue.prototype 在多个地方都需要使用但不想污染全局作用域的情况下,这样定义,在每个 Vue 实例中都可用。$ 表示这是一个在 Vue 所有实例中都可用的属性 常用于方法、变量等 vue.prototype:实例上挂载属性/方法 每一个vue组件…