共计 3633 个字符,预计需要花费 10 分钟才能阅读完成。
JavaScript 常用继承方式主要分为(7 种):原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承以及继承多个对象。
1:原型链继承(核心:将父类的实例作为子类的原型)
基本概念:重写原型对象,赋予一个新的对象的实例。基本思想就是让一个原型对象指向另一个父类的实例。
function Super() { | |
// 基本数据类型 | |
this.text = 'Hello'; | |
} | |
Super.prototype.getSuperText = function() {return this.text;} | |
function Sub() {this.subText = 'Word';} | |
Sub.prototype = new Super(); | |
const instance = new Sub(); | |
console.log(instance); |
特点:非常纯粹的继承关系,实例是子类的实例,也是父类的实例。父类新增原型方法或属性,子类都能访问到。
优点:简单易于操作
缺点:对引用类型数据操作会互相(多个实例之间)影响
function Super() { | |
// 复杂对象,也就是引用类型 | |
this.value = [1, 2, 3, 4]; | |
} | |
Super.prototype.getSuperValue = function() {return this.value;} | |
function Sub() {this.subText = 'Word';} | |
Sub.prototype = new Super(); | |
const instance1 = new Sub(); | |
const instance2 = new Sub(); | |
instance1.value.push(5); | |
console.log(instance2.value); | |
// (5) [1, 2, 3, 4, 5] |
2:构造函数继承
// 定义构造函数 | |
function Super(){this.value = [1, 2, 3, 4]; | |
} | |
// 新增属性 getSuperValue | |
Super.prototype.getSuperValue = function() {return this.value;} | |
//sub 每次执行都要重新调用 | |
function Sub(){Super.call(this); | |
} | |
const instance1 = new Sub(); | |
instance1.value.push(5); | |
console.log(instance1.value); | |
// (5) [1, 2, 3, 4, 5] | |
const instance2 = new Sub(); | |
console.log(instance2.value); | |
// (4) [1, 2, 3, 4] |
构造函数的特点:(对引用数据类型没有影响)上面的代码输出 instance1 是 1,2,3,4,5,instance2 是 1,2,3,4。这是因为 sub 每次在执行时都是重新调用了一个 super.call(),而且构造函数在构建对象的过程中,每次都是创建了一个新的 object,因此 每次调用 sub 都会执行一遍 super,每次执行时都会有申请一个新的内存空间,所以得到的两个 value 值是不一样互不影响的。
缺点 :在整个构造函数的基础过程中,上面的代码并没有使用 proto 和 prototype 的属性,没有使用的话那么原型链就没有接上。所以说,构造函数基础 只能继承父类的实例属性和方法,不能继承原型链上的属性和方法。
3:组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
保留了构造函数继承与原型链继承的优点。但是执行了两次 Person,属性重复了。
function Person(name) { | |
this.name = name; | |
this.value = ["head", "body", "legs"]; | |
} | |
Person.prototype.getName = function() {return this.name;}; | |
// 构造函数继承 | |
function Teacher(name, school){ | |
// 执行又一次 Person | |
Person.call(this, name); | |
this.school = school; | |
} | |
// 原型链继承 | |
// 执行一次 Person | |
Teacher.prototype = new Person(); | |
const Eric = new Teacher("Eric",27); | |
Eric.getName(); | |
// 输出:Eric | |
// prototype 构造器指回自己 | |
Teacher.prototype.constructor = Teacher; | |
Teacher.prototype.getSchool = function() {return this.school;}; |
特点:既可以继承实例属性和方法,也可以继承原型属性和方法。既是子类的实例也是父类的实例,不存在引用属性共享的问题。可以传参,函数可复用。
缺点:调用两次父类构造函数,生成了两份实例。
4:原型式继承
借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。
原理:(本质)利用一个空对象作为一个中介。
const lakers = { | |
name: "lakers", | |
value: ["Micheal", "Wade", "Kobe"] | |
}; | |
const lakers1 = Object.create(lakers); | |
const lakers2 = Object.create(lakers); | |
lakers1.value.push('Fish'); | |
console.log(lakers); |
模拟 Object.create()
object.create()原理:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意添增属性的实例或对象。
Object.prototype.create = function(obj) {function Fun() {} | |
Fun.prototype = obj; | |
return new Fun();} |
缺点有两点:第一点是无法传递参数,第二点是引用类型存在变量的污染。
5:寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数。
目的:在原型式继承的基础上,寄生增加了一些新的方法和属性。
它的特点同原型式继承一样,也是无法传递参数,而且引用的数据类型也容易存在样式污染。
Object.createNew()
Object.prototype.createNew = function(obj){var newObj = Object.create(obj); | |
// 获取长度等于一个 function | |
newObj.getLength = function(){ ...}; | |
return newObj; | |
} |
6:寄生组合继承
目的:为了解决数据重复拷贝两遍的问题。
Super 只执行一次。
// 定义 Super 构造函数 | |
function Super(name) { | |
this.name = name; | |
this.value = ["Hello", "Word"]; | |
} | |
// 在 super 的原型链添加一个 getName | |
Super.prototype.getName = function() {return this.name;}; | |
// 定义 Sub | |
function Sub(name, age) { | |
// 调用构造函数继承 | |
Super.call(this, name); | |
this.age = age; | |
} | |
let prototype = Object.create(Super.prototype); | |
prototype.constructor = Sub; | |
Sub.prototype = prototype; | |
Sub.prototype.getAge = function(){return this.age;} | |
const instance1 = new Sub("Eric", 23); | |
const instance2 = new Sub("Vico", 23); | |
instance1.value.push("!"); | |
instance2.value.push("!!"); |
7:继承多个对象
借助原型式继承 Object.create 拿到 SuperClass,也就是父类,拿到父类的 prototype 之后把它赋给 ClassOne,再然后我们将 ClassTwo 的 prototype 使用一个 Object.assign,一个对象的拷贝,把它拷贝到 ClassOne 里面来,然后最后 ClassOne.prototype.constructor 等于 ClassOne。
也就是使用一个 Class.assign 把所有我们想要继承的父类的 prototype 全部组合到一起完成一个拷贝,之后再赋给对象。
function | |
ClassOne.prototype = Object.create(SuperClass.prototype); | |
Object.assign(ClassOne.prototype, ClassTwo.prototype); | |
ClassOne.prototype.constructor = ClassOne; |