JavaScript继承

继承是指一个对象能够直接使用另一个对象的属性方法

JS不提供原生的继承机制,下面来实现自定义继承
先定义两个类

function person(name, age){
  this.name = name
  this.age = age
}

person.prototype.printName = function(){
  console.log(this.name)
}

function male(sex){
  this.sex = sex
}

male.prototype.printSex = function(){
  console.log(this.sex)
}

属性的继承

属性获取的思路是在一个类中执行另一个类的构造函数,并且在执行时把另一个类中的this换成自己的(通过call来改变this),才能将这个属性继承到自己身上。
下面实现male类继承person的属性

function male(name, age, sex){
  person.call(this, name, age)
  this.sex = sex
}

male类实例化,看看属性中有没有person类中的name和age属性

var m = new male('hsc', 25, 'man')
console.log(m.name) // "hsc"
console.log(m.age) // 25

方法的继承

类的方法一般都定义在prototype中,想要实现方法的继承,需要将父类的原型放到原型链上即可

male.prototype = Object.create(person.prototype) //①
var m = new male('hsc', 25, 'man') //②
m.printName() //"hsc"
  • Object.create(person.prototype)方法创建了一个对象,该对象的__proto__指向了传入的参数person.prototype,然后将该对象赋值给子类的原型(male.prototype),最终实现了父类的原型加入到了原型链上
  • 修改子类的prototype一定要在创建子类实例之前,否则会报错(上述代码①和②位置交换就会报错)
  • 修改子类的prototype一定要在给子类的prototype添加自己的方法之前,否则子类自己的方法会被覆盖掉(printAge方法需要在①后面②之前定义)
  • 之所以将父类prototype加入原型链,而不是直接赋给子类的prototype,是防止修改子类的prototype也会修改父类的prototype

上面子类male的实例是可以调用父类person的printName方法了,相当于实现了方法的继承了,但是还是有点缺陷,我们知道prototype对象中有一个constructor属性指向其类型,但是我们复制的父元素的prototype,这时候constructor属性指向是不对的,是指向父类型person的。

console.log(male.prototype.constructor === person); //true

因此还需要修改一下constructor

male.prototype.constructor = male
console.log(male.prototype.constructor === person); //false
console.log(male.prototype.constructor === male); //true

代码归总

function inherit(superType, subType){
  var _prototype = Object.create(superType.prototype)
  _prototype.constructor = subType
  subType.prototype = _prototype
}

function person(name, age){
  this.name = name
  this.age = age
}

person.prototype.printName = function(){
  console.log(this.name)
}

function male(name, age, sex){
  person.call(this, name, age)
  this.sex = sex
}

inherit(person, male)
//子类方法的定义要在实现继承之后
male.prototype.printSex = function(){
  console.log(this.sex)
}

var m = new male('hsc', 25, 'man')
m.printName()              //"hsc"
console.log(m.age)         //25
m.printSex()               //"man"

有个问题,Object.create()是ES5的函数,如果不用ES5的写法的话,如下实现

function person(name, age){
  this.name = name
  this.age = age
}
person.prototype.printName = function(){
  console.log(this.name)
}

function male(name, age, sex){
  person.call(this, name, age)
  this.sex = sex
}

male.prototype = new person() 
//new person()相当于创建了一个对象,并且该对象的__proto__指向了person.prototype。
//该对象赋值给了male.prototype,所以male.prototype.__proto__ === person.prototype。
//即最终person的prototype被加入到了原型链上

male.prototype.printSex = function(){
  console.log(this.sex)
}
male.prototype.constructor =  male

var m = new male('hsc', 25, 'man')
m.printName()              //"hsc"
console.log(m.age)         //25
m.printSex()               //"man"
  • 核心思想和上面一样,是把父类的prototype放到子类实例的原型链上,这样子类的实例就可以掉用父类的方法了,这种方法也比较符合继承的理念

补充

hasOwnPerperty是Object.prototype的一个方法,可以判断一个对象是否包含自定义属性(属性是否是自己的),而不是原型链上的属性,hasOwnProperty是JavaScript中唯一一个处理属性但是不查找原型链的函数

m.hasOwnProperty('name');         // true,因为对象m有name这个属性
m.hasOwnProperty('sex');          // true,因为对象m有sex这个属性
m.hasOwnProperty('printName');    // false,因为对象m没有printName这个方法
m.hasOwnProperty('printSex');     // false,因为对象m没有printSex这个方法
m.__proto__.hasOwnProperty('printName');   // false,因为对象m.__proto__没有printName这个方法
m.__proto__.hasOwnProperty('printSex');    // true,因为对象m.__proto__有printSex这个方法
m.__proto__.__proto__.hasOwnProperty('printName');  // true,printName在m.__proto__.__proto__,即person.prototype上
person.prototype.hasOwnProperty('printName');       // true

最后附上一张手绘实现继承的图


继承

ps: 2018/7/12补充

发现一个问题

最近时间发现一个问题,使用male.prototype = Object.create(person.prototype)是没有问题的,而使用male.prototype = new person()会出现一个小问题,问题如下。

  • new person()创造出来的对象中,会包含person的属性(即name、age。即使你构造时没传参数,也会有,只不过两个的值为undefined),把它赋给male的prototype。也就是说,male.prototype上面会有name和age两个属性,这显然不是我们想看到的。我们期待的male的prototype,只有一个__proto__指向person.prototype,还有一个constructor指向male自身,不在有多余属性。因此,可以将male.prototype = new person()改成下面三行代码
var fn = function(){}
fn.prototype = person.prototype
male.prototype = new fn()

上述修改的原理是创建一个不带属性的空函数,然后把person的prototype赋给这个空函数的prototype。最后在把这个空函数new出来的对象赋给male.prototype。这样实现了只继承person的prototype中的方法,属性。而不会把person自身的属性加入到male的prototype中。

最后补充一下es6的类的写法

其实就是上面方法的语法糖

class Person{
  constructor(name, age){
    this.name = name
    this.age = age
  }
  printName(){
    console.log(this.name)
  }
}
class Male extends Person{
  constructor(name, age, sex){
    super(name, age)
    this.sex= sex
  }
  printSex(){
    console.log(this.sex)
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容