JavaScript 五十问——从源码分析 ES6 Class 的实现机制
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 继承
后记
终于写完了,在没有网络辅助的情况下写博客真是太难了!绝知此事要躬行呀!
原来觉得写一篇关于class的博客还不简单吗,就是原型链继承那一套呗,现在总结下来,还是有很多地方需要注意的;学习到了很多!嗯 不说了, 我还有好几个坑要填呢~
如果这篇文章对你有帮助的话,欢迎点赞收藏!
如果你有疑问的话,希望积极留言,共同讨论,共同进步!
参考文档
JavaScript 红宝书