工厂模式
由于ECMAScript中无法像java那样创建类,所以就有了用函数来封装特定接口来完成创建相同的对象。下面是代码实例:function CreatePerson(name,age,sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var person1 = new CreatePerson("zhangshan",'28','男');
var person2 = new CreatePerson("lisi",'27','女');
console.log(person1.name); // zahngshan
console.log(person1.age); // 28
console.log(person1.sex); // 男
console.log(person1.sayName()); // zhangshan
console.log(person2.name); // lisi
console.log(person2.age); // 27
console.log(person2.sex); // 女
console.log(person2.sayName()); // lisi
// 返回都是object 无法识别对象的类型 不知道他们是哪个对象的实列(缺点)
console.log(typeof person1); // object
console.log(typeof person2); // object
console.log(person1 instanceof Object); // true
函数CreatePerson()能够根据接收的参数来构建一个含有name、age、sex属性及sayName()方法的对象。
这样解决了创建多个相似对象的问题,但从上述代码中可以发现无法识别对象的问题有产生了。随着javascript的发展,一个新的模式出现了。
构造函数模式
除了ECMAScript中的原生构造函数(像Object和Array这样)。也可以创建自定义的构造函数,来达到创建对象的目的。下面是构造函数模式代码:function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("zhangshan",29,"student");
var person2 = new Person("lisi",27,"Doctor");
相比于工厂模式,这里的构造函数模式存在以下几点不同:
(1)没有显示的创建对象;
(2)直接将属性和方法赋给了this对象;
(3)没有return语句。
下面来看一下构造函数是否解决了识别对象的问题:
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Person); //true
构造函数模式的缺点: 使用构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,person1和person2都有一个sayName()方法,但它们却不是同一个Function的实例。也就是说: 每定义一个函数,就实例化了一个对象。 然而,创建两个完全同样任务的Function实例的确没有必要。所以就有了下面这种把函数定义转移到构造函数外的方法:
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("zhangshan",29,"student");
var person2 = new Person("lisi",27,"Doctor");
人类的需求是永无止尽的, 这样做又带来了新的问题:如果对象需要定义很多方法,那么就要定义很多全局函数。这样一来这个自定义的引用类型就毫无封装性可言了。 于是又有了原型模式。
原型模式
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。简单来说:prototype就是通过调用构造函数而创建的那个对象治理的原型对象。它的好处就是可以让所有对象实例共享它所包含的属性和方法。下面来看一下原型模式的代码:function Person() {
}
Person.prototype.name = "zhangshan";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName(); //zhangshan
var person2 = new Person();
person2.sayName(); //zhangshan
无论什么时候,只要创建了一个新函数,ECMAScript就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。就拿前面的例子,Person.prototype.constructor指向Person。而通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法。
创建了自定义指针之后,其原型对象默认只会取得constructor属性;至于其它方法,都会从Object对象继承而来。当调用构造函数创建一个新实例之后,该实例的内部将包括一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个叫[[Prototype]]。关于原型的理解我提供了一个链接方便大家去查看,点击“这里”查看。https://www.cnblogs.com/zxj159/archive/2013/05/20/3089513.html
原型的问题也很明显。由于它省略了为构造函数传递初始化参数这一环节,结果所有的实例在默认情况下都取得了相同的值。原型模式最大的问题是由其共享的本性所导致的。简单点说就是:当一个对象修改了原型的内容,另一个对象也会相应的发生变化。
构造函数模式和原型模式组合
构造函数模式的弊端是方法实例化无法共享,而原型模式的弊端是过于共享。结合这两种模式就可以实现:每个实例都会有自己的一份实例属性的副本,同时共享着对方法的引用,最大限度节省内存。同时,这种混杂模式还支持向构造函数传递参数。下面来看代码:function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["Machiel","Dophe"];
};
Person.prototype={
constructor:"Person",
sayName:function(){
alert(this.name)
}
};
var person1=new Person("Niche",12,"Software");
var person2=new Person("Greg",27,"Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Machiel","Dophe","Van"
alert(person2.friends); //"Machiel","Dophe"
动态原型模式
利用原型的共享特性,其实Person实例的方法只需要定义一次就可以了。下面来看动态原型模式代码:function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
Person是一个构造函数,通过new Person(...)来生成实例对象。每当一个Person的对象生成时,Person内部的代码都会被调用一次。
如果去掉if的话,你每new一次(即每当一个实例对象生产时),都会重新定义一个新的函数,然后挂到Person.prototype.sayName属性上。而实际上,你只需要定义一次就够了,因为所有实例都会共享此属性的。所以如果去掉if的话,会造成没必要的时间和空间浪费;而加上if后,只在new第一个实例时才会定义sayName方法,之后就不会了。
至于第二个问题,是这样的:假设除了sayName方法外,你还定义了很多其他方法,比如sayAge、sayJob等等。此时你只需要把它们都放到对sayName判断的if块里面就可以了。
这样一来,要么它们全都还没有定义(new第一个实例时),要么已经全都定义了(new其他实例后),即它们的存在性是一致的,用同一个判断就可以了,而不需要分别对它们进行判断。
寄生构造函数模式
这个模式除了使用new操作符并把使用的包装函数叫做构造函数之外,它跟工厂模式是一模一样的。下面是寄生虫模式代码:function Person(name,age,job){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function(){
alert(this.name);
}
return obj;
}
var friend = new Person("zhangshan",27,"Software");
这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式。
function SpecialArray(){
var obj = new Array();
obj.push.apply(obj,arguments);
obj.toPipedString = function(){
return this.join("|");
};
return obj;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.toPipedString()); //"red|blue|green"
关于寄生虫模式如果我们像构造函数那样书写代码,来看一下会是什么情况:
function SpecialArray(){
this = new Array();
this.push.apply(this, arguments);
this.toPipedString = function(){
return this.join("|");
};
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString());//报错
这里报错的原因就出在this身上。this是只读保留字,进而我们不能去修改this。
寄生虫模式的问题:这个模式下返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。
稳妥构造函数模式
这个模式和工厂模式也很相似,但是它的属性访问只能通过提供的指定方法访问。并且创建之后也没有办法把需要添加的数据成员传入到构造函数中的原始数据。这就使它拥有了足够的安全性。下面是稳妥构造函数模式的代码:function Person(name, age, job) {
var o = new Object();
o.sayName = function() {
alert(name);
};
return o;
}
var person = Person("Nicholas", 32, "software Engineer");
person.sayName(); // "Nicholas"
alert(person.name); // undefined
通过上述实例可以发现,这里只能使用sayName来访问name属性。那么再通过代码来看一下修改:
function person(name,age,sex){
var o=new Object();
o.sayName=function(){
alert(name);
}
return o;
}
var person=person("zhangsan",29,"M");
person.sayName(); //zhangsan
person.name="lisi";
person.sayName(); //zhangsan
alert(person.name); //lisi
上面修改了person.name的属性,但是并没有能够把值传入到构造函数的原始数据中。所以这样只是单纯的给person.name属性附上了一个新值。