聊一聊ES6 CLASS 实现原理

38次阅读

共计 3942 个字符,预计需要花费 10 分钟才能阅读完成。

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 红宝书

正文完
 0