开篇点题:javascript
const deepCloneKnowledgePoints = {
title: '浅拷贝和深拷贝',
chapterOne: {
title: '章节一',
point: [
'浅拷贝和深拷贝初探索',
'基本数据类型和引用数据类型',
],
},
chapterTwo: {
title: '章节二',
point: [
'手写浅拷贝',
'Object.assign()',
'Array.prototype.concat()',
'Array.prototype.slice()',
'...obj 展开运算符',
],
extend: [
'for...in',
'for...of',
'for...in 和 for...of 的区别',
'hasOwnProperty',
],
},
chapterThree: {
title: '章节三',
point: [
'手写深拷贝',
'JSON.parse(JSON.stringify())',
'函数库 Lodash',
'框架 jQuery',
],
extend: [
'typeof',
'instanceof',
'constructor',
{
point: 'Object.prototype.toString.call()',
extend: [
'Function.prototype.apply()',
'Function.prototype.bind()',
'Function.prototype.call()',
'apply()、bind() 以及 call() 的区别',
],
},
{
point: 'JSON.parse(JSON.stringify())',
extend: [
'JSON.parse()',
'JSON.stringify()',
]
}
],
},
};
复制代码
不折腾的前端,和咸鱼有什么区别php
返回目录css
在某次写业务代码中,忽然来了个场景:html
代码表现为:前端
// 初始数据
const initData = {
// ...
};
// finalData 数据来源于 initData
// 可是 finalData 的修改不能影响到 initData
const finalData = _.deepClone(initData);
// 返回最终形式 result 给后端
// finalData 和 initData 是两份不一样的数据
const result = {
initData: initData,
finalData: finalData,
};
return res;
复制代码
那么问题来了,若是采用 const finalData = initData
,可不能够直接修改 finalData
的数据呢?它会不会影响到 initData
?java
我们能够小试一下:node
const initData = { obj: '123' };
const finalData = initData;
finalData.obj = '456';
console.log(initData);
console.log(finalData);
// 猜猜上面两个 console.log 返回啥?
复制代码
若是不能够,那么咱们须要怎样操做,才能返回新旧对比数据给后端?jquery
返回目录git
如今,咱们开始揭晓上面答案。github
在这以前,先看看 基本数据类型和引用数据类型。
数据分为基本数据类型和引用数据类型。
为了形象了解,我们看下面代码:
// 基本数据类型
let str1 = '123';
str2 = str1;
str2 = '456';
console.log(str1); // '123'
console.log(str2); // '456'
// 引用数据类型
let arr1 = [1, 2, 3];
arr2 = arr1;
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [1, 2, 3, 4]
复制代码
如上,因为基本数据类型是直接存储的,因此若是咱们对基本数据类型进行拷贝,而后修改新数据后,不会影响到原数据。
而当你对引用数据类型进行拷贝,而后修改新数据后,它就会影响到原数据。
形象举例:
在 JavaScript 中,房子、车子这些 单个 属性的,就是基本数据类型,它有本身的私人领域,你能够克隆它,可是新的它不会影响原有的它(旧车坏,新车照样开)。
而若是是羊群、大草原这种 集体 属性的,就是对象数据数据,若是咱们一个一个存储,那就太麻烦了,很差一会儿找到,因此浏览器会给它开辟一大块领域,而后告诉你怎么找到它(索引)。因此当你修改它当中的内容(羊吃草,草减小),它内部就会发生变化。
OK,了解 基本数据类型和引用数据类型 后,我们进一步探索赋值、浅拷贝和深拷贝:
/** * @name 赋值 */
const dataOne = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataTwo = dataOne;
dataTwo.title = 'play';
dataTwo.number = ['null'];
console.log(dataOne);
// dataOne: { title: 'play', number: ['null'] }
console.log(dataTwo);
// dataTwo: { title: 'play', number: ['null'] }
/** * @name 浅拷贝 */
const dataThree = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataFour = shallowClone(dataThree); // shallowClone 待实现
dataFour.title = 'play';
dataFour.number = ['null'];
console.log(datadataThreeOne);
// dataThree: { title: 'study', number: ['null'] }
console.log(dataFour);
// dataFour: { title: 'play', number: ['null'] }
/** * @name 深拷贝 */
const dataFive = {
title: 'study',
number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'],
};
const dataSix = deepClone(dataFive); // deepClone 待实现
dataSix.title = 'play';
dataSix.number = ['null'];
console.log(dataFive);
// dataFive: { title: 'study', number: ['jsliang', 'JavaScriptLiang', 'LiangJunrong'] }
console.log(dataSix);
// dataSix: { title: 'play', number: ['null'] }
复制代码
如上,咱们给出结论:
上面说法可能有问题:
一、有的大佬认为浅拷贝是拷贝一层,即第一层数据无论是数组仍是字符串等都是互不影响的;
二、有的则认为浅拷贝只对基本数据类型有效,对于引用数据类型会引用其地址。
这里 jsliang 我的理解是将浅拷贝区分于赋值,若是你有更好观点,能够提出来~
图表化以下所示:
和原数据是否指向同一对象 | 原数据为基本数据类型 | 原数据包含子对象 | |
---|---|---|---|
赋值 | 是 | 改变【会】使原数据一同改变 | 改变【会】使原数据一同改变 |
浅拷贝 | 否 | 改变【不会】使原数据一同改变 | 改变【会】使原数据一同改变 |
深拷贝 | 否 | 改变【不会】使原数据一同改变 | 改变【不会】使原数据一同改变 |
在探讨经过工具进行浅拷贝以前,咱们尝试 “手写” 一份浅拷贝:
const arr1 = [1, 2, ['jsliang', 'JavaScriptLiang'], 4];
const shallowClone = (arr) => {
const dst = [];
for (let prop in arr) {
if (arr.hasOwnProperty(prop)) {
dst[prop] = arr[prop];
}
}
return dst;
}
const arr2 = shallowClone(arr1);
arr2[2].push('LiangJunrong');
arr2[3] = 5;
console.log(arr1);
// [ 1, 2, [ 'jsliang', 'JavaScriptLiang', 'LiangJunrong' ], 4 ]
console.log(arr2);
// [ 1, 2, [ 'jsliang', 'JavaScriptLiang', 'LiangJunrong' ], 5 ]
复制代码
能够看到,这里咱们修改引用数据类型里面的引用数据类型的时候,仍是会影响到原数据,可是若是咱们修改里面的基本数据类型的时候,就不会影响到原数据了。
那么,在更深刻讲解探讨以前,咱们先聊聊为了更好讲解上面代码思路,我作了啥:
for...in
,发现属于 JavaScript 语句和声明,因此给本身文档库起了个目录 语句和声明。for...in
和 for...of
类似,因而分别查了 for...in 和 for...of,顺带对着两个进行了对比 for...in 和 for...of 对比。for...of
还能够涉及到继承和原型链,因而顺带将以前面试写的文章弄过来:继承和原型链。hasOwnProperty
,发现我还没建 Object
分类,因而新建了目录:Object,并写好了 hasOwnProperty。闲扯完毕,咱们仍是讲解下上面代码为啥能浅拷贝数据:
for...in
:遍历 Object
对象 arr1
,将可枚举值列举出来。hasOwnProperty()
:检查该枚举值是否属于该对象 arr1
,若是是继承过来的就去掉,若是是自身的则进行拷贝。这样,咱们就成功实现了浅拷贝,而且了解了其中涉及的知识点~
下面咱们再介绍 3 种便捷形式,实现快速浅拷贝。
Object.assign()
方法能够把任意多个的源对象自身的可枚举属性拷贝给目标对象,而后返回目标对象。
可是须要注意的是,Object.assgin()
进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象自己。
const obj1 = {
username: 'LiangJunrong',
skill: {
play: ['basketball', 'computer game'],
read: 'book',
},
girlfriend: ['1 号备胎', '2 号备胎', '3 号备胎'],
};
const obj2 = Object.assign({}, obj1);
obj2.username = 'jsliang'; // 修改基本类型
obj2.skill.read = 'computer book'; // 修改二层基本类型
obj2.skill.play = ['footboll']; // 修改二层引用类型
obj2.girlfriend = ['以前的都是瞎吹的!'];
console.log(obj1);
// { username: 'LiangJunrong',
// skill: { play: [ 'footboll' ], read: 'computer book' },
// girlfriend: [ '1 号备胎', '2 号备胎', '3 号备胎' ] }
console.log(obj2);
// { username: 'jsliang',
// skill: { play: [ 'footboll' ], read: 'computer book' },
// girlfriend: [ '以前的都是瞎吹的!' ] }
复制代码
能够看到的是,Object.assign()
对于第一层的数据来讲,是深拷贝,对于第二层及以上的数据来讲,是浅拷贝。
concat()
是数组的一个内置方法,用户合并两个或者多个数组。
这个方法不会改变现有数组,而是返回一个新数组。
详细内容能够看 jsliang 的文章或者看 MDN 的文章:
在这里,咱们经过 concat()
来浅拷贝一个数组:
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = arr1.concat();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
复制代码
看到这里,小伙伴们应该明白,经过 concat()
进行的浅拷贝,能够修改里面的基本数据类型而不影响原值,可是修改里面的引用数据类型,就会影响到原有值了。
slice()
也是数组的一个内置方法,该方法会返回一个新的对象。
slice()
不会改变原数组。
详细能够看下 jsliang 的 concat
或者直接看 MDN 的文献:
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = arr1.slice();
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'LiangJunrong' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
复制代码
能够看到的是,它和前面的 concat()
表现的浅拷贝如出一辙,若是小伙伴但愿研究更深层次的内容,能够看下 Array.prototype.concat()
和 Array.prototype.slice()
的源码具体实现。
展开运算符是 ES6 中新提出来的一种运算符。
在拷贝数组、对象以及拼接数组等方面均可以使用。
这边咱们也能够尝试下使用 const obj2 = {...obj1}
的形式进行浅拷贝。
/** * @name ...obj拷贝数组 */
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // like arr.slice()
arr2.push(4);
console.log(arr1); // [ 1, 2, 3 ]
console.log(arr2); // [ 1, 2, 3, 4 ]
/** * @name ...obj拷贝对象 */
const obj1 = {
name: 'jsliang',
arr1: ['1', '2', '3'],
obj: {
name: 'JavaScriptLiang',
arr2: ['4', '5', '6'],
},
};
const obj2 = {...obj1};
obj2.name = 'LiangJunrong';
obj2.arr1 = ['null'];
obj2.obj.name = 'jsliang';
obj2.obj.arr2 = ['null'];
console.log(obj1);
// { name: 'jsliang',
// arr1: [ '1', '2', '3' ],
// obj: { name: 'jsliang', arr2: [ 'null' ] } }
console.log(obj2);
// { name: 'LiangJunrong',
// arr1: [ 'null' ],
// obj: { name: 'jsliang', arr2: [ 'null' ] } }
复制代码
---小节 1---
那么,手写深拷贝的话,要怎么实现呢?
咱们尝试稍微修改下前面的浅拷贝代码:
const deepClone = (source) => {
const target = {};
for (const i in source) {
if (source.hasOwnProperty(i)
&& target[i] === 'object') {
target[i] = deepClone(source[i]); // 注意这里
} else {
target[i] = source[i];
}
}
return target;
};
复制代码
固然,这份代码是存在问题的:
typeof
判断是否对象的逻辑不够严谨。---小节 2---
既然存在问题,那么咱们就须要尝试克服:
// 定义检测数据类型的功能函数
const checkedType = (target) => {
return Object.prototype.toString.call(target).slice(8, -1);
}
// 实现深度克隆对象或者数组
const deepClone = (target) => {
// 判断拷贝的数据类型
// 初始化变量 result 成为最终数据
let result, targetType = checkedType(target);
if (targetType === 'Object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
// 遍历目标数据
for (let i in target) {
// 获取遍历数据结构的每一项值
let value = target[i];
// 判断目标结构里的每一项值是否存在对象或者数组
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
// 若是对象或者数组中还嵌套了对象或者数组,那么继续遍历
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
// 返回最终值
return result;
}
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = deepClone(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1' },
// ],
// ]
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ]
复制代码
下面讲解下这份深拷贝代码:
首先,咱们先看检查类型的那行代码:Object.prototype.toString.call(target).slice(8, -1)
。
在说这行代码以前,咱们先对比下检测 JavaScript 数据类型的 4 种方式:
null
或者 new String()
等数据类型。'jsliang'
、123
等数据类型。null
和 undefined
会直接报错。详细研究能够看 jsliang 的学习文档:
而后,咱们经过方法 targetType()
中的 Object.prototype.toString.call()
,判断传入的数据类型属于那种,从而改变 result
的值为 {}
、[]
或者直接返回传入的值(return target
)。
最后,咱们再经过 for...in
判断 target
的全部元素,若是属于 {}
或者 []
,那么就递归再进行 clone()
操做;若是是基本数据类型,则直接传递到数组中……从而在最后返回一个深拷贝的数据。
---小节 3---
以上,咱们的代码看似没问题了是否是?假设咱们须要拷贝的数据以下:
const obj1 = {};
obj1.a = obj1;
console.log(deepClone(obj1));
// RangeError: Maximum call stack size exceeded
复制代码
看,咱们直接研制了个死循环出来!
那么咱们须要怎么解决呢?有待实现!
乘我还没找到教好的解决方案以前,小伙伴们能够看下下面文章,思考下是否能解决这个问题:
……好的,虽然口头说着但愿小伙伴们自行翻阅资料,可是为了防止被寄刀片,jsliang 仍是在这里写下本身以为 OK 的代码:
function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
}
function deepClone(source, hash = new WeakMap()) {
if (!isObject(source)) return source;
// 新增代码,查哈希表
if (hash.has(source)) return hash.get(source);
var target = Array.isArray(source) ? [] : {};
// 新增代码,哈希表设值
hash.set(source, target);
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
// 新增代码,传入哈希表
target[key] = deepClone(source[key], hash);
} else {
target[key] = source[key];
}
}
}
return target;
}
/** * @name 正常深拷贝测试 */
const a = {
name: 'jsliang',
book: {
title: '深拷贝学习',
price: 'free',
},
a1: undefined,
a2: null,
a3: 123
};
const b = deepClone(a);
b.name = 'JavaScriptLiang';
b.book.title = '教你如何泡妞';
b.a3 = 456;
console.log(a);
// { name: 'jsliang',
// book: { title: '深拷贝学习', price: 'free' },
// a1: undefined,
// a2: null,
// a3: 123 }
console.log(b);
// { name: 'JavaScriptLiang',
// book: { title: '教你如何泡妞', price: 'free' },
// a1: undefined,
// a2: null,
// a3: 456 }
/** * @name 解决死循环 */
const c = {};
c.test = c;
const d = deepClone(c);
console.log(c);
// { test: [Circular] }
console.log(d);
// { test: [Circular] }
复制代码
---小节 4---
既然搞定完死循环,我们再看看另外一个问题:
const checkedType = (target) => {
return Object.prototype.toString.call(target).slice(8, -1);
}
const deepClone = (target) => {
let result, targetType = checkedType(target);
if (targetType === 'Object') {
result = {};
} else if (targetType === 'Array') {
result = [];
} else {
return target;
}
for (let i in target) {
let value = target[i];
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
return result;
}
// 检测深度和广度
const createData = (deep, breadth) => {
const data = {};
let temp = data;
for (let i = 0; i < deep; i++) {
temp = temp['data'] = {};
for (let j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
};
console.log(createData(1, 3));
// 1 层深度,每层有 3 个数据 { data: { '0': 0, '1': 1, '2': 2 } }
console.log(createData(3, 0));
// 3 层深度,每层有 0 个数据 { data: { data: { data: {} } } }
console.log(deepClone(createData(1000)));
// 1000 层深度,无压力 { data: { data: { data: [Object] } } }
console.log(deepClone(createData(10, 100000)));
// 100000 层广度,没问题,数据遍历须要时间
console.log(deepClone(createData(10000)));
// 10000 层深度,直接爆栈:Maximum call stack size exceeded
复制代码
是的,你的深拷贝爆栈了!!!
虽然业务场景中可能爆栈的几率比较少,毕竟数据层级没那么多,可是仍是会存在这种状况,须要怎么处理呢?
只想大体了解深拷贝可能出现问题的小伙伴能够跳过下面内容
举个例子,假设有数据结构:
const a = {
a1: 1,
a2: {
b1: 1,
b2: {
c1: 1
}
}
};
复制代码
若是咱们将其当成数来看:
a
/ \
a1 a2
| / \
1 b1 b2
| |
1 c1
|
1
复制代码
那么,咱们就能够采用迭代的方法,循环遍历这棵树了!
key
用来存储哪个父元素的那一个子元素拷贝对象const deepClone = (x) => {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x
}
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key 为 undefined 则拷贝到父元素,不然拷贝到子元素
let res = parent;
if (typeof key !== "undefined") {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === "object") {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k]
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
复制代码
这时候咱们再经过 createData
进行广度和深度校验,会发现:
console.log(deepClone(createData(10, 100000)));
// 100000 层广度,没问题,数据遍历须要时间
console.log(deepClone(createData(100000)));
// 100000 层深度,也没问题了:{ data: { data: { data: [Object] } } }
复制代码
这样,咱们就解决了爆栈的问题。
这里推荐下引用思路来源于大佬的文章:深拷贝的终极探索,而后它附带了一个深拷贝库:@jsmini/clone,感兴趣的小伙伴能够去看看。
其实利用工具,达到目的,是很是聪明的作法,下面咱们讨论下 JSON.parse(JSON.stringify())
。
JSON.stringify()
:将对象转成 JSON 字符串。JSON.parse()
:将字符串解析成对象。经过 JSON.parse(JSON.stringify())
将 JavaScript 对象转序列化(转换成 JSON 字符串),再将其还原成 JavaScript 对象,一去一来咱们就产生了一个新的对象,并且对象会开辟新的栈,从而实现深拷贝。
注意,该方法的局限性:
一、不能存放函数或者 Undefined,不然会丢失函数或者 Undefined;
二、不要存放时间对象,不然会变成字符串形式;
三、不能存放 RegExp、Error 对象,不然会变成空对象;
四、不能存放 NaN、Infinity、-Infinity,不然会变成 null;
五、……更多请自行填坑,具体来讲就是 JavaScript 和 JSON 存在差别,二者不兼容的就会出问题。
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = JSON.parse(JSON.stringify(arr1));
arr2[0] = 2;
arr2[1].username = 'LiangJunrong';
console.log(arr1);
// [ 1, { username: 'jsliang' } ]
console.log(arr2);
// [ 2, { username: 'LiangJunrong' } ]
复制代码
Lodash 做为一个深受你们喜好的、优秀的 JavaScript 函数库/工具库,它里面有很是好用的封装好的功能,你们能够去试试:
这里咱们查看下它的 cloneDeep()
方法:
能够看到,该方法会递归拷贝 value
。
在这里,咱们体验下它的 cloneDeep()
:
// npm i -S lodash
var _ = require('lodash');
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = _.cloneDeep(obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1' },
// ],
// ]
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ]
复制代码
这里咱们使用的是 Node 安装其依赖包的形式,若是须要用 MDN 等,小伙伴能够前往它官网瞅瞅。(地址在本节开头)
固然,不可厚非你的公司还在用着 jQuery,可能还须要兼容 IE6/7/8,或者你使用 React,可是有些场景还使用了 jQuery,毕竟 jQuery 是个强大的框架。
下面咱们尝试下使用 jQuery 的 extend()
进行深拷贝:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>尝试 jQuery 深拷贝</p>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script>
$(function() {
const obj1 = [
1,
'Hello!',
{ name: 'jsliang1' },
[
{
name: 'LiangJunrong1',
}
],
]
const obj2 = {};
/**
* @name jQuery深拷贝
* @description $.extend(deep, target, object1, object2...)
* @param {Boolean} deep 可选 true 或者 false,默认是 false,因此通常若是须要填写,最好是 true。
* @param {Object} target 须要存放的位置
* @param {Object} object 能够有 n 个原数据
*/
$.extend(true, obj2, obj1);
obj2[0] = 2;
obj2[1] = 'Hi!';
obj2[2].name = 'jsliang2';
obj2[3][0].name = 'LiangJunrong2';
console.log(obj1);
// [
// 1,
// 'Hello!',
// { name: 'jsliang1' },
// [
// { name: 'LiangJunrong1'},
// ],
// ];
console.log(obj2);
// [
// 2,
// 'Hi!',
// { name: 'jsliang2' },
// [
// { name: 'LiangJunrong2' },
// ],
// ];
});
</script>
</body>
</html>
复制代码
这里因为 Node 直接引用包好像没尝试成功,因此咱经过 index.html
的形式,引用了 jQuery 的 CDN 包,从而尝试了它的深拷贝。
推荐经过
live-server
来实时监控 HTML 文件的变化
若是须要查看
jQuery.extend()
源码能够观看文章:
《深拷贝与浅拷贝的实现(一)》
《JavaScript 浅拷贝和深拷贝》
jQuery.extend 源码
jQuery.extend = jQuery.fn.extend = function() {
var options,
name,
src,
copy,
copyIsArray,
clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if (typeof target === "boolean") {
deep = target;
// Skip the boolean and the target
target = arguments[i] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== "object" && !jQuery.isFunction(target)) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null) {
// Extend the base object
for (name in options) {
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy) {
continue;
}
// Recurse if we're merging plain objects or arrays
if (
deep &&
copy &&
(jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
) {
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = jQuery.extend(deep, clone, copy);
// Don't bring in undefined values
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
};
复制代码
很遗憾,咱们就这样暂时完成了 浅拷贝和深拷贝 的第一期探索啦~
在写这系列内容中,jsliang 犹豫过,熬夜过,想放弃过……但仍是坚持下来,过了一遍。
在查资料丰富这系列知识的过程当中,jsliang 忽略了一些知识点探索,要否则会产生更多的疑惑,最终每天通宵达旦到晚上 04:00 还在精神抖擞折腾~
咱们还未深刻的一些点有:
可能有些小伙伴会以为:
enm...怎么说,首先在写这篇文章的时候,jsliang 作的是广度探索,即碰到的每个知识点都接触了解一下,其实这样写很是累,可是进步是很是大的,由于你把一些知识点都挖掘出来了。
而后,jsliang 一开始的目标,只是想了解下手写深拷贝,以及一些工具快速实现深拷贝,因此我的以为本次目标已经达到甚至超标了。
最后,还有一些知识点,例如手写一个 Object.assign()
或者了解 Lodash 的深拷贝源码,其实但愿进一步了解的小伙伴,确定会自行先探索,固然若是小伙伴但愿有 “前人躺坑”,那么能够期待个人后续完善。
毕竟:不折腾的前端,跟咸鱼有什么区别!
因此,若是小伙伴们想持续跟进,能够到 jsliang 的 GitHub 仓库 首页找到个人公众号、微信、QQ 等联系方式。
那么,咱们后期再会~
jsliang 广告推送:
也许小伙伴想了解下云服务器
或者小伙伴想买一台云服务器
或者小伙伴须要续费云服务器
欢迎点击 云服务器推广 查看!
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/LiangJunron…上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。