为了账号安全,请及时绑定邮箱和手机立即绑定

TypeScript 动态或以编程方式链接函数

TypeScript 动态或以编程方式链接函数

慕莱坞森 2023-07-20 14:37:35
TypeScript 函数链接,但我想以编程方式链接它们。示例类:chain.tsclass MyChain {  value: number = 0;  constructor() {    this.value = 0;  }  sum(args: number[]) {    this.value = args.reduce((s, c) => s + c, 0);    return this;  }  add(v: number) {    this.value = this.value + v;    return this;  }  subtract(v: number) {    this.value = this.value - v;    return this;  }}const mc = new MyChain();console.log(mc.sum([1, 2, 3, 4]).subtract(5).value);5我在控制台上看到了这个数字。现在,我对 JavaScript 和 TypeScript 还很陌生,所以我发现这个类中的函数实际上是该类实例数组的元素。因此,我可以这样做:console.log(mc["sum"]([1, 2, 3, 4]).value);这确实返回了数字10。现在,我对如何以编程方式链接它感到困惑。例如(无论如何,这显然不是我想做的,并且表明我对 JavaScript 缺乏理解:console.log(mc["sum"]([1, 2, 3, 4]).mc["subtract"](5).value);错误:类型“MyChain”上不存在属性“mc”。ts(2339)好吧,老实说,我直觉地知道这是行不通的。但是,想一想,我将如何以任何合理的语言访问多维数组的元素?console.log(mc["sum"]([1, 2, 3, 4])["subtract"](5).value);答对了。这可以解决问题。但是,这并不是我真正需要的解决方案。我需要的是这样的:interface IChainObject {  action: string;  operand: number | number[];}const chainObj: IChainObject[] = [  { action: "sum", operand: [1, 2, 3, 4] },  { action: "subtract", operand: 5 },];首先,我想尝试一下:console.log(mc[chainObj[0].action](chainObj[0].operand).value);因此,生成一种机制,最终构建如下内容:console.log(  mc[chainObj[0].action](chainObj[0].operand)[chainObj[1].action](    chainObj[1].operand  ).value);因此,在我看来,我想要的是某种方式来生成它:[chainObj[0].action](chainObj[0].operand)[chainObj[1].action](chainObj[1].operand)由此,我的链对象具有一个或多个操作/操作数对象集:const chainObj: IChainObject[] = [  { action: "sum", operand: [1, 2, 3, 4] },  { action: "subtract", operand: 5 },];现在,这就是我的大脑或多或少关闭的地方。我认为我需要生成一串字符串值,但它们只是字符串,并且不会真正按照我想要的方式作为函数的数组索引。我为什么要这样做?最终,我想从 JSON 对象构建一个复杂的 Yup 模式对象。
查看完整描述

3 回答

?
PIPIONE

TA贡献1829条经验 获得超9个赞

让我们从我发现的第一个误解开始:

现在,我对 JavaScript 和 TypeScript 还很陌生,所以我发现这个类中的函数实际上是该类实例数组的元素。

不是这种情况。Javascript 中的方括号用于所有属性查找,而不仅仅是数组索引。x.foo实际上相当于x["foo"], 并且相同的语法适用于数组,因为数组只是对象。Javascript 中的类只是具有原型属性的对象,原型属性本身就是一个对象。它包含默认属性列表,如果您实例化一个类并查找对象中不存在的属性,它将在原型中搜索它。那么,看一下代码:

mc["sum"]([1, 2, 3])

它在 中搜索“sum”属性mc,但找不到任何属性,因为您尚未定义该属性,因此它在 of 中搜索prototypeMyChain并找到该mc方法。因此,mc["sum"]就是sum的方法mc。现在,这段代码:

console.log(mc["sum"]([1, 2, 3, 4]).mc["subtract"](5).value);

不起作用,而且看起来很不对劲是有原因的。mc["sum"]([1, 2, 3, 4])返回mc,那么为什么您必须访问该mc属性(并不是该mc属性甚至存在)?subtract这就是为什么你的第二个例子(直接调用的例子)有效:

console.log(mc["sum"]([1, 2, 3, 4])["subtract"](5).value);

现在,让我们看看工作代码:

const mc = new MyChain();


interface IChainObject {

  action: string;

  operand: number | number[];

}


const chainObj: IChainObject[] = [

  { action: "sum", operand: [1, 2, 3, 4, 5] },

  { action: "subtract", operand: 5 },

];


let myChain = {};

chainObj.forEach((o) => {

  myChain = mc[o.action](o.operand);

});

console.log("myChain is", myChain["value"]);

实际上您不需要很多这样的代码。它可以简化为:


const mc = new MyChain();


interface IChainObject {

  action: keyof MyChain;

  operand: number | number[];

}


const chainObj: IChainObject[] = [

  { action: "sum", operand: [1, 2, 3, 4, 5] },

  { action: "subtract", operand: 5 },

];


chainObj.forEach((o) => {

  // bypass typescript type checking with cast

  (mc[o.action] as Function)(o.operand);

});

console.log("myChain is", mc.value);

本质上,按顺序forEach循环遍历元素chainObj。元素的值存储在变量 中o。mc[o.action]获取存储在 中的方法名称o.action,并使用方括号访问它。这基本上就是查方法了。然后,该方法被调用(o.operand)(在Javascript中,函数只是值,你可以像函数一样调用任何值,但如果它不是函数,则会出错)。mc然后修改自身,然后继续下一个循环。如果我们debugger在函数中插入一条语句,然后在第一个循环中中断,我们可以检查变量:

//img1.sycdn.imooc.com/64b8d68e0001272d04950364.jpg

正如您所看到的,该值从 0 开始,o.action是“sum”,并且mc[o.action]是 sum 方法。然后我们可以使用 调用 sum 方法o.operand,它将元素相加并将值设置为 15。然后,在第二个循环中:

//img4.sycdn.imooc.com/64b8d6990001160603490298.jpg

mc[o.action]是减法,我们用 来调用它o.operand,即 5,将值降低到 10。



查看完整回答
反对 回复 2023-07-20
?
汪汪一只猫

TA贡献1898条经验 获得超8个赞

Javascript 中的大多数东西classes基本上都是objects1

这意味着可以通过点符号或方括号符号来访问属性(或者在本例中为函数) 。

让我们看一个可能有助于解释的示例:

class MyClass {

  myFunction(x) {

    console.log(x);

  }

}

const x = new MyClass();

// attribute accessed via the dot notation

x.myFunction("Hello World!");

// attribute accessed via the bracket notation and a string 

x['myFunction']("Hello World, again!");

// attribute accessed via a variable that is a string 

const functionName = 'myFunction';

x[functionName]("Well uh, Hello World again?");

// attribute accessed via a variable that is a string, and passing in an argument

const argument = "This is " + "an argument";

x[functionName](argument);

为了进一步说明这一点:


class MyClass {

  myFunction(x) {

    console.log(x);

  }

}

const x = new MyClass();

console.log(x.myFunction) // returns a function

console.log(x["myFunction"]) // returns a function


// executing the function

x.myFunction("Method One");

x["myFunction"]("Method Two")

我们可以看到返回的函数可以被调用。

让我们回到你的例子

chainObj.forEach((o) => {
  myChain = mc[o.action](o.operand);
});
  • o.action是函数名

  • o.operand因此,is 大致翻译为:

chainObj.forEach((o) => {
  myChain = mc[functionName](arugment);
});

就像我们之前的例子一样。

“类基本上只是对象”


查看完整回答
反对 回复 2023-07-20
?
紫衣仙女

TA贡献1839条经验 获得超15个赞

这方面的内容有很多;我只想关注“让代码正常工作的秘诀是什么forEach()? ”


“秘密”在于 的实例MyChain有一个名为 的属性value,该属性在调用每个方法后都会更新。代码并forEach()没有真正将调用链接在一起;它只是对每次命名的原始MyChain变量进行操作。mc


MyChain由于该更新的所有方法this.value也返回this,因此您是否真的链接调用(对每个方法调用的返回值进行操作)并不重要:


const chaining = new MyChain();

console.log(chaining.add(3).subtract(1).value); // 2

或者如果您只是连续调用原始对象上的方法:


const notChaining = new MyChain();

notChaining.add(3);

notChaining.subtract(1);

console.log(notChaining.value) // 2

如果您希望它们之间存在差异,您可以通过制作两个版本来显示它MyChain;一种只能通过链接起作用,一种只能连续起作用。


以下内容需要链接,因为它永远不会更新原始对象,并且方法调用会返回带有方法调用结果的新对象:


class RealChain {

  constructor(public value: number = 0) { }


  sum(args: number[]) {

    return new RealChain(args.reduce((s, c) => s + c, 0));

  }


  add(v: number) {

    return new RealChain(this.value + v);

  }


  subtract(v: number) {

    return new RealChain(this.value - v);

  }

}

    

const realChaining = new RealChain();

console.log(realChaining.add(3).subtract(1).value); // 2


const notRealChaining = new RealChain();

notRealChaining.add(3);

notRealChaining.subtract(1);

console.log(notRealChaining.value) // 0

并且以下内容禁止链接,因为它只更新原始对象并且其方法不返回任何内容:


class NotChain {

  value: number = 0;

  constructor() {

    this.value = 0;

  }


  sum(args: number[]) {

    this.value = args.reduce((s, c) => s + c, 0);

  }


  add(v: number) {

    this.value = this.value + v;

  }


  subtract(v: number) {

    this.value = this.value - v;

  }

}


const realNotChaining = new NotChain();

realNotChaining.add(3);

realNotChaining.subtract(1);

console.log(realNotChaining.value) // 2


const badNotChaining = new NotChain();

console.log(badNotChaining.add(3).subtract(1).value); // error! 

// badNotChaining.add(3) is undefined so you can't call subtract() on it

代码forEach()仅适用于NotChain实例,不适用于RealChain实例。


如果您想要一个类似编程循环的东西,它实际上可以与链接一起使用,而不是在原始对象上调用方法,那么您可能应该使用reduce()而不是forEach():


const realChainReduced = chainObj.reduce(

  (mc, o) => mc[o.action](o.operand), 

  new RealChain() // or MyChain, doesn't matter

);

console.log("realChainReduced is", realChainReduced.value); // 10

请注意,我没有介绍任何其他部分,包括 TypeScript 细节(此处使用的类型会产生一些编译器错误),因此请注意。

Playground 代码链接


查看完整回答
反对 回复 2023-07-20
  • 3 回答
  • 0 关注
  • 155 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信