二. 对象继承的方式有哪些?
(1)原型链
每个 构造函数 都有一个 原型对象 ,原型有一个属性(constructor)指回构造函数,而 实例 有一个内部指针指向 原型对象。如果原型是另一个类型的实例,那就意味着这个原型本身有一个内部指针指向一个另一个原型,相应地,另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。默认情况下,所有引用类型都继承自Object。
【原型与继承关系】
- 原型与实例的关系可以通过两种方式来确定。第一种方式是使用 instanceOf 操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回 true
console.log(f1 instanceof Foo)//true
console.log(f1 instanceof Object)//true
- 第二种方式是使用 isPrototypeOf( )方法。原型链中每个原型都可以调用这个方法,如下例所示:
console,log(Object.prototype.isPrototypeOf(f1)) //true
console,log(Foo.prototype.isPrototypeOf(f1)) //true
- 关于方法
// 1.关于方法
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
// 通过字面量添加新方法,这会导致上一行SubType.prototype = new SuperType();无效
// SubType.prototype = {
// getSubValue(){
// return this.subproperty
// }
// }
//新方法
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
// 覆盖原有方法
SubType.prototype.getSuperValue = function () {
return false;
};
let instance1 = new SubType();
console.log(instance1.getSuperValue()); //false
let instance2 = new SuperType();
console.log(instance2.getSuperValue()); //true
- 原型链的问题
1) 原型中包含的引用值会在所有实例间共享
2) 子类型在实例化时不能给父类型的构造函数传参
(2)盗用构造函数/对象伪装/经典继承
为了解决原型包含引用值导致的继承问题,盗用构造函数被提出,其基本思路为: 在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定的上下文执行代码的简单对象,所以可以使用apply( ) 和 call( )方法以新创建的对象为上下文执行 构造函数
优点:可以在子类构造函数中向父类构造函数传参
缺点:(也是使用构造函数模式自定义类型的问题)1)必须在构造函数中定义方法,因此函数不能重用。2)此外,子类也不能访问父类原型上定义的方法
function SuperType(name) {
this.name = name;
}
function SubType() {
// 继承SuperType并传参
SuperType.call(this, "Mary");
// 实例属性
this.age = 24;
}
let instance = new SubType();
console.log(instance.name); //Mary
console.log(instance.age); //24
(3)组合继承
组合继承综合了 原型链 和 盗用构造函数 ,将两者的优点集中了起来。基本思路是 使用原型链继承原型上的属性和方法,而通过盗用函数继承实例属性。 这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
console.log(this.age);
};
let instance1 = new SubType("Mary", 24);
instance1.colors.push("black");
console.log(instance1.colors); //[ 'red', 'blue', 'green', 'black' ]
instance1.sayAge(); //24
instance1.sayName(); //Mary
let instance2 = new SubType("Jane", 24);
console.log(instance2.colors); //[ 'red', 'blue', 'green' ]
instance2.sayAge(); //24
instance2.sayName(); //Jane
该方法存在的问题:由于我们是以父类型(SuperType)的实例来作为子类(SubType)的原型,所以调用了两次父类型(SuperType)的构造函数(即第一次调用代码为SubType.prototype = new SuperType();第二次调用代码为SuperType.call(this, name);),造成了子类型的原型中多了很多必要的属性
(4)原型式继承(Prototypal Inheritance)
原型式继承适用于这种情况:你有一个对象,想在它的基础上再创建一个新对象。你需要把这个对象先传给object( ),然后再对返回的对象进行适当修改。
注意:属性中包含的引用值始终会在相关对象间共享,跟原型链模式是一样的
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
let person = {
name: "Mary",
friends: ["Jane"],
};
let anotherperson1 = object(person);
//等价于let anotherperson1 = Object.create(person)
anotherperson1.name = "May";
anotherperson1.friends.push("John");
let anotherperson2 = object(person);
//等价于let anotherperson2 = Object.create(person)
anotherperson2.name = "Cindy";
anotherperson2.friends.push("Alice");
console.log(anotherperson2.friends); //[ 'Jane', 'John', 'Alice' ]
console.log(person.friends); //[ 'Jane', 'John', 'Alice' ]
console.log(person.name); //Mary
ES5通过增加 Object.create( )方法将原型式继承的概念规范化了,这个方法接收两个参数:作为新对象原型的对象,一以及给新对象定义额外属性的对象(第二个可选) ,在只有一个参数时,Object。create( )与上面的object( )方法效果相同
(5)寄生式继承(Parasitic inheritance)
寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回对象。 注意:通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
let clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function () {
//以某种方式增强这个对象
console.log("hi~");
};
return clone; //返回这个对象
}
let person = {
name: "Mary",
friends: ["Jane", "Alice"],
};
let anotherperson = createAnother(person);
anotherperson.sayHi(); //hi~
(6)寄生式组合继承
提出背景: 解决组合继承存在的效率问题,即父类构造函数始终会被调用两次
// 4.组合继承
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);//第二次调用SuperType,此时在新对象中创建实例属性name和colors
this.age = age;
}
// 继承方法
//SubType.prototype = new SuperType(); //第一次调用SuperType,此时SubType.prototype上有两个属性:name和colors 它们同时是SuperType的实例属性,同时成为了SubType.prototype上的原型属性
//SubType.prototype.constructor = SubType
// 解决:寄生式组合继承
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function () {
console.log(this.age);
};
寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是:不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(SubType, SuperType){
let protoype = object(SuperType.prototype) //Object.create(SuperType.prototype)//创建父类原型的一个副本
protoype.constructor = SubType //给返回的protoype设置constructor属性,解决由于重写原型导致默认constructor丢失的问题
SubType.protoype = protoype //将创建的对象赋给子类型的原型
}