上篇,我们提到了原型模式的缺点,就是每个实例不能拥有自己的属性,因为纯原型模式所有的属性都是公开给每个实例的,故我们可以组合使用构造函数模式和原型模式。构造函数用来定义实例的属性,而原型模式用来定义方法和公用属性。这样的话,每个实例都有自己的属性副本(而不是指向原型的引用),同时它也共享原型上方法的引用。而且,这种混合模式还支持对构造函数传参,所以可以说是结合了两种模式的优点。示例如下:
```javascript function Animal(name,type){ this.name = name; this.type = type; } Animal.prototype = { constructor: Animal, sayName : function(){ console.log("oh ~~~~我的名字叫"+this.name); } }; var tom = new Animal("tom","Cat"); var jerry = new Animal("jerry","Mouse"); console.log(tom.name === jerry.name); //false console.log(tom.sayName === jerry.sayName); //true ```
上面的例子中,实例属性都都是在构造函数中定义的,故每个实例的属性都是私有的副本,不会互相影响。而在原型中定义的方法,每个实例的引用都是指向同一个方法(原型上的)。这种组合模式是在JS中使用最广的一种创建自定义类型的方法,推荐使用。
动态原型模式是用来解决原型和构造函数独立的问题。很多情况下,构造函数和原型都是相互独立的,这有的时候不太美观,而为了解决这么一批对代码有洁癖的程序猿(例如我),在JS中衍生了一种奇葩的解决方案。这种方案就是动态原型模式,他把所有的信息都封装在构造函数中,而通过在构造函数中初始化原型(仅在必要情况下),这样就不仅保留了构造函数模式和原型模式的优点,而且单独的相对独立也得到了解决。
而仅在必要情况下初始化原型,可以通过检查某个应该存在的方法是否有效来判断。代码如下:
```javascript function Animal(name,type){ this.name = name; this.type = type; if(typeof this.sayName != "function"){ Animal.prototype.sayName = function(){ console.log("oh~~~my name is "+this.name); } } } var tom = new Animal("tom","Cat"); var jerry = new Animal("jerry","Mouse"); console.log(tom.name === jerry.name); //false console.log(tom.sayName === jerry.sayName); //true ```
以上代码会检查原型是否初始化完成,如果没有就对原型进行初始化。要注意的是使用动态原型模式时,不能以字面量的形式重写原型对象,因为这样就切断了已有实例和新原型的关系。不过虽然这种方法很好用,但是我觉得这种方式(以if判断)容易造成代码的混淆,而且把原型init放在构造函数中也不是一个很好的方法,所以最后我还是选择了独立原型和构造函数,只不过我对原型和构造函数上面再做了一层封装。(个人喜好,要喷请轻点。。。)
通过这三篇(这一篇算是收尾),对原型模式做了大体的归纳,由于经验和阅历的关系,没有办法面面俱到。文章内容参考了高程,设计模式,编程模式等,在写的过程中,也对自己的知识体系进行了一遍梳理,收获很大。
如果你在文中发现有什么错误或则疑惑,我们可以共同讨论。群 239147101(N多JS大神)。