JS几种常用对象设计模式
2017-07-10 14:53
369 查看
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
很明显,当一个对象对原型对象的引用类型值做出修改时,其他的实例对象也会随之改变。解决这个问题的方法是组合使用构造函数模式和原型模式(大家可以结合上面的模式来进行),具体实现方法是将属性使用构造函数模式,将方法使用原型模式。
写得可能有点乱,不过主要是理解了,当然可能还有一些小的细节理解不透彻,希望大家能指点,一起交流,一起学习!
关于对象的创建在这里就不多说了,就说说几种常用的对象设计模式。
关于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创建对象的几种常用方式小结(推荐)
- js创建对象的几种常用方式
- js的面向对象和设计模式
- 常用的几种设计模式
- js创建对象的几种常用方式
- js创建对象的几种常用方式小结(推荐)
- 由浅入深,带你玩转几种常用java设计模式
- Java中常用的几种设计模式
- 几种常用的设计模式介绍
- 面试-几种常用的设计模式
- 几种常用的设计模式
- 韩顺平_轻松搞定网页设计(html+css+javascript)_第30讲_类和对象细节_创建对象的几种方式_js对象内存分析_学习笔记_源代码图解_PPT文档整理
- javascript学习笔记(九) js对象 设计模式
- J2EE中的几种常用设计模式
- Objective-C的几种常用设计模式的总结
- Symbian常用设计模式之可伸缩对象工厂
- Symbian常用设计模式之可伸缩对象工厂
- javascript学习(十五):js中对象的常用的几种创建方式
- 常用的几种设计模式解析
- 几种常用的设计模式介绍