对于间接赋值、浅拷贝、深拷贝的区别和图示,之前写json的文章里有介绍,能够参考这一篇 json尽管简略,但这些细节你未必晓得
拷贝第一层
首先,咱们定义一个最简略的浅拷贝,只有可能保留原对象的第一层数据就行~
function deepClone(value) { const newObj = {}; // 遍历原对象,将其赋值到新对象中 for (let key in value) { newObj[key] = value[key]; } return newObj;}// 定义一个对象const obj = { name: "alice", friend: { name: "windy", },};// 拷贝进去的新对象const user = deepClone(obj);user.name = "kiki";user.friend.name = "kiki";console.log("原对象obj-", obj);console.log("新对象user-", user)
批改新对象第一层属性name的值时,原对象是不会被批改的,但将新对象中第二层属性 friend.name 的值由 windy 批改为 kiki 时,原对象也被更改了
递归解决
对象的属性中有可能还存在对象的状况,仅一层的判断是不够的,所以须要通过递归来解决,当递归到值不为对象时,间接返回
// 判断传入的数据是否为对象function isObject(obj) { // 函数也是对象 return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value) { // 当value不为对象时,间接返回value if (!isObject(value)) return value; const newObj = {}; for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key]); } return newObj;}const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"],};const user = deepClone(obj);user.friend.name = "kiki";user.friend.address.city = 'Shanghai';console.log("原对象obj-", obj);console.log("新对象user-", user)
递归调用后,拷贝的对象都是开拓了一块新的内存空间来保留,所以批改新对象的值,原对象不会变动。
但咱们发现一个新的问题,原对象中hobbies属性值类型为 "array",拷贝到新对象中变成了"object"。
数组
所以对于数组的解决和对象要辨别开来,对传入的数据进行判断,如果为数组,创立用于复制的数据类型就应该为数组
// 判断传入的数据是否为对象function isObject(obj) { return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value) { // 当value不为对象时,间接返回value if (!isObject(value)) return value; // 判断value值是否为数组,为数组时创立新的数组 const newObj = Array.isArray(value) ? [] : {}; for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key]); } return newObj;}const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"], styding() { return "I am reading~"; },};const user = deepClone(obj);console.log("原对象obj-", obj);console.log("新对象user-", user)
此时对象和数组都能够正确的拷贝
但咱们在原对象中加了一个"studying办法",拷贝的新对象中"办法"变成了"空对象"
办法
函数自身就是为了可能复用,所以对象中的办法在进行深拷贝时,能够复用原来发办法,无需新创建,间接返回原办法即可
// 判断传入的数据是否为对象function isObject(obj) { return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value) { // 当value为function时,间接返回原function if (typeof value === "function") return value; // 当value不为对象时,间接返回value if (!isObject(value)) return value; // 判断value值是否为数组,为数组时创立新的数组 const newObj = Array.isArray(value) ? [] : {}; for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key]); } return newObj;}const symbolKey = Symbol("symbolKey");const symbolValue = Symbol("symbolValue");const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"], styding() { return "I am reading~"; }, [symbolKey]: "key", value: symbolValue,};const user = deepClone(obj);console.log("原对象obj-", obj);console.log("新对象user-", user)
原对象和新对象的办法指向同一个内存地址,但咱们个别也不须要对办法进行批改
在原对象中减少了symbol别离作为key和value的场景,symbol属性作为key时,在新对象中间接失落了
Symbol
Symbol作为key也是很常见的,防止key值反复,为了获取symbol所有的值,须要通过getOwnPropertySymbols,并增加到新对象中。
// 判断传入的数据是否为对象function isObject(obj) { return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value) { // 当value为symbol时,返回一个新symbol if (typeof value === "symbol") return Symbol(value.description); // 当value为function时,间接返回原function if (typeof value === "function") return value; // 当value不为对象时,间接返回value if (!isObject(value)) return value; // 判断value值是否为数组,为数组时创立新的数组 const newObj = Array.isArray(value) ? [] : {}; for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key]); } // 获取symbol为key的所有数据 const symbols = Object.getOwnPropertySymbols(value); for (let sym of symbols) { newObj[sym] = deepClone(value[sym]); } return newObj;}const symbolKey = Symbol("symbolKey");const symbolValue = Symbol("symbolValue");const set = new Set([1, 2, 3, 4, 5]);const map = new Map([ ["name", "alice"], ["age", 20],]);const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"], styding() { return "I am reading~"; }, [symbolKey]: "key", value: symbolValue, set, map,};const user = deepClone(obj);console.log("原对象obj-", obj);console.log("新对象user-", user);
symbol属性无论是作为key还是value,都能被胜利拷贝
但新对象拷贝的map和set的值都变成了"空对象"
map和set
map和set作为value的场景比拟少,所以这里就间接应用的浅拷贝,当值为map/set时,创立一个新的map/set,并返回。
// 判断传入的数据是否为对象function isObject(obj) { return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value) { // 当value为set时,返回一个新set if (value instanceof Set) return new Set([...value]); // 当value为map时,返回一个新map if (value instanceof Map) return new Map([...value]); // 当value为symbol时,返回一个新symbol if (typeof value === "symbol") return Symbol(value.description); // 当value为function时,间接返回原function if (typeof value === "function") return value; // 当value不为对象时,间接返回value if (!isObject(value)) return value; // 判断value值是否为数组,为数组时创立新的数组 const newObj = Array.isArray(value) ? [] : {}; for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key]); } // 获取symbol为key的所有数据 const symbols = Object.getOwnPropertySymbols(value); for (let sym of symbols) { newObj[sym] = deepClone(value[sym]); } return newObj;}const symbolKey = Symbol("symbolKey");const symbolValue = Symbol("symbolValue");const set = new Set([1, 2, 3, 4, 5]);const map = new Map([ ["name", "alice"], ["age", 20],]);const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"], styding() { return "I am reading~"; }, [symbolKey]: "key", value: symbolValue, set, map,};const user = deepClone(obj);console.log("原对象obj-", obj);console.log("新对象user-", user);
map和set也胜利拷贝到新对象中了
循环援用
对象是有可能存在循环援用,在window中就存在window属性,而且能够一直的调用。
如果须要拷贝的对象也是有属性指向本身的话,如 obj.info = obj。
咱们下面的深拷贝函数就会呈现死循环,报 RangeError 栈溢出的谬误。
所以还须要对深拷贝的代码进行优化,定义一个map/weakMap用于保留传入的value值和新创建的对象值,每一次对函数调用时,先判断传入的value是否曾经拷贝过,如果拷贝过,就间接返回之前拷贝的值,避免出现死循环。
// 判断传入的数据是否为对象function isObject(obj) { return obj !== null && (typeof obj === "function" || typeof obj === "object");}function deepClone(value, map = new WeakMap()) { // 当value为set时,返回一个新set if (value instanceof Set) return new Set([...value]); // 当value为map时,返回一个新map if (value instanceof Map) return new Map([...value]); // 当value为symbol时,返回一个新symbol if (typeof value === "symbol") return Symbol(value.description); // 当value为function时,间接返回原function if (typeof value === "function") return value; // 当value不为对象时,间接返回value if (!isObject(value)) return value; // 当map中存在value时,间接返回map中的value if (map.has(value)) { return map.get(value); } // 判断value值是否为数组,为数组时创立新的数组 const newObj = Array.isArray(value) ? [] : {}; // 将函数接管到的value和新创建的obj保留到map中 map.set(value, newObj); for (let key in value) { // 递归解决value值 newObj[key] = deepClone(value[key], map); } // 获取symbol为key的所有数据 const symbols = Object.getOwnPropertySymbols(value); for (let sym of symbols) { newObj[sym] = deepClone(value[sym], map); } return newObj;}const symbolKey = Symbol("symbolKey");const symbolValue = Symbol("symbolValue");const set = new Set([1, 2, 3, 4, 5]);const map = new Map([ ["name", "alice"], ["age", 20],]);const obj = { name: "alice", friend: { name: "windy", address: { city: "Beijing", }, }, hobbies: ["swiming", "dancing", "tennis"], styding() { return "I am reading~"; }, [symbolKey]: "key", value: symbolValue, set, map,};obj.info = obj;const user = deepClone(obj);console.log("原对象obj-", obj);console.log("新对象user-", user);
以上就实现了自定深拷贝的所有步骤,应用深拷贝时,不必放心批改一处变量,另一处通过拷贝获取到的变量也被扭转的状况,可能无效升高代码的bug率。
对于js高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~