五、深拷贝、浅拷贝
一、数据类型
当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值。
1、基本数据类型:(简单数据类型)
Number、String 、Boolean、Null和Undefined。基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。示例:
var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
上面,b获取的是a值得一份拷贝,虽然,两个变量的值相等,但是两个变量保存了两个不同的基本数据类型值。
b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。
下图演示了这种基本数据类型赋值的过程:
2、引用类型数据:(复杂数据类型)
也就是对象类型Object type,比如:Object 、Array 、Function 、Data等。
javascript的引用数据类型是保存在堆内存中的对象。
与其他语言的不同是,你不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存中的引用地址。
所以,引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
图:
栈(stack)和堆(heap)
栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
3、总结区别
a 声明变量时不同的内存分配:
1)原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。
2)引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。
地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
b 不同的内存分配机制也带来了不同的访问机制
1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。
2)引用值:会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。
这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针
在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,
首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。
二、深拷贝和浅拷贝:
1、基本类没有问题,
因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。
2、引用类型有问题
》》》》 参考别的博客
》》》》
因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容),强烈建议把前面的第二点(基本类型和引用类型在赋值时内存的变化)多看几遍,以保证理解深刻。这样,一劳永逸,以后在碰到任何跟引用类型有关的话题(如:继承时,父类的属性是引用类型)都没有问题。
深拷贝
深拷贝_如果属性是数组等非键值对的对象
就得单独处理:要么给数组增加一个自我复制的函数(建议这样做),
要么单独判断。
一、JSON方法实现深拷贝
var obj = {
name: 'a',
arr:[1,2,3]
}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.name = "cc"
console.log(obj) //{ name: 'a', arr: [ 1, 2, 3 ] }
console.log(newObj) //{ name: 'cc', arr: [ 1, 2, 3 ] }
对于一般常用的数据拷贝这种方法无疑是非常简单的,但是遇到复杂的数据,比如date,正则,函数,值为undefinde的对象时键值对直接被删除,就会出现问题,可以根据业务返回数据类型,来酌情使用
var obj = {
name: 'a',
arr:[1,2,3],
date: [new Date(1536627600000), new Date(1540047600000)],
RegExp: new RegExp('\\w+'),
job:undefined
}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.name = "cc"
console.log(obj) //{ name: 'a',
// arr: [ 1, 2, 3 ],
// date: [ 2018-09-11T01:00:00.000Z, 2018-10-20T15:00:00.000Z ],
// RegExp: /\w+/,
// job: undefined }
console.log(newObj) //{ name: 'cc',
// arr: [ 1, 2, 3 ],
// date: [ '2018-09-11T01:00:00.000Z', '2018-10-20T15:00:00.000Z' ],日期格式变为字符串了
// RegExp: {} } 正则变成了空对象,值为undefinde的键值对直接被删除
二、使用for … in遍历复制
function deepClone(obj) {
var result = {}
if (obj && typeof obj === 'object') {
for (let key in obj) {
if (obj[key] && typeof obj[key] === 'object') {
result[key] = deepClone(obj[key]);//如果对象的属性值为object的时候,递归调用deepClone,即把某个值对象复制一份到新的对象的对应值中。
} else {
result[key] = obj[key];//如果对象的属性值不为object的时候,直接复制键值对到新的对象。
}
}
return result;
}
return obj;
}
let obj = {
name:'lily',
arr:[1,2,3],
date: [new Date(1536627600000), new Date(1540047600000)],
RegExp: new RegExp('\\w+'),
job:undefined,
obj2:{
fun:function(){}
}
};
let testObj = deepClone(obj);
testObj.name = '不知火舞';
console.log(obj); //{ name: 'lily',
// arr: [ 1, 2, 3 ],
// date: [ 2018-09-11T01:00:00.000Z, 2018-10-20T15:00:00.000Z ],
// RegExp: /\w+/,
// job: undefined,
// obj2: { fun: [Function: fun] } }
console.log(testObj) //{ name: '不知火舞',
// arr: { '0': 1, '1': 2, '2': 3 },
// date: { '0': {}, '1': {} }, 日期对象也变成了空对象
// RegExp: {}, /* 正则依然时空对象 */
// job: undefined,
// obj2: { fun: [Function: fun] } }
这种遍历方法相比JSON方法,函数,和值为undefinde的键值对可以正常复制了,但是正则,和日期对象依然是不能复制的。
三、Object.defineProperty 与forEach遍历复制
function deepClone(obj) {
let copy = Object.create(Object.getPrototypeOf(obj));
let propertyNames = Object.getOwnPropertyNames(obj);
propertyNames.forEach(function (items) {
let item = Object.getOwnPropertyDescriptor(obj, items);
Object.defineProperty(copy, items, item);
});
return copy;
}
let obj = {
name:'lily',
arr:[1,2,3],
date: [new Date(1536627600000), new Date(1540047600000)],
RegExp: new RegExp('\\w+'),
job:undefined,
obj2:{
fun:function(){}
}
};
let testObj = deepClone(obj);
testObj.name = '不知火舞';
console.log(obj); //{ name: 'lily',
// arr: [ 1, 2, 3 ],
// date: [ 2018-09-11T01:00:00.000Z, 2018-10-20T15:00:00.000Z ],
// RegExp: /\w+/,
// job: undefined,
// obj2: { fun: [Function: fun] } }
console.log(testObj) //{ name: '不知火舞',
// arr: [ 1, 2, 3 ],
// date: [ 2018-09-11T01:00:00.000Z, 2018-10-20T15:00:00.000Z ],
// RegExp: /\w+/,
// job: undefined,
// obj2: { fun: [Function: fun] } }
这个方法目测可以拷贝到我所关注到的所有对象属性,但是涉及到的知识我不怎么懂,需要再研究下Object的自带属性知识。
//基本数据类
var arr=[1,2,3,4];
var list =[...arr]; //开运算符
console.log(arr==list) //两个数组 所以是 false
当数组只有一层时,浅拷贝就能解决 共同使用 引用的问题。
//当是引用数据类型的时候
var arr = [1,2,3,4,[5,6]];
// 浅拷贝、
var list =[...arr];
打印的会是一样的结果 更改也是一样
console.log(arr[4],list[4])
手写 深拷贝、
1、方法一
JSON.Stringify配合JSON.parse
//初始的数据值
var arr = [1,2,3,4,[5,6]];
//先转为字符串的形式
var str = JSON.Stringify(arr);
console.log(str); //打印出字符串形式的数据
//在转化为数组的形式
var list = JSON.parse(str);
console(list); // 打印出数组形式的数据
console.log(list==arr); 两个数据也是两个不同的数据 所以也是false
//改变arr 深层次的一项
arr[4][0]=8;
console.log(arr); //打印 改变后的 arr 数组
console.log(list); //打印 深拷贝后的数据 还是原来的数据 不会跟着原数组改变
2、方法二
深拷贝 递归遍历
//函数方法 自定义
function Clone(val){ // val----形参
1、//首先判断数据类型
//使用 typeof 方法 判断 如果是引用数据类型的话就返回 object (特殊的有一个 null 也是返回 object)所以我们要考虑全面
if(typeof val !=='object' || val == null){
return val ; //只要判断为基本数据类型的话就直接返回,不需后面的麻烦
}
2、//到这里就是引用数据类型
首先会 初始化一个变量
但是现在 我们不确定 **数据类型是数组形式的还是 对象形式**
let obj; 所以先定义变量
//判断 val是数组还是对象的形式
//使用 instanceof 的 true进行 判断
if(val instanceof Array){
//如果判断的是数组 就 定义成空数组的形式
obj=[];
}else{
//另外的话 那就是对象
//定义成空对象的形式
obj={};
}
//循环递归 内层数据
****数组和对象本身我们创建的用来拷贝的都自身存在了 ********
for (var key in val){
//检测 key 的val 的私有属性 数组都有的属性就不需要拷贝了
if(val.hasOwnProperty(key)){
//hasOwnProperty() 方法会返回一个布尔值,
指示对象自身属性中是否具有指定的属性。
//自身调用自身的方法
obj[key]=Clone(val[key])或obj[key]=arguments.callee(val)[key])
//------------//-------------//---------------//---------------//
}
}
return obj;
}
//复杂的数据类型
var a = {b:[1,2,3,{c:[4,5,{d:hello,word}]}]}
//使用封装的方法进行深复制 并且把a这个引用数据类型 作为实参传到Colne()的方法里面
var b = Colne(a)
//打印
//console.log(b);
console.log(a==b);