创建对象
创建对象方式有很多,每一种都有自己的特点,可以根据不同场景去选择创建对象的方式
工厂模式
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
优点:消除了对象间的耦合,将所有实例化的代码集中在一个位置防止代码重复
缺点:对象无法识别,因为所有的实例都指向一个原型
构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
优点:实例可以识别为一个特定的类型(自己定义)
缺点:每次创建实例时,每个方法都要重新创建一次
原型模式
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();
person1.sayName(); //"Nicholas"
var person2 = new Person();
优点:所有的属性和方法都共享,不需要每次实例化都重新创建一次
缺点:并且不能初始化参数
理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。就拿前面的例子来说,Person.prototype. constructor指向 Person
创建了自定义的构造函数之后,其原型对象默认只会取得constructor
属性;至于其他方法,则都是从Object继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]
。虽然在脚本中没有标准的方式访问[[Prototype]]
,但Firefox、Safari和 Chrome 在每个对象上都支持一个属性__proto__
;而在其他实现中,这个属性对脚本则完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间
[图片上传失败...(image-beef89-1533106139264)]
虽然在所有实现中都无法访问到[[Prototype]]
,但可以通过isPrototypeOf()
方法来确定对象之间是否存在这种关系
lert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
ECMAScript 5 增加了一个新方法,叫Object.getPrototypeOf()
,在所有支持的实现中,这个方法返回[[Prototype]]
的值
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为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();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
person1.name = null;
alert(person1.name);//null,设置为null不会回复指向原型的连接
delete person1.name;//通过操作符delete,会恢复指向原型的连接
alert(person1.name); //"Nicholas"——来自原型
原型与 in 操作符
有两种方式使用in操作符:单独使用
和在for-in
循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中
同时使用hasOwnProperty()
方法和in操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
由于 in 操作符只要通过对象能够访问到属性就返回 true,hasOwnProperty()只在属性存在于实例中时才返回 true,因此只要 in 操作符返回true而hasOwnProperty()返回 false,就可以确定属性是原型中的属性
更简单的原型语法
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
在上面的代码中,我们将Person.prototype
设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor属性不再指向Person了。前面曾经介绍过,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的prototype对象,因此constructor 属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向 Person 函数此时,尽管 instanceof操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
以上代码特意包含了一个constructor属性,并将它的值设置为Person,从而确保了通过该属性能够访问到适当的值
组合使式
构造模式和原型模式组合
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
优点:每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用
动态原型模式
function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
缺点:使用动态原型模式时,不能使用对象字面量重写原型。
寄生构造函数模式
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。
构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值
稳妥构造函数模式
function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同
- 一是新创建对象的实例方法不引用 this
- 二是不使用 new 操作符调用构造函数