创建对象
这部分属于JavaScript的面向对象编程,探讨如何更好的创建对象。在java中有类的概念,用new就可以创建对象, 但JavaScript没有类,所以也就无法创建某一类对象,但是可以利用一些技巧达到创建对象的目的。以下所说的各种模式是循序渐进的,如果理解了,记住各种方式也不是难事。
最原始的方式
var person = new Object();
person.name="jun";
person.age=12;
person.sayName=function (){
console.log(this.name);
}
以上是最原始、最简单的创建对象的方法,但是它有一个缺点,就是代码重复,如果再创建一个person,就需要再次敲上述语句,这就是典型的代码重复,那直接的解决方案就是将上述语句放在一个方法中,这就是工厂模式了。
工厂模式
//工厂模式,缺点无法判断类型
function createPerson(name,age){
var a = new Object();
a.name=name;
a.age=age;
a.sayName=function (){
console.log(this.name);
}
return a;
}
var p1 = createPerson("jun",20);
var p2 = createPerson("chun",18);
console.log(p1 instanceof Object);//true
console.log(p2 instanceof Object);//true
工厂模式实现了代码封装,它的缺点是不能判断对象类型,创建的对象都是Object类型,为了解决这个问题,就需要构造函数模式了。
构造函数模式
//构造函数模式
function Person(name,age){
this.name=name;
this.age=age;
this.sayName=function(){
console.log(this.name);
}
}
var p3 = new Person("jack",40);
var p4 = new Person("jordan",50);
console.log(p3 instanceof Person);//true
console.log(p4 instanceof Person);//true
console.log(p3.sayName==p4.sayName);//false
构造函数模式创建的对象可以判断出类型,它的缺点是每个实例都要创建一个新的function。我们都知道在js中函数也是对象,上述代码中this.sayName=function(){}
就等同于this.sayName=new Function("")
所以每个实例都会创建一个新的函数,这就造成了实例之间的函数不相等。为了fix这个问题,自然而然的想到将函数拿到构造函数之外。
function Person(name,age){
this.name=name;
this.age=age;
this.sayName=sayName;
}
function sayName(){
console.log(this.name);
}
var p3 = new Person("jack",40);
var p4 = new Person("jordan",50);
console.log(p3.sayName==p4.sayName);//true
这样函数相等的目的达到了,但是可访问性增大,污染了全局环境,破坏了封装性,为了修正这个问题就想到将函数放在原型上,所有的实例就可以共享了。
原型模式
我们知道函数都有一个prototype属性指向它的原型,所有的实例都可以共享原型的属性、方法。将方法定义在函数的原型上,就能避免函数的重复创建。
function Person(){
}
Person.prototype.sayName=function (){
console.log(this.name);
}
Person.prototype.name="jun";
var p3 = new Person();
var p4 = new Person();
console.log(p3.name);//jun
console.log(p4.name);//jun
console.log(p3.sayName==p4.sayName);//true
函数共享了,但是实例属性也共享了。到这里读者可以思考了,构造函数模式分离了属性、函数,原型模式共享了属性、函数,如果能结合一下构造函数分离属性,原型模式共享函数该多好,这就是原型+构造函数模式。
原型+构造函数混合模式
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function (){
console.log(this.name);
}
var p3 = new Person("jack",40);
var p4 = new Person("jordan",50);
console.log(p3.name);//jack
console.log(p4.name);//jordan
console.log(p3.sayName==p4.sayName);//true
这个模式还是有个缺点!^_^ 函数放在外边破坏了封装性,所以就有了
function Person(name,age){
this.name=name;
this.age=age;
if(typeof this.sayName!="function"){
Person.prototype.sayName=function (){
console.log(this.name);
}
}
}
这种事业界内最推崇、最成熟的创建对象方式了。
寄生构造函数模式
修改原型以扩展方法虽然方便但也带来了缺点,比如String没有startsWith方法,我们会通过扩展原型来实现
String.prototype.startsWith=function(){
//do something
}
但是你有没有想到,未来的js版本可能会实现这个方法,到那时js版本带的方法就跟扩展的方法冲突了,为了解决这个问题,就产生了寄生构造函数模式。
比如我们想创建一个新的Array,它有原生Array没有的方法
function SpecialArray(){
var x = new Array();
x.push.apply(x,arguments);
x.pipedStr=function(){
console.log(x.join("|"));
}
return x;
}
var xx = new SpecialArray("a","b","c");
xx.pipedStr();
我们可以看到寄生构造函数模式跟工厂模式基本一样,为了使用起来像创建新类型一样,用new创建对象。
稳妥构造函数模式
有时我们不像让外界修改对象的值,怎么实现呢?只提供访问的方法,不提供修改的方法就可以了!因为值不能改变,是稳定的、可靠的,所以就叫稳妥方式。
function Person(name){
var x = new Object();
x.sayName=function(){
console.log(name);
}
return x;
}
var y = Person("jun");
y.sayName();
注意,这里并没有使用new。
总结
以上所有的模式除了最后两种,都可以一步步推倒出来,循序渐进的演变过来的,所以,不用死记硬背。
参考
JavaScript高级程序设计(第2版)