继承是指一个对象能够直接使用另一个对象的属性和方法
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)
}
}
网友评论