日常好奇-看看ES6的类如何实现的[一]

为了真正理解ES6中类的概念,来学习类是如何实现的

我们都知道在JS中,函数是“一等公民”,“类”的概念是在ES6中提出的,它好像跟我们自己写的函数构造器一样,但又有好像有些不一样的地方,那么它到底是如何实现的那?为了达到这个目的,我们利用babel来看下它编译后的代码。

不带继承的类

首先我们写一个简单的类,该类没有任何继承,只有一个简单的构造函数和getName函数

class App {
	constructor(name) {
		this.name = name;
	}
	
	getName() {
		return this.name;
	}
}
复制代码

然后babel一下,我们得到以下结果:

"use strict";

var _createClass = function () {
  function defineProperties(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);
    }
  }
  return function (Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var App = function () {
  function App(name) {
    _classCallCheck(this, App);

    this.name = name;
  }

  _createClass(App, [{
    key: "getName",
    value: function getName() {
      return name;
    }
  }]);

  return App;
}();
复制代码

东西还挺多,一眼并看不出来什么东西来,我们接下来一点点分析。我们先看最后一个函数:

// 立即执行函数
var App = function () {

  // 构造函数变形成这样
  function App(name) {
	
    // 从这个函数的名字上看,好像是类调用检查,我们暂时先不看这个函数
    _classCallCheck(this, App);

    this.name = name;
  }

  // 调用了一个_createClass函数,应该是在给App附加一些值
  _createClass(App, [{
    key: "getName",
    value: function getName() {
      return name;
    }
  }]);

  // 返回一个名为App的函数
  return App;
}();
复制代码

下面来看_createClass函数,该函数用来定义各个属性值:

// 从返回值看,该函数是一个高阶函数
var _createClass = function () {

  // 为目标值添加多个属性
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {

      // 开始设定描述符对象
      var descriptor = props[i];

      // 默认不可枚举
      descriptor.enumerable = descriptor.enumerable || false;

      // 默认可配置
      descriptor.configurable = true;

      // 存在value值则默认可写
      if ("value" in descriptor) descriptor.writable = true;

      // 使用Object.defineProperty来设置属性
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  // 函数接收三个参数,分别是:构造函数,原型属性,静态属性
  return function (Constructor, protoProps, staticProps) {

    // 为构造函数prototype添加属性(即为用构造函数生成的实例原型添加属性,可以被实例通过原型链访问到)
    if (protoProps) defineProperties(Constructor.prototype, protoProps);

    // 为构造函数添加属性
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();
复制代码

好像很简单,跟我们平时使用函数实现差别不是很多,就相差了一个描述符的设定过程。最后看一下类调用检查函数_classCallCheck

// 类调用检查,不能像普通函数一样调用,需要使用new关键字
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
复制代码

增加了错误处理,当我们调用方式不正确时,抛出错误。

模拟实践

我们简单实现以下没有继承的方式,来加深我们的印象,为了简化不添加错误处理和描述符的设定过程。

var App = function(name) {
  this.name = name;
}

App.prototype.getName = function() {
  return this.name;
}

var app = new App('miniapp');

console.log(app.getName()); // 输出miniapp
复制代码

这个很简单,就是我们平常模拟“类”所使用的方法,js所有对象都是通过原型链的方式“克隆”来的。注意我们这里的App不能叫做类,在js中没有类的概念。它是一个函数构造器,它可以被当做普通函数调用,也可以被当做函数构造器调用,调用函数构造器使用new关键字,函数构造器会克隆它的prototype对象,然后进行一些其他操作,如赋值操作,最后返回一个对象。

下面想一个问题,实现继承我们一般都是利用原型链的方式,像下面这样:

var dog = {
  name: 'goudan'
};

var animal = {
	getName: function() {
    return this.name;
  }
}

// 对象的原型通过`__proto__`暴露出来(tip: 实际中不要这么写)
dog.__proto__ = animal;
console.log(dog.getName()); // 输出goudan
复制代码

我们如何在两个类之间继承那?在ES6中实现很简单

class Animal {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class Dog extends Animal{
  constructor(name) {
    super(name);
    this.name = name;
  }
}
复制代码

如果我们自己实现一个要怎么实现,我们先写一个:

var Animal = function(name) {
  this.name = name;
}

Animal.prototype.getName = function() {
  return this.name;
}

var Dog = function(name) {
	Animal.call(this, name);
  this.name = name;
}

Dog.prototype = Animal.prototype;

var dog = new Dog('goudan');
console.log(dog.getName()); // 输出goudan
复制代码

但这种方式总感觉不太好,那么ES6中的的继承是如何实现的?我们下一篇继续一起学习

猜你喜欢

转载自juejin.im/post/5b4fd3686fb9a04f8856cef0
今日推荐