无法访问装饰功能内的功能属性

Can't access function attribute inside decoration function

在下面的代码中,我尝试向函数添加装饰器。由于某些原因,我想显示函数属性“名称”。但是,我一进入各个功能就无法访问它。另外,我不确定为什么这些函数是自下而上调用的。出现上述所有问题的原因是什么?我该如何避免?

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"));

不是从下往上,是你设置的顺序。

对于你的装饰器,你基本上只是这样做了:

requireIntegers(countParams(rectangleArea(20, 30, "hey"))).

这意味着它首先执行 requireIntegers,将其作为输入传递给其他所有内容 (countParams(rectangleArea(20, 30, "hey")))。

然后您会看到控制台日志和错误,因为 params.forEach 扫描参数并发现 'hey' 不是数字。

第一次为给定函数创建装饰函数时,返回的函数没有名称——它是匿名的。因此,当您再次传递 that 装饰函数时,fn 将是那个匿名函数。

要解决此问题,请将 fn 函数的名称也分配给返回的修饰函数。这样,即使您一次又一次地装饰该函数,名称也会保留...

这是一个辅助函数,它将名称 属性 分配给给定函数:

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"));

Why the functions are called from the bottom up.

因为在你的装饰器中 最后 步骤是调用 fn.

fn 可能是一个已经修饰过的函数,所以 早期 函数的修饰 运行 以后是正常的.

就像把生日礼物包装好几次,每次都用不同颜色的包装纸。当您的朋友打开包装时,他们会看到包装纸的颜色与您使用它们的顺序相反。

所以你想用你的装饰器做一些额外的事情?他们需要一些共同的行为?我们已经展示了我们知道如何做到这一点:使用装饰器。您的装饰器需要自己的装饰器!

这里我写了一个 decorator-decorator keep 它接受一个装饰器函数和 returns 一个新的装饰器函数,它保留函数的 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,后来我才意识到我个人也希望这个函数能保持元数不变。我无法为此想出一个明确有用的名称...这对我来说是一个很大的警告信号。所以这个设计可能还是有问题。