一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 浅拷贝
 3.1 手写浅拷贝
 3.2 Object.assign
 3.3 数组 API
四 深拷贝
 4.1 手写深拷贝
 4.2 JSON.parse(JSON.stringify())
 4.3 函数库 Lodash
 4.4 五 框架 jQuery
五 参考文献

二 前言

返回目录

面试官:如何浅拷贝和深拷贝一个数组?

要搞懂为啥问这个问题,还得从引址和引值说起:

  • 咱们复制援用根底数据类型,都是拷贝对应的值
let number1 = 1;let number2 = 2;console.log(number1, number2); // 1 2number2 = 3;console.log(number1, number2); // 1 3// 复制根本数据类型,并进行批改,是不会扭转本来的值(number1)
  • 咱们复制援用简单数据类型,都是拷贝对应的址
let obj1 = {  name: 'jsliang',};let obj2 = obj1;console.log(obj1); // { name: 'jsliang' }console.log(obj2); // { name: 'jsliang' }obj2.name = 'zhazhaliang';console.log(obj1); // { name: 'zhazhaliang' }console.log(obj2); // { name: 'zhazhaliang' }// 复制援用数据类型,并进行批改,会扭转本来的值(obj1)

简略来说,ArrayObject 这些简单数据类型,它们开拓了一块本人的空间来存放数据,它们援用的是对应的地址。

你能够将 Array 外面的数据看做一家人,那么要查找这家人,是不是就得有个户口,如果你批改某个家庭成员,是不是就批改了这家人户口的信息?

因而,你日常的拷贝数组或者对象,是复制了它们的地址。

如果你想批改的时候不净化原内容,就须要进行深拷贝。

三 浅拷贝

返回目录

如何实现浅拷贝,个别会答 3 种状况:

  1. 手写浅拷贝
  2. 应用 Object.assign
  3. 应用数组 API,如 concat 或者 slice 以及拓展运算符

3.1 手写浅拷贝

返回目录
const shallowClone = (target) => {  // 设置拷贝后果  const result = [];  // 通过 let ... in ... 遍历  for (let prop in target) {    // 如果这个属性是 target 本身的    // 而不是通过原型链给的,那就赋值过去    if (target.hasOwnProperty(prop)) {      result[prop] = target[prop];    }  }  // 返回拷贝后的后果  return result;}
  • for...in:遍历 Object 对象 arr1,将可枚举值列举进去。
  • hasOwnProperty():查看该枚举值是否属于该对象 arr1,如果是继承过去的就去掉,如果是本身的则进行拷贝。

当然,这个只能拷贝数组,那么拷贝对象呢?

咱们 “略微” 欠缺一下:

// 通过 Object.prototype.toString.call(xxx) 能够判断一个指标的属性// Object 会返回 [Function Object]// Array 会返回 [Function Array]const checkedType = (target) => {  return Object.prototype.toString.call(target).slice(8, -1);}// 浅拷贝const shallowClone = (target) => {  // 设置后果  let result;  // 如果指标是对象,那么设置成对象  if (checkedType(target) === 'Object') {    result = {};  } else if (checkedType(target) === 'Array') { // 如果指标是数组,那么设置成数组    result = [];  }  // 通过 let...in... 遍历数组或者对象  // 并通过 hasOwnProperty 判断是否为本身的属性  for (let i in target) {    if (target.hasOwnProperty(i)) {      result[i] = target[i];    }  }  // 返回拷贝后果  return result;};

实例测一测:

/* ———————————————— 测试数组 ———————————————— */const arr1 = [1, 2, ['jsliang', 'JavaScriptLiang'], 4];Array.prototype.sayHello = () => console.log('你好');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 ]/* ———————————————— 测试对象 ———————————————— */const obj1 = {  name: 'jsliang',  like: ['eat', 'play'],}Object.prototype.sayHello = () => console.log('你好');const obj2 = shallowClone(obj1);console.log(obj2);obj2.name = 'zhazhaliang';obj2.like.push('sleep');console.log(obj1); // { name: 'jsliang', like: [ 'eat', 'play', 'sleep' ] }console.log(obj2); // { name: 'zhazhaliang', like: [ 'eat', 'play', 'sleep' ] }

3.2 Object.assign

返回目录
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 用于拷贝对象。

它对于第一层来说,是齐全拷贝;对于第二层及以上来说,是简略复制。

3.3 数组 API

返回目录
  • Array.prototype.concat(target)concat() 是数组的一个内置办法,用户合并两个或者多个数组。这个办法不会扭转现有数组,而是返回一个新数组。const b = [].concat(a)
  • Array.prototype.slice(start, end)slice() 也是数组的一个内置办法,该办法会返回一个新的对象。slice() 不会扭转原数组。const b = a.slice()
  • 开展运算符[...arr] 能够失去一个浅拷贝新数组。

四 深拷贝

返回目录

如果面试官问你深拷贝的实现,jsliang 认为的答题思路应该是这样的:

  1. 手写深拷贝
  2. 借助 JSON.parse(JSON.stringify())
  3. 借助第三方库 Lodash、jQuery 等

而后如果面试官问起如何本人手写实现,那就用下一大节的代码答复吧!

4.1 手写深拷贝

返回目录
// 定义检测数据类型的性能函数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;}

留神这个深拷贝还是有问题的,例如:

  • 循环援用(通过 Map 或者 Set 记录存储空间)(WeakMap 在这里有更好的作用)
  • 同级援用
  • ……等

这里就不一一去讲具体解决方案了。

简略测一测:

/* ———————————————— 测试数组 ———————————————— */const arr1 = [1, 2, ['jsliang', 'JavaScriptLiang'], 4];Array.prototype.sayHello = () => console.log('你好');const arr2 = deepClone(arr1);arr2[2].push('LiangJunrong');arr2[3] = 5;console.log(arr1); // [ 1, 2, [ 'jsliang', 'JavaScriptLiang' ], 4 ]console.log(arr2); // [ 1, 2, [ 'jsliang', 'JavaScriptLiang', 'LiangJunrong' ], 5 ]/* ———————————————— 测试对象 ———————————————— */const obj1 = {  name: 'jsliang',  like: ['eat', 'play'],}Object.prototype.sayHello = () => console.log('你好');const obj2 = deepClone(obj1);obj2.name = 'zhazhaliang';obj2.like.push('sleep');console.log(obj1); // { name: 'jsliang', like: [ 'eat', 'play' ] }console.log(obj2); // { name: 'zhazhaliang', like: [ 'eat', 'play', 'sleep' ] } 

4.2 JSON.parse(JSON.stringify())

返回目录
  • JSON.stringify():将对象转成 JSON 字符串。
  • JSON.parse():将字符串解析成对象。

通过 JSON.parse(JSON.stringify()) 将 JavaScript 对象转序列化(转换成 JSON 字符串),再将其还原成 JavaScript 对象,一去一来咱们就产生了一个新的对象,而且对象会开拓新的栈,从而实现深拷贝。

留神,该办法的局限性:
1、不能寄存函数或者 Undefined,否则会失落函数或者 Undefined;
2、不要寄存工夫对象,否则会变成字符串模式;
3、不能寄存 RegExp、Error 对象,否则会变成空对象;
4、不能寄存 NaN、Infinity、-Infinity,否则会变成 null;
5、……更多请自行填坑,具体来说就是 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' } ]

4.3 函数库 Lodash

返回目录

Lodash 作为一个深受大家青睐的、优良的 JavaScript 函数库/工具库,它外面有十分好用的封装好的性能,大家能够去试试:

  • Lodash

这里咱们查看下它的 cloneDeep() 办法:

  • Lodash - _.cloneDeep(value)

能够看到,该办法会递归拷贝 value

在这里,咱们体验下它的 cloneDeep()

//  npm i -S lodashvar _ = 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' },//   ],// ]

4.4 五 框架 jQuery

返回目录

当然,不可厚非你的公司还在用着 jQuery,可能还须要兼容 IE6/7/8,或者你应用 React,然而有些场景还应用了 jQuery,毕竟 jQuery 是个弱小的框架。

能够尝试下应用 jQuery 的 extend() 进行深拷贝,这里就不贴办法了。

五 参考文献

返回目录
  • [x] 如何写出一个惊艳面试官的深拷贝?【浏览倡议:2h】
  • [x] 深拷贝的终极摸索(90%的人都不晓得)【浏览倡议:1h】
  • [x] JavaScript根底心法——深浅拷贝【浏览倡议:30min】
  • [x] JavaScript专题之深浅拷贝【浏览倡议:20min】
  • [x] javaScript中浅拷贝和深拷贝的实现【浏览倡议:20min】
  • [x] 深刻分析 JavaScript 的深复制【浏览倡议:20min】
  • [x] 「JavaScript」带你彻底搞清楚深拷贝、浅拷贝和循环援用【浏览倡议:20min】
  • [x] 面试题之如何实现一个深拷贝【浏览倡议:30min】

jsliang 的文档库由 梁峻荣 采纳 常识共享 署名-非商业性应用-雷同形式共享 4.0 国内 许可协定 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。