原型链继承
让一个构造函数的原型等于另一个类型(父类)的实例,那么这个构造函数new出来的实例就具有该(父类)实例的属性。
实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
function Father(name){
this.name = name
this.say = function(){
console.log('My name is '+ this.name)
}
}
Father.prototype.age = '10'
function Son(){
this.name = 'son'
}
Son.prototype = new Father()
var son = new Son()
son.say()
console.log(son.age)
// My name is son
// 10
优缺点
优点:写法方便简洁,容易理解。
缺点:
- 新实例无法向父类构造函数传参。
- 因为Son.prototype(即原型对象)继承了Father实例化对象,这就导致了所有Son实例化对象都一样,都共享有原型对象的属性及方法。(在父类型构造函数中定义的引用类型值的属性,会在子类型原型上变成原型属性被所有子类型实例所共享)
function Father(name){
this.name = name
this.type = ['html','css','js']
this.number = 20
this.say = function(){
console.log('My name is '+ this.name)
}
}
function Son(){
this.name = 'son'
}
Son.prototype = new Father()
var son1 = new Son()
var son2 = new Son()
son2.type.push('vue')
son2.number = 30
console.log(son1.type) // ["html","css","js","vue"]
console.log(son2.type) // ["html","css","js","vue"]
console.log(son1.number) // 20
console.log(son2.number) // 30
构造函数继承
在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
function Father(name){
this.name = name
this.type = ['html','css','js']
this.say = function(){
console.log('My name is '+ this.name)
}
}
function Son(name,age){
Father.call(this,name)
this.age = age
}
var son1 = new Son('xg',20)
var son2 = new Son('zs',25)
son2.type.push('vue')
console.log(son1.type) // ["html","css","js"]
console.log(son2.type) // ["html","css","js","vue"]
son1.say() // My name is xg
son2.say() // My name is zs
console.log(son1.name) // xg
console.log(son2.age) // 25
优缺点
优点:
- 解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
- 可以继承多个构造函数属性(call多个)
缺点:
- 方法都在构造函数中定义,每次实例化对象都得创建一遍方法,基本无法实现函数复用。
- call方法仅仅调用了父级构造函数的属性及方法,没有办法调用父级构造函数原型对象的方法。
- 每个新实例都有父类构造函数的副本,臃肿。
function Father(name){
this.name = name
this.type = ['html','css','js']
this.say = function(){
console.log('My name is '+ this.name)
}
}
Father.prototype.run = function(){
console.log('run')
}
function Son(name,age){
Father.call(this,name)
this.age = age
}
var father = new Father()
var son1 = new Son('xg',20)
var son2 = new Son('zs',25)
father.run() // run
son1.run() // Uncaught TypeError: son1.run is not a function
组合继承
利用原型链继承和构造函数继承的各自优势进行组合使用。
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
function Father(name){
this.name = name
this.say = function(){
console.log('My name is '+ this.name)
}
}
Father.prototype.run = function(){
console.log('run')
}
function Son(name,age){
Father.call(this,name) // 实现对实例属性的继承
this.age = age
}
Son.prototype = new Father() // 继承原型属性/方法
var son1 = new Son('xg',20)
son1.say() // My name is xg
son1.run() // run
优缺点
优点:
- 解决了原型链继承和借用构造函数继承造成的影响,可以传参,可以复用。
- 每个新实例引入的构造函数属性是私有的。
缺点:
- 调用了两次父类构造函数(耗内存):一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
原型式继承
在一个函数内部创建一个临时性的构造函数,然后将传入的参数(对象)作为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上,函数fun是对传入的对象执行了一次浅复制。
function fun(obj){
function Son(){}
Son.prototype = obj
return new Son()
}
var father= {
name:'xg'
}
var son1 = fun(father)
console.log(son1.name)
ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型对象的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的函数fun方法效果相同。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log(rect.x) // 0
console.log('Is rect an instance of Rectangle?',
rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
rect instanceof Shape); // true
rect.move(1, 1); // 'Shape moved.'
优缺点
优点:
- 不需要单独创建构造函数
缺点:
- 属性中包含的引用值始终会在相关对象间共享
function fun(obj){
function Son(){}
Son.prototype = obj
return new Son()
}
var father= {
name:'xg',
type:['html','css']
}
var son1 = fun(father)
var son2 = fun(father)
son2.type.push('js')
console.log(son1.type) // ["html","css","js"]
寄生式继承
在原型式继承的基础上,在函数内部丰富对象。
寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function fun(obj){
function Son(){}
Son.prototype = obj
return new Son()
}
function Jisheng(obj){
var clone = fun(obj)
clone.say = function (){
console.log('jisheng')
}
return clone
}
var father= {
name:'xg'
}
var son1 = Jisheng(father)
son1.say()
优缺点
优点:
- 写法简单,不需要单独创建构造函数
缺点:
- 调用一次函数就得创建一遍方法,无法实现函数复用,效率较低
寄生组合式继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承父类的原型,然后再将结果指定给子类型的原型。
组合继承方法的缺点是两次调用父级构造函数,一次是在创建子级原型的时候,另一次是在子级构造函数内部,那么我们只需要优化这个问题就行了,即减少一次调用父级构造函数,正好利用寄生继承的特性,继承父级构造函数的原型来创建子级原型。
function Father(name){
this.name = name
this.type = ['html','css']
}
function Son(name){
Father.call(this,name)
}
Father.prototype.say = function(){
console.log(this.name)
}
function Jisheng(son,father){
var clone = Object.create(father.prototype)
son.prototype = clone // 指定对象
clone.constructor =son // 增强对象
}
var father = new Father('xg')
father.say() // xg
Jisheng(Son,Father)
var son1 = new Son('xxg1')
son1.say() // xxg
var son2 = new Son('xxg2')
son2.type.push('js')
console.log(son1.type) // ["html","css"]
console.log(son2.type) // ["html","css","js"]
优缺点
优点:
- 高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。
缺点:
- 代码复杂
- 需要手动绑定constructor
Class (ES6)
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。
super关键字用于访问和调用一个对象的父对象上的函数。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class Person { //调用类的构造方法
constructor(name, age) {
this.name = name
this.age = age
} //定义一般的方法
showName() {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
}
let p1 = new Person('xg', 25)
console.log(p1) //定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age) // 通过super调用父类的构造方法
this.salary = salary
}
showName() {//在子类自身定义方法
console.log("调用子类的方法")
console.log(this.name, this.age, this.salary);
}
/*
showName(){
super.showName() // 调用父类的showName()
}
*/
}
let s1 = new Student('xxg', 18, 1000000000)
console.log(s1)
s1.showName()
// {"name":"xg","age":25}
// {"name":"xxg","age":18,"salary":1000000000}
// 调用子类的方法
// xxg,18,1000000000
优缺点
优点:
- 语法简单易懂,操作更方便。
缺点:
- 并不是所有的浏览器都支持class关键字
代码、理论知识及图片绝大多数来自看过的博客&视频
俺只是加以整合并写了部分注释便于自己理解