从babel实现es6类的继承来深刻理解js的原型及继承

先聊个5毛钱的背景吧

自从有了babel这一个利器以后,es6如今已经被普遍的使用。JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。也就是说babel不管是实现类,仍是实现继承,本质上都是基于原型及原型链的,那我咱们就顺这这个思路,一步一步往下走,直到揭开babel是如何实现类及类的继承的。javascript

原型和原型链

javascript中原型的概念比较抽象,不是很好理解,咱们仍是老老实实上代码,用代码来举例说明。前端

function Person (name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
let p1 = new Person('张三', 18, '前端攻城狮')
复制代码

咱们建立了一个Person的构造函数,并用new 建立了一个该对象的实例对象p1。java

  • 在js中,每个函数都有一个prototype属性。
  • 每个js对象(除null)外都有一个__proto__的属性。 那么问题来啦,函数Person的prototype的属性指向的是什么呢?是否是Person的原型呢?咱们在chrome中输入以下代码

    咱们发现Person.prototype指向一个对象,实际上这个对象就是p1的原型。如图:
    所以,咱们也能够用下面的这个图来表示他们之间的关系
  • 前面咱们说过,Person.prototype指向一个对象,那么这个对象中都有什么呢?咱们再在chrome中输入以下代码如图:
    咱们发现Person.prototype的确是一个对象,这个对象里面有两个属性:constrcutor, 和__proto__这两个属性。constructor指向Person(),也说明js对象果然都有一个__proto__的属性啊。那问题来啦constructor到底和Person是什么关系呢?实际上,constructor属性指向的就是构造函数自己。有图有真相

咱们再来总结一下吧:

  • 每一个原型上都有一个constructor的属性指向关联的构造函数。他们的关系以下:
    前面咱们发现Person.prototype也有__proto__属性,那Person.prototype.__proto__是什么呢?换言之,原型的原型是什么呢。先来一张图
    咱们发现它只有constructor属性,该属性指向Object(),没有__proto__属性。是否是颇有意思。那咱们再作个实验吧。如图

经过这张图咱们发现

  • 原型对象就是经过Object构造函数生成的,实例的__proto__指向构造函数的prototype,Object构造函数的原型的__proto__指向null
  • 咱们用一张图总结一下吧:
    实际上这个就是原型链啦。

ES5中的继承

前面咱们聊了聊原型和原型链,其实就是为了咱们聊继承作铺垫的。废话很少说,咱们上代码吧:es6

// 咱们定义一个动物类,里面有一个类型的属性
function Animal (type) {
    this.type = type
}
// 动物都有吃东西的方法
Animal.prototype.eat = function () {
    console.log('我在吃东西')
}
// 咱们来个小猫咪类吧
function Cat () {}
// 如今咱们要实现让Cat继承Animal的属性和方法,该怎么作呢?
复制代码
  • 第一种方法
// 咱们将cat类的原型直接指向Animal的实例
Cat.prototype = new Animal()
let cat = new Cat()
console.log(cat.eat())
复制代码

结果如图 chrome

咱们发现没法继承父类的属性。咱们改造一下,借助call方法,代码入下:

function Animal (type) {
    this.type = type
}
Animal.prototype.eat = function () {
    console.log('我在吃东西')
}
function Cat (type) {
    Animal.call(this, type)
}
Cat.prototype = new Animal()
let cat = new Cat('喵咪')
console.log(cat.eat())
console.log(cat.type)
复制代码

运行结果以下:数组

nice!!!彷佛完美的解决了问题,但真的是这样么? 我就不卖关子啦,其实是有问题的,有什么问题呢?咱们上代码:

function Animal (type, val) {
    this.type = type
    this.sum = [4,5,6]  // 随便给的属性啊,为了说明问题。
    this.setSum = function () {
        this.sum.push(val)
    }
}
Animal.prototype.eat = function () {
    console.log('我在吃东西')
}
function Cat (type, val) {
    Animal.call(this, type, val)
}
Cat.prototype = new Animal()
let cat = new Cat('喵咪', 1)
let cat2 = new Cat('猫咪2', 2)
console.log(cat.setSum())
console.log(cat2.setSum())
console.log(cat.sum)
console.log(cat2.sum)
复制代码

运行结果如图: bash

发现了没有,图中setSum方法和sum属性都是父类定义的,可是子类能够调用。因此这个不是咱们想要的结果;还有一点就是Cat的constructor属性,此时指向的并非Cat而是Animal。那应该怎么解决呢?

  • 第二种方法: 这个时候咱们就不得不拿出咱们的终极神器啦。它就是ES5的Object.create()方法。代码以下:
function inherit(C, P) {
    // 等同于临时构造函数
    C.prototype = Object.create(P.prototype);

    C.prototype.constructor = C; // 修复constructor
    C.super = P;//存储超类
}
function Animal(type) {
    this.type = type;
}
Animal.prototype.eat = function () {
    console.log('动物都是要吃饭滴')
}
function Cat(type, talk) {
    Cat.super.call(this, type)
    this.talk = talk
    this.getTalk = function () {
        return this.talk
    }
}
inherit(Cat, Animal)
let cat = new Cat('猫咪', '咱们一块儿学猫叫')
console.log(cat.getTalk())
复制代码

代码运行以下:babel

这样咱们就比较完美的解决了继承的问题。

babel对es6类的实现

在背景中咱们已经聊到了es6中类的继承其实是一个语法糖,如今咱们就想办法拨开这颗糖。函数

  • es6类重写上面代码
class Animal {
	constructor (type) {
    	this.type = type
    }
  	eat () {
    	console.log('动物都要吃饭')
    }
}

class Cat extends Animal {
  	constructor (type,talk) {
    	super(type)	  // 继承父类constructor的属性
      	this.talk = talk
    }
  	getTalk () {
    	return this.talk
    }
}
let cat = new Cat('喵咪', '喵喵喵')
console.log(cat.getTalk())  // 喵喵喵
console.log(cat.type) // 喵咪
复制代码
  • babel中类的实现
    babel中类的实现主要是三个步骤:
    • 步骤一: 构造函数的实现,这部分的实现与咱们普通的构造函数的区别就是,经过一个自执行函数包裹,代码以下
      var Animal = function () {
           function Animal (type) {
               this.type = type
               this.getType = function () {
                   return this.type
               }
           }
           return Animal   
          }()
          
          var Cat = function () {
             function Cat (talk) {
                 this.talk = talk
                 this.getTalk = function () {
                     return this.talk
                 }
             }
             return Cat
          }()
      复制代码
    • 步骤二: 以下代码:
      function Animal() {
              return {
                  name: '牛逼',
                  age: 18
              }
          }
          let animal = new Animal() 
          console.log(animal) // { name: '牛逼', age: 18}
      复制代码
      所以babel中对此作了校验,这就是第二步要干的事情。代码以下:
      var _classCallCheck = function (instance, Constructor) {
              if (!(instance instanceof Constructor)) {
                  throw new TypeError("不能返回一个对象")
              }
          }
          // 嗯,你没有看错就是这么几行代码
          var Animal = function () {
              function Animal (type) {
                  _classCallCheck(this, Animal)
                  this.type = type
              }
          }()
          ...此处省略喵咪类
      复制代码
    • 步骤三: 原型上方法的添加。前面两步主要是对构造函数的实现及校验,步骤三就是把原型上的方法添加到对应的类上。这里咱们主要要聊聊babel是如何对 es6中类的方法的处理,也就是
      class Animal {
              eat () {
                  console.log('aaa')
              }
              sleep () {
                  console.log('bbb')
              }
          }
      复制代码
      如上面代码中的eat方法和sleep方法的处理。那怎么能把方法添加到一个对象上呢?没错,Object.defineProperty()这个方法就是用来干这件事情的,babel中也是用他来处理的。回到咱们的话题,在babel中,它是经过_createClass这个函数来处理的。废话很少说咱们上代码:
      /* 
          _createClass()这个函数接受两个参数,
          一个是这个类的构造函数,即给哪一个类添加方法。
          第二个参数是一个数组对象,相似这样婶的
          [{key: 'eat', val: function eat () { console.log('aaa') }}]  
          是否是恍然大悟?那接下来咱们就实现一下吧
          */
          function definePropties (target, props) {
              for (var i = 0; i < props.length; i++) {
                  var descriptor = props[i];
                  descriptor.enumerable = descriptor.enumerable || false;
                  descriptor.configurable = true;
                  if ('value' in descriptor) descriptor.writable = true;
                  Object.defineProperty(target, descriptor.key, descriptor)
              }
          }
          var _createClass = function () {
              return function (Constructor, protoProps, staticProps) {
                  // 添加原型上的方法
                  if (protoProps) definePropties(Constructor.prototype, protoProps)
                  // 添加静态的方法
                  if (staticProps) definePropties(Constructor, staticProps) 
                  return Constructor
              }
          }()
          _createClass(Animal,[{
              key: 'eat',
              value: function () {
                  console.log('aaa')
              }
          }])
      复制代码
    到这里babel实现类就基本上实现啦。上面代码能够多看,多思考,仍是挺收益的。

babel中继承的实现

接下来,终于到了咱们的大boss啦,上面咱们聊了聊babel中是怎么处理es6中类的,这节咱们就聊聊babel中是怎么处理es6中的继承。ui

  • babel中继承的实现 babel中继承的实现的思路基本跟咱们前面聊的es5的继承思路基本一致。下面咱们来聊一聊。
function _inherits(subClass, superClass) {
    subClass.prototype = Object.create(superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true;
        }
    })
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}
复制代码

好啦,这个话题就聊到这里,欢迎你们拍砖啊。

相关文章
相关标签/搜索