【重学前端】es6中class做了什么

前言

在JavaScript中不论是es5之前利用function定义一个对象的构造方法还是es6利用class定义类,都可以实现对象的实例化

  • es5中定义一个对象
// es5
function Parent () {
  this.name = 'parent'
  this.age = 22
  this.work = function () {
    console.log('the ' + this.name + ' is working')
  }
}

Parent.prototype.speak = function () {
  console.log('hello!')
}

Parent.prototype.color = 'yello'
  • es6中定义一个对象
class Parent {

  constructor () {
    this.name = 'parent'
    this.age = 22
  }

  work () {
    console.log('the ' + this.name + ' is working')
  }
}

Parent.prototype.speak = function () {
  console.log('hello!')
}

Parent.prototype.color = 'yello'

class Son extends Parent {
  constructor () {
    super()
    this.sonName = 'son'
  }
}

const son = new Son()

上述两种方法是等效的,都可以通过关键字new进行实例化

es6的class做了什么

我们利用babel转码工具对es6的class进行转码得到以下es5代码

'use strict';

// _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; 
      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; 
  }; 
}();

// _possibleConstructorReturn
function _possibleConstructorReturn(self, call) { 
  if (!self) { 
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
  } 
  
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

// _inherits
function _inherits(subClass, superClass) { 
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
  } 
  
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); 
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

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

// Parent 类声明
var Parent = function () {
  function Parent() {
    _classCallCheck(this, Parent);

    this.name = 'parent';
    this.age = 22;
  }

  _createClass(Parent, [{
    key: 'work',
    value: function work() {
      console.log('the ' + this.name + ' is working');
    }
  }]);

  return Parent;
}();

Parent.prototype.speak = function () {
  console.log('hello!');
};

Parent.prototype.color = 'yello';

// Son类声明(继承Parent)
var Son = function (_Parent) {
  _inherits(Son, _Parent);

  function Son() {
    _classCallCheck(this, Son);

    var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));

    _this.sonName = 'son';
    return _this;
  }

  return Son;
}(Parent);

// Son类实例化
var son = new Son();

从以上代码可以看出,es6的class转码后分为以下几个部分

  • _createClass
  • _classCallCheck
  • _inherits继承
  • _possibleConstructorReturn(super 方法)
  • Parent定义
  • Parent原型方法及属性定义
  • Son类定义(继承Parent)
  • Son实例化

_createClass 方法

核心:通过一个闭包+立即执行函数,内部维护一个私有的defineProperties方法,遍历传入的自定义对象属性列表,调用Object.defineProperty给类添加方法,将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上

  • 代码解析
var _createClass = function () {
  /**
   * 创建class方法
   * @param {*} target 目标对象
   * @param {*} props 属性 Array<{ key: string, value: Function | String | Number | ...}>
   */
  function defineProperties(target, props) {
    // 遍历传入的属性列表,调用Object.defineProperty向目标对象上添加定义属性
    for (var i = 0; i < props.length; i++) { 
      var descriptor = props[i]; 
      // 属性描述符 enumerable 是否可枚举
      descriptor.enumerable = descriptor.enumerable || false; 
      // 属性描述符 configurable 该属性的属性描述符是否可改变
      descriptor.configurable = true; 
      // 属性描述符 writable 如果设置值, writable为true : value可以被赋值运算符修改
      if ("value" in descriptor) descriptor.writable = true; 
      Object.defineProperty(target, descriptor.key, descriptor); 
    } 
  } 
  
  // 暴露一个方法,接收参数
  /**
   * 
   * @param {*} Constructor // 目标对象
   * @param {*} protoProps // 非静态方法
   * @param {*} staticProps // 静态方法
   */
  return function (Constructor, protoProps, staticProps) {
    // 非静态方法,添加到构造函数(对象)的原型对象上 
    if (protoProps) defineProperties(Constructor.prototype, protoProps); 
    // 静态方法,添加到构造函数上
    if (staticProps) defineProperties(Constructor, staticProps); 
    return Constructor; 
  }; 
}();
  • js对象的属性描述符(JSPropertyDescriptor)
Field Name Description
getter get 语法为属性绑定一个函数,每当查询该属性时便调用对应的函数,查询的结构为该函数的返回值
setter 如果试着改变一个属性的值,那么对应的 setter 函数将被执行
value 描述指定属性的值 , 可以是任何有效的 Javascript 值(函数 , 对象 , 字符串 ...).
configurable 当且仅当该属性的 configurable 为 true 时,该属性 描述符 才能够被改变, 同时该属性也能从对应的对象上被删除.
enumerable 描述指定的属性是否是 可枚举 的.
writable 当且仅当该属性的 writabletrue 时, value 才能被赋值运算符改变。

_classCallCheck方法解析

/**
 * 防止类的构造函数以普通函数的方式调用
 * 判断构造函数是否存在于传入实例的原型链上(实例是构造函数的实例对象)
 * @param {*} instance 实例
 * @param {*} Constructor 构造函数
 */
function _classCallCheck(instance, Constructor) {
  // instance 即传入的this对象,判断Constructor是否存在于this的原型链上
  if (!(instance instanceof Constructor)) { 
    throw new TypeError("Cannot call a class as a function"); 
  } 
}

new的原理

/**
 * 创建一个new操作符
 * @param {*} Con 构造函数
 * @param  {...any} args 忘构造函数中传的参数
 */
  function createNew(Con, ...args) {
    let obj = {} // 创建一个对象,因为new操作符会返回一个对象
    Object.setPrototypeOf(obj, Con.prototype) // 将对象与构造函数原型链接起来
    // obj.__proto__ = Con.prototype // 等价于上面的写法
    let result = Con.apply(obj, args) // 将构造函数中的this指向这个对象,并传递参数
    return result instanceof Object ? result : obj
}

如果使用普通函数的调用方式,则调用时不会修改设置实例的原型,则以上判断就会出行false,就可以限制class 类以普通函数的方式进行调用

_inherits实现继承

/**
 * _inherits实现继承
 * @param {*} subClass 子类
 * @param {*} superClass 父类
 */
function _inherits(subClass, superClass) { 
  // 判断父类,既不是函数方法也不是null的情况报错
  // null是所有对象的原型链的顶端
  if (typeof superClass !== "function" && superClass !== null) { 
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
  } 
  
  // 基于父类创建子类的原型对象
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); 
  // 判断父类及环境是否存在Object.setPrototypeOf api
  // 将父类设置为子类的原型 继承父类
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

_possibleConstructorReturn(super 方法)

结合Son中的调用分析

function Son() {
  _classCallCheck(this, Son);

  var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));

  _this.sonName = 'son';
  return _this;
}

/**
 * _possibleConstructorReturn 修改this的指向
 * @param {*} self // 子类的this
 * @param {*} call // 子类的原型(父类)
 */
function _possibleConstructorReturn(self, call) { 
  if (!self) { 
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
  } 
  
  // 修改this指向,将子类的原型指向父类
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

Parent定义方法解析

var Parent = function () {
  /**
   * 声明一个构造函数方法,定义Parent类
   */
  function Parent() {
    // 判断原型(防止以函数的方式调用)
    _classCallCheck(this, Parent);
    // 属性定义
    this.name = 'parent';
    this.age = 22;
  }

  // 调用_createClass方法定义Parent类上的方法
  _createClass(Parent, [{
    key: 'work',
    value: function work() {
      console.log('the ' + this.name + ' is working');
    }
  }]);

  // 返回声明的Parent类
  return Parent;
}();

// 定义原型链上的属性和方法
Parent.prototype.speak = function () {
  console.log('hello!');
};

Parent.prototype.color = 'yello';

补充:es5中的构造函数于es6中的class有什么区别

  • ES6语法定义的class类,不能被提前调用,无法执行预解析;ES5的function函数可以提前调用,但是只有属性没有方法
  • class内部会启用严格模式
    • class内部不可以使用未声明的对象
    • es5构造函数中可以
  • class的所有方法都是不可枚举的
  • class必须使用new关键词调用
  • class 的继承有两条继承链
    • 一条是: 子类的proto 指向父类
    • 另一条: 子类prototype属性的proto属性指向父类的prototype属性.
    • es6的子类可以通过proto属性找到父类,而es5的子类通过proto找到Function.prototype
// es5
function Super() {}
function Sub() {}
Sub.prototype = new  Super()
Sub.prototype.constructor = Sub
var sub = new Sub()
console.log( Sub.__proto__ === Function.prototype) // true

// es6
class Super{}
class Sub extends Super {}
let sub = new Sub()
console.log( Sub.__proto__ === Super)// true

你可能感兴趣的:(【重学前端】es6中class做了什么)