最近到了面试季节,做为前端的热门问题我们今天来盘一盘
说到深浅拷贝那就要说到JavaScript的数据类型
基本数据类型和引用数据类型
基本数据类型
- String
- Number
- Boolean
- Null
- Undefined
- Symbol(new in ES 6)
引用数据类型
- Object
- Array
- Date
- RegExp
- Function
两者特点
- 基本数据类型:直接存储在栈(stack)中的数据
- 引用数据类型:存储的是该对象在栈中引用,真实的数据存放在堆内存里
简而言之就是
- 浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝: 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
我们可以通过一段简单代码论证浅拷贝
let data = 2;
let data2 = data;
data2 = 4
console.log(data,data2) // 2 4
let data = {arr:1};
let data2 = data;
data2.arr = 4
console.log(data.arr,data2.arr) // 4 4
浅拷贝的实现方式:
- Object.assign() 方法: 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值
- **Array.prototype.slice():**slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
- 拓展运算符…:是对象第一层的深拷贝。后面的只是拷贝的引用值。
Object.assign()
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }
**Array.prototype.slice()
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
let data = animals.slice(2)
let data2 = animals.slice(2, 4)
let data3 = animals.slice(1, 5)
let data4 = animals.slice(-2)
let data5 = animals.slice(2, -1)
let data6 = animals.slice()
console.log(data);
// expected output: Array ["camel", "duck", "elephant"]
console.log(data2);
// expected output: Array ["camel", "duck"]
console.log(data3);
// expected output: Array ["bison", "camel", "duck", "elephant"]
console.log(data4);
// expected output: Array ["duck", "elephant"]
console.log(data5)
// expected output: Array ["camel", "duck"]
console.log(data6);
// expected output: Array ["ant", "bison", "camel", "duck", "elephant"]
拓展运算符…
let data = {
name: "wang",
flag: {
title: "wangxiaohegn",
time: "2022-03-14"
}
}
let data2 = {...data};
data2.name = 'wang1';
console.log(data.name,data2.name) // wang wang1
data2.flag.time = '2022-03-15'
console.log(data.flag.time,data2.flag.time) // 2022-03-15 2022-03-15
深拷贝的实现方式:
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
- 乞丐版: JSON.parse(JSON.stringify(object)),缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date())
- 基础版(面试够用): 浅拷贝+递归 (只考虑了普通的 object和 array两种数据类型)利用 JSON 对象中的 parse 和 stringify利用递归来实现每一层都重新创建对象并赋值
JSON.stringify()和JSON.parse()
let data = {
name: "wang",
flag: {
title: "wangxiaohegn",
time: "2022-03-14"
}
}
let data2 = JSON.parse(JSON.stringify(data))
data2.name = 'wang1';
console.log(data.name,data2.name) // wang wang1
data2.flag.time = '2022-03-15'
console.log(data.flag.time,data2.flag.time) // 2022-03-14 2022-03-15
方法1
function cloneDeep(target,map = new WeakMap()) {
if(typeof target ==='object'){
let cloneTarget = Array.isArray(target) ? [] : {};
if(map.get(target)) {
return target;
}
map.set(target, cloneTarget);
for(const key in target){
cloneTarget[key] = cloneDeep(target[key], map);
}
return cloneTarget
}else{
return target
}
}
方法2
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
function clone (obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj;
if(obj.constructor===Date) return new Date(obj);
if(obj.constructor === RegExp) return new RegExp(obj);
let newObj = new obj.constructor (); //保持继承链
for (var key in obj) {
// 不遍历其原型链上的属性
if (obj.hasOwnProperty(key)) {
let val = obj[key];
// 使用arguments.callee解除与函数名的耦合
newObj[key] = typeof val === 'object' ? arguments.callee(val) : val;
}
}
return newObj;
};
终极方案参考:如何写出一个惊艳面试官的深拷贝
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;
}
module.exports = {
clone
};
写到最后的话大家不要忘记点赞收藏以防不备之需 发现新的好方法也可以写道评论区