无法访问装饰函数内的函数属性
Posted
技术标签:
【中文标题】无法访问装饰函数内的函数属性【英文标题】:Can't access function attribute inside decoration function 【发布时间】:2021-12-08 13:29:12 【问题描述】:在下面的代码中,我试图将装饰器添加到函数中。由于某些原因,我想显示函数属性“名称”。但是,一旦我进入各个功能,我就无法访问它。另外,我不确定为什么要自下而上调用这些函数。上述所有问题的原因是什么?我该如何避免?
let rectangleArea = (length, width) =>
return length * width;
const countParams = (fn) =>
return (...params) =>
console.log('countParams', fn.name)
if (params.length !== fn.length)
throw new Error(`Incorrect number of parameters for $fn.name!`);
return fn(...params);
const requireIntegers = (fn) =>
return (...params) =>
console.log('requireIntegers', fn.name)
params.forEach(param =>
if (!Number.isInteger(param))
throw new TypeError(`Params must be integers at $fn.name!`); //Can't access fn.name
);
return fn(...params);
//Why running from bottom to top?
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
【问题讨论】:
【参考方案1】:第一次为给定函数创建修饰函数时,返回的函数没有名称——它是匿名的。因此,当您将 那个 修饰函数再次进行修饰时,fn
将是那个匿名函数。
要解决这个问题,请将fn
函数的名称也分配给返回的修饰函数。这样,即使您一次又一次地装饰该功能,名称也会保持不变...
这是一个辅助函数,它将 name 属性分配给给定的函数:
const setName = (deco, value) =>
Object.defineProperty(deco, "name", value, writable: false);
return deco;
let rectangleArea = (length, width) =>
return length * width;
const countParams = (fn) =>
return setName((...params) =>
console.log('countParams', fn.name)
if (params.length !== fn.length)
throw new Error(`Incorrect number of parameters for $fn.name!`);
return fn(...params);
, fn.name);
const requireIntegers = (fn) =>
return setName((...params) =>
console.log('requireIntegers', fn.name)
params.forEach(param =>
if (!Number.isInteger(param))
throw new TypeError(`Params must be integers at $fn.name!`); //Can't access fn.name
);
return fn(...params);
, fn.name);
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
为什么函数是自下而上调用的。
因为在您的装饰器中,最后一个步骤是调用fn
。
fn
可能是一个已经修饰过的函数,因此早期函数的修饰稍后运行是正常的。
这就像多次包装生日礼物,每次都用不同颜色的包装纸。当您的朋友打开包装时,他们会看到包装纸的颜色,顺序与您使用包装纸的顺序相反。
【讨论】:
【参考方案2】:所以你想用你的装饰器做一些额外的事情吗?他们需要一些共同的行为吗?我们已经表明我们知道如何做到这一点:使用装饰器。您的装饰器需要自己的装饰器!
在这里,我编写了一个装饰器-装饰器keep
,它接受一个装饰器函数并返回一个新的装饰器函数,该函数将函数的name
和length
属性传递给它。 (快说五倍!)
它使用与 trincot 的答案相同的技术,但侵入性较小,因为您可以像包装底层函数一样简单地包装装饰器函数。在这里,我在定义时这样做,因为我们从不真正希望这些装饰器没有这种行为,但你可以随心所欲地做。
let rectangleArea = (length, width) =>
return length * width;
const keep = (decorator) => (fn) =>
Object .defineProperties (decorator (fn),
name: value: fn .name, writable: false,
length: value: fn .length, writable: false
)
const countParams = keep ((fn) =>
return (...params) =>
console.log('countParams', fn.name)
if (params.length !== fn.length)
throw new Error(`Incorrect number of parameters for $fn.name!`);
return fn(...params);
)
const requireIntegers = keep ((fn) =>
return (...params) =>
console.log('requireIntegers', fn.name)
params.forEach(param =>
if (!Number.isInteger(param))
throw new TypeError(`Params must be integers at $fn.name!`); //Can't access fn.name
);
return fn(...params);
)
//Why running from bottom to top? -- answered by @balastrong
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30));
console.log(rectangleArea(20, 30, "hey"));
.as-console-wrapper max-height: 100% !important; top: 0
名称keep
最初是keepName
,然后我才意识到我个人也希望该功能保持原样不变。我想不出一个明确有用的名字……这对我来说是一个很大的警告信号。所以这个设计可能还是有问题的。
【讨论】:
【参考方案3】:不是从下到上,是你设置的顺序。
使用你的装饰器,你基本上只是这样做了:
requireIntegers(countParams(rectangleArea(20, 30, "hey"))).
这意味着它首先执行 requireIntegers
,将其作为输入传递给其他所有内容 (countParams(rectangleArea(20, 30, "hey"))
)。
然后您会看到控制台日志和错误,因为params.forEach
扫描参数并找到不是数字的'hey'
。
【讨论】:
“你基本上就是这么做的”:不,这不是正在发生的事情。参数20, 30, "hey"
不直接传递给原始rectangleArea
,而是首先传递给装饰器函数。以上是关于无法访问装饰函数内的函数属性的主要内容,如果未能解决你的问题,请参考以下文章