无法访问装饰函数内的函数属性

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,它接受一个装饰器函数并返回一个新的装饰器函数,该函数将函数的namelength 属性传递给它。 (快说五倍!)

它使用与 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,而是首先传递给装饰器函数。

以上是关于无法访问装饰函数内的函数属性的主要内容,如果未能解决你的问题,请参考以下文章

装饰器的内部函数无法访问装饰器变量

回调函数无法访问父函数范围内的变量

为啥 dolphindb 脚本中的函数无法访问外部范围内的变量

装饰类无法访问其属性

无法在回调函数中访问属性React

Stat() 函数返回值 -1,因此无法访问文件属性。我该如何纠正?