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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
person1.sayName() // Nicholas
person2.sayName() // Greg
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("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName() // Nicholas
person2.sayName() // Greg
构造函数模式,和 工厂模式相比
- 1.没有显示的创建对象
- 2.直接将属性和方法,赋给了this对象
- 3.没有return语句
使用构造函数创建对象,会经历四个步骤
- 1.创建对象
- 2.将构造函数的作用域赋给新对象(this指向新对象)
- 3.执行构造函数中的代码(为新对象增加属性)
- 4.返回新对象
使用构造函数创建的对象,都有一个constructor属性,指向构造函数。
创建自定义的构造函数,意味着,可以将它的实例,识别为一种特定的类型
constructor 属性,用来标识对象类型。可以使用instanceof 检测
person1 instanceof Object // true
构造函数的问题:
- 每个方法,都相当于在实例上创建了一遍
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
//this.sayName = function(){
// console.log(this.name);
//};
// 相当于下边的写法
this.sayName = new Function("alert(this.name)"); // 每定义一个函数,都相当于实例化了一个对象
}
var person1 = new Person();
var person2 = new Person();
person1.sayName == person2.sayName; //false
因此,我们可以把函数定义,放到全局作用域
function Person(name, age, job){
// some code
this.sayName= sayName
}
function sayName(){}
如此,sayName指向同一个函数,实现了函数共享。但是如果方法过多,就需要在全局作用域创建多个函数,那么我们自定义的引用类型,就没有什么意义了。 这个问题,可以通过原型模式解决
3.原型模式
每个函数都有一个prototype(原型)属性,指向一个对象(原型对象)。
这个对象的用途是:包含可以有特定类型的所有实例共享的属性和方法。
即:所有实例都可以共享prototype的属性和方法
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
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
所有实例(person1 和 person2)访问的都是同一组属性和一个sayName方法
理解原型对象
函数都有一个prototype属性,指向函数的原型对象。在默认情况下,原型对象都有一个constructor(构造函数)属性,这个属性,包含指向prototype所在函数的指针
Person.prototype.constructor === Person // true
创建了自定义函数之后,构造函数的原型对象默认只包含constructor属性,至于其他方法,都是从Object对象继承而来。
当构造函数创建一个实例之后,该实例包含一个内部属性,指向 构造函数的原型对象。
该属性存在于实例 和 原型对象之间,和构造函数没有直接关系
person1.__proto__ === Person.prototype // true
这个内部属性,ECMA5叫做 [[prototype]],FireFox、safari、chrome 叫 __proto__,
可以使用 isPrototypeOf 检测 [[prototype]] 属性的指向
Person.prtotype.isPrototypeOf(person1) // true
也可以使用getPrototypeOf获取原型对象
(Object.getPrototypeOf(person1) == Person.prototype // true
读取属性的过程
每当代码读取某个属性时,会先去对象实例本身开始查找,查找到的话,就会返回;查不到的话,就会继续搜索[[prototype]](__proto__)属性指向的原型对象,直至查到Object.prototype。(这种查找机制,叫做原型链)
当为对象添加一个属性时,会自动屏蔽掉原型的同名属性,但不会修改。
即使将该属性赋值为null,也不会恢复指向原型的链接
但是可以通过delete操作符,删除实例上的属性,恢复链接
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
console.log(person1.name); //"Greg" --实例
console.log(person2.name); //"Nicholas" -- 原型
delete person1.name;
console.log(person1.name); //"Nicholas" -- 原型
使用 hasOwnProperty 检测一个属性,是否存在于一个实例中
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg" -- 实例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas" -- 原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas" -- 原型
alert(person1.hasOwnProperty("name")); //false
使用in操作符,判断属性是否在原型链上
person1.hasOwnProperty("name"); //false
"name" in person1; //true
person1.name = "Greg";
person1.name; //"Greg" -- 实例
person1.hasOwnProperty("name"); //true
"name" in person1; //true
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person = new Person();
alert(hasPrototypeProperty(person, "name")); //true name存在于原型中
person.name = "Greg";
alert(hasPrototypeProperty(person, "name")); //false 实例中重写name属性,那么name就存在于实例中了
Object.keys() 返回实例中所有可枚举属性
for-in循环,返回是 能过通过对象访问的、可枚举的属性,包括实例 以及 原型中的
Object.getOwnPropertyNames()获取所有实例属性,包括不可枚举
var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); //"constructor,name,age,job,sayName"
注意:
当我们使用字面量方式创建新对象,要注意constructor的指向
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
}
Person.prototype.constructor === Person; // false
Person.prototype.constructor=== Object ; // true
当prototype被重写的时候,constructor不再指向Person,而是指向新对象的构造函数(Object) 需要手动指定constructor
Person.prototype.constructor = Person
但是此时,constructor属性,将会变成可枚举的
Object.keys(Person.prototype) // ["name", "age", "job", "sayName", "constructor"]
使用
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
原型的动态性
在原型中查找值的过程是一次搜索,因此,我们对原型对象所做的任何修改都能立即在实例上显现出来。即使先创建实例,也是如此。
function Person(){
}
var p1 = new Person() // 先创建的实例
//Person.prototype.sayHi = function(){
// alert("hi");
//};
//p1.sayHi(); // hi
// 重写圆形原型
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
}
p1.sayName() //p1.sayName is not a function
console.log(Person.prototype.constructor === Person); // false
原型对象被重写,将会不同。
调用构造函数时,会为实例添加一个[[prototype]](或者说是__proto__)指针,指向原型,而重写原型,将会斩断构造函数和原型之间的联系。
所以,导致 p1.sayName() 调用报错
对象实例所对应的原型对象,引用的仍是重写之前的原型。
原型对象的问题
引用值共享
function Person(){
this.hobby = ['basket ball','tennis']
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); //"Shelby,Court,Van"
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //true
person1.hobby.push('ping-pong')
console.log(person1.hobby) // [ 'basket ball', 'tennis', 'ping-pong' ] 构造函数中的属性,是实例私有的,不会共享
console.log(person2.hobby) // [ 'basket ball', 'tennis' ]
原型中所有实例的属性和方法是共享的,可以节省大量内存,但是也同时导致了引用值共享的问题(person1.friends=['Ann'] 并不会有这种问题-- 在实例添加同名属性,会屏蔽调原型上的属性)
那么如何解决原型对象的应用值共享问题?-- 组合使用原型和构造函数
4.组合使用原型模式和构造函数模式
组合使用原型模式和构造函数模式,是创建自定义类型最常见的方式。原型模式定义共享的属性和方法,构造模式定义实例属性
每个实例都会拥有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度的节省了内存。
另外,还支持像构造函数传递参数
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
5.动态原型模式
即:通过检查某个存在的方法是否有效,来决定否需要初始化原型
//方法
if (typeof this.sayName != "function"){// sayName不存在的时候才会执行,即在初次调用构造函数时
Person.prototype.sayName = function(){
alert(this.name);
};
}
注意:不能使用字面量的方式对原型赋值,会导致构造函数与原型之间的关系被切断,那么if判断自然就无效了
6.寄生构造函数模式
原理:创建一个函数,该函数仅仅是封装创建对象的方法,然后再返回新创建的对象
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("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
除了使用new 操作符之外,和工厂模式一毛一样。
该模式下创建的对象,和构造函数之间没有任何关系,也不可以使用instanceOf检测类型
7.稳妥构造函数模式
所谓稳妥对象,指 没有公共属性,其方法也不引用this对象。
function Person(name, age, job){
var o = new Object(); // 创建要返回的对象
// 可以在这里定义私有变量和函数
o.sayName = function(){ // 添加方法
alert(name);
};
//返回对象
return o;
}
在这种模式下,只有调用sayName()才可以访问其数据成员。
同样,该模式下创建的对象,和构造函数之间没有任何关系,也不可以使用instanceOf检测类型。