在JS中组合使用构造函数模式和原型模式创建对象

在JS中创建对象有很多种方法,而创建自定义类型的最常见的方式,就是使用组合使用构造函数模式和原型模式创建对象。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,那么来看看为什么这种方式是最常用的。
先简单介绍在JS中创建对象的方式有如下几种:

  • 工厂模式
  • 构造函数模式
  • 原型模式
  • 组合使用构造函数模式和原型模式
  • 动态原型模式
  • 寄生构造函数模式
  • 稳妥构造函数模式

依次来看:

  1. 工厂模式
    这种模式就是抽象了创建具体对象的过程,也是最基本的一种设计模式,就像下面这样咯:
function createPerson(name,age,gender){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.gender = gender;
    obj.sayName = function(){
        alert(this.name);
    };
    return obj;
}
//接下来就可以创建对象了
var person = createPerson("Stan",0000,"male");

如果创建多个这种类似的对象,当然很ok啦,但是有更好的模式创建对象。
2. 构造函数模式

function Person(name,age,gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName = function(){
        alert(this.name);
    };
}
//然后可以用new操作符来创建Person的新实例
var person = new Person("Stan",0000,"male");
//最直观的就是代码比工厂模式少吧。
//另外,创建自定义的构造函数意味着将来可以将它的实例标识为一种特定类型,这是构造函数模式胜过工厂模式的地方(努力理解中。)
//也可以像下面这种创建并调用
Person("Stan",0000,"male");
window.sayName();
//或是在另一个对象的作用域中调用
var obj = new Object();
Person.call(obj,"Stan",0000,"male");
obj.sayName();
//这里是在obj对象的作用域中调用Person(),因此调用后obj就拥有了所有属性和sayName()方法

这里说说构造函数模式的问题,定义在构造函数中的方法在每次实例化的时候都会被创建一次,并且每次被创建的方法都是一个新的对象(JS中函数即对象),即创建两个完成同样任务的Function实例是没有必要的,也就是说,如果一个方法可以被共享使用的话,不应该这么做。如果写成下面这样:

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

这样把sayName()定义成全局函数,虽然解决了多个函数做同一样件事情而不用每次创建的问题,但是假如需要N个全局函数,那么我们这个自定义的引用类型就没有丝毫的封装性可言了。所以有更好的原型模式可以解决这个问题

3.原型模式
我们创建的每个函数都 一个prototype属性,这个属性是一个指针,指向一个对象(原型对象),使用原型对象的好处是不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。就像下面这样:

function Person(){}

Person.prototype.name = "Stan";
Person.prototype.age = 0000;
Person.prototype.gender = "male";
Person.prototype.sayName = function(){
    alert(this.name);
}

var person = new Person();
person.sayName();
//原型对象中的所有属性和方法都是可以被实例所共享的

当我们在调用person.sayName()方法时,会先后执行两次搜索,先从对象实例本身开始,如果在实例中找到该方法,则调用 ,若没有找到,会继续搜索指针指向的原型对象,找到则调用方法。有一个问题,如果我们在实例中添加一个属性,而该属性与实例原型中的一个属性同名,该属性将会屏蔽掉原型中的那个属性,就像下面这样:

function Person(){}

Person.prototype.name = "Stan";

var person = new Person();
person.name = "Joe";
alert(person.name);//结果是Joe

即使将这个name属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的连接 。可以使用delete操作符完全的删除实例属性,从而可以重新访问到原型中的属性。像下面这样:

function Person(){}

Person.prototype.name = "Stan";

var person = new Person();
person.name = null;
alert(person.name);//结果是null,而不是Stan

//可以这样做:
delete person.name;
alert(person.name);

另外还可以把原型语法像下面这样写:

function Person(){}

Person.prototype = {
    name : "Stan",
    age : 0000,
    gender : "male",
    sayName : function(){
        alert(this.name);
    }
};

这种写法实际上是重写了原型对象,所以接下来看一个问题,即原型的动态性
所谓的原型的动态性,即随时可以为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,像下面这样:

function Person(){}

var person  = new Person();
Person.prototype.name = "Stan";
alert(person.name);

虽然person实例是在添加新属性之前创建的,但是仍然可以立即在实例中访问到name属性,但是如果全部重写了原型对象,就会出问题了,像下面这样:

function Person(){}

var person  = new Person();
Person.prototype = {
    name : "Stan",
    age : 0000,
    gender : "male",
    sayName : function(){
        alert(this.name);
    }
};
alert(person.name);//undefined

这是为什么呢?因为重写原型切断了现有原型(重写后的原型)与任何之前已经存在的对象实例之间的联系,person引用的仍然是最初的原型,这里person实例最初的原型中除了默认的一些属性外,是没有name属性的,所以就会undefined咯!

原型对象看似还可以,但它也是有问题的,什么问题呢,就是其共享的本性,分析下,原型中所有属性是被很多实例共享的,这种共享对于函数非常合适,对于那些包含基本值的属性也还行,因为我们还可以通过在实例上添加一个同名属性来隐藏掉原型中的对应属性(不会影响到其它的实例的属性),但是如果包含引用类型值的属性来说,问题就来了,看下面:

function Person(){}

Person.prototype = {
    colors : ["red","green","pink"]
};

var person1 = new Person();
person1.colors.push("black");

var person2 = new Person();
alert(person2.colors); // red,green,pink,black

看到问题了吧,大多数时候,实例一般都是要属于自己的全部属性的,即我们不会这么单独使用原型模式,所以这才到今天我们要说的主题:组合使用构造函数模式和原型模式创建对象
怎么组合呢,其实就是用构造函数模式定义实例属性(不会被共享),而用原型模式用于定义方法和共享的属性,另外这种组合模式还支持向构造函数传递参数,像下面这样:

function Person(name,age,gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.colors = ["red","green","pink"];
}
Person.prototype = {
    sayName : function(){
        alert(this.name);
    }
}

var person1 = new Person("Stan",0000,"male");
var person2 = new Person("Joe",1111,"female");

person1.colors.push("black");
alert(person1.colors); // red,green,pink,black
alert(person2.colors); // red,green,pink
alert(person1.sayName == person2.sayName); // true

先说到这里吧。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值