一些方法
Person.prototype.isPrototypeOf(实例); //返回布尔值
Object.getPrototypeOf(实例); //返回原型对象
实例.hasOwnProperty(属性名) ;返回布尔值
for in 和 in会访问实例和原型属性;for in只遍历可枚举属性
Object.keys(o)返回可枚举实例属性名组成的数组,与for in中出现的顺序一致。要遍历所有实例属性(包括不可枚举的)使用Object.getOwnPropertyNames()。
如果通过这样的方式给原型对象添加属性和方法:
Person.prototype = {
XXX: XXX,
...
};
那么实际上是重写了原型对象,则该原型对象中的constructor
属性不再指向构造函数Person
, 而是指向Object
。除非重新更改constructor
的值,使其指向Person
.
重写了原型,仍然是实例仍然是可以用instanceof
来检测类型的。对于原型链上的对象以及构造函数都是返回true
。
7. 如果在创建了一个实例之后再重写原型对象,原来的实例的[prototype]
属性仍然是指向之前的原型对象的(因为构造函数会为实例添加一个指向最初原型对象的属性[prototype]
),因此应当在重写原型对象之后再创建实例:
function Person() {}
Person.prototype = {
constructor: Person,
.....
};
var p = new Person();
创建类
- 构造函数中创建实例属性,原型中创建共享属性和方法。
function Person(name, age) {
this. name = name;
this.age = age;
this.hobby = ['swimming', 'tennis'];
};
person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
};
- 动态原型模式:
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function() {};
Person.prototype......
}
该方式的优点是将原型的添加也放到了构造函数里,封装性更好;原型的添加也只有在第一次new
对象的时候才会执行,只需检测一个要添加的原型属性是否存在即可。
需注意的是该方式不能重写原型,否则实例指向的原型仍然是原来的原型。
- 寄生构造函数
function SpecialArray() {
var arr = new Array();
arr.push.apply(arr, arguments);
arr.toPipeString = function() {
return this.join('|');
};
return arr;
}
var names = new SpecialArray('lily', 'anna', 'mike');
console.log(names.toPipeString());
console.log(names instanceof SpecialArray); //false
传统的构造函数会默认返回生成的实例,如果在构造函数中返回一个对象,则会重写返回的对象。
返回的对象和构造函数以及构造函数关联的原型没有半毛钱关系,不能用instanceof
来确定对象的类型。最好不要使用这种方式。
- 稳妥构造函数
没有this和new,适合在需要安全性的环境中使用。并且没有公用属性。
function Person(name, age) {
//定义私有变量
var o = new Object();
o.sayName = function() {
alert(name);
};
return o;
}
var obj = Person("lily", 10);
只有在o.sayName
中可以访问到name
。避免修改。
继承
继承依赖原型链,主要思想是将超类的实例赋值给子类的原型,由于实例中持有对超类原型的引用,因此可以访问到超类中的原型属性(所有属性)。
- 采用构造函数和原型组合的方式
如果仅采用原型,由于SuperType的实例属性会成为SubType的原型属性,如果SuperType中包含引用类型数据,那么子类的多个实例会共享这些引用类型属性,这是不希望看到的。
而如果仅采用构造函数的方式,则不能函数复用,因此采用两者结合的组合继承方式:
function SuperType(name, hobby) {
this.name = name;
this.hobby = hobby;
}
SuperType.prototype.getSuperValue = function() {
console.log(this.name + ' ' + this.hobby);
};
function SubType(age, name, hobby) {
SuperType.call(this, name, hobby);// 给子类实例属性添加上父类实例属性
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
console.log(this.age);
};
由于父类实例中有一个不可访问的属性[prototype]
指向SuperType.prototype
,而子类的原型对象是父类的实例,因此形成了一条原型链。
采用这种方式,超类构造函数会被调用两次,超类的实例属性在子类中会以两种形式出现,第一次是在:
SubType.prototype = new SuperType();
超类的实例属性成为子类的原型属性。第二次是:
SuperType.call(this, name, hobby);
在构造函数中,子类的同名实例属性屏蔽了原型属性。可以采用寄生组合继承来解决这个问题。
- 原型继承
不使用构造函数。新对象的原型是某个已知的对象。相当于创建了一个已知对象的副本,修改新对象,也会影响到已知对象。例如:
function create(o) {
function F(){}
F.prototype = o;
return new F();
}
在ES5中提供了更为规范的函数Object.create(o[, {}])
后面的可选参数是一个对象,包含要为新对象添加的属性。
- 寄生组合式继承
组合继承的缺点是原型那里有冗余的属性,我们希望子类的原型是这么一个对象:它不拥有超类的实例属性,但持有超类原型对象的引用。因此不能直接以超类的实例来作为子类的原型对象。
//原型继承,适用于没有自定义类型,仅想继承o
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(SubType, SuperType) {
var prototype = object(SuperType.prototype);
prototype.constructor = SubType;
SubType.prototype = prototype;
}
//寄生组合继承
function SuperType(name, hobby) {
this.name = name;
this.hobby = hobby;
}
SuperType.prototype.getSuperValue = function() {
console.log(this.name + ' ' + this.hobby);
};
function SubType(age, name, hobby) {
SuperType.call(this, name, hobby);//父类实例属性成为子类实例属性
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.getSubValue = function() {
console.log(this.age);
};
上述代码中,返回的F实例其原型对象就是SuperType.prototype
,再把这个没有任何实例属性的F实例作为子类的原型。即子类的原型对象中的[prototype]
属性指向一个纯粹的父类的原型对象,而不是包含冗余的父类实例属性的父类实例。同时也只调用了一次父类的构造函数。刚好符合我们的两个要求。
寄生组合继承是目前最理想的继承方式。