都知道JavaScript有原型,但设计模式里的原型模式你会用吗?

1,071 阅读4分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」,欢迎大家来交流。

背景介绍

这是设计模式系列的第四节,学习的是patterns.dev里设计模式中原型模式内容,由于是资料是英文版,所以我的学习笔记就带有翻译的性质,但并不是翻译,记录的是自己的学习过程和理解

第一节:高并发造成的数据统计困难?看我单例模式一招制敌

第二节:JS和迪丽热巴一样有专业替身?没听过的快来补补课...

第三节:还在层层传递props?来学学非常实用的供应商模式吧

写在前面

前端同学都知道原型是JavaScript原生就支持的,可以通过prototypeproto 访问原型。在项目中,通常要创建包含继承关系的对象,现在通常大家都是通过es6的class关键词来创建。大家也或许听过JavaScript的继承是通过原型链的方式实现的,那今天就来深入探究下这种原型模式

function Dog() {}
//or
class Dog{}
// 访问原型
Dog.prototype

const dog = new Dog();
// 访问原型
dog.__proto__

第一种方式是构造函数访问原型的方式;第二种方式是实例对象访问原型的方式。

极简释义

在某一类对象中共享属性或方法

class关键词

下面我们来看一下class的常见用法:

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    return `Woof!`;
  }
}

const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");

请注意,Dog类的constructor里为啥会有一个name属性(this.name)呢?这个类本身还有一个bark属性。其实当使用es6的class,所有的属性都定义在这个类的原型上,比如上面的bark方法自动被添加到原型上。

我们可以在某个类的构造方法和它的实例对象里直接发现prototype的存在:

console.log(Dog.prototype);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()

console.log(dog1.__proto__);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()

其中,某个实例的prototype直接指向对应的构造方法的prototype

当我们访问一个对象不存在的属性时,JS会接着在原型链上检索看是否存在该属性。

原型模式对于使用公共属性是非常有用的。我们可以直接把公共属性添加在原型对象上,这样所有的实例都能访问到,而不需要每次都复制相同的属性;并且即便是在实例对象被创建之后,在原型上添加属性会直接生效。

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    return `Woof!`;
  }
}

const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");

Dog.prototype.play = () => console.log("Playing now!");

dog1.play();

在dog1/2/3实例被初始化之后,给Dog原型上添加play方法,依然可以被dog1/2/3直接调用。

给Dog类的prototype直接添加属性的效果,有点类似JS中的继承的效果。下面我们来新建个SuperDog类,它继承自Dog类,当然它继承了Dog类的所有属性和方法(name/bark),同时给它新加个fly方法,代码如下:

class SuperDog extends Dog {
  constructor(name) {
    super(name);
  }

  fly() {
    return "Flying!";
  }
}

const dog1 = new SuperDog("Daisy");
dog1.bark();
dog1.fly();

大家或许都听说过,JS的继承是通过prototype原型链来实现的。具体怎么实现的呢?其实SuperDog类的原型prototype指向父类Dog的原型prototype,并且dog1实例对象的原型prototype指向SuperDog类的原型prototype,这样就形成了原型链

image.png

这就是为什么取名叫原型链的原因 ——— 会依次递归查找某个属性,直到找到为止。

Object.create 指定原型

Object.create方法用来给某个对象指定原型prototype对象:

const dog = {
  bark() {
    return `Woof!`;
  }
};

const pet1 = Object.create(dog);

pet1.bark(); // Woof!

console.log(Object.keys(pet1)); // []
console.log(Object.keys(pet1.__proto__)); // ["bark"]

从上述代码中我们不难看出:给pet1指定原型对象时,pet1本身是没有任何属性,但是pet1的原型被指定成dog对象;同时Object.create方法可以快速实现从其他对象继承属性和方法 ———— 通过原型链的方式添加新属性和方法。

总结

原型模式可以很方便的让某个对象拥有或者继承其他对象的属性或方法;同时原型链的存在允许调用自己本身没有的属性或方法,这样可以避免重复定义属性和方法,也可以减少内存使用,从而可以优化性能。