浅析深拷贝和浅拷贝
深拷贝和浅拷贝是面试中经常会被问到的问题,手写深拷贝也是前端手撕题的热点。那么,为什么面试官们都热衷于让大家手写深拷贝呢?当然不只是看你默写代码,这其实主要是为了考察其中所涉及的JavaScript的基础知识。
数据类型
基本数据类型和引用数据类型
首先,为什么会有深浅拷贝?其源头就是因为在JavaScript中,存在两大数据类型:
- 基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt
- 引用数据类型:Object
基本数据类型的特点是数据直接存储在栈中;
引用数据类型的特点是存储在栈中的是对象的引用地址,而真实的数据存放在堆内存中。
堆和栈
栈:
- 栈会自动分配内存空间,占据固定大小的空间会自动释放,存放基本类型
- 定义变量的方法执行结束,内存栈会销毁
- 存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享
- 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性
堆:
-
动态分配的内存,大小不定也不会自动释放,存放引用类型
-
堆内存中的对象再方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)
变量复制
基本数据类型的复制
let a = "你好";
let b = a;
b = "不好"
console.log(a); //"你好"
由示例可知,a、b是基本数据类型,数据存储在栈中,复制a相当于新开一个栈存储数据,所以改变b的值不会对a有影响
引用数据类型的复制
let arr1 = [1,2,3]
let arr2 = arr1;
arr2[1] = "two"
console.log(arr1); //[1,"two",3]
由示例可知,arr1、arr2是引用数据类型,其真实数据存放在堆内存,而在栈中只存储地址值。复制arr1即仅仅只复制了其地址值,该地址指向同一个堆内存。改变arr2的值,arr1的值也会改变。
深拷贝和浅拷贝
浅拷贝
浅拷贝:是指只拷贝一层,如果属性是基本类型,拷贝的就是基本类型的值;如果属性时引用类型,拷贝的就是内存地址,拷贝前后的对象,因为引用类型共享了同一块堆内存,修改会相互影响
在JavaScript中,存在浅拷贝的现象有:
- Object.assign():当对象只有单层是深拷贝,多层则是浅拷贝
- Array.prototype.slice(),Array.prototype.concat()
- 使用拓展运算符实现的复制
深拷贝
深拷贝:是递归拷贝深层次,属性为对象时,开辟一个新的栈,两个对象的属性完全相同,但是对应两个不同的地址,修改一个对象的属性, 不会改变另一个对象的属性
常见的深拷贝方式有:
- _.cloneDeep()
- jQuery.extend()
- JSON.parse(JSON.stringify()):当对象中有function,Symbol(),undefined时,会造成数据丢失
手写深拷贝
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;
}
js自带的属性实现深拷贝,会忽略undefined、symbol和函数,以上循环递归函数可以包含各种情况。该循环递归函数多次用到了数据类型检测
typeof 数据类型检测
1、typeof不能判断null和array,结果都是“object”,一般用来检测基本数据类型
2、typeof检测function返回是“function”
instanceof检测引用数据类型
1、语法:A/object instanceof B/constructor
即B函数的显示原型对象再A对象的原型链上,返回true,否则false
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
function Person(){};
new Person() instanceof Person;
[] instanceof Object; //true
new Date() instanceof Object;//true
new Person instanceof Object;//true
从示例可以看出,instanceof能够正确判断[]是Array的实例对象,但是不能辨别[]不是Object的实例对象。这里又涉及到原型和原型链的知识了
原型和原型链
1、构造函数和实体对象的关系
- 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object)
console.log(Function.prototype instanceof Object)
console.log(Object.prototype instanceof Object) //false
-
所有函数的实例都是Function的实例(包括Function)
-
Object的原型对象是原型链的尽头
3、constructor根据数据类型和构造函数返回类型,无法判断null和undefined
标签:instanceof,obj,对象,Object,数据类型,拷贝,浅析 From: https://www.cnblogs.com/moeyi/p/17249694.html