面向对象可以把程序中的关键模块都视为对象,而模块拥有属性及方法。这样我们如果把一些属性及方法封装起来,日后使用将非常方便,也可以避免繁琐重复的工作。接下来将为大家讲解在JS中面向对象的实现。
- 工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,而由于在ECMAScript中无法创建类,因此用函数封装以特定接口创建对象。其实现方法非常简单,也就是在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。
// 1.工厂模式
function createDog (age, color, name) {
var dog = new Object();
dog.age = age;
dog.color = color;
dog.name = name;
dog.sayHi = function () {
alert('Hi, I am ' + dog.name);
}
return dog;
}
var dog1 = createDog(3, 'black', 'Jack');
dog1.sayHi();
var dog2 = createDog(5, 'white', 'Nick');
dog2.sayHi();
console.log(dog1 instanceof createDog);
// false:工厂模式缺陷——不能识别对象的类型
可以看到工厂模式的实现方法非常简单,解决了创建多个相似对象的问题,但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,因此出现了构造函数模式。
- 构造函数模式
// 2.构造函数模式
function Dog (age, color, name) {
this.age = age;
this.color = color;
this.name = name;
this.sayHi = function () {
alert('Hi, I am ' + this.name);
}
}
var dog3 = new Dog(2, 'yellow', 'HaHa');
dog3.sayHi();
var dog4 = new Dog(5, 'colorful', 'rainbowl');
dog4.sayHi();
console.log(dog3 instanceof Dog); // true:构造函数模式的进步——可以识别对象的类型
console.log(dog4.sayHi === dog3.sayHi);
// false: dog3和dog4的sayHi方法不一样
// 因为通过构造函数模式创建每个实例都要创建一遍该对象所包含的方法,所以还是有缺陷
这个例子与工厂模式中除了函数名不同以外,细心的童鞋应该发现许多不同之处:
1. 函数名首写字母为大写 (虽然标准没有严格规定首写字母为大写,但按照惯例,构造函数的首写字母用大写
2. 没有显示的创建对象
3. 直接将属性和方法赋值给了this对象
4. 没有return语句
5. 使用new创建对象
6. 能够识别对象(这正是构造函数模式胜于工厂模式的地方)
构造函数虽然好用,但也并非没有缺点,使用构造函数的最大的问题在于每次创建实例的时候都要重新创建一次方法(理论上每次创建对象的时候对象的属性均不同,而对象的方法是相同的),然而创建两次完全相同的方法是没有必要的,因此,我们可以将函数移到对象外面
- 原型模式
我们创建的每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所有对象实例共享它所包含的属性及方法。
// 3.原型模式
function DogP () {}
DogP.prototype.name = 'tommy';
DogP.prototype.color = 'black';
DogP.prototype.age = 1;
DogP.prototype.sayHi = function () {
alert('Hi, I am ' + this.name);
}
var dog5 = new DogP();
dog5.name = 'Jerry';
var dog6 = new DogP();
console.log(dog6 instanceof DogP);// true: 原型模式能够识别对象类型
console.log(dog5.sayHi === dog6.sayHi);
// true: dog5和dog6的sayHi方法不一样
// 通过原型模式创建每个实例只需要创建一遍该对象所包含的方法
原型模式也不是没有缺点,首先,它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得了相同的属性值,这样非常不方便,但这还是不是原型的最大问题,原型模式的最大问题在于共享的本性所导致的,由于共享,因此因此一个实例修改了引用,另一个也随之更改了引用。因此我们通常不单独使用原型,而是结合原型模式与构造函数模式。
- 混合模式:原型模式+构造函数模式
// 4.混合模式: 构造函数+原型模式
function DogM (age, color, name) {
this.age = age;
this.color = color;
this.name = name;
}
DogM.prototype.sayHi = function () {
alert('Hi, I am ' + this.name);
};
var dog7 = new DogM(3, 'white', 'Richal');
console.log(dog7 instanceof DogM);
混合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性,但同时又共享着方法,最大限度的节省了内存。另外这种模式还支持传递初始参数。优点甚多。这种模式在ECMAScript中是使用最广泛、认同度最高的一种创建自定义对象的方法。
- 动态原型模式
// 5.动态模式
function DogD (age, color, name) {
this.age = age;
this.color = color;
this.name = name;
if (typeof this.sayHi != 'function') {
DogD.prototype.sayHi = function () {
alert('Hi, I am ' + this.name);
}
}
}
var dog8 = new DogD(7, 'black', 'brook');
dog8.sayHi();
本文参考《JavaScript高级程序设计》第3版和博文面向对象JS基础讲解,工厂模式、构造函数模式、原型模式、混合模式、动态原型模式