JavaScript 五十问——从源码分析 ES6 Class 的实现机制

哈希极昼
• 阅读 4793

Class是ES6中新加入的继承机制,实际是Javascript关于原型继承机制的语法糖,本质上是对原型继承的封装。本文将会讨论:
1、ES6 class的实现细
2、相关Object API盘点
3、Javascript中的继承实现方案盘点

正文

1、Class 实现细节

class Person{
   constructor(name, age){
     this.name = name
     this.age = age
    }

    static type = 'being'

  sayName (){
    return this.name
    }

  static intro(){
    console.log("")
    }
}

class Men extends Person{
    constructor(name, age){
        super()
      this.gender = 'male'
    }
}

const men = new Men()

以上代码是ES6 class的基本使用方式,通过babel解析后,主要代码结构如下:

'use strict';

var _createClass = function () {...}();// 给类添加方法

function _possibleConstructorReturn(self, call) { ...}//实现super

function _inherits(subClass, superClass) {...}// 实现继承

function _classCallCheck(instance, Constructor) {...} // 防止以函数的方式调用class

var Person = function () {
  function Person(name, age) {
      _classCallCheck(this, Person);

      this.name = name;
      this.age = age;
  }

  _createClass(Person, [{
      key: 'sayName',
      value: function sayName() {
        return this.name;
      }
  }], [{
      key: 'intro',
      value: function intro() {
        console.log("");
      }
  }]);

  return Person;
  }();

Person.type = 'being'; //静态变量

var Men = function (_Person) {
  _inherits(Men, _Person);

  function Men(name, age) {
    _classCallCheck(this, Men);

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

    _this.gender = 'male';
    return _this;
  }
    
  return Men;
  }(Person);

var men = new Men();

为什么说es6的class 是基于原型继承的封装呢? 开始省略的四个函数又有什么作用呢?
下面,我们就从最开始的四个函数入手,详细的解释es6的class 是如何封装的。

第一:_classCallCheck函数, 检验构造函数的调用方式:
代码

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

我们知道,在javascript中 person = new Person() ,通常完成以下几件事:
1、创建一个新的对象 Object.create()
2、将 新对象的 this 指向 构造函数的原型对象
3、新对象的__proto__ 指向 构造函数
4、执行构造函数
而普通函数调用,this通常指向全局
因此,_classCallCheck函数是用来检测类的调用方式。防止类的构造函数以普通函数的方式调用。

第二: _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; // 静态函数 -> 构造函数
    }; 
}();

_createClass是一个闭包+立即执行函数,以这种方式模拟一个作用域,将defineProperties私有化。
这个函数的主要作用是通过Object.defineProperty给类添加方法,其中将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上。

第三: _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,  // 子类的原型的__proto__指向父类的原型
        //给子类添加 constructor属性 subclass.prototype.constructor === subclass
        { constructor: 
            { 
                value: subClass, 
                enumerable: false, 
                writable: true, 
                configurable: true 
            } 
        }
    ); 
    if (superClass) 
        //子类__proto__ 指向父类
        Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

从这个函数就能够很明显的看出来,class实现继承的机制了。

第四: _possibleConstructorReturn super()

function _possibleConstructorReturn(self, call) { 
    if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); //保证子类构造函数中 显式调用 super()
    } 
    return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

要想理解这个函数的作用,需要结合他的调用场景

var _this = _possibleConstructorReturn(this, (Men.__proto__ || Object.getPrototypeOf(Men)).call(this));// function Men(){}

此时已经执行完_inherits函数,Men.__proto__ === Person
相当于:

var _this = _possibleConstructorReturn(this, Person.call(this));

很明显,就是将子类的this 指向父类。

API 总结

根据以上的分析,es6 class 的实现机制也可以总结出来了:
毫无疑问的,class机制还是在prototype的基础之上进行封装的
——contructor 执行构造函数相关赋值
——使用 Object.defineProperty()方法 将方法添加的构造函数的原型上或构造函数上
——使用 Object.create() 和 Object.setPrototypeOf 实现类之间的继承 子类原型__proto__指向父类原型 子类构造函数__proto__指向父类构造函数
——通过变更子类的this 作用域实现super()

盘点JavaScript中的继承方式

1.原型链继承
2.构造函数继承
3.组合继承
4.ES6 extends 继承

详细内容可以参考 聊一聊 JavaScript的继承方式https://segmentfault.com/a/11...

后记

终于写完了,在没有网络辅助的情况下写博客真是太难了!绝知此事要躬行呀!
原来觉得写一篇关于class的博客还不简单吗,就是原型链继承那一套呗,现在总结下来,还是有很多地方需要注意的;学习到了很多!嗯 不说了, 我还有好几个坑要填呢~
如果这篇文章对你有帮助的话,欢迎点赞收藏!
如果你有疑问的话,希望积极留言,共同讨论,共同进步!

参考文档

ES6—类的实现原理 https://segmentfault.com/a/11...

JavaScript 红宝书

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
ZY ZY
4年前
js继承的几种方式
1.原型链继承原型链继承:想要继承,就必须要提供父类(继承谁,提供继承的属性)//父级functionPerson(name)//给构造函数添加参数this.namename;this.age10;this.sumfunction()console.log(this.name)//原
Jacquelyn38 Jacquelyn38
4年前
你不可不知的JS面试题(第二期)
1、什么是继承?子类可以使用父类的所有功能,并且对功能进行扩展。新增方法改用方法(1)、ES6使用extends子类继承父类的方法。// 父类    class A        constructor(name)            this.name name;                getNa
Stella981 Stella981
3年前
JavaScript学习总结(十七)——Javascript原型链的原理
一、JavaScript原型链ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。在JavaScript中,用__proto__属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript会向上遍历原型
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
JavaScript强化教程——Cocos2d
JavaScript语言本身没有提供类,没有其它语言的类继承机制,它的继承是通过对象的原型实现的,但这不能满足Cocos2dJS引擎的要求。由于Cocos2dJS引擎是从Cocos2dx演变而来的,在Cocos2dJS的早期版本Cocos2dHTML中几乎全部的API都是模拟Cocos2dxAPI而设计的,Cocos2dx本身是有C编写的
Stella981 Stella981
3年前
JavaScript 基于原型链的继承
JavaScript对象是动态的属性“包”(指其自己的属性)。JavaScript对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。遵循ECMAScript标准,someObject.Prototype
Stella981 Stella981
3年前
Javascript中,实现类与继承的方法和优缺点分析
Javascript是一种弱类型语言,不存在类的概念,但在js中可以模仿类似于JAVA中的类,实现类与继承第一种方法:利用Javascript中的原型链1//首先定义一个父类23functionAnimal(name,age){4//定义父类的属性5thi
Stella981 Stella981
3年前
Javascript继承5:如虎添翼
/寄生式继承其实就是对原型继承的第二次封装,在封装过程中对继承的对象进行了扩展。也存在原型继承的缺点!!这种思想的作用也是为了寄生组合式继承模式的实现。///声明基对象varbook{name:'jsbook',al
哈希极昼
哈希极昼
Lv1
好雨知时节,当春乃发生。随风潜入夜,润物细无声。
文章
6
粉丝
0
获赞
0