前言
JavaScript的设计模式简单的说,就是早期前端开发人员经过相当长的一段时间的试验和错误总结出来较为通用的在前端开发过程中面临的一般问题的解决方案。它代表了最佳的实践,通常被有经验的面向对象的开发人员所采用。JavaScript是一门面向对象的语言,面向对象的语言都有一个标志就是有类的概念,通过类再来创建任意多个具有相同属性、方法的对象实例。JavaScript 在 ECMAScript 6 之前没有类的概念(ES6添加了类这个概念,使其更为清晰、强大!),这里主要讨论在对象中相关的设计模式。
设计模式类型
工厂模式
工厂模式我们可以联想一下,工厂有一套流水线,你只要给它需求,它可以造出很多相同的物品。那么工厂模式实际上就是把创建对象的过程封装在一个函数里,这个函数在你需要创建对象调用它会返回给你你想要的对象。例子:
function createObj (name,age,id){
var o = new object();
o.name = this.name;
o.age = this.age;
o.id = this.id;
o.theName = function () {
console.log(this.name)
}
return o;
}
var obj1 = createObj("YeeJone",22,1);
var obj2 = createObj("other",24,2);
在代码中可看出,这种创建对象的方法在创建很多相似对象时很便捷,而且代码量相比单个创建要少得多。但这种模式有个很明显的弊端,就是在创建实例对象时不知道自己创建的是什么对象,没有解决对象的识别问题。
构造函数模式
之前只在Java,c++中用过构造函数,同为面向对象语言的JavaScript同样也有构造函数。在平时想Object和Array原生的构造函数我们用的比多,自定义的构造函数基本没用过(惭愧)......。这种方式创建对象和Java类中的构造函数类似,例子:
function Student (name,age,id) {
this.name = name;
this.age = age;
this.id = id;
this.theName = function () {
console.log(this.name);
}
}
var stu1 = new Student("YeeJone",22,12);
var stu2 = new Student("other",24,11);
以这种方式创建对象时函数名首字母要大写。用这种方式创建实例的时候必须使用new操作符,你可能会在想构造函数没有对象,怎么调用后就创建了一个实例,实际上调用构造函数时会经历4个过程:
1.创建一个新对象;(这一步主要由new操作符来实现)
2.将构造函数的作用域赋给新对象,并且this指向这个新对象;
3.执行构造函数中代码,为新对象添加新对象并赋值;
4.返回新对象;
stu1和stu2分别保存了Student的一个不同实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Student。构造函数模式虽然相比工厂模式有了识别度,但它也有自己的缺点——在使用构造函数的时候,每创建一个实例对象的时候都要再重新创建一个构造函数中的方法。如例子中,stu1和stu2的theName方法,这两个theName是不同的Function实例,以下代码可以验证这一点。
console.log(stu1.theName = stu2.theName) //false
所以我们一起来看看原型模式......
原型模式
原型是什么?JavaScript中的原型(prototype)是一个对象,它的作用就是给所有实例共享属性和方法。当我们创建一个函数时都有一个名为prototype(原型)的属性,这个属性是一个指针,指向一个对象(原型对象),这个对象里的属性和方法可以共享给实例。如果你还没理解的话,体会下面的代码:
function Person () {}
//设置函数的原型对象
Person.prototype.name = "YeeJone";
Person.prototype.age = 22;
Person.prototype.theName = function(){
console.log(this.name);
};
//或者也可以用更为简单的coding方法
//Person.prototype ={
//name:"YeeJone",
//age:22,
//theName : function (){
//console.log(this.name)
//}
//}
var per1 = new Person();
var per2 = new Person();
per1.name = "author";
alert(per1.theName);//author
alert(per2.theName);//YeeJone
从代码可以看出per1.name添加了一个name属性赋了一个新值,这个时候注意这个被重新赋值的同名属性会屏蔽掉原型对象中所保存的同属性名的值。也就是说,当实例添加与原型对象同名的属性时,会阻止实例去访问原型对象的属性值,而不会修改原型属性保存的值。使用delete操作符可删除实例中所添加的属性,使其指向原来的原型对象。如:
delete per1.name;
其实原型模式不仅应用于创建自定义对象方面,就连原生的应用类型也同样有用这种模式,像(object,Array,String...)它们的原型对象中同样定义了方法。如:
所以我们就可以为原生引用对象添加自己的方法了不是么!!!!!! 如:
String.prototype.isStart = function(text){
return this.indexOf(text) == 0;
};
var msg = "I am a message";
console.log(msg.isStart("I"));//true
刺激。这么来看原型对象真的完美么?当然不是!有没有考虑过一个问题,如果原型对象中的属性值是一个引用类型的值会怎么样?由于原型对象的共享特性,使得当属性值为引用类型的值得时候,会造成在一个实例改变了属性值,另一个实例中同属性名的值也会改变,这当然不是我们想要的。例子:
function Person () {}
//设置函数的原型对象
Person.prototype = {
name:"YeeJone",
age:22,
teacher:["Jone","Lisa"],
theName : function () {
console.log(this.name)
}
}
var per1 = new Person();
var per2 = new Person();
per1.teacher.push("Tom");
alert(per1.teacher);//["Jone","Lisa","Tom"]
alert(per2.teacher);//["Jone","Lisa","Tom"]
所以我们需要结合前面的构造函数模式的一些优点来弥补原型模式的这个问题。
组合使用构造函数模式和原型模式
直接看例子:
function Person (name,age,teacher) {
this.name = name;
this.age = age;
this.teacher = teacher;
}
Person.prototype = {
constructor:Person,
theName : function () {
alert(this.name);
}
}
var per1 =new Person("YeeJone",22,["lisa","Bob","Kaiss"]);
var per2 = new Person("Tom",45,["jack","Gaga"]);
per1.teacher.push("jession");
alert(per1.teacher);//["lisa","Bob","Kaiss"]
alert(per2.teacher);//["jack","Gaga"]
所以取长补短,这种混合模式也是应用最为广泛的。
结语
除了这几种较为常见的模式外,还有动态原型模式、寄生构造函数模式,稳妥构造函数模式同样也是创建自定义对象类型的设计模式。如果有兴趣可以去看《JavaScript高级程序设计》里面讲的很详细透彻。自己之前看书看到,觉得很有必要了解,所以分享给大家,如文章中有错误的地方,欢迎留言指出....