javascript继承

原型链继承

让一个构造函数的原型等于另一个类型(父类)的实例,那么这个构造函数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关键字

代码、理论知识及图片绝大多数来自看过的博客&视频
俺只是加以整合并写了部分注释便于自己理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值