class 语法已经出来很长时间了,借助 babel 我们可以在生产中使用,下面就通过一个例子来看看 babel 是怎么处理 class 的
es6
下面是一段代码,我们分别为实例、静态、实例的原型添加方法和属性
class Foo {
static foo = "Foo";
foo = "foo";
static getName() {
return this.foo;
}
getName() {
return this.foo;
}
static get value() {
return this.foo + "static";
}
get value() {
return this.foo + "example";
}
}
上面用到了static,static表明属性或者方法添加到 Class 本身而不是实例上,下面再来对比一下 es5 的写法
es5
function Foo() {
this.name = "foo";
}
Foo.foo = "Foo";
Foo.getName = function() {
return this.foo;
};
Foo.prototype.getName = function() {
return this.foo;
};
Object.defineProperty(Foo, "value", {
configurable: true,
enumerable: true,
get() {
return this.foo + "static";
}
});
Object.defineProperty(Foo.prototype, "value", {
configurable: true,
enumerable: true,
get() {
return this.foo + "example";
}
});
上面我们使用了Object.defineProperty来为对象定义属性,其中 getter/setter 是可选,不过在严格模式下中 getter/setter 必须同时出现,使用 getter/setter 后 value 和 writable 不可出现,否则报错。
babel 转码
"use strict";
function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, 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);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var Foo =
/*#__PURE__*/
(function() {
function Foo() {
_classCallCheck(this, Foo);
_defineProperty(this, "foo", "foo");
}
_createClass(
Foo,
[
{
key: "getName",
value: function getName() {
return this.foo;
}
},
{
key: "value",
get: function get() {
return this.foo + "example";
}
}
],
[
{
key: "getName",
value: function getName() {
return this.foo;
}
},
{
key: "value",
get: function get() {
return this.foo + "static";
}
}
]
);
return Foo;
})();
_defineProperty(Foo, "foo", "Foo");
转码过的代码看起来有点多,不过我们顺着执行顺序来看,首先var Foo =内部执行了一个_classCallCheck函数,那么它有什么用呢?
其实这个函数是为了保证 class 必须通过 new 来调用,ES6 规定 class 必须通过 new 调用,不然会显式报错。
必须为 new 调用
判断一个函数是否为new调用可以通过 instanceof 来进行判断,例如:
function F() {
if (!(this instanceof F)) {
throw new Error("必须通过new调用");
}
}
在全局作用域下如果不通过 new 调用 this 为 undefined,instanceof与非对象对比总是返回false;
在 ES6 还可以通过new.target来判断
function F() {
if (!new.target) {
throw new Error("必须通过new调用");
}
}
new.target 指向被 new 调用的构造函数,如果不存在返回 undefined。
上面介绍了两种判断方法再来看下 babel 是怎么处理这一过程的
- 将 this 和构造函数传递给
_classCallCheck; _classCallCheck将参数传递给_instanceof函数;
_instanceof函数负责了什么事情呢?
其实也是判断函数是否通过 new 调用,不过它同时对Symbol.hasInstance也进行了判断。
Symbol.hasInstance
当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法,
方法定义在类本身
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass(); // true
上面演示了一个例子,我们书接上文看下_instanceof函数内部怎么判断这一过程
function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
判断构造函数存在且同时存在 Symbol.hasInstance方法,如果有就执行Symbol.hasInstance(),没有的话就直接执行instanceof判断。
撒花,这里初始判断讲完,下面就是为对象赋值
赋值
我们继续顺着代码看,发现调用了_defineProperty,它是为对象辅助赋值,转到函数内部我们看到
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
不过为什么要通过key in obj来判断呢,其实下面的_defineProperties函数也用到了这一判断,简单来说就是不是getter/setter就直接把 value 添加到Object.defineProperty的 value 上,否则就直接复制。
再来看代码最后一句也调用了这个函数为 Foo 静态属性赋值,对应的我们已经走完了对应 class 的静态和实例属性的赋值
class Foo {
static foo = "Foo";
foo = "foo";
}
在顺着代码往下看发现调用了_createClass函数,同时还把构造函数以及我们的静态方法和实例方法通过数组传递,我们转到_createClass函数
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
这一步就是为构造函数和构造函数的prototype属性赋值,再来看_defineProperties函数
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);
}
}
_defineProperties执行的步骤很简单
- 遍历数组,取到每次循环的数组下标对象;
- 为对象添加
enumerable、configurable属性描述符,注意enumerable赋值为 false,这样做的原因是类的内部所有定义的方法,都是不可枚举的; - 判断 value 存在,如果存在添加
writable描述属性 - 通过
Object.defineProperty为对象赋值
最后
上面把 class 转化为构造函数讲完了,不过你是否好奇 babel 怎么处理继承的呢?
下一篇文章从babel看class(下)就讲解这一过程,如果喜欢请点赞一下,^_^