codes 中的最后一个“深度克隆”需要知道如何封装。

Syntax

  1. ...数组; // => 对 数组 展开 ES6
  2. ...对象; // => 对 对象 展开 ES7

展开运算符和剩余参数

虽然两者在写法上一模一样,但是含义却大不相同。区分它们也很简单:

  • 写在定义一个函数的形参位置上:表示的是剩余参数。
  • 否则:表示的是展开运算符。

codes

  • 1.js
  1. /*
  2. 需求:未知数组求和
  3. */
  4. /**
  5. * 根据指定的长度来创建一个随机数组
  6. * @param {Number} len 随机数组的长度
  7. */
  8. function createRandomArr(len) {
  9. const resultArr = [];
  10. for (let i = 0; i < len; i++) {
  11. const item = getRandom(1, 10);
  12. resultArr.push(item);
  13. }
  14. return resultArr;
  15. }
  16. function getRandom(min, max) {
  17. return Math.round(Math.random() * (max - min) + min);
  18. }
  19. function sum(...args) {
  20. let result = 0;
  21. args.forEach(arg => {
  22. result += arg;
  23. });
  24. return result;
  25. }
  26. const arr = createRandomArr(3);
  27. console.log(arr);
  28. // 错误调用方式:
  29. sum(arr); // 相当于仅传入了一个参数
  30. // 正确调用方式:
  31. sum.apply(null, arr); // 将参数合并到一个数组中传入
  32. sum(...arr); // 将数组展开后传入
  • 2.js
  1. /*
  2. 需求:数组浅度克隆
  3. */
  4. const arr1 = [1, 2, 3];
  5. const arr2 = [...arr1];
  6. arr1 === arr2; // => false
  7. arr1; // => [1, 2, 3]
  8. arr2; // => [1, 2, 3]
  • 3.js
  1. /*
  2. 需求:对象浅度克隆
  3. */
  4. const obj1 = {
  5. name: '成哥',
  6. age: 18,
  7. love: '邓嫂'
  8. }
  9. const obj2 = {
  10. ...obj1
  11. }
  12. /* 等价于下面这种写法
  13. const obj2 = {
  14. name: obj1.name,
  15. age: obj1.age,
  16. love: obj1.love
  17. }
  18. */
  19. obj1; // => { name: '成哥', age: 18, love: '邓嫂' }
  20. obj2; // => { name: '成哥', age: 18, love: '邓嫂' }
  21. obj1 === obj2; // => false
  • 4.js
  1. /* 重复定义的对象属性
  2. 对象属性不能重复定义,若重复定义的话,那么之后定义的属性值会覆盖之前的。
  3. 利用这一特点,可以实现很多需求。
  4. 比如:对象混合。
  5. 既能确保源对象不变,又能创建一个新的对象。
  6. 这种操作在 React 中会很常见。
  7. */
  8. const obj1 = {
  9. name: '成哥',
  10. age: 18,
  11. love: '邓嫂'
  12. }
  13. const obj2 = {
  14. ...obj1,
  15. name: '邓哥'
  16. }
  17. obj1; // => { name: '成哥', age: 18, love: '邓嫂' }
  18. obj2; // => { name: '邓哥', age: 18, love: '邓嫂' }
  19. obj1 === obj2; // => false
  • 5.js
  1. /* [示例] 在封装插件的时候,一般都会用到对象混合。
  2. 配置对象中的参数,若默认配置对象中的值与用户传入的值有冲突,那么优先使用用户传入的值。
  3. */
  4. // 用户传入的配置对象
  5. let options = {
  6. width: '100',
  7. height: '100'
  8. }
  9. // 默认的配置对象
  10. const defaultOptions = {
  11. width: '200',
  12. height: '200',
  13. color: '#008c8c'
  14. }
  15. options = {
  16. ...defaultOptions,
  17. ...options
  18. }
  19. options; // => { width: '100', height: '100', color: '#008c8c' }
  20. defaultOptions; // => { width: '200', height: '200', color: '#008c8c' }
  • 6.js
  1. // 用户传入的配置对象
  2. let options = {
  3. width: '100',
  4. height: '100'
  5. }
  6. // 默认的配置对象
  7. const defaultOptions = {
  8. width: '200',
  9. height: '200',
  10. color: '#008c8c'
  11. }
  12. options = Object.assign({}, defaultOptions, options);
  13. options; // => { width: '100', height: '100', color: '#008c8c' }
  14. defaultOptions; // => { width: '200', height: '200', color: '#008c8c' }
  15. /*
  16. 个人对 Object.assign() 的理解
  17. Object.assign({}, defaultOptions, options); // 以该语句为例
  18. 第一个参数是一个 {} 空对象 内存空间的地址假设为 a
  19. 第二个参数是 defaultOptions对象
  20. 第三个参数是 options对象
  21. Object.assign() 做的事情就是
  22. 1. 先把第二个对象给展开 然后把它的所有键值对 丢到 a 中
  23. 2. 再把第三个对象给展开 同样地把它的所有键值对 丢到 a 中
  24. 3. ...
  25. 一旦发现了了冲突的键 那么 以后面丢进来的为准
  26. 最后将 a 返回
  27. */
  • 7.js
  1. /*
  2. 浅度克隆
  3. */
  4. const obj1 = {
  5. name: '成哥',
  6. age: 18,
  7. love: '邓嫂',
  8. address: {
  9. country: '中国',
  10. province: '黑龙江',
  11. city: '哈尔滨'
  12. }
  13. }
  14. const obj2 = {
  15. ...obj1
  16. }
  17. obj1;
  18. obj2;
  19. obj1.address === obj2.address;
  20. /* output
  21. {
  22. name: '成哥',
  23. age: 18,
  24. love: '邓嫂',
  25. address: { country: '中国', province: '黑龙江', city: '哈尔滨' }
  26. }
  27. {
  28. name: '成哥',
  29. age: 18,
  30. love: '邓嫂',
  31. address: { country: '中国', province: '黑龙江', city: '哈尔滨' }
  32. }
  33. true
  34. */
  • 8.js
  1. /*
  2. 在我们清楚知道被克隆的对象的结构的前提下,我们可以采用下面这种操作来实现深度克隆。
  3. */
  4. const obj1 = {
  5. name: '成哥',
  6. age: 18,
  7. love: ['邓嫂', '成嫂1', '成嫂2'], // love 是一个引用类型
  8. address: { // address 也是一个引用类型
  9. country: '中国',
  10. province: '黑龙江',
  11. city: '哈尔滨'
  12. }
  13. }
  14. const obj2 = {
  15. ...obj1,
  16. address: { // 引用类型 进一步展开
  17. ...obj1.address
  18. },
  19. love: [...obj1.love, '成嫂3', '成嫂4'] // 引用类型进一步展开 并且 还可以新增一些成员
  20. }
  21. obj1;
  22. obj2;
  23. obj1.address === obj2.address;
  24. obj1.love === obj2.love;
  25. /* output
  26. {
  27. name: '成哥',
  28. age: 18,
  29. love: [ '邓嫂', '成嫂1', '成嫂2' ],
  30. address: { country: '中国', province: '黑龙江', city: '哈尔滨' }
  31. }
  32. {
  33. name: '成哥',
  34. age: 18,
  35. love: [ '邓嫂', '成嫂1', '成嫂2', '成嫂3', '成嫂4' ],
  36. address: { country: '中国', province: '黑龙江', city: '哈尔滨' }
  37. }
  38. false
  39. false
  40. */
  • 深度克隆.js
  1. /* 深度克隆
  2. 在不清楚对象结构的情况下,可以使用 clone 函数来实现深度克隆。
  3. */
  4. /**
  5. * 克隆
  6. * @param {any} target 被克隆的目标
  7. * @param {Boolean}} isDeep 是否深度克隆
  8. */
  9. function clone(target, isDeep) {
  10. // 1. 克隆数组
  11. if (Array.isArray(target)) {
  12. if (isDeep) {
  13. let newArr = [];
  14. target.forEach(item => {
  15. newArr.push(clone(item, isDeep));
  16. });
  17. return newArr;
  18. } else { // 浅拷贝一个数组
  19. return [...target];
  20. /* 或者
  21. return target.slice();
  22. target.slice() 等价于 target.slice(0, target.length); 等价于 [ ...target ] */
  23. }
  24. }
  25. // 2. 克隆对象
  26. if (typeof target === 'object') {
  27. let newObj = {};
  28. if (isDeep) {
  29. for (const prop in target) {
  30. newObj[prop] = clone(target[prop], isDeep);
  31. }
  32. } else {
  33. for (const prop in target) {
  34. newObj[prop] = target[prop];
  35. }
  36. /* 或者
  37. newObj = {
  38. ...target
  39. }
  40. */
  41. }
  42. return newObj;
  43. }
  44. // 3. 克隆基本数据类型
  45. return target;
  46. }