在JavaScript创建对象(一)—— 工厂模式中留下了一个问题,就是创建一个对象怎么判断一个对象的类型。换句话说使用下面这种方式:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
只是函数的名字叫createPerson
,其实返回的对象的本质还是一个Object。现在只能你说他是人他就是人,你说他是狗他就是狗,有没有一种方法比如类似下面这样:
var p1 = createPerson('zhangsan', 18, 'JavaScript');
if(p1 == Person){
alert('p1是人');
}
if(p1 == Dog){
alert('p1是狗');
}
在程序上有一种判断逻辑可以区分对象的类型呢?答案是有的,这就是本篇文章要讨论的构造函数模式。
现在使用构造函数模式改写上面的工厂模式:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var p1 = new Person('zhangsan', 18, 'JavaScript');
var p2 = new Person('lisi', 20, 'Java');
我们比较构造函数模式和工厂模式发现构造函数模式有以下不同:
- 没有显式的创建对象,如工厂模式 var o = new Object();
- 直接将属性和方法赋给了this,而不是o
- 没有return语句
- 创建对象使用new关键字,而不是直接调用函数
现在我们可以来讨论如何解决判断对象类型的问题了,很简单,可以使用如下代码:
alert(p1.constructor == Person); //true
alert(p2.constructor == Person); //true
同理,判断一个对象是否是狗,也可以这样:
function Dog(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var d1 = new Dog('xiaohei', 2, 'kanjia');
var d2 = new Dog('xiaohua', 3, 'huyuan');
alert(d1.constructor == Dog); //true
alert(d2.constructor == Dog); //true
这样就可以用代码在逻辑上判断一个对象的类型了。
检测对象的类型除了可以使用对象的constructor
属性还可以使用instanceof
关键字。JavaScript和Java一样,所有对象均继承自Object
,使用instanceof
可以检测到继承链,但是constructor
不行,所以从这个角度来说instanceof
更可靠一些,因为一个对象是子类型,当然也是父类型。看下面代码:
alert(p1.constructor == Object); //false
alert(p1.constructor == Person); //true
alert(p1 instanceof Object); //true
alert(p1 instanceof Person); //true
至此构造函数函数不止具有了工厂模式的优点,还解决了工厂模式的缺点,那么构造函数模式有没有缺点呢?当然也是有的。
以上述p1
、p2
为例,这两个对象都具有sayName
函数,而且功能也一样。在构造函数中sayName
是一个指针,function(){alert(this.name)}
是一个匿名函数,我们知道js中函数也是对象,每次创建对象都要执行构造函数,那么也就会创建一个匿名函数对象,若是创建若干个对象,那么就要创建若干个匿名函数对象,功能都相同,没必要不说,还占用内存。
通过以下代码可以说明p1
的sayName
和p2
的sayName
确实是两个实例。
alert(p1.sayName == p2.sayName); //false
那么我们可以尝试像下面这样解决这个问题,把函数定义到构造函数之外:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var p1 = new Person('zhangsan', 18, 'JavaScript');
var p2 = new Person('lisi', 20, 'Java');
alert(p1.sayName == p2.sayName); //true
这样虽然解决了多个函数做同一件事的问题,但是又带来了新的问题:定义在全局作用域中的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。比如这里的sayName()
,实际上是为Person
准备的,但因为是定义在全局作用域所以可以直接调用sayName()
,也就是window.sayName()
, 那么此时的this.name
就是window.name
了,违背了定义的初衷。另一个问题是:如果Person
上需要定义很多方法,那么就要定义很多个全局函数,若再加上Dog
等等其他对象的方法,这些方法全都定义为全局函数,相互揉杂在一起,那么我们自定义的引用类型就丝毫没有封装性可言了。
那么,构造函数模式多个函数做同一件事的问题到底怎么解决呢?请看下一篇——原型模式。
文章参考:《JavaScript高级程序设计》第3版