JS几种常用对象设计模式

ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值,对象或者函数”。

关于对象的创建在这里就不多说了,就说说几种常用的对象设计模式。

关于JavaScript的几种常用对象设计模式:

1.工厂模式

function factory(arg1, arg2){
var  obj = new Object();
obj.name = arg1;
obj.age = arg2;
obj.say_name = function(){

alert(obj.name);

     };

return obj;
}

以上就是工厂模式的封装函数,它是一个函数,然后在函数里面创建了一个obj对象,然后把参数赋值给该对象的name和age属性(当然,属性是按自己需求来设计的),此外,还有一个say_name方法,这个方法是弹出一个显示obj中name属性的警告框。

可以看出,factory是一个函数,我们知道,函数是可以无数次调用的,因此,有许多类似的对象要创建的时候,减少了大量的代码,只需将对应的属性值作为参数去调用该函数即可,这是工厂模式的方便之处。

var obj1 = factory("perf", 21);

alert(obj1.name);  // perf

alert(obj1.age);  // 21

使用这个函数是没有问题的,访问实例obj1中的属性也是会正常显示,我们再看下面的代码:

alert(typeof obj1); // object (测试变量类型)

alert(obj1 instanceof Object); // true (测试变量是否是一个对象的实例)

可以看出,obj1是一个对象,并且是Object的一个实例,但是,具体是哪一个对象的实例呢?我们并不能够知道,所以工厂模式最大的缺点就是对象识别的问题。

2.构造函数模式

function My_obj(arg1, arg2){
this.name = arg1;
this.age = arg2;
this.say_name = function(){

alert(this.name);

     };

}

构造函数模式,相比于工厂模式,看起来要简洁一些,函数内部并没有创建一个对象,而是直接把属性和方法全部放在this对象中,也没有使用return语句。

var obj2 = new My_obj("perf", 21);
alert(obj2.name); // perf
alert(obj2.age); // 21

因为是构造函数,所以我们调用的时候要在函数名前面+new关键字,才能引用到函数里边的this执行环境。

虽然是构造函数,也可以当普通函数一样调用,只要调用的时候不加上new关键字,就跟普通函数没区别了。

My_obj("perf", 21);

这样调用的话,name和age都会被添加到全局环境中,也就是用window.name会访问到perf(但这并不是我们想要的)。

回到正题,obj2是My_obj的一个实例,它拥有一个constructor属性,这个属性是每个构造函数的实例都有的,它指向My_obj。

alert(obj2.constructor == My_obj); // true

再看看对象识别的问题

alert(obj2 instanceof Object);  // true

alert(obj2 instanceof My_obj); // true

obj2是Object的实例,也是My_obj对象的实例,工厂模式就不能知道具体是哪个对象的实例。当然,构造函数模式也并不是没有缺点,缺点就是每个方法都要在实例上重新创建一次,创建两个相同的函数实在没有必要,况且有this对象:

var obj3 = new My_obj("per", 21); // obj3又创建了一次function

alert(obj2.say_name == obj3.say_name); // false

// 我们可以把代码修改为:

function My_obj(arg1, arg2){
this.name = arg1;
this.age = arg2;
this.say_name = say_name_win;
}

function say_name_win(){ // 此命名为了与构造函数中的say_name区分开
alert(this.name);
}

var obj2 = new My_obj("perf", 21);
var obj3 = new My_obj("per", 21);

我们把构造函数中的say_name方法设置等于全局环境的say_name_win函数,由于构造函数中的say_name包含的是一个指向函数的指针,所以obj2和obj3就共享了say_name_win这个函数,解决了每个方法都要在实例上重新创建一次的问题,但是新问题出现了,

say_name_win这个全局函数只能被某个对象去调用,这样全局函数的意义就不大了,而且对象如果需要定义很多方法的话,就要在全局环境下定义一样多的函数,那我们这个封装的构造函数模式就一点意义也没有了。

3.原型模式

针对构造函数模式出现的问题,可以用原型模式来解决,我们创建的每个函数都有一个prototype(原型)属性,它是调用构造函数而创建的那个对象实例的原型对象,而每个原型对象都会有一个constructor属性,该属性指向函数本身。使用原型模式的好 处是:可以让所有对象实例共享原型对象所包含的属性和方法。

function My_proto(){
}

My_proto.prototype.name = "default_name";
My_proto.prototype.age = "default_age";
My_proto.prototype.say_name = function(){
alert(this.name);
};

var obj4 = new My_proto();
obj4.say_name(); // default_name

构造函数My_proto是一个空函数,我们把属性还有方法都添加到了My_proto的prototype中,但是构造函数仍然可以调用,而且新对象会拥有相同的属性和方法,实例对象的属性和方法都是由My_proto.prototype原型对象共享的。

我们可以使用isPrototypeOf()方法来确定对象之间的关系:

alert(My_proto.prototype.isPrototypeOf(obj4));  // true (obj4是My_proto的一个实例)

我们可以通过实例对象重写原型对象中属性或者方法:

alert(obj4.name); // default_name (原型对象中的name属性)
obj4.name = "perf";
alert(obj4.name); //  perf (实例对象中的name属性)

我们为obj4重写了原型对象中的name属性,所以下面访问obj4.name的时候输出的就是perf,在实例对象中覆盖了原型对象中的属性后,即使把实例对象中的属性设置为null,访问到的也是实例对象中该属性的值,就返回null;不过我们可以用delete操作符来删除实例对象中的属性,这样又可以访问原型对象中的值了:


delete obj4.name;
alert(obj4.name); // default_name

当实例对象和原型对象的同一个属性名的值都有设置,这时候我们可以使用hasOwnProperty()方法来检测:

alert(obj4.hasOwnProperty("name")); // false
obj4.name = "per";
alert(obj4.hasOwnProperty("name")); // true
delete obj4.name;
alert(obj4.hasOwnProperty("name")); // false

可以很清楚的看出,当obj4没有设置name属性的值,调用hasOwnProperty()会返回false(此时name属性是存在原型对象中),给obj4设置了name属性后,hasOwnProperty()方法返回true,再用delete操作符删除obj4的name属性后,hasOwnProperty()又会返回false

in操作符: 不论属性是存在于原型对象中还是存在于实例对象中,使用in操作符都会返回true;

alert("name" in obj4); // true
obj.name = "per";
alert("name" in obj4); // true
delete obj4.name;
alert("name" in obj4); // true

可以用字面量的方式写出原型对象:

function My_proto(){
}

My_proto.prototype = {
name: "default_name",
age: "default_age",
say_name: function(){
alert(this.name);
};
}

我们说过,创建每个函数的时候都会拥有一个prototype属性,而该属性指向一个原型对象,原型对象拥有一个constructor属性,constructor属性指向函数本身,所以,用这个方式相当于整个原型对象都被重写了,因此就会导致constructor属性的值受到影响

它不再指向函数本身了,不过可以在字面量添加一个constructor属性,该属性的值设置为函数名就可以了。

原型对象的问题出现在当属性包含引用类型的时候,因为原型对象的属性和方法是共享的,当原型对象中有引用类型的值,实例对象1改变该值,会导致实例对象2也随着改变;

function My_proto(){
}

My_proto.prototype.name = "default_name";
My_proto.prototype.age = "default_age";
My_proto.prototype.friends = ["default_friend"];
My_proto.prototype.say_name = function(){
alert(this.name);
};

var obj4 = new My_proto();
var obj5 = new My_proto();
alert(obj4.friends); // default_friend
alert(obj5.friends); // default_friend
obj4.friends.push("fander");
alert(obj4.friends); // default_friend, fander
alert(obj5.friends); // default_friend, fander

很明显,当一个对象对原型对象的引用类型值做出修改时,其他的实例对象也会随之改变。解决这个问题的方法是组合使用构造函数模式和原型模式(大家可以结合上面的模式来进行),具体实现方法是将属性使用构造函数模式,将方法使用原型模式。

写得可能有点乱,不过主要是理解了,当然可能还有一些小的细节理解不透彻,希望大家能指点,一起交流,一起学习!

你可能感兴趣的:(JS几种常用对象设计模式)