需求:有个sum函数,传入(1,2,3),输出1+2+3的值为6 。如果传入(1,‘2’,‘3’),输出的为字符串123。希望通过装饰器完成不管输入哪一种,都可以得出相加的和。也就是这两种入参都能得到的是6。
代码如下:
function toNumber(target: any, prop: string, desc: PropertyDescriptor) {
// console.log(target[prop] === desc.value) // true
let oldMethod = desc.value // 指向sum 方法
//这种写法是可以的
// desc.value = function (...args: any[]) {
// args = args.map(item => parseFloat(item))
// return oldMethod.apply(this, args)
// }
// 这种写法是不可以的。 但是target[prop] === desc.value 是true的。也就是指向同一个地址的。
// 分析:这里写成如下这样却不可以。要是想改原始值 只能改desc.value中的。 理论上是可以的 因为target[prop] 表示的就是Person.prototype.sum。猜测是拦截修改的原因。
target[prop] = function (...args: any[]) {
args = args.map(item => parseFloat(item))
return oldMethod.apply(this, args)
}
}
class Person {
@toNumber
sum(...args: any[]) {
return args.reduce((accu, item) => {
return accu + item
}, 0)
}
}
let p = new Person()
console.log(p.sum(1, 2, 3))
console.log(p.sum('1', 2, '3'))
如果直接顺序修改原型的方法,绝对是可以修改的了的。所以看一下拦截是如果做到的,对其做了些什么。猜测拦截用到了Object.defineProperty。
可以把当前ts转成js,然后分析是如果做到的。转成的js如下:
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function toNumber(target, prop, desc) {
// console.log(target[prop] === desc.value) // true
var oldMethod = desc.value; // 指向sum 方法
//这种写法是可以的
// desc.value = function (...args: any[]) {
// args = args.map(item => parseFloat(item))
// return oldMethod.apply(this, args)
// }
// 这种写法是不可以的。 但是target[prop] === desc.value 是true的。也就是指向同一个地址的。
// 分析:这里写成如下这样却不可以。要是想改原始值 只能改desc.value中的。 理论上是可以的 因为target[prop] 表示的就是Person.prototype.sum。猜测是拦截修改的原因。
target[prop] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
args = args.map(function (item) { return parseFloat(item); });
return oldMethod.apply(this, args);
};
}
var Person = /** @class */ (function () {
function Person() {
}
Person.prototype.sum = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return args.reduce(function (accu, item) {
return accu + item;
}, 0);
};
__decorate([
toNumber
], Person.prototype, "sum", null);
return Person;
}());
var p = new Person();
console.log(p.sum(1, 2, 3));
console.log(p.sum('1', 2, '3'));
源码乍一看,内容比较多,这里分享一下心得。第一需要耐心,一点点缕顺。第二看的过程把不必要的删掉。还有一些判断都可以删除掉。最后得到精简的代码。
上面代码可以分解为如下代码:
//[toNumber ], Person.prototype, "sum", null
function __decorate(decorators, target, key, desc) {
var c = 4
var r = desc = Object.getOwnPropertyDescriptor(target, key)
var d;
for (var i = decorators.length - 1; i >= 0; i--) {
d = decorators[i]
r = d(target, key, r) || r;
}
Object.defineProperty(target, key, r)
return r;
};
function toNumber(target, prop, desc) {
// console.log(target[prop] === desc.value) // true
var oldMethod = desc.value; // 指向sum 方法
//这种写法是可以的
// desc.value = function (...args: any[]) {
// args = args.map(item => parseFloat(item))
// return oldMethod.apply(this, args)
// }
// 这种写法是不可以的。 但是target[prop] === desc.value 是true的。也就是指向同一个地址的。
// 分析:这里写成如下这样却不可以。要是想改原始值 只能改desc.value中的。 理论上是可以的 因为target[prop] 表示的就是Person.prototype.sum。猜测是拦截修改的原因。
target[prop] = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
args = args.map(function (item) { return parseFloat(item); });
return oldMethod.apply(this, args);
};
}
function Person() {
}
Person.prototype.sum = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return args.reduce(function (accu, item) {
return accu + item;
}, 0);
};
__decorate([
toNumber
], Person.prototype, "sum", null);
var p = new Person();
console.log(p.sum(1, 2, 3));
console.log(p.sum('1', 2, '3'));
主要是__decorate方法实现。就是装饰器的模拟实现。在调用之后,把参数传过来,然后代码里面,可以通过具体分析的参数来代进入。比如c得到的就是4.这样一步步来。
//[toNumber ], Person.prototype, "sum", null
function __decorate(decorators, target, key, desc) {
var c = 4 // 获取参数个数,源码中通过这个来判断很多要执行哪个。这个把它具体化,然后把那些判断都也删除掉。
// 这里获取此属性的描述信息
var r = desc = Object.getOwnPropertyDescriptor(target, key)
var d;
// 装饰器可以传多个,所以入参用数组。并且这里相当于一个链式调用,挨着近的先执行,并把执行结果传给下一个装饰器。
for (var i = decorators.length - 1; i >= 0; i--) {
d = decorators[i]
r = d(target, key, r) || r;
}
Object.defineProperty(target, key, r) // 这里是关键 通过Object.defineProperty来改变的。所以desc.value 可以,但是改原型不行。如果这行删除了。改原型就可以了,desc.value就不可以了。 所以这里是关键。
return r;
};
以上为提出问题,分析问题的思路。有时候这种疑问 ,还真得看如何实现才能理解。因为疑惑了半天。才找到思路。
记录一下~~~每天一点小进步!
网友评论