js中数据类型包括基本数据类型和引用数据类型,基本数据类型存储在栈中,引用数据类型存储在堆中。
浅拷贝
浅拷贝指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址。
直接赋值
实现浅拷贝的方法有很多,最常见的就是遍历赋值
let target = {}
let sorce = {'a':1,'b':2}
for(ele in sorce){
target[ele] = sorce[ele]
}
需要注意的是,这里都是一层基本数据类型,所以浅拷贝也相当于这里的深拷贝了,但是如果sorce中有深层的引用数据类型,则会不一样,看看下面的测试就知道了
let target = {}
let sorce = { a: 0, b: { c: 0 } }
for(ele in sorce){
target[ele] = sorce[ele]
}
console.log(target,sorce) // { a: 0, b: { c: 0 } } { a: 0, b: { c: 0 } }
target.a = 2
console.log(target,sorce)// { a: 2, b: { c: 0 } } { a: 0, b: { c: 0 } }
sorce.a = 3
console.log(target,sorce)// { a: 2, b: { c: 0 } } { a: 3, b: { c: 0 } }
target.b.c = 4
console.log(target,sorce)// { a: 2, b: { c: 4 } } { a: 3, b: { c: 4} }
sorce.b.c = 2
console.log(target,sorce)//{ a: 2, b: { c: 2 } } { a: 3, b: { c: 2}
因为是浅拷贝,对于深层的对象,只是拷贝了地址,修改地址指向的数据的值,两个对象的值会同时改变
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 === target);
// Expected output: true
拷贝后的对象就是原本target,相当于,调用sorce的get和target的set
const obj1 = { a: 0, b: { c: 0 } };
const obj2 = Object.assign({}, obj1);
console.log(obj2); // { a: 0, b: { c: 0 } }
obj1.a = 1;
console.log(obj1); // { a: 1, b: { c: 0 } }
console.log(obj2); // { a: 0, b: { c: 0 } }
obj2.a = 2;
console.log(obj1); // { a: 1, b: { c: 0 } }
console.log(obj2); // { a: 2, b: { c: 0 } }
obj2.b.c = 3;
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 2, b: { c: 3 } }
es6扩展运算符
对象和数组的解构赋值,遇到同名属性会进行替换覆盖
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2; // 修改obj里面a属性中键值
x.a.b // 2,影响到了结构出来x的值
const fxArr = ["One", "Two", "Three"]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
slice 和concat
const fxArr = ["One", "Two", {a : 1}]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
fxArrs[2].a = 2
console.log(fxArr)
console.log(fxArrs)
slice用于拷贝简单数组很方便,但是要注意这个而也是浅拷贝
const fxArr = ["One", "Two", "Three"]
const fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
深拷贝
JSON.stringify()
但是这种方式存在弊端,会忽略undefined、symbol和函数
const obj2=JSON.parse(JSON.stringify(obj1));
循环递归
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;
}
loadash 的cloneDeep()
const _ = require('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
看卡是怎么实现的
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG)
}
function baseClone(value, bitmask, customizer, key, object, stack) {
let result
const isDeep = bitmask & CLONE_DEEP_FLAG
const isFlat = bitmask & CLONE_FLAT_FLAG
const isFull = bitmask & CLONE_SYMBOLS_FLAG
if (customizer) {
result = object ? customizer(value, key, object, stack) : customizer(value)
}
if (result !== undefined) {
return result
}
if (!isObject(value)) {
return value
}
const isArr = Array.isArray(value)
const tag = getTag(value)
if (isArr) {
result = initCloneArray(value)
if (!isDeep) {
return copyArray(value, result)
}
} else {
const isFunc = typeof value === 'function'
if (isBuffer(value)) {
return cloneBuffer(value, isDeep)
}
if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
result = (isFlat || isFunc) ? {} : initCloneObject(value)
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
} else {
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
result = initCloneByTag(value, tag, isDeep)
}
}
// Check for circular references and return its corresponding clone.
stack || (stack = new Stack)
const stacked = stack.get(value)
if (stacked) {
return stacked
}
stack.set(value, result)
if (tag == mapTag) {
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
if (tag == setTag) {
value.forEach((subValue) => {
result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack))
})
return result
}
if (isTypedArray(value)) {
return result
}
const keysFunc = isFull
? (isFlat ? getAllKeysIn : getAllKeys)
: (isFlat ? keysIn : keys)
const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
return result
}
解析:首先判断是否存在用户自定义函数,如果有就调用得到一个结果,这个结果存在的话就返回实现用户自定义拷贝赋值;
如果value不是对象或者函数类型,则代表是基本数据类型,直接返回
如果值是数组,它初始化一个克隆数组,并使用copyArray函数将原始数组的值复制到克隆数组中。如果不需要深拷贝,则返回克隆数组。
如果值不是数组,它使用getTag函数检查值的标签。如果标签是对象标签、参数标签或函数标签,则初始化一个克隆对象,并使用copyObject函数将原始对象的属性复制到克隆对象中。如果不需要深拷贝,则返回克隆对象。
如果值是缓冲区,则使用cloneBuffer函数对缓冲区进行克隆,并根据需要进行深拷贝。
数组浅拷贝
function copyArray(source, array) {
let index = -1
const length = source.length
array || (array = new Array(length))
while (++index < length) {
array[index] = source[index]
}
return array
}
export default copyArray
数组初始化
返回一个和数组长度一样的空数组
function initCloneArray(array) {
const { length } = array
const result = new array.constructor(length)
// Add properties assigned by `RegExp#exec`.
if (length && typeof array[0] === 'string' && hasOwnProperty.call(array, 'index')) {
result.index = array.index
result.input = array.input
}
return result
}
缓存区拷贝
function cloneBuffer(buffer, isDeep) {
if (isDeep) {
return buffer.slice()
}
const length = buffer.length
const result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length)
buffer.copy(result)
return result
}
对象深浅拷贝
if (!isDeep) {
return isFlat
? copySymbolsIn(value, copyObject(value, keysIn(value), result))
: copySymbols(value, Object.assign(result, value))
}
function copyObject(source, props, object, customizer) {
const isNew = !object
object || (object = {})
for (const key of props) {
let newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined
if (newValue === undefined) {
newValue = source[key]
}
if (isNew) {
baseAssignValue(object, key, newValue)
} else {
assignValue(object, key, newValue)
}
}
return object
}
function baseAssignValue(object, key, value) {
if (key == '__proto__') {
Object.defineProperty(object, key, {
'configurable': true,
'enumerable': true,
'value': value,
'writable': true
})
} else {
object[key] = value
}
}
const props = isArr ? undefined : keysFunc(value)
arrayEach(props || value, (subValue, key) => {
if (props) {
key = subValue
subValue = value[key]
}
// Recursively populate clone (susceptible to call stack limits).
assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack))
})
} else {
if (isFunc || !cloneableTags[tag]) {
return object ? value : {}
}
result = initCloneByTag(value, tag, isDeep)
}
}