ts中方法装饰器拦截方法修改参数,直接修改原型和修改描述valu

2021-11-17  本文已影响0人  黎明的叶子

需求:有个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; 
};

以上为提出问题,分析问题的思路。有时候这种疑问 ,还真得看如何实现才能理解。因为疑惑了半天。才找到思路。
记录一下~~~每天一点小进步!

上一篇 下一篇

猜你喜欢

热点阅读