原型继承的方式
1. 原型链继承
💡 方式: 将构造函数的实例赋值给原型对象
// 父类构造函数
function superf() {
this.name = "xuxiaotong";
this.color = ["red","green"]
}
// 子类构造函数
function subf() {
this.age = "21";
}
// 原型链继承
subf.prototype = new superf();//subf继承了superf,通过原型,形成链条
// 子类实例
var test = new subf();
console.log(test.__proto__.__proto__ === superf.prototype) //true
保留了 instanceof 操作符和 isPrototypeOf() 方法判断继承关系的能力
console.log(superf.prototype.isPrototypeOf(test)) // true
console.log(test instanceof superf) // true
❌ 存在问题:
-
引用共享: 原型中包含的所有引用值会在所有实例间共享
test.color.push("balck") var test1 = new subf() console.log(test1.color) // [ 'red', 'green', 'balck' ] -
超类无法传参: 子类在实例化时不能给父类的构造函数传参,无法在不影响所有对象实例的情况下把参数传进父类的构造函数中
2. 盗用构造函数
💡 方式: 在子类构造函数中调用父类的构造函数,使用 apply()、call() 方法以新创建的对象为上下文执行构造函数
// 父类构造函数
function superf(name) {
this.name = name
this.color = ["red","green"]
this.sayColor = () => {
console.log(this.color)
}
}
// 父类原型上的方法
superf.prototype.sayName = function(){
console.log(this.name)
}
// 子类
function subf(name, age) {
superf.call(this, name) // 盗用构造函数
this.age = age
}
✅ 优点:
-
在子类实例化时可以向父类构造函数传参
let test = new subf("Tom", 23) console.log(test.age) // 23 console.log(test.name) // Tom -
没有引用共享的问题:
superf.call(this, name)为子类的实例创建的新对象的上下文执行父类的初始化代码,每个实例都有自己的 color 属性test.color.push("balck") var test1 = new subf() console.log(test1.color) // [ 'red', 'green']
❌ 存在问题:
- 必须在构造函数中定义方法,通过父类原型定义的方法无法被子类访问到,无法复用
test.sayColor() // [ 'red', 'green', 'balck' ]
test1.sayColor() // [ 'red', 'green']
// console.log(test.sayName()) // 子类实例对象调用父类原型上的方法报错
3. 组合继承
💡 方式:
- 在子类构造函数中调用父类的构造函数
- 把父类的实例对象赋值给子类的原型对象
function superf(name) {
this.name= name
}
superf.prototype.getName = function() {
return this.name
}
// 盗用构造函数继承父类构造函数上的属性和方法
function subf(name,age) {
superf.call(this, name)
this.age = age
}
// 使用原型链继承父类原型上的属性和方法
subf.prototype = new superf()
var test = new subf("Tom", 23)
console.log(test.getName()) // Tom
弥补了原型链继承和盗用构造函数的不足
保留了 instanceof 操作符和 isPrototypeOf() 方法判断继承关系的能力
console.log(superf.prototype.isPrototypeOf(test)) // true
console.log(test instanceof superf) // true
❌ 存在问题: 调用了两次父类构造函数,存在效率问题
4. 原型式继承
💡 方式:
- 创建一个临时构造函数
- 将传入的对象赋值给这个构造函数的原型
- 返回临时构造函数的一个实例
function obj(o) {
function F(){}
F.prototype = o
return new F()
}
let person = {
name: "Tom",
hobby: ["sing", "dance"]
}
let person1 = obj(person)
person1.name = "Ami"
person1.hobby.push("swim")
let person2 = obj(person)
person2.name = "John"
person2.hobby.push("read")
console.log(person.name) // Tom
console.log(person1.name) // Ami
console.log(person2.name) // John
console.log(person.hobby) // [ 'sing', 'dance', 'swim', 'read' ]
console.log(person1.hobby)// [ 'sing', 'dance', 'swim', 'read' ]
console.log(person2.hobby)// [ 'sing', 'dance', 'swim', 'read' ]
es5 通过 增加 Object.create(obj[, {新增的属性}]) 方法将原型式继承的概念规范化了。
-
当只有一个参数时,
Object.create()跟obj()方法的效果相同 -
Object.create()第二个参数新增的属性会遮蔽原型上的同名属性let person3 = Object.create(person, { name: {value:"Samu"}, }) console.log(person3.name) // Samu
✅ 使用场景: 有一个对象,想在已有对象的基础上创建一个新的对象
❌ 存在问题: 引用共享问题,属性中包含的引用值始终会在相关对象空间中共享
5. 寄生式继承
💡 方式: 创建一个实现继承的函数,以某种方式增强对象,然后返回对象
function createAnother(original) {
let clone = Object(original) // 1.调用函数创建一个新的对象
clone.sayHi = function(){ // 2. 增强对象
console.log("Hi")
}
return clone
}
let person = {
name: "Tom",
hobby: ["sing", "dance"]
}
let person1 = createAnother(person)
person1.sayHi() // Hi
注意: 这里调用函数创建一个新的对象不一定要通过 Object(original),其他返回新对象的函数都可以
✅ 使用场景: 适合主要关注对象,不在乎类型、构造函数的场景
❌ 存在问题:
-
引用共享
let person2 = createAnother(person) person1.hobby.push("swim") console.log(person1.hobby)// [ 'sing', 'dance', 'swim' ] console.log(person2.hobby)// [ 'sing', 'dance', 'swim'] -
复用问题
6. 寄生式组合继承
💡 方式:
- 盗用构造函数继承属性
- 寄生式继承父类原型
- 让返回的新对象赋值给子类的原型
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); //1. 盗用构造函数继承属性
this.age = age;
}
function inheritPrototype(subType, superType){
// 2. 寄生式继承父类原型
var prototype = Object(superType.prototype); // 创建父类原型的副本
prototype.constructor = subType; // 让副本的 constructor 属性指向子类
subType.prototype = prototype; // 让子类原型指向副本
}
inheritPrototype(SubType, SuperType);//实现继承
SubType.prototype.sayAge = function(){
console.log(this.age);
};
let test = new SubType("Tom", 23)
test.sayName() // Tom
✅ 优点:
- 解决了组合式继承两次调用构造函数的问题
- 保留了
instanceof操作符和isPrototypeOf()方法判断继承关系的能力
✅ 使用场景: 引用类型继承的最佳模式