虽然Object构造函数或对象字面量可以创建单个对象,但他们有个明显缺点:使用同一个接口创建很多对象,会产生大量的重复代码。
1.工厂模式
ECMAScript中没有类,用函数来封装以特定接口创建对象的细节。但没有解决对象识别问题,不能判断对象的类型。
1 | function createPerson(name,age,job){ |
2.构造函数模式
原生构造函数+自定义构造函数
1 | 自定义构造函数 |
- 构造函数必须以大写字母开头
- 要创建新实例,必须使用
new
操作符,会经历4个步骤- 创建一个新对象
- 新对象原型链接到构造函数原型对象
- 将构造函数的作用域赋给新对象(this绑定到这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
- 显式的
return
表达式将会影响返回结果,但仅限于返回的是一个对象。1
2
3
4
5
6
7
8
9
10
11
12
13function Bar() {
return 2;
}
new Bar(); // 返回新创建的对象
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); // 返回的对象
2.1将构造函数当作函数
构造函数与其他函数唯一区别,在于调用方式不同,用不用new
!
用new就是构造函数
不用就是普通函数
1 | //当作构造函数使用 |
2.2构造函数的问题
主要问题:每个方法都要在每个实例上重新创建一遍。从而不同实例上的同名函数是不想等的。
解决方法:把函数定义转移到构造函数外部
1
2
3
4
5
6
7
8
9
10function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
alert(this.name);
}引出新的问题:如果对象定义很多方法,那么就要定义很多个全局函数,于是自定义的引用类型全无封装性了。
新问题解决方法:原型模式
3.原型模式(原型对象是实例对象的子对象,一个属性)
- 含义:创建的每个函数都有一个
prototype
(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含着特定类型的所有实例共享的属性和方法。 prototype
:是原型对象,是通过构造函数而创建的那个对象实例的原型对象。- 好处:不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
1
2
3
4
5
6
7
8
9function Person(){
}
Person.prototype.name="libowen";
Person.prototype.age=29;
Person.prototype.sayName=function(){
alert(this.name);
};
3.1理解原型对象
原型对象:只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个
prototype
属性,这个属性指向函数的原型对象。constructor
属性:默认情况下,所有原型对象自动获得一个constructor
属性,该属性是一个指针,指向prototype属性所在函数的指针。constructor
属性可以人为修改。[[Prototype]]:当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个指针(内部属性)是[[Prototype]]。
[[Prototype]]是存在于实例与原型对象之间的连接,不是存在于实例和构造函数的连接。
isPrototypeOf():来确定原型对象或构造函数与对象实例之间的关系。
1
2alert(Person.prototype.isPrototyprOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //trueObject.getPrototypeOf():ECMAScript5中新增。返回[[prototype]]的值。
1
2alert(Object.prototype.getPrototypeOf(person1)==Person.prototype); //true
alert(Object.prototype.getPrototypeOf(person2).name); //"libowen"对象实例共享原型所保存的属性和方法的基本原理:先从对象实例本身开始搜索,如果找不到,继续搜索指针指向的原型对象。
屏蔽与屏蔽原因:当为对象实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性。原因则是:添加这个属性会阻止我们访问原型中的那个属性,但不会修改那个属性。
null
与delete
:null只会在实例中设置属性,不能恢复指向原型的链接,delete可以完全删除实例属性,恢复原型链接。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function Person(){
}
Person.prototype.name="libowen";
Person.prototype.age=29;
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
var person2=new Person();
person1.name="Greg";
alert(person1.name); //"Greg",来自实例
alert(person2.name); //"libowen",来自原型
delete(person1.name);
alert(person1.name); //"libowen",来自原型hasOwnProperty()
:只在给定属性存在于对象实例中,才返回true。
3.2原型与in操作符
- 两种使用方式:单独使用、
for-in
循环。- 单独使用时,不区分实例和原型,能够访问到给定属性就返回
true
。 - 确定属性在原型中的方法
for-in
循环,返回的是所有能够通过对象访问的、可枚举的属性。1
2
3function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);
}
- 单独使用时,不区分实例和原型,能够访问到给定属性就返回
3.3原型的动态性
- 由于在原型中查找值是一次搜索,因此对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建实例后修改原型也是如此。
- 如果重写整个原型对象,就切断现有原型(构造函数的
prototype
属性只指向现有原型)与任何之前已经存在的对象实例之间的联系,对象实例引用的仍然是最初的原型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){
}
var friend=new Person();
Person.prototype={
constructor:Person,
name:"Nicholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name)
}
}
friend.sayName(); //error
过程如图
3.4原型对象的问题
- 缺点:不能单独使用原型模式的原因。
- 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
- 所有实例都共享原型的属性,而且原型的属性可以动态更改,导致实例没有属于自己的全部属性。包含基本类型值的属性可以添加屏蔽;包含引用类型值的属性被操作修改后,则会被所有共享反映出来。
4.组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性
原型模式用于定义方法和共享的属性
最常用、默认
1 | function Person(name,age,job){ |
5.动态原型模式
可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
1 | //例子 |
只在sayName()
方法不存在的情况下,才会将它添加到原型中。
6.寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
1 | function Person(name, age, job){ |
- 除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实
是一模一样的。 - 通过在构造函数的末尾添加一个
return
语句且返回的是一个对象,可以重写调用构造函数时返回的值。 - 这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊
数组。由于不能直接修改Array
构造函数,因此可以使用这个模式。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function 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");
alert(colors.toPipedString()); //"red|blue|green"
注意:
- 首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系,为此,不能依赖
instanceof
操作符来确定对象类型 - 建议在可以使用其他模式的情况下,不要使用这种模式
7.稳妥构造函数模式
- 稳妥对象,指的是没有公共属性,而且其方法也不引用
this
的对象 - 用处: 在一些安全的环境中(这些环境中会禁止使用
this
和new
),或者在防止数据被其他应用程序(如 Mashup程序)改动时使用。 - 特点:稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同
- 新创建对象的实例方法不引用
this
; - 不使用
new
操作符调用构造函数。
- 新创建对象的实例方法不引用
1 | function Person(name, age, job){ |
代码解释:
- 变量
friend
中保存的是一个稳妥对象,而除了调用sayName()
方法外,没有别的方式可以访问其数据成员。 - 即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。