你们都知道,JavaScript 中的基础数据类型,好比 number
, boolean
, string
, null
, undefined
这些类型的变量在赋值的时候会分配独立的内存空间。而复合类型,好比Object
,这种类型的变量是引用型的,也就是保存内存的引用地址,可能多个变量指向的是同一个内存地址。这样在修改变量的某个属性时,其余变量的属性也跟着变了。面试
// 值类型
const a = 5;
let b = a;
b = 6;
console.log(b) // 6
console.log(a) // 5
// 引用类型
const person1 = {
name: 'tom',
};
let person2 = person1;
person2.name = 'jerry';
console.log(person1.name); // jerry
复制代码
所以,这种状况在有的时候会形成数据互相影响,致使意外的结果。这就须要用到对象的深拷贝了。深拷贝的方法有多种,面试的时候也常常会被问到。今天就来总结下,都有哪些经常使用的深拷贝实现方式。json
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = {name: original.name, work: {...original.work}};
// 拷贝成功
assert.deepEqual(original, copy);
// 确实是深拷贝
assert.ok(original.work !== copy.work);
复制代码
是一种取巧的方式,可是很是快捷。为了深拷贝一个对象original
,先把它转成JSON字符串,而后再解析这个JSON字符串:数组
function jsonDeepCopy(original) {
return JSON.parse(JSON.stringify(original));
}
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = jsonDeepCopy(original);
assert.deepEqual(original, copy);
复制代码
这种方法的显著缺点是,只能复制JSON格式支持的属性名和值。bash
不支持的属性名和值会直接忽略:微信
assert.deepEqual(
jsonDeepCopy({
[Symbol('a')]: 'abc',
b: function () {},
c: undefined,
}),
{} // empty object
);
复制代码
其余状况会抛出异常:函数
assert.throws(
() => jsonDeepCopy({a: 123n}),
/^TypeError: Do not know how to serialize a BigInt$/);
复制代码
下面是一个通用的深拷贝函数 :ui
function deepCopy(original) {
if (Array.isArray(original)) {
const copy = [];
for (const [index, value] of original.entries()) {
copy[index] = deepCopy(value);
}
return copy;
} else if (typeof original === 'object' && original !== null) {
const copy = {};
for (const [key, value] of Object.entries(original)) {
copy[key] = deepCopy(value);
}
return copy;
} else {
// 基础类型无需拷贝
return original;
}
}
复制代码
这个函数处理了三种状况:this
original
是一个数组,咱们就建立一个新数组,并将original
里的元素深复制进去。original
这一个对象,咱们使用相似的方法。original
是原始类型的值,咱们什么也不用作。咱们尝试调用一下 deepCopy()
:spa
const original = {a: 1, b: {c: 2, d: {e: 3}}};
const copy = deepCopy(original);
// 副本和原始值深度相等吗?
assert.deepEqual(copy, original);
// 是否真的复制了全部层级
// (内容相等,对象不一样)
assert.ok(copy !== original);
assert.ok(copy.b !== original.b);
assert.ok(copy.b.d !== original.b.d);
复制代码
注意,deepCopy()
只解决了展开操做符的一个问题。其余问题仍然存在:原型没有拷贝,特殊对象只有部分被拷贝,不可枚举属性被忽略,大部分属性特被忽略。code
实现通用的完整拷贝几乎是不可能的:并不是全部的数据都是树状的,有时候你不须要复制全部的属性等等。
deepCopy()
若是使用 .map()
和 Object.fromEntries()
,前面实现的deepCopy()
能够更加简洁:
function deepCopy(original) {
if (Array.isArray(original)) {
return original.map(elem => deepCopy(elem));
} else if (typeof original === 'object' && original !== null) {
return Object.fromEntries(
Object.entries(original)
.map(([k, v]) => [k, deepCopy(v)]));
} else {
// 原始类型值无需拷贝
return original;
}
}
复制代码
实现类的实例拷贝,一般会用到两种技术:
.clone()
方法.clone()
方法这种技术为须要实例深拷贝的类引入一个.clone()
方法。它返回this
的一个深拷贝。下面这个例子展现了三个能够复制的类。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
clone() {
return new Point(this.x, this.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
clone() {
return new Color(this.name);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
clone() {
return new ColorPoint(
this.x, this.y, this.color.clone()); // (A)
}
}
复制代码
带注释行 A 展现了这种技术的一个重要方面:复合型的实例属性值必须也要递归地复制。
复制构造器是一种利用当前类的另外一个实例来初始化当前实例的构造器。复制构造器在静态语言中很是流行,好比和C++和Java。你能够经过静态重载来提供多个版本的构造器。静态的意思是它发生在编译时。
在 JavaScript 中,你能够这样作(虽然不太优雅) :
class Point {
constructor(...args) {
if (args[0] instanceof Point) {
// 复制构造器
const [other] = args;
this.x = other.x;
this.y = other.y;
} else {
const [x, y] = args;
this.x = x;
this.y = y;
}
}
}
复制代码
这个类的使用方式以下:
const original = new Point(-1, 4);
const copy = new Point(original);
assert.deepEqual(copy, original);
复制代码
相反,JavaScript 中静态工厂方法更合适。(静态意味着它是类方法)
下面这个例子中,三个类 Point
,Color
和 ColorPoint
各有一个静态工厂方法 .from()
:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static from(other) {
return new Point(other.x, other.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
static from(other) {
return new Color(other.name);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
static from(other) {
return new ColorPoint(
other.x, other.y, Color.from(other.color)); // (A)
}
}
复制代码
带注释行A中,咱们再次用到了递归拷贝。
ColorPoint.from()
的用法以下:
const original = new ColorPoint(-1, 4, new Color('red'));
const copy = ColorPoint.from(original);
assert.deepEqual(copy, original);
复制代码
下次面试再碰到这个问题,不用跟面试官客气!
欢迎关注微信公众号“1024译站”,获取国际最新互联网技术资讯。