JS继承的七种实现方式

1. 构造函数继承

关键代码:Son:Parent.call(this,...args)

原理:在Son构造函数中,调用父类的构造函数,利用call改变了this指向,使得Son对象添加了name属性,完成继承

缺点:父函数原型链上的属性和方法不能被继承

(Child对象没有继承Parent对象的原型,只是通过调用父类构造方法,给子类添加了属性)

继承实现:

function Parent(name) {
  this.name = name
}
function Son(name, age) {
  // ******
  Parent.call(this, name)
  this.age = age
}
Parent.prototype.sayHi = function() {
  console.log('hi')
}

代码测试:

let p = new Parent('ls')
p.sayHi()// hi

let son = new Son('zs', 15)
console.log(son)
son.sayHi() // 报错

运行结果:

2.  原型链继承

关键代码: Son.prototype = new Parent();

原理:通过子类的原型指向父类构造函数的实例,使得子类可以沿原型链访问到父类及父类原型上的属性和方法;

优点:能继承构造函数属性,也能继承父类原型上的属性和方法

缺点: 父类原型上的引用类型的属性被修改后,可能会影响所有的实例

注意:// s2.address = {1:1} 这样对某个实例整体修改,只会改变该实例的address

        // s2.address[0] = 444 这样对某个引用属性的具体某一项进行修改,会改变所有实例

  只是可以继承父类的属性和方法,但在子类实例对象上并不具有父类的所有属性和方法

function Parent(name) {
  this.name = name
  this.address = ['122','144']
}
Parent.prototype.sayHi = function() {
  console.log('hi')
}
function Son(name, age) {
  this.name = name
  this.age = age
}
Son.prototype = new Parent('ls')

代码测试:

let s = new Son('ww',17)
console.log(s)//Son {name: 'ww', age: 17}
console.log(Son.prototype)// Parent {name: 'ls', address: Array(2)}
s.sayHi() // hi

let s2 = new Son('yj', 22)
console.log(s2)
s2.address[0] = 444
// s2.address = {1:1}
console.log(s.address)// [444, '144']
console.log(s2.address)// [444, '144']


s2.address = {1:1}
console.log(s.address)// [444, '144']
console.log(s2.address)// {1:1}

运行结果:

   只是可以继承父类的属性和方法(通过原型链查找到父类的属性和方法),但在子类实例对象上并不具有父类的所有属性和方法

 3. 组合继承

组合继承 :构造函数(Parent.call(this, name))

                  +原型链继承(Son.prototype = new Parent('zs'))

原理:结合前两者,即给子实例中添加了父类的属性和方法,也通过原型链子类可以访问父类及父类原型上的方法(父类上的属性和方法重复继承了两遍(耗内存),子类的构造函数会覆盖原型上的父类构造函数)

优点:解决了父类引用属性被共享的问题;子实例中具有父类的所有属性和方法

缺点:使用子类创建实例对象时,其原型中会存在两份相同的属性和方法,造成性能上的浪费;

继承实现:

function Parent(name) {
  this.name = name
  this.address = ['bj','xa','sh']
}
function Son(name, age) {
  // ******
  Parent.call(this, name)
  this.age = age
}
Parent.prototype.sayHi = function() {
  console.log('hi')
}
Son.prototype = new Parent('zs')

代码测试:

let s1 = new Son('ls', 22)
console.log(s1) // Son {name: 'ls', address: Array(3), age: 22}
let s2 = new Son('ww', 20)
console.log(s2) // Son {name: 'ww', address: Array(3), age: 20}

s1.address[0] = '11111'
console.log(s1.address) // ['11111', 'xa', 'sh']
console.log(s2.address) // ['bj', 'xa', 'sh']

运行结果:

可见,具有重复的属性

 可见,解决了引用参数共享的问题。

4. 原型式继承

核心原理:object.create()原理:

   用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了一个可以随意增添属性的实例或对象

实现方法:创建一个空函数,空函数的原型 等于 需要创建的对象,返回 对象的一个实例

特点:用函数封装,将传入的参数对象 变成了 返回参数对象的原型

缺点:所有实例都会继承原型上的属性,无法实现复用(新实例属性都是后面添加的)

继承实现:

function createObj(obj) {
  function Parent() { }
  Parent.prototype = obj
  return new Parent()
}

let person = {
  name: 'zs',
  age: 20,
  address: ['xa', 'sh'],
  sayHi: function() {
    console.log('hi')
  }
}

let p1 = createObj(person)
let p2 = createObj(person)
console.log(p1)
console.log(p2)
// p1,p2为两个空的父类实例对象,该实例对象的原型为:传入参数的对象 
// 将传入的参数对象 变成了 返回参数对象的原型
console.log(p1.__proto__ === person) // true
console.log(p2.__proto__ === person) // true

p1.sayHi()
p2.age = 32 // 给p2加了一个age属性=32
console.log('p1.age='+p1.age) // 20// 原型上的age
console.log('p2.age='+p2.age) // 32// 实例上的age

运行结果:

 5. 寄生式继承

核心思想:使用Object.create api克隆一个(传入的对象的拷贝)对象,并返回该拷贝对象

创建了一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象

优点:没有创建自定义类型,只是给原型式继承 套了个盒子,返回该对象

缺点:没用到原型,无法复用

继承实现:

function createObj(obj) {
  let cloneObj = Object.create(obj)
  cloneObj.sayHi = function() {
    console.log('Hi')
  }
  return cloneObj
}

let person = {
  name: 'zs',
  age: 20,
  address: ['xa', 'sh'],
  sayHi: function() {
    console.log('hi')
  }
}

let p1 = createObj(person)
let p2 = createObj(person)
console.log(p1)
console.log(p2)

console.log(p1.__proto__ === person) // true
console.log(p2.__proto__ === person) // true



p1.sayHi()//Hi
p2.age = 32
console.log('p1.age='+p1.age) // 20
console.log('p2.age='+p2.age) // 32

运行结果:

 6. 寄生组合式继承

核心代码:Son.prototype = Object.create(Parent.prototype)

              +Parent.call(this, name)

组合继承最大的问题:调用两次父类构造函数;一次是在创建子类时,一次是在子类构造函数内部

思想:组合继承的改良,通过子类的原型指向父类原型的拷贝,解决了组合继承的缺点

      不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个拷贝

      使用寄生式继承来继承父类原型,然后将返回的 新对象 赋给子类原型

代码实现:

function Parent(name) {
  this.name = name
  this.address = ['xa','sh','bj']
  this.ParentFun = function() {
    console.log('ParentFun')
  } 
}
Parent.prototype.sayHi = function() {
  console.log('Hi ! Parent.prototype.sayHi')
}

function Son(name,age) {
  Parent.call(this, name)
  this.age = age
}
Son.prototype = Object.create(Parent.prototype)
Son.prototype.SonFun = function() {
  console.log('SonFun')
}

测试:

let s1 = new Son('zs',12)
let s2 = new Son('ls',32)
console.log(s1)
console.log(s2)

运行结果:

可见,没有重复的父类属性和方法

测试修改父类引用属性的某项值:

s1.address[0] = 's1'
s2.address[0] = 's2'
console.log(s1.address)
console.log(s2.address)
console.log(s1)
console.log(s2)

运行结果

 可见,可以正常修改,且不影响其他实例的修改。

7. class + extends+super ES6继承

代码实现:

class Parent{
  constructor(name) {
    this.name = name
    this.address = ['sh','xa']
  }
  parentFun() {
    console.log('parent Function')
  }
}

class Son extends Parent{
  constructor(name,  age) {
    super(name)
    this.age = age
    this.grade = ['122','333']
  }
  parentFun() {
    console.log('Son Fun')
  }
  sonFun() {
    console.log('son own fun')
  }
}

测试代码:

let p = new Parent('fq', 44)
console.log(p)
let s = new Son('zs', 20)
console.log(s)

运行截图:

 在类中写的方法都会挂载到原型上?

总结:

1. 构造函数继承:Son:Parent.call(this,...args)

2. 原型链继承:Son.prototype = new Parent();

3. 组合继承:Son:Parent.call(this,...args) + Son.prototype = new Parent();、

4. 原型式继承:

 function createObj(obj) {
   function Parent() { }
   Parent.prototype = obj
   return new Parent()
 }

 5. 寄生式继承:

function createObj(obj) {
  let cloneObj = Object.create(obj)
  cloneObj.sayHi = function() {
    console.log('Hi')
  }
  return cloneObj
}

 6. 寄生组合式:Son.prototype = Object.create(Parent.prototype) + Parent.call(this, name)

7. ES6 继承:class + super + extends

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值