工厂模式
由于在ECMAScript中无法创建类,因此用函数封装以特定接口创建对象。其实现方法非常简单,也就是在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。
function a(name) {
let b = new Object();
b.name = name;
b.say = function () {
alert(this.name);
}
return b;
}
var a1 = a("xiaoming", 20);
可以看到工厂模式的实现方法非常简单,解决了创建多个相似对象的问题,但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,因此出现了构造函数模式。
构造函数模式
构造函数可以创建特定类型的对象,类似于Array、Date等原生JS的对象。其实现方法如下:
function Person(name, age) {
this.name = "xiaoming";
this.age = 20;
this.alertAge = function () {
alert(this.age);
};
}
var person = new Person();
这个例子与工厂模式中除了函数名不同以外,应该可以发现许多不同之处:
- 没有显示的创建对象
- 直接将属性和方法赋值给了this对象
- 没有return语句
- 使用new创建对象
- 能够识别对象(这正是构造函数模式胜于工厂模式的地方)
构造函数虽然好用,但也并非没有缺点,使用构造函数的最大的问题在于每次创建实例的时候都要重新创建一次方法(理论上每次创建对象的时候对象的属性均不同,而对象的方法是相同的),然而创建两次完全相同的方法是没有必要的,因此,我们可以将函数移到对象外面。
function Person(name, age) {
this.name = "xiaoming";
this.age = 20;
this.alertAge = alertAge;
}
function alertAge() {
alert(this.age);
}
var person1 = new Person();
var person2 = new Person();
我们将alertAge设置成全局函数,这样一来person1与person2访问的都是同一个函数,可是问题又来了,在全局作用域中定义了一个实际只想让person1使用的函数,显示让全局作用域有些名副其实,更让人无法接受的是在全局作用域中定义了许多仅供特定对象使用的方法,浪费空间不说,显然失去了面向对象封装性了,因此可以通过原型来解决此问题。
原型模式
我们创建的每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所有对象实例共享它所包含的属性及方法。
function Person() {
}
Person.prototype.name = "Jay";
Person.prototype.age = 21;
Person.prototype.sayName = function () {
alert(this.name)
}
var person1 = new Person();
var person2 = new Person();
alert(person1.name); // Jay
alert(person2.name); // Jay
person1.sayName(); // Jay
person2.sayName(); // Jay
person1.name = "666";
alert(person1.name); // 666
alert(person2.name); // Jay
person1.sayName(); // 666
person2.sayName(); // Jay
原型模式也不是没有缺点,首先,它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得了相同的属性值,这样非常不方便,但这还是不是原型的最大问题,原型模式的最大问题在于共享的本性所导致的,由于共享,因此因此一个实例修改了引用,另一个也随之更改了引用。因此我们通常不单独使用原型,而是结合原型模式与构造函数模式。
混合模式(原型模式 + 构造函数模式)
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.alertInfo = function(){
alert(this.name + this.age);
}
var person1 = new Person("Jay", 21);
var person2 = new Person("Ami", 20);
person1.age = 22;
person1.alertInfo(); // Jay22
person2.alertInfo(); // Ami20
混合模式中构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。每个实例都会有自己的一份实例属性,但同时又共享着方法,最大限度的节省了内存。另外这种模式还支持传递初始参数。
动态原型模式
动态原型模式将所有信息封装在了构造函数中,而通过构造函数中初始化原型(仅第一个对象实例化时初始化原型),这个可以通过判断该方法是否有效而选择是否需要初始化原型。
function Person(name, age){
this.name = name;
this.age = age;
if(typeof this.alertAge != "function"){
// 这段代码只执行一次
alert("excuse me ???");
Person.prototype.alertAge = function() {
alert(this.age);
}
}
}
var person1 = new Person("Jay", 23);
var person2 = new Person("Ami", 24);
可以看到上面的例子中只弹出一次窗,'excuse me ',即当person1初始化时,这样做person2就不在需要初始化原型。