JavaScript中没有类的概念,这给OO
的编程带来了一些困难,但并不是不能解决的,使用以下几种的设计模式可以解决JavaScript对象的创建问题。
工厂模式
工厂模式是创建对象的一种抽象方式。
但由于JavaScript不支持类,可以用函数代替,这个函数封装了特定接口创建对象的细节。
function createPerson(name, age, job){
var i=0;
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
return name;
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
这样可以快速地创建相似对象,但是用这种方法创建的所有对象都是Object
类,也就是你无法分辨它们的类型。
function createDog(name, age, owner){
var o = new Object();
o.name = name;
o.age = age;
o.owner = owner;
o.sayName = function () {
return name;
};
return o;
}
var dog = createDog("hali", 2, "Nicholas");
console.log(typeof dog == typeof person1); //true
我觉得你一定不希望狗和你属于同一个类型。
构造函数模式
构造函数是很多拥有class
的编程语言的做法。
我们希望,使用JavaScript也能实现构造函数。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
console.log(typeof person1); //Person
现在,你创建的类终于有了和函数同名的类型了。
这个函数没有返回值,并且和很多编程语言一样,使用了new
来创建对象。
任何函数,只要使用了new
来创建,那么就是构造函数,构造函数本质上也是函数。
但依然有一个问题,同一个类之间无法实现代码的共用。
比如sayName
需要被创建两次,第一次在创建person1
时,第二次在创建person2
时。浪费了很多空间。
如何实现公用呢?
原型模式
原型(prototype)是JavaScript很重要的一个概念。
原型模式使得对象实例可以共享属性和方法。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
console.log(person1.sayName() == person2.sayName()); //true
对象的原型是一个什么概念呢?
Person
有一个Prototype
,指向Person
的原型,原型是另外一个对象。
当JavaScript需要访问属性时,首先在Person
找,如果没找到就回去Prototype
指向的地方找。
原型中还会存储构造器constructor,指向Person,这幅图里没有明示出来,你也需要知道。
console.log(Person.prototype.constructor) //Person
但是这种方式无法让Person
拥有独立的变量,也就是说,person1
和person2
都在使用同一个Prototype指向的对象,那么当person1
试图改变name的时候,person2
的name也会变。
一种解决方法是屏蔽。
person1.name = "Greg";
console.log(person1.name); //Greg
只要为person1
重新定义name
属性,就能屏蔽原型的属性,因为之前说过,搜索变量是从构造函数开始的,如果在构造函数中找到了变量也就不会去原型中搜索。
另外,我们还能用对象字面量来简写原型的定义。
比如上面的可以这样写:
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
console.log(this.name);
}
};
构造函数和原型混成模式
既然如此,干脆把一些属性在构造函数中定义,一些在原型中定义好了。
这也是我们一般推荐的。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
动态原型模式
混成模式虽然很好,但是需要把Person和Person.prototype定义分开写,可能会遭到一些oo使用者的反对,我们提出一个更好的模式。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
if( typeof Person.prototype.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
先用一个if判断是否已经存在了,是为了防止重载导致的屏蔽行为。
到这一步为止,终于我们把所有都在Person构造函数内完成了。
寄生构造函数模式
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(name);
};
return o;
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
person1.sayName(); //Nicholas
通过new
可以看出我们使用了构造函数,但有点奇怪的是,我们依然返回了一个对象。
其实这个对象把构造函数创建的对象覆盖掉了。
这个方法创建的对象类型也是不能分辨的。
稳妥构造函数模式
虽然我们实现了共用的属性和方法,还实现了对象自己的属性和方法,但摆在我们面前最大的问题是访问权限,根据面向对象的设计原则,实例对象的属性不应当能直接访问。
根据这个,设计了一种叫稳妥构造函数模式的方法去创建对象。
function Person(name, age, job){
var o = new Object();
o.sayName = function () {
console.log(name);
};
return o;
}
var person1 = Person("Nicholas", 29, "Software Engineer");
person1.sayName(); //Nicholas
无法直接访问name,但可以通过函数访问。
但这个方法无法查看对象的类型。