木灵鱼儿

木灵鱼儿

阅读:544

最后更新:2022/05/29/ 22:50:07

创建型模式:原型模式

简介

原型模式指的是:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或者相似的新对象。

这个原型不同于js中的prototype,它指的是被克隆的对象。

在java中克隆可以通过Object父类中的clone方法实现,但是在前端中,是没有这种方式的,我们需要通过其他方法实现克隆。

克隆又分浅克隆和深克隆,因为引用类型的存在,浅克隆对引用类型只会克隆该类型的内存地址,而深克隆则会完全复制一份引用类型对象,达到完全克隆。

原型模式也是用于创建一个对象,而这个对象可能非常复杂,为了减少消耗的资源,更高效的生成对象。

反例

假设我们现在有这么一段代码:

const run = {
  a: 1,
  b: 2,
};

class A {
  name: string;
  age: number;
  run: object;

  constructor(name: string, age: number, run: object) {
    this.name = name;
    this.age = age;
    this.run = run;
  }

  getName() {
    return this.name;
  }

  getAge() {
    return this.age;
  }

  getRun() {
    return this.run;
  }
}

const a = new A("小明", 16, run);

当我们希望能有一个实例a的克隆对象,用于做一些其他操作,而不影响原对象时,可能常常是这么去做:

const a = new A("小明", 16, run);

const b = new A("小明", 16, run);

重新new一个新的对象,但是这就会有一些问题:

  1. 我没有准确的拿到a的实例属性,它有可能在实例后,被其他人做了特殊操作,改动了属性,然而new是无法感知到的。
  2. 如果这个类操作非常复杂,重复的new会产生很大的性能消耗。

为了准确拿到a的实例属性,我们可以这么写:

const b = new A(a.getName(), a.getAge(), a.getRun());

这么写虽然可以拿到准确的属性,但是不太现实,第一代码量非常之大,第二a实例也不一定会有对应的get方法获取。

为了解决这些问题,我们可以通过原型模式,进行克隆拷贝。

浅克隆

我们将a这个实例,作为我们的原型进行浅克隆。

//方法1
const b = Object.create(a, {});

//方法2
const c = Object.assign({}, a);

//方法3
const d = { ...a };

浅克隆的对象,它的属性如果是引用类型,那么其实都是同一个对象的引用,通过c.run.a = 3的改动,会影响到所有的浅克隆对象的run属性。

这显然不是我们期待的,我们可能希望克隆的对象是一个完全独立,但是属性相似的对象。

深度克隆

实现深度克隆,最简单的就是JSON的序列化。

const b = JSON.parse(JSON.stringify(a));

a实例里面如果有RegExp、Error对象,那么序列化后你将得到一个空的对象。

如果有function、undefined对象,那么序列化后得到的是一个null

如果有NaN、Infinity、-Infinity、null,那么序列化后得到也都是一个null

看这个例子:

const a = {
  a: 1,
  b: new RegExp("23", "ig"),
  c: new Error("2131"),
  d: function () {},
  e: undefined,
  f: NaN,
  g: Infinity,
  o: -Infinity,
  p: null,
};

const b = JSON.stringify(a);
//得到: '{"a":1,"b":{},"c":{},"f":null,"g":null,"o":null,"p":null}'

所以需要注意下序列化并不是万能的。

为此就出现了手写深度克隆的方式:

function deepClone(obj) {
    // 先把特殊情况全部过滤掉 null undefined date reg
    if (obj == null) return obj;  // null 和 undefined 都不用处理
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj;  // 普通常量直接返回

    // 不直接创建空对象的目的:克隆的结果和之前保持相同的所属类,
    // 同时也兼容了数组的情况
    let newObj = new obj.constructor;
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {  // 不拷贝原型链上的属性
            newObj[key] = deepClone(obj[key]);  // 递归赋值
        }
    }
    return newObj;
}

更多深度克隆的内容可以查看该文章:《Javascript经典面试之深拷贝VS浅拷贝》

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 544

相关文章