[PS:如果觉得太啰嗦,直接跳转目录:es5 实现继承即可;](#PS:如果觉得太啰嗦,直接跳转目录:es5 实现继承即可; "#heading-0")
这不是快过年了,在家里闲的没事干,就一直在倒腾编译器,我一直好奇这个装饰器打包的时候怎么处理的,最后处理成什么样了,于是我写了一个类并加上了装饰器:
js
export default class Super {
@decorateMethod
hello() {
console.log("hello");
}
}
function decorateMethod(target, key, descriptor) {
console.log(target, key, descriptor);
}
同时创建了一个 js 文件,先读取源码然后进行 ast 解析,写到这里我就江郎才尽了,不知道对这个 ast 应该如何 tranform 得到 es5 的装饰器实现:
js
const { parse } = require("@babel/core");
const traverse = require("@babel/traverse");
const generate = require("@babel/generator").default;
const fs = require("fs");
const path = require("path");
fs.readFile(
path.join(__dirname, "./index.js"),
{
encoding: "utf-8",
},
(err, data) => {
if (err) {
console.log("error", err);
} else {
main(data);
}
}
);
function main(code) {
const ast = parse(code, {
plugins: [
[
"@babel/plugin-syntax-decorators",
{
decoratorsBeforeExport: false,
},
],
],
});
traverse.default(ast, {
ClassMethod(node) {
const decorators = node.node.decorators;
node.node.decorators = [];
if (decorators.length > 0) {
decorators.forEach((decorator) => {
const methodName = decorator.expression.name;
node.parent;
});
}
},
});
const { code: newCode } = generate(ast);
fs.writeFile(path.join(__dirname, "./newIndex.js"), newCode, (err) => {
if (err) {
} else {
console.log("success");
}
});
}
既然不知道怎么写,那就直接抄答案,看看 rollup 打包之后的源代码,先安装father,新建一个文件命名为:.fatherrc.js,然后配置一下,看这确实是零配置:
js
const { defineConfig } = require("father");
export default defineConfig({
esm: {},
cjs: {},
umd: {},
platform: "browser",
});
得到了源码:
js
function _typeof(o) {
"@babel/helpers - typeof";
return (
(_typeof =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (o) {
return typeof o;
}
: function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype
? "symbol"
: typeof o;
}),
_typeof(o)
);
}
var _class;
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, _toPropertyKey(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;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : String(i);
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
Object.keys(descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ("value" in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators
.slice()
.reverse()
.reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object.defineProperty(target, property, desc);
desc = null;
}
return desc;
}
var Super =
((_class = /*#__PURE__*/ (function () {
function Super() {
_classCallCheck(this, Super);
}
_createClass(Super, [
{
key: "hello",
value: function hello() {
console.log("hello");
},
},
]);
return Super;
})()),
_applyDecoratedDescriptor(
_class.prototype,
"hello",
[decorateMethod],
Object.getOwnPropertyDescriptor(_class.prototype, "hello"),
_class.prototype
),
_class);
export { Super as default };
function decorateMethod(target, key, descriptor) {
console.log(target, key, descriptor);
}
看完源码恍然大悟,es5 实现装饰器只不过就是把这个装饰器函数执行了一下,那么其实 es6 的装饰器就类似于语法糖了;同样我在这段源码中还发现了类的实现;
es5 中的类一般是怎么写的?一般是定义一个构造函数,然后定义它的原型对象,如下:
js
function Super(){
}
Super.prototype = {
}
封装一下得到这个方法:
js
function _createClass(Constructor, protoProps = {}, staticProps = {}) {
Object.keys(protoProps).forEach((key) => {
Constructor.prototype[key] = protoProps[key];
});
Object.keys(staticProps).forEach((key) => {
Constructor[key] = staticProps[key];
});
return Constructor;
}
function Super() {
if (!(this instanceof Super)) {
throw new Error("Super must be called with the new operator");
} else {
Super.instanceCount++;
}
}
// 用 function 实现 class
_createClass(
Super,
{
hello() {
console.log("hello");
},
},
{
instanceCount: 0,
}
);
再来看看别人实现的类:
js
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, _toPropertyKey(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;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : String(i);
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
这里只说区别
- 他是用 Object.defineProperty实现的给对象添加属性
- 它将 key 进行了Primitive转化
这里可以看出来别人实现的比我想的要周到的多,所以到这里我想到了继承,这里我先自己心里背一下八股文:class 与 es5 实现继承的区别,es5 实现继承的源码
es5实现继承
如果面试官恰好问了这一道题,大家应该都觉得是送分题,但是送分题就一定能够答好吗?答案是否定的。先来看看我心目中是怎么实现继承的:
js
function $extends(Super) {
function Child() {
Super.call(this);
}
Child.prototype = Object.create(Super.prototype);
Child.prototype.constructor = Child;
return Child;
}
一直以来我觉得这样实现就是 100 分了,直到编译了一下,看看大佬是怎么实现这个继承的。在一开始的代码里面加上一个子类,继承 Super:
diff
export default class Super {
@decorateMethod
hello() {
console.log("hello");
}
}
+ class Child extends Super {}
function decorateMethod(target, key, descriptor) {
console.log(target, key, descriptor);
}
运行:father dev,先梳理一下大致的执行逻辑:
_inherites
做了三件事:第一是校验父类是否为函数,第二:设置构造函数的prototype(这一步我做了),第三:设置构造函数的__proto__(这一步我没做),这里需要画原型图理解一下,为什么要将父类设置为子类的原型呢?这是为了继承父类的静态属性和静态方法。
js
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);
}
_createSuper
:用来创建父类函数,然后在子类中调用该函数,并返回它的返回值,这个时候就可以通过getPrototypeof(Child)获取到父类 Super,因为它们已经有了继承关系;
js
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);
};
}
_super.apply
:这里就类似于上面的 Super.call(this)
那么自己到底哪些地方没有实现完整呢?
- 没有继承父类的静态属性和静态方法:解决方案就是将父类设置为子类的原型,需要判断是否支持setPrototypeOf,支持则直接使用该方法设置原型,不支持则使用__proto__,__proto__不是规范中规定的属性,所以不稳定,但是也没有其他办法来兼容setPrototypeOf
- 无法继承内置对象 上面的$extend方法传入 Date,这个时候 new 调用会发现返回的并不是 date 对象;为什么会出现这样的情况呢?因为 ES5 的继承根本无法实现内置对象的继承,怎么办呢?这里就用了一个讨巧的方式,既然调用子类不行,这里直接全部改为调用父类,然后返回其值,子类以及子类原型的属性和方法手动添加到这个对象上去,简单来说就是这样的:
js
function Child(){
// 子类不返回 new 新创建的对象而是直接返回父类执行后的对象
return Super.apply(this,arguments)
}
当然这里如果支持Reflect.construct,那么就用Reflect.construct来生成实例对象:
js
var NewTarget = _getPrototypeOf(this).constructor; // Child
result = Reflect.construct(Super, arguments, NewTarget);
Reflect.construct()等同于 new 操作符,唯一不同的就是它的第三个参数可以指定 new.target对象;在构造函数中可以访问,new.target对象,它通常用来检测函数是否被 new 操作符调用
- 在执行父类时没有传参数:别看这个小细节,还是很容易漏掉的
思考题
最后留下一个思考题:不支持Reflect.construct的情况下,可以这样返回吗:result = new Super(...Array.from(arguments));