二、创建对象的几个模式
虽然Object构造函数或对象字面量都可以创建单个对象,但是同时在一个接口创建很多个对象,使用他们会产生大量重复代码。为了解决这个问题,产生了工厂模式
和构造函数模式
。
1. 工厂模式
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;
}
var person1 = createPerson("YuJia","27","Software Engineer");
var person2 = createPerson("Greg","27","Software Engineer");
- 优点:可以创建多个相似对象,相对单个去构造对象代码量少。
- 缺点:不知道创建的对象的类型(下面2会讲到)。
2. 构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name)
};
}
var person1 = new Person("YuJia","27","Software Engineer");
var person2 = new Person("Greg","27","Software Engineer");
- 相对于工厂模式,构造函数模式的特点一目了然。
- 没有显式地创建对象。
- 直接将属性赋值给了
this
对象。- 没有
return
语句。- 使用
new
操作符创建对象的新实例。
- 构造函数模式的过程
- 创建一个新的对象。
- 将函数的作用域赋给新对象。
- 执行构造函数中的代码(
为对象赋值新的属性
)。- 返回新对象。
- 深入:对象的
constructor(
构造函数)
最初是用来标识对象类型的,但是instanceof
操作符要更可靠。
person1.constructor == Person //true
person2.constructor == Person //true
person1 instanceof Person //true
person2 instanceof Person //true
person1 instanceof Object //true
person2 instanceof Object //true
- 创建自定义的构造函数意味着将来可以将它的实例标识为一种特殊的类型,这是构造函数模式胜过工厂模式的地方。
- 上面的person同时是
Object
和Person
构造函数的实例,因为所有对象都继承自Object
- 如果将构造函数直接当函数来运行(不使用
new
操作符)要考虑作用域,即this
的指向.
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name)
};
} ;
// 普通调用,this指向window
Person("YuJia","27","Software Engineer");
window.sayName() // YuJia
//换一个作用域
var o = new Object;
Person.call(o,"Jia","27","Software Engineer")
o.sayName() //Jia
- 构造函数的缺点
像上面的
Person
构造函数,在构造多个实例的时候,构造函数内部的方法sayName()
都要被重新创建一遍,这就代表指针不一样,导致堆内存占用的代价。与下面的例子在逻辑上是等价的。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("console.log(this.name)");
//每次创建Person实例,都会重新创建一次sayName
} ;
var person1 = new Person("YuJia","27","Software Engineer");
var person2 = new Person("Greg","27","Software Engineer");
person1.sayName === person2.sayName ///false
- 我们可以这样解决构造的多个对象内部方法为同一个指针
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
} ;
function sayName(){
console.log(this.name)
};
- 上面的方法可以解决构造
Person
的多个对象,内部方法都指向外部的sayName()
,但是在全局作用域中定义的方法,却只被Person
构造方法调用,你让全局作用域情何以堪呢?- 更何况如果
Person
构造函数不只是sayName()
,还有多个方法,那我们要在全局作用域下面定义更多的Person
私人方法啦?
- 由此,我们欢迎一下原型模式(👏👏👏👏👏👏)
3. 原型模式
- 我们创建的每一个函数都有一个
prototype
(原型)属性,它是一个指针,指向一个对象,这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。 -
prototype
让构造模式上的方法和属性共享(指针都指向同一个地方)
function Person(){
Person.prototype.name ="小明";
Person.prototype.age = 27;
Person.prototype.job = "搬砖";
Person.prototype.sayName = function(){
console.log(this.name)
};
} ;
var person1 = new Person();
person1.sayName() // 小明
var person2 = new Person();
person2.sayName() // 小明
person1.sayName === person2.sayName // true
上面的原型模式下,构造出来的实例
person1
和person2
他们共享属性和方法,即他们内部的同名函数指针指向同一个方法。这样不管构造多少实例,内部的方法或者属性,都指占用一个堆。
- 单用原型模式的缺点
- 它省略了为构造函数传递初始化参数环节,导致所有实例在默认情况下都将取得相同的属性值。
- 原型的共享本质是最大问题.
4. 和组合使用
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。两种结合使用,每个实例都会有自己的一份实力属性的副本,但同时又共享着对方发的引用,最大限度节省内存,集两种模式的长处。
function Person (name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Dog','Cat'];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name) ;
}
}
var person1= new Person("余嘉",27,"Software Engineer");
var person2= new Person("狗子",2,"看家护院");
上面生成的两个实例,把所有信息属性都封装在了构造函数中,而通过初始化构造函数的原型(重新给原型赋值,断开了与原来默认原型的关系),既保持了自己某些方面的独立性,又公用着同一个方法。
5. 动态原型模式
动态原型模式是基于原型的动态性实现的,即在原型上添加需要的方法和属性(和组合模式相比,不需要给原型重新赋值),来实现实例的共享方法或属性。
function Person(name,age,job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if(type of this.sayName != 'function'){
Person.prototype.sayName = function(){
console.log(this.name) ;
};
}
}
上面添加方法的时候,在
sayName
方法不存在的情况下,才会将他添加到原型中,添加方法的过程只会在初次调用时才会执行,此后原型的初始化已经完成了,不需要再修改了。
6. 寄生构造函数模式
这种模式的构造方法有点像工厂模式那样,通过在方法的末尾添加return
语句,重新定义通过构造函数模式的new
方法默认返回的值。
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("狗",2,"看家护院");
friend.sayName(); // 狗
这种模式可以在特殊的情况下用来为对象创建构造函数。比如我们想创建一个具有额外方法的特殊数组,我们又不能直接在Array构造函数上直接修改。由此我们可以使用这种模式。
function specialArray(){
//创建数组
var values = new Array();
//添加值
values.push.apply(values,arguments)
//添加方法
values.toPipedstring = function(){
return this.join("|");
}
return values;
}
var colors = new specialArray("red","blue","green");
console.log(colors.toPipedstring()) //red|blue|green
7. 稳妥构造函数模式(安全执行环境中)
js中有稳妥对象这个概念(管TM谁发明的),稳妥构造函数模式具有以下情况:
- 没有公共属性
- 不使用
new
操作符调用构造函数- 创建的实例上的方法不引用
this
;
function Person(name,age,job){
var o = new Object;
o.sayName = function(){
console.log(name)
};
return o
}
var friend = Person("狗",2,"看家护院")
这种模式创建的对象(稳妥对象),除了使用
sayName()
,没有其他办法访问name
的值。即使我们在稳妥对象中添加方法或数据成员,也不可能用其他办法访问到传入构造函数中的原始的数据。