变量拷贝

Copy Composite Data Types: 复合类型的拷贝

Shallow Copy: 浅拷贝

顶层属性遍历

浅拷贝是指复制对象的时候,指对第一层键值对进行独立的复制。一个简单的实现如下:

// 浅拷贝实现
function shadowCopy(target, source) {
  if (!source || typeof source !== "object") {
    return;
  } // 这个方法有点小trick,target一定得事先定义好,不然就不能改变实参了。// 具体原因解释可以看参考资料中 JS是值传递还是引用传递
  if (!target || typeof target !== "object") {
    return;
  } // 这边最好区别一下对象和数组的复制
  for (const key in source) {
    if (source.hasOwnProperty(key)) {
      target[key] = source[key];
    }
  }
}

//测试例子
const arr = [1, 2, 3];
const arr2 = [];
shadowCopy(arr2, arr);
console.log(arr2);
//[1,2,3]

const today = {
  weather: "Sunny",
  date: {
    week: "Wed",
  },
};

const tomorrow = {};
shadowCopy(tomorrow, today);
console.log(tomorrow);
// Object {weather: "Sunny", date: Object}

Object.assign

不过需要注意的是,assign 是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:

const defaultOpt = {
    title: {
        text: 'hello world',
        subtext: 'It\'s my world.'
    }
};

const opt = Object.assign({}, defaultOpt, {
    title: {
        subtext: 'Yes, your world.'
    }
});

console.log(opt);

// 预期结果
{
    title: {
        text: 'hello world',
        subtext: 'Yes, your world.'
    }
}
// 实际结果
{
    title: {
        subtext: 'Yes, your world.'
    }
}

上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实 assign 并没有解决对象的引用混乱问题,参考下下面这个例子:

const defaultOpt = {
    title: {
        text: 'hello world',
        subtext: 'It\'s my world.'
    }
};

const opt1 = Object.assign({}, defaultOpt);
const opt2 = Object.assign({}, defaultOpt);
opt2.title.subtext = 'Yes, your world.';

console.log('opt1:');
console.log(opt1);
console.log('opt2:');
console.log(opt2);

// 结果
opt1:
{
    title: {
        text: 'hello world',
        subtext: 'Yes, your world.'
    }
}
opt2:
{
    title: {
        text: 'hello world',
        subtext: 'Yes, your world.'
    }
}

使用 [].concat 来复制数组

同样类似于对于对象的复制,我们建议使用[].concat来进行数组的深复制

const list = [1, 2, 3];
const changedList = [].concat(list);
changedList[1] = 2;
list === changedList; // false

同样的,concat方法也只能保证一层深复制

> list = [[1,2,3]]
[ [ 1, 2, 3 ] ]
> new_list = [].concat(list)
[ [ 1, 2, 3 ] ]
> new_list[0][0] = 4
4
> list
[ [ 4, 2, 3 ] ]

浅拷贝的缺陷

不过需要注意的是,assign 是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:

const defaultOpt = {
  title: {
  text: 'hello world',
  subtext: 'It\'s my world.'
  }
};


const opt = Object.assign({}, defaultOpt, {
  title: {
  subtext: 'Yes, your world.'
  }
});


console.log(opt);


// 预期结果
{
  title: {
  text: 'hello world',
  subtext: 'Yes, your world.'
  }
}
// 实际结果
{
  title: {
  subtext: 'Yes, your world.'
  }
}

上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实 assign 并没有解决对象的引用混乱问题,参考下下面这个例子:

const defaultOpt = {
  title: {
    text: "hello world",
    subtext: "It's my world.",
  },
};

const opt1 = Object.assign({}, defaultOpt);
const opt2 = Object.assign({}, defaultOpt);
opt2.title.subtext = "Yes, your world.";

console.log("opt1:");
console.log(opt1);
console.log("opt2:");
console.log(opt2);

// 结果
// opt1:
// {
// title: {
// text: 'hello world',
// subtext: 'Yes, your world.'
// }
// }
// opt2:
// {
// title: {
// text: 'hello world',
// subtext: 'Yes, your world.'
// }
// }

DeepCopy: 深拷贝

递归属性遍历

一般来说,在 JavaScript 中考虑复合类型的深层复制的时候,往往就是指对于 Date、Object 与 Array 这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是 JavaScript 中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty方法来过滤掉那些继承自原型链上的属性:

function clone(obj) {
  const copy; // Handle the 3 simple types, and null or undefined

  if (null == obj || 'object' != typeof obj) return obj; // Handle Date

  if (obj instanceof Date) {
    copy = new Date();
    copy.setTime(obj.getTime());
    return copy;
  } // Handle Array

  if (obj instanceof Array) {
    copy = [];
    for (const i = 0, len = obj.length; i < len; i++) {
      copy[i] = clone(obj[i]);
    }
    return copy;
  } // Handle Object

  if (obj instanceof Object) {
    copy = {};
    for (const attr in obj) {
      if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
    }
    return copy;
  }

  throw new Error("Unable to copy obj! Its type isn't supported.");
}

调用如下:

// This would be cloneable:
const tree = {
  left: { left: null, right: null, data: 3 },
  right: null,
  data: 8,
};

// This would kind-of work, but you would get 2 copies of the
// inner node instead of 2 references to the same copy
const directedAcylicGraph = {
  left: { left: null, right: null, data: 3 },
  data: 8,
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
const cylicGraph = {
  left: { left: null, right: null, data: 3 },
  data: 8,
};
cylicGraph["right"] = cylicGraph;

利用 JSON 深拷贝

JSON.parse(JSON.stringify(obj));

对于一般的需求是可以满足的,但是它有缺点。下例中,可以看到 JSON 复制会忽略掉值为 undefined 以及函数表达式。

const obj = {
  a: 1,
  b: 2,
  c: undefined,
  sum: function () {
    return a + b;
  },
};

const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
//Object {a: 1, b: 2}
上一页
下一页