Prototype pattern可便于同类型的多个对象共享属性。原型(prototype)是JS原生的对象,其他对象可以通过原型链(prototype chain)来访问原型。单独看这句描述可能还是有点儿抽象,下面通过具体的示例来详细阐述。
class Dog {
constructor(name) {
this.name = name;
}
bark() {
return `Woof!`;
}
}
const dog1 = new Dog("Kadi");
console.log(dog1.__proto__);
console.log(Dog.prototype);
这里可以看到,constructor
有一个name属性;根据ES6类的语法规则,所有在类中定义的属性(本例的属性bark
),都自动加入到类的prototype
中,Dog类本身有两个属性:constructor和bark。
有两种方式可以查看类的原型中的属性,一种是通过类本身的prototype
,另一种是通过实例的__proto__。
从上图的调试信息可以看到,Dog类的prototype
也是一个object,其中有两个属性bark和constructor,另外还有一个原型对象([[Prototype]])。
通常类的实例的__proto__直接引用类的prototype,如果类本身不包含某个属性,JS就会向下搜索原型链,查看在原型链中是否能找到被访问的属性。而在dog1实例中,发现有两个[[prototype]],而且还有包含关系,这就是所谓的原型链。
因为所有实例都可以访问类的原型对象,因此原型模式使得实例在访问相同属性时,不用每次都创建该属性的副本。只需要将属性加入到原型中,则所有的实例都可以访问。另外,在创建实例对象后,也支持添加新的属性到原型中,其他实例对象也可以访问这个新加入的属性。
const dog2 = new Dog("Husky")
Dog.prototype.play = ()=> console.log(`playing`);
dog1.play();
我们再创建一个“Husky”的实例,然后对Dog类的原型添加一个新的属性play,接着通过dog1实例来调用play函数,看能否正常运行。
从运行结果来看,dog1能正常访问play属性。
再举个例子,定义一个SuperDog并继承Dog,SuperDog有一个fly属性。通过创建一个SuperDog的实例dog3,且dog3调用bark属性
class SuperDog extends Dog {
constructor(name) {
super(name);
}
fly() {
console.log("Flying!");
}
}
const dog3 = new SuperDog("Super")
dog3.fly();
dog3.bark();
console.log(dog3.__proto__);
此示例中有3级原型链,实例访问属性的搜索路径也非常清晰。dog3._proto_ -> SuperDog.prototype -> Dog.prototype。
class Dog {
constructor(name) {
this.name = name;
}
bark() {
console.log("Woof!");
}
}
class SuperDog extends Dog {
constructor(name) {
super(name);
}
fly() {
console.log("Flying!");
}
}
const dog1 = new Dog("Kadi");
console.log(dog1.__proto__);
console.log(Dog.prototype);
const dog2 = new Dog("Husky")
Dog.prototype.play = ()=> console.log(`playing`);
dog1.play();
const dog3 = new SuperDog("Super")
dog3.fly();
dog3.bark();
console.log(dog3.__proto__);
debugger