ES6类的使用及原理
ES6新增的Class(类)给我们编程带来了极大方便,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已, ES6中类实际上也是函数,我们使用typeof
去判断类时可以看到其返回的就是function
。在ES6中类的实现是基于原型来完成的,下面我们就来看看es6中类的实现原理
在es6中使用类
class Parent {
constructor(name,age){
this.name = name;
this.age = age;
}
speakSomething(){
console.log("I can speek chinese");
}
}
上面的代码经过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 Parent = function () {
function Parent(name, age) {
_classCallCheck(this, Parent);
this.name = name;
this.age = age;
}
_createClass(Parent, [{
key: "speakSomething",
value: function speakSomething() {
console.log("I can speek chinese");
}
}]);
return Parent;
}();
接下来我们就来看看babel为我们做了哪些工作
- 在ES6中,类是不能直接作为函数使用的,必须使用
new
关键字,虽然类本质上还是函数,所以babel定义了_classCallCheck
来判断函数是否是通过new
关键字来使用的
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
- 与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。所以我们在类的直接定义的方法或者属性都是定义在原型上的。ES6中还定义了静态属性,静态属性是定义在类上的属性。从babel为我们转码生成的代码可以看出
_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;
};
}();
ES6类的继承及其原理
我们知道javascript中类的继承是通过原型链来继承的,所以ES6中继承的本质当然也是通过原型链来完成的
ES6中继承的使用
class Parent {
static height = 12
constructor(name,age){
this.name = name;
this.age = age;
}
speakSomething(){
console.log("I can speek chinese");
}
}
Parent.prototype.color = 'yellow'
//定义子类,继承父类
class Child extends Parent {
static width = 18
constructor(name,age){
super(name,age);
}
coding(){
console.log("I can code JS");
}
}
var c = new Child("job",30);
c.coding()
经过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 _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;
}
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;
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Parent = function () {
function Parent(name, age) {
_classCallCheck(this, Parent);
this.name = name;
this.age = age;
}
_createClass(Parent, [{
key: "speakSomething",
value: function speakSomething() {
console.log("I can speek chinese");
}
}]);
return Parent;
}();
Parent.height = 12;
Parent.prototype.color = 'yellow';
//定义子类,继承父类
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
_classCallCheck(this, Child);
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));
}
_createClass(Child, [{
key: "coding",
value: function coding() {
console.log("I can code JS");
}
}]);
return Child;
}(Parent);
Child.width = 18;
var c = new Child("job", 30);
c.coding();
_createClass
和_classCallCheck
就不多在类原理中已经介绍了,这里我们就主要介绍一下_inherits
和_possibleConstructorReturn
这两个函数
_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;
}
_inherits
函数首先调用Object.create
函数并将返回值赋值给subClass.propotype,
Object.create的原理如下
// polyfill
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (!(proto === null || typeof proto === "object" || typeof proto === "function")) {
throw TypeError('Argument must be an object, or null');
}
var temp = new Object();
temp.__proto__ = proto;
if(typeof propertiesObject ==="object")
Object.defineProperties(temp,propertiesObject);
return temp;
};
}
熟悉寄生组合继承原理的朋友应该知道这里使用了寄生组合继承。
寄生组合继承的原理如下
function inherits(subClass, superClass){
var F = function(){}
F.propotype = superClass.prototype
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
}
_inherits
最后的赋值主要是为了实现ES6继承中这样的一个功能
class A{}
class B extends A{}
B.__proto__ === A // true
同时也为_possibleConstructorReturn
的调用做准备
_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;
}
看定义好像_possibleConstructorReturn
没做什么,我们还是看看他的调用吧
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age));
结合_possibleConstructorReturn
的定义和调用可以看出其作用了,这里首先先调用和父类的构造函数,然后根据父类的构造函数返回值来进行判断,如果父类的构造函数的返回值是个对象或者函数,那么就将父类的返回值作为new Child()
返回的实例,否则只用原来的实例,这里可能有点懵,我们来看一个例子吧
function A(){
this.a = 1
this.b = 2
return 2;
}
function B() {
this.a = 1
this.b = 2
return {a: 2, b: 3}
}
var a = new A()
var b = new B()
a // {a: 1, b: 2}
b // {a: 2, b: 3}
从上面的两个例子中我们就可以知道为什么_possibleConstructorReturn
要这样子做了
总结
总而言之,ES6中类的定义和继承只不过是一个语法糖罢了,本质上还是使用了javascript的函数以及原型链。当然,了解ES6中类以及继承的实现原理可以让我们更了解javascript的原型链以及工作机制