使用es5和es6实现继承详解

1.所有的对象都是由Object构造的,所以对象的__ proto __最终都指向于Object.prototype

//下面两个都是对象
a = new Object()
Array.prototype
//所以他们的__proto__都等于Object.prototype
a.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype

2.js继承的实质就是两次的原型搜索就是继承
比如:

var a = new Array()
a.valueOf()

上面的a可以使用valueOf方法实际上是通过了两次原型链的搜索最终在Object.prototype里拿到了valueOf方法,所以可以说valueOf方法是继承来的

14361446-d413799edff51c68.png

再换一种通俗易懂的方法来描述对于下图

14361446-fa4b32ab998b533a.png

父元素div里面有个span,父元素div的fontsize是18px,如果给span设置一个14px,那span里面的字就是14px,这不叫继承,这是span本身就有的,而如果不给span设置字体大小span里的字就是18px,这叫继承,继承了div的18px,所以上面的Array.prototype就相当于span,Object.prototype就相当于div

自己实现原型链继承

14361446-07b528cc046021d0.png

上面的结构就是我们要实现的,其中子类的Human和父类Object之间的继承是自带的,我们需要在Human后再加一个Man继承Human

es5写法
function Human(name){
  this.name = name
}
Human.prototype.run = function(){
  console.log('走你')
}

function Man(){
    this.sex = '男'
}
Man.prototype.habit = function(){
    console.log('喜欢女人')
}
var object = new Man()
14361446-2ed83d036d90b7a3.png

上面的代码实现了最底层的Man,但是他还没有Human的属性和方法,因为男人是人的一种需要有名字和会跑,也就是我们需要Man继承Human的属性和方法,这时候我们只需要在第一层原型和第二层中间加一层,让这一层指向Human.prototype就行,然后因为每个人都有自己的名字,所以name加载sex下,也就是下图(这里注意一点chrome里面显示的__ proto __后面没有带prototype)

14361446-1289d55f8c8d487b.png

第一步:将name加到Man本身,也就是将Human本身的属性直接写在Man上,

function Man(name){
  this.sex = '男',
  //我们希望这里面把Human里自己的属性和方法都同样在Man里写一遍
//并且让this指向Man的实例对象
//所以我们就需要在这里调用Human并且把Man里的this传给它
Human.call(this,name)
}
14361446-0c597eaa190546b3.png

这时候就实现了我们的第一步,接下来就是在两层原型之间再加一层也就是让第一层的Man.prototype.__ proto __ = Human.prototype

14361446-371c590f7771b0a6.png

这样一个继承就实现了

最后把前面的代码总结一下:

function Human(name){
  this.name = name
}
Human.prototype.run = function(){
  console.log('走你')
}

function Man(name){
    this.sex = '男'
        Human.call(this,name)
}
Man.prototype.__proto__ = Human.prototype
//这里需要注意的问题是被修改的原型链的属性必须修改完后才能声明
//也就是这句话必须得写在habit前,否则原型链一修改它里面的属性和方法就没了
Man.prototype.habit = function(){
    console.log('喜欢女人')
}
  1. 继承的私房图
14361446-0b11b0fc974114da.png

构造函数里面有prototype,而函数.prototype实际上是一个对象,所以存的是一个内存地址,也就是存放共有属性的地址,上图中Object里的prototype存放的是左边的101,Human存放的是309,然后Man中存放的是401,之后当我们声明一个实例对象的时候(这里以图中的m为例),m里面就有我们的sex和name属性还有对象固有的__ proto __,因为实例对象. __ proto 指向构造函数.prototype所以它的 __ proto __ 指向的是Man.prototype而它存的地址是401,所以m里的 __ proto __ 指向的就是401,这也就是第一层;然后继续401也就是Man.prototype. proto 指向的是Human.prototype也就是309,这也就是第二层;之后309也就是Huam.prototype. proto 指向的是Object.prototype也就是101,这也就是第三层;最后101也就是Object.prototype. proto __指向的是null

  1. 改进
    因为ie不支持proto的操作,所以我们不能直接用
Man.prototype.__proto__  = Human.prototype

使用下面三句代码代替上面的一句

var f = function(){}
f.prototype = Human.prototype
Man.prototype = new f()

我们先通过一个简单的例子来对上面的三句话理解,比如我要实现一个a.__ proto __ = b,因为我不能直接操作__ proto __,但是我可以通过new来操作它,因为当我们new一个实例对象的时候,这里以var obj = new Fn()为例,其实这个new的过程中进行了五步操作

  1. 产生一个空对象
  2. this = 空对象
  3. this.__ proto __ = Fn.prototype
  4. 执行Fn.call(this,x,y,z...)
  5. 返回 this

一、首先我们想对__ proto __进行操作直接在new的第3步中就可以,只要能让this等于a,让Fn.prototype等于b就可以。首先让this等于a,因为this本身就是实例对象,所以我们只需要把obj换成a就可以了也就是a = new Fn(); 其次让Fn.prototype等于b,也就是直接将b赋值给Fn.prototype,即Fn.prototype = b
二、然后把a. __ proto __ = b换成我们一开始的Man.prototype.proto = Human.prototype,所以a就是Man.prototype,b就是Human.prototype这时候我们一中的Fn.prototype = b就可以换成Fn.prototype = Human.prototype,所以Fn就等于Human,这时候我们就可以把a = new Fn()换成Man.prototype = new Human(),所以Man.prototype = new Human()就等价于Man.prototype. __ proto __ = Human.prototype

问题:上面的Man.prototype = new Human()虽然实现了我们想要的Man.prototype. __ proto __ = Human.prototype,但是因为new过程中的第四步对this的操作,会导致我们的Human.prototype中会有我们不想要的东西,比如将上面的实现继承的代码进行更改

14361446-650fadff817215c0.png

运行后发现Man.prototype里面多了个name,因为在我们new Human()的时候,它会去执行Human在你当前对象中加个name,当前对象就是man.prototype,所以才会多个name,但是我们又不能直接把Human里的this.name=name删掉,所以我们就需要一个没有this.name=name的但是原型链是Human.prototype的空函数,也就是我们最开始的三句代码

var f = function(){}
f.prototype = Human.prototype
//这里之所以是new f()而不是new Human
//因为f就是去掉了this.name=name的Human
Man.prototype = new f()

最终es5实现继承的完整代码:

function Human(name){
  this.name = name
}
Human.prototype.run = function(){
  console.log('走你')
}

function Man(name){
    this.sex = '男'
    Human.call(this,name)
}
var f = function(){}
f.prototype = Human.prototype
Man.prototype = new f()
Man.prototype.habit = function(){
    console.log('喜欢女人')
}
es6实现继承
class Human{
    constructor(name){
        this.name = name
    }
    run(){
        console.log('走你')
    }
}
class Man extends Human{
    constructor(name){
        super(name)
        this.sex='男'
    }
    habit(){
        console.log('喜欢女人')
    }
}

代码详解:
es6中类的写法:自身属性写在constructor里面,共有属性(原型链上的)run直接和他并列着写
继承的写法:class后面是子类然后extends你的父类
也就是

Man extends Human
//等价于
Man.prototype.__proto__ = Human.prototype

然后super(name)就等价于Human.call(this,name),super就是调用Human

另外如果要使用class在原型中声明一个属性的话需要通过get

class Human{
    constructor(name){
        this.name = name
    }
    get age(){
        return 12
    }
    run(){
        console.log('走你')
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_34326429/article/details/88212572