前言
对象字面量以及Object构造函数均可以创建单个对象,但是这样的方式有一个致命缺点,会产生大量的重复代码,为了解决这个问题,人们开始使用工厂模式创建对象。
工厂模式
工厂模式抽象化了创建具体对象的过程,ES5中无法创建类,开发人员就创建了一种函数,用函数来封装创建对象的细节。
function createPerson(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName() = function(){
console.log(this.name);
}
return obj;
}
var person1 = createPerson("James",28);
var person2 = createPerson("Harden",18);
从上可以看出,虽然工厂模式解决了创建多个相似对象的问题,但是没有解决对象识别的问题,即如何判别一个对象的类型。随着JS的不断壮大,一种新的模式出现了。
构造函数模式
通过Object和Array这样的原生构造函数可以创建特定类型的对象。受此启发,我们是不是可以构建自定义的构造函数,从而定义自定义类型的属性和方法?答案是肯定的。
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("James",28);
var person2 = new Person("Harden",18);
此模式与工厂模式的不同之处有:
- 没有显示的创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句。
此外,按照惯例,构造函数都应该以一个大写的字母开头。
使用new操作符调用构造函数实际上经历的步骤为:
1. 创建一个新对象;
2. 将构造函数的作用域赋给新对象(因此this指向了这个新对象);
3. 执行构造函数中的代码(为新对象添加属性);
4. 返回新的对象。
上述例子中的两个实例对象都有一个constructor(构造函数)属性,该属性指向构造函数Person。
console.log(person1 instance of Object); //true
console.log(person1 instance of Person); //true
console.log(person2 instance of Object); //true
console.log(person2 instance of Person); //true
上述代码使用* instanceof*操作符可验证person1和person2既是Object的实例,也是Person的实例。
创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型,这正是构造函数模式胜过工厂模式的地方。
构造函数的问题
使用构造函数模式的主要问题在于:每个方法要在每个实例上重新创建一遍。从逻辑角度来说,此时的构造函数可以这样定义:
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = new Function("console.log(this.name)");
}
console.log(person1.sayName === person2.sayName) //false
说明白些,以这种方式创建函数,会导致不同的作用域链和标识符解析。上述代码说明不同实例上的同名函数式不等的。
然而,创建两个完成相同任务的Function实例确实没必要。因此,我们可以这么做:
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
这样实例就可共享在全局作用域中定义的sayName函数。这样确实解决了上述问题,但是新问题又来了:r如果对象需要定义很多方法,那么就要定义很多个全局函数,那我们自定义的封装性可言了。而这些问题可以通过原型模式来解决。
原型模式
我们创建的函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有的实例共享的方法和属性。
prototype就是通过构造函数而创建的对象实例的原型对象。
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = "24";
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
person1.sayName();//"Nicholas"
var person2 = new Person();
person2.sayName();//"Nicholas"
console.log(person1.sayName === person2.sayName)//true
原型模式问题
1:省略了构造函数传递初始化参数这一环节,导致所有实例都会取 得相同的属性值。
2:原型模式共享的本性可能导致,一个实例上的操作会影响另一个实例。
原型模式问题示例:
function Person(){}
Person.prototype={
constructor:Person,
friends:["James","Harden"]
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Paul");
console.log(person1.friends);//["James", "Harden", "Paul"]
console.log(person2.friends);//["James", "Harden", "Paul"]
console.log(person1.friends === person2.friends);//true
构造函数模式和原型模式组合
创建自定义类型的最常见,认同度最高的方式:
1.构造函数模式用于定义实例属性;
2.原型模式用于定义方法和共享的属性。
示例:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends=["James","Harden"];
}
Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
var person1 = new Person("Gordon",30,"basketball player");
var person2 = new Person("Eric",30,"basketball player");
person1.friends.push("cp3");
console.log(person1.friends);// ["James", "Harden", "cp3"]
console.log(person2.friends);// ["James", "Harden"]
console.log(person1.friends === person2.friends);//false
console.log(person1.sayName === person2.sayName);//true
动态原型模式
动态原型模式把所有的信息都封装在了构造函数里,通过在构造函数中初始化原型。
示例:
function Person(name,age,job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
上述sayName方法代码,只会在初次调用Person构造函数的时候执行。
寄生构造函数模式
基本思想:
创建一个函数,函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。
示例:
function Person(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;
}
var friend = new Person("James",28,"player");
除了使用new操作符和把使用的包装函数叫做构造函数之外,这个模式和工厂模式一模一样。
用途:创建一个具有额外方法的特殊数组,由于不可以直接修改Array构造函数,可以使用这个模式。
function SpecialArray(){
//创建数组
var arr = new Array();
//添加值
arr.push.apply(arr,arguments);
//添加方法
arr.toPipeString = function(){
return this,join("|");
}
return arr;
}
var specialArr = new SpecialArray("James","Harden","Cp3");
console.log(specialArr.toPipeString());//James|Harden|Cp3
注意:
此模式返回的对象与构造函数以及构造函数的原型属性之间没有任何关系,不能依赖instanceof操作符来确定对象类型。
建议:
在可以使用其他模式的情况下,不要使用此模式。
稳妥构造函数模式
稳妥对象:没有公共属性,其方法也不引用this对象,最适合应用在一些安全环境中(这些环境中会禁止使用this和new),或者用于防止数据被其他应用改动。
示例:
function Person(name,age,job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
console.log(name);
}
return o;
}
var friend = Person("James",28,"player");
friend.sayName();//James
除了调用sayName()方法外,没有别的办法访问传入到构造函数中的原始数据。