为了真正理解ES6中类的概念,来学习类是如何实现的
我们都知道在JS中,函数是“一等公民”,“类”的概念是在ES6中提出的,它好像跟我们自己写的函数构造器一样,但又有好像有些不一样的地方,那么它到底是如何实现的那?为了达到这个目的,我们利用babel来看下它编译后的代码。
不带继承的类
首先我们写一个简单的类,该类没有任何继承,只有一个简单的构造函数和getName
函数
class App {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
复制代码
然后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 App = function () {
function App(name) {
_classCallCheck(this, App);
this.name = name;
}
_createClass(App, [{
key: "getName",
value: function getName() {
return name;
}
}]);
return App;
}();
复制代码
东西还挺多,一眼并看不出来什么东西来,我们接下来一点点分析。我们先看最后一个函数:
// 立即执行函数
var App = function () {
// 构造函数变形成这样
function App(name) {
// 从这个函数的名字上看,好像是类调用检查,我们暂时先不看这个函数
_classCallCheck(this, App);
this.name = name;
}
// 调用了一个_createClass函数,应该是在给App附加一些值
_createClass(App, [{
key: "getName",
value: function getName() {
return name;
}
}]);
// 返回一个名为App的函数
return App;
}();
复制代码
下面来看_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;
// 存在value值则默认可写
if ("value" in descriptor) descriptor.writable = true;
// 使用Object.defineProperty来设置属性
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 函数接收三个参数,分别是:构造函数,原型属性,静态属性
return function (Constructor, protoProps, staticProps) {
// 为构造函数prototype添加属性(即为用构造函数生成的实例原型添加属性,可以被实例通过原型链访问到)
if (protoProps) defineProperties(Constructor.prototype, protoProps);
// 为构造函数添加属性
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
复制代码
好像很简单,跟我们平时使用函数实现差别不是很多,就相差了一个描述符的设定过程。最后看一下类调用检查函数_classCallCheck
:
// 类调用检查,不能像普通函数一样调用,需要使用new关键字
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
复制代码
增加了错误处理,当我们调用方式不正确时,抛出错误。
模拟实践
我们简单实现以下没有继承的方式,来加深我们的印象,为了简化不添加错误处理和描述符的设定过程。
var App = function(name) {
this.name = name;
}
App.prototype.getName = function() {
return this.name;
}
var app = new App('miniapp');
console.log(app.getName()); // 输出miniapp
复制代码
这个很简单,就是我们平常模拟“类”所使用的方法,js所有对象都是通过原型链的方式“克隆”来的。注意我们这里的App
不能叫做类,在js中没有类的概念。它是一个函数构造器,它可以被当做普通函数调用,也可以被当做函数构造器调用,调用函数构造器使用new
关键字,函数构造器会克隆它的prototype
对象,然后进行一些其他操作,如赋值操作,最后返回一个对象。
下面想一个问题,实现继承我们一般都是利用原型链的方式,像下面这样:
var dog = {
name: 'goudan'
};
var animal = {
getName: function() {
return this.name;
}
}
// 对象的原型通过`__proto__`暴露出来(tip: 实际中不要这么写)
dog.__proto__ = animal;
console.log(dog.getName()); // 输出goudan
复制代码
我们如何在两个类之间继承那?在ES6中实现很简单
class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Dog extends Animal{
constructor(name) {
super(name);
this.name = name;
}
}
复制代码
如果我们自己实现一个要怎么实现,我们先写一个:
var Animal = function(name) {
this.name = name;
}
Animal.prototype.getName = function() {
return this.name;
}
var Dog = function(name) {
Animal.call(this, name);
this.name = name;
}
Dog.prototype = Animal.prototype;
var dog = new Dog('goudan');
console.log(dog.getName()); // 输出goudan
复制代码
但这种方式总感觉不太好,那么ES6中的的继承是如何实现的?我们下一篇继续一起学习