12_ ES6 类的使用和转 ES5 源码阅读
认识 class 定义类
我们会发现,按照前面的构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码不容易理解
- 在 ES6(ECMAScript2015)新的标准中使用了 class 关键字来直接定义类
- 但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已
- 所以学好了前面的构造函数、原型链更有利于我们理解类的概念和继承关系
那么如何使用 class 来定义一个类呢?
- 可以使用两种方式来声明类: 类声明和类表达式
// 类声明
class Person {}
// 类的表达式
const Student = class {};
类和构造函数的异同
本质上类只是构造函数的一种语法糖写法,所以很大一部分是相同的
class Person {}
const p = new Person();
console.log(Person); // [class Person]
console.log(Person.prototype); // {}
console.log(Person.prototype.constructor); // [class Person]
console.log(p.__proto__ === Person.prototype); // true
console.log(typeof Person); // function
类的构造函数
如果我们希望在创建对象的时候给类传递一些参数,这个时候应该怎么做呢?
- 每个类都可以有一个自己的构造函数,这个方法的名称是固定的 constructor
- 当我们通过 new 操作符,操作一个类的时候会调用这个类的构造函数 constructor
- 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常
当我们通过 new 关键字操作类的时候,会调用这个 constructor 函数,并且执行如下参数:
- 在内存中创建一个新的对象(空对象)
- 这个对象内部的
[[prototype]]
属性会被赋值为该类的 prototype 属性 - 构造函数内部的 this,会指向创建出来的新对象
- 执行构造函数的内部代码(函数体代码)
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
类的实例方法
- 在上面我们定义的属性都是直接放到 this 上,也就意味着它是放到了创建出来的新对象中
- 在前面我们说过对于实例的方法,我们是希望放到原型上的,这样可以被多个实例来共享
- 这个时候我们可以直接在类中定义
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " eating Person~");
}
}
类的访问器方法
我们之前讲对象的属性描述符时有讲过对象可以添加 setter 和 getter 函数的,那么类也是可以的
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " eating Person~");
}
set name(newName) {
console.log("调用了name的setter方法");
this.name = newName;
}
get name() {
console.log("调用了name的getter方法");
return this.name;
}
}
类的静态方法
静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用 static 关键字来定义
要注意一下: 静态方法是定义在构造函数上的,并不是原型
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " eating Person~");
}
static studying() {
console.log(this.name + " studying Person Static~");
}
}
ES6 类的继承-extends
前面我们讨论了在 ES5 中实现继承的方案,虽然最终实现了相对满意的继承机制,但是过程却依然是非常繁琐的
在 ES6 中新增了使用 extends 关键字,可以很方便的帮助我们实现继承:
class Person {}
class Student extends Person {}
super 关键字
我们会发现在上面的代码中我是用了一个 super 关键字,这个 super 关键字有不同的使用方式:
- 注意:在子类(派生类)的构造函数中使用 this 或者返回默认对象之前,必须先通过 super 调用父类的构造函数
- super 的使用位置有三个: 子类的构造函数、实例方法、静态方法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eating() {
console.log(this.name + " eating Person~");
}
static studying() {
console.log(this.name + " studying Person Static~");
}
}
class Student extends Person {
constructor(name, age, sno) {
// 1. 在子类构造函数中使用super
super(name, age);
this.sno = sno;
}
running() {
console.log(this.name + "running~");
}
static studying() {
// 2. 在子类静态方法中使用super
super.studying();
}
eating() {
// 3. 在子类实例方法中使用super
super.eating();
}
}
使用 babel 查看转换后的代码
我们可以访问https://www.babeljs.cn/repl
这个网站将代码转成 ES5 以前代码的样子:
简单的 class
首先来看一下简单的单纯定义 class
- 初看转换后的代码有一点需要注意,代码中这一段
/*#__PURE__*/
将函数声明为纯函数,方便 webpack 再压缩代码的时候做 tree sharking,如果这段函数没有使用到,就会被移除; - 这里还有一个自执行函数,目的是防止当前代码中的局部变量和项目中的其它变量重名
- 继续,我们会看到有一个边界处理,作用是什么,作用就是防止再使用时将我们的构造函数直接当成一个函数去调用,这里就用 instanceof 去做了一个判断
- 继续,下来就是在构造函数的原型上添加方法,并直接在构造函数上添加静态方法,并返回
// 转换前
class Obj {
constructor(name) {
this.name = name;
}
eating() {
console.log("eating");
}
}
// 转换后
("use strict");
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a 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);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", { writable: false });
return Constructor;
}
var Obj = /*#__PURE__*/ (function () {
function Obj(name) {
_classCallCheck(this, Obj);
this.name = name;
}
_createClass(Obj, [
{
key: "eating",
value: function eating() {
console.log("eating");
},
},
]);
return Obj;
})();
继承的转换
再来看一下继承,这块有点复杂,没去细读了
- 首先在创建子类的时候调用了
_inherits(Stu, _Obj);
这行代码就是实现继承的方法,和之前我们实现寄生组合式继承原理是一致的 - 但是这里注意在使用
Object.create
的时候,直接将 constructor 修改了,将原先两步操作合并了 - 注意看下一步
_setPrototypeOf(subClass, superClass)
这里目的是为了静态方法的继承,因为子类可以直接通过例如Stu.xxx
访问父类的静态方法的 - 接下来就是创建 super 了,涉及到 reflect,这个没什么印象了
// 转换前
class Obj {
constructor(name) {
this.name = name;
}
eating() {
console.log("eating");
}
}
class Stu extends Obj {
constructor(name, age) {
super(name);
this.age = age;
}
running() {
console.log("running");
}
}
// 转换后
("use strict");
function _typeof(obj) {
"@babel/helpers - typeof";
return (
(_typeof =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (obj) {
return typeof obj;
}
: function (obj) {
return obj &&
"function" == typeof Symbol &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
}),
_typeof(obj)
);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
});
Object.defineProperty(subClass, "prototype", { writable: false });
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf
? Object.setPrototypeOf.bind()
: function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError(
"Derived constructors may only return object or undefined"
);
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf.bind()
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a 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);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", { writable: false });
return Constructor;
}
var Obj = /*#__PURE__*/ (function () {
function Obj(name) {
_classCallCheck(this, Obj);
this.name = name;
}
_createClass(Obj, [
{
key: "eating",
value: function eating() {
console.log("eating");
},
},
]);
return Obj;
})();
var Stu = /*#__PURE__*/ (function (_Obj) {
_inherits(Stu, _Obj);
var _super = _createSuper(Stu);
function Stu(name, age) {
var _this;
_classCallCheck(this, Stu);
_this = _super.call(this, name);
_this.age = age;
return _this;
}
_createClass(Stu, [
{
key: "running",
value: function running() {
console.log("running");
},
},
]);
return Stu;
})(Obj);