首页 > 技术文章 > Javascript 构造函数模式、原型模式

tisikcci 2016-09-06 22:03 原文

前两天写完组合继承,打算总结一下原型继承的,不过今天看了一下工厂模式、构造函数模式和原型模式,觉得有必要总结一下以加深印象。

——————————————————————————————————————————————————————————————————————————————————碎碎念。

1.工厂模式

《Javascript 高级程序设计(第3版)》 用了寥寥十多行介绍了工厂模式。我找了一些相关资料,想确定一下这种模式的具体适用场景和优势。按照资料中的说法,是考虑到 ECMAScript 无法创建类,所以:

创建一个对象,紧接着描述对象的属性和方法,最后用另一个对象把它们封装起来当作接口。

按照上面描述的,工厂函数是用来在 Javascript 中实现类似于 Java 中类的功能。通过调用工厂函数,可以创建多个相似的对象实例。

    简单工厂模式:使用一个类(通常为单体)来生成实例。
    复杂工厂模式:使用子类来决定一个成员变量应该是哪个具体的类的实例。

关于 Javascript 使用工厂模式的优点,根据网上的总结,大概有以下几点:

a. 消除对象之间的耦合(这个不太明白,消除谁与谁的耦合?);

b. 在进行批次相关设置时,把所有实例化的代码都集中在一个位置,有助于创建模块化的代码,减少代码量;

c.  用于许多小型对象组成一个大对象。

缺点:

a. 没有解决对象识别的问题。

 

.....这个模式先留着吧,没太搞透彻优势在哪。以后领悟了的话再来补充。引用《Javascript 设计模式与开发实践》P5中的一段话:

“而在 Javascript 这种类型模糊的语言中,对象多态性是天生的,一个变量既可以指向一个类,又可以随时指向另外一个类。Javascript 不存在类型耦合问题,自然也没有必要刻意把对象“延迟”到子类创建,也就是说,Javascript 实际上是不需要工厂方法模式的。 模式的存在首先是能为我们解决什么问题,这种牵强的模拟只会让人觉得设计模式既难懂又没什么用。”

2. 构造函数模式

关于封装: 在 Javascript 中,可以将一些属性和方法封装到构造函数内;

关于多态:在 Javascript 中不存在重载的概念,但可以通过参数个数和类型判断来模拟重载,但是 Javascript 中多态是与生俱来的。一个最简单的栗子:

同一个构造函数实例化得到的两个对象实例,可以给它们传入不同的参数,这两个对象实例是不同的。这就是面向对象编程的多态。

利用构造函数模式,可以创建多个实例,这些实例都被标定为了特定的类型。构造函数模式相比于工厂模式更为简单且易于实现,但也存在缺点:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.arr = [];
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");

console.log(person1.sayName==person2.sayName);//false
person1.arr.push(1);
console.log(person1.arr);//1

person2.arr.push(2);
console.log(person2.arr);//2

利用构造函数每实例化一个实例对象,构造函数内的方法都要在实例上重新创建一次。通过 person1.sayName==person2.sayName 返回的返回结果可以看到两个实例引用的方法是不同的。

注意:这一点也适用于数组。

可以通过将构造函数的方法放在外部,使得对象实例每次都引用同一个方法。

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName(){
        alert(this.name);
}

var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");

console.log(person1.sayName==person2.sayName);//true

通过将构造函数的方法提到外部作为:特权方法。可以使实例获得相同的方法引用。

但是这种方法也存在一些问题:将方法提到外部作为特权方法使得封装性变差。如果构造函数内部有很多个方法,这样做后果更为明显。

另外再看一个问题:如果构造函数内有多个固定属性,并且存在多个方法。实例化的时候要为每个对象都复制相同的固定属性,如果将方法放在构造函数内部,这些方法也要复制,即使都作为特权函数,也存在弊端。

总结,构造函数模式存在两点不足:1. 浪费内存;2. 可能会使封装性变差。

3.原型模式

原型模式的出现,解决了构造函数实例化过程中对固定属性个方法重复深复制的问题。

对于相同的属性和方法,只要在构造函数的原型对象上申明一次,构造函数的实例就可以共享同一个属性和方法。

function Person(){

}

Person.prototype.name = "Nicholas";
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.sayName===person2.sayName);//true
console.log(person1.name===person2.name);//true

 4.组合使用构造函数模式和原型模式(混合模式)

但是可以看到,虽然原型模式能够解决固定属性和方法多次复制的问题,如果属性要根据不同实例对象改变,这时候最好还是把会随实例对象改变的属性放置在构造函数内部,这又用到了构造函数模式。将构造函数模式和原型模式相结合,就能利用这两个模式的优势。

function Person(name,age){
    //会随实例对象改变的属性
    this.name = name;
    this.age = age;
}
//不变的属性或者是方法
Person.prototype.job = "Soft Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
}

var person1 = new Person("Nicholas",28);
var person2 = new Person("Shelby",25);

 

推荐阅读