【问题标题】:Create ES6/ESNext prototype function with different scope (not an inline function)创建具有不同范围的 ES6/ESNext 原型函数(不是内联函数)
【发布时间】:2018-03-20 06:10:30
【问题描述】:

好吧,我们有这个:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    printName() {
        console.log('this.name');
    }
}

我想做的是定义 printName,像这样:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    // we want to define printName using a different scope
    // this syntax is close, but is *not* quite correct
    printName: makePrintName(foo, bar, baz) 
}

其中 makePrintName 是一个仿函数,如下所示:

exports.makePrintName = function(foo, bar, baz){
   return function(){ ... }
};

这在 ES6 中可行吗?我的编辑器和 TypeScript 不喜欢这个

注意:使用 ES5,这很容易做到,看起来像这样:

var Car = function(){...};

Car.prototype.printName = makePrintName(foo, bar, baz);

使用类语法,目前最适合我的是:

const printName = makePrintName(foo,bar,baz);

class Car {
  constructor(){...}
  printName(){
    return printName.apply(this,arguments);
  }
}

但这并不理想。 如果你尝试使用类语法来做 ES5 语法可以做的事情,你会发现问题。因此,ES6 类包装器是一个泄漏抽象。

要查看实际用例,请参阅:

https://github.com/sumanjs/suman/blob/master/lib/test-suite-helpers/make-test-suite.ts#L171

使用TestBlock.prototype.startSuite = ... 的问题在于,在这种情况下,我不能简单地在线返回课程:

https://github.com/sumanjs/suman/blob/master/lib/test-suite-helpers/make-test-suite.ts#L67

【问题讨论】:

  • “注意,使用 ES5,这很容易做到” 你可以做同样的事情:ES6 - declare a prototype method on a class with an import statement
  • 是的,但这是 ES6 比 ES5 更少动态和更严格的完美示例。导入语句的技术是 ES5 语法而不是 ES6 语法。
  • 呃?所有在 ES5 中有效的东西在 ES6 中也有效。如果您指的是 ES6 中引入的语法,那当然。但是如果不使用 ES6 之前引入的语法,就无法编写 JavaScript 程序......所以这个论点有点愚蠢。
  • 老兄,我不知道我们为什么要争论这个......这是 ES6 类的限制,简单明了。如果你是某个 JS 委员会的成员,请解决这个问题。
  • “使用TestBlock.prototype.startSuite = ...的问题是,在这种情况下,我不能简单地在线返回类”但是你不是必需的把return语句放在那里。声明后可以return TestBlock;

标签: javascript typescript ecmascript-6


【解决方案1】:

我还没有看到提到的另一个想法是使用method decorator。以下装饰器采用方法实现并根据需要将其放在原型上:

function setMethod<T extends Function>(value: T) {
    return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>): void {
      descriptor.value = value;
      delete descriptor.get;
      delete descriptor.set;
    };
}

把它放在图书馆的某个地方。以下是您的使用方法:

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
    }

    @setMethod(makePrintName(foo, bar, baz))
    printName() {} // dummy implementation
}

唯一的缺点是你必须在类中放置一个带有正确签名的方法的虚拟实现,因为装饰器需要一些东西来装饰。但它在运行时的行为完全符合您的要求(它不是为每个实例花费一个新函数定义的实例方法,也不是在每次使用时花费一个额外函数调用的访问器)。

这有帮助吗?

【讨论】:

  • 我认为@estus 可能提出了类似的建议?看看他们的答案 thx
  • 那是修改类的类装饰器;这是一个方法装饰器,它只涉及声明它的方法。它们相似但不相同,所以我想我会提到它。
【解决方案2】:

是我没听懂还是..??

class 关键字通常只是 ES5 中委托原型的语法糖。我只是在打字稿中尝试过

class Car {
private kind: string;
private name : string;
printName :Function;
    constructor(name) {
                this.kind = 'Car';
                this.name = name;
            }

   }
var makePrintName = function (foo, bar, baz) {
    return function () { console.log(foo, bar, baz); };
};
Car.prototype.printName = makePrintName('hello', 'world', 'did you get me');
var bmw = new Car('bmw');
bmw.printName();
console.log(bmw.hasOwnProperty('printName'));

【讨论】:

  • 对,使用非内联函数分配给原型的语法糖在哪里!?
  • 如果你要使用 TypeScript 回答,请使用合理的类型。
  • 是的,我们可以为类型做得更好,但这里关注的是如何将原型函数添加到类中,因此我们抽象了类型定义以更加关注问题
【解决方案3】:

由于类型系统的限制,在 JavaScript 和 TypeScript 中执行此操作的优选方法可能会有所不同,但如果 printName 应该是原型方法而不是实例方法(前者有利于 several reasons),那么没有那么多选择。

可以通过访问器检索原型方法。在这种情况下,最好将其记忆或缓存到变量中。

const cachedPrintName = makePrintName(foo, bar, baz);

class Car {
    ...
    get printName(): () => void {
        return cachedPrintName;
    }
}

并且可以懒惰地评估:

let cachedPrintName;

class Car {
    ...
    get printName(): () => void {
        return cachedPrintName || cachedPrintName = makePrintName(foo, bar, baz);
    }
}

或者可以直接分配给prototype类。在这种情况下,它应该被额外输入为类属性,因为 TypeScript 忽略了prototype 赋值:

class Car {
    ...
    printName(): () => void;
}

Car.prototype.printName = makePrintName(foo, bar, baz);

其中() =&gt; voidmakePrintName 返回的函数的类型。

TypeScript 很自然的一种方法是不修改类原型,而是扩展原型链并通过 mixin classes 引入新的或修改的方法。这在 JavaScript 中引入了不必要的复杂性,但让 TypeScript 对类型感到满意:

function makePrintNameMixin(foo, bar, baz){
  return function (Class) {
    return class extends Class {
      printName() {...}
    }
  }
}

const Car = makePrintNameMixin(foo, bar, baz)(
  class Car {
    constructor() {...}
  }
);

此时无法无缝使用 TypeScript 装饰器,因为类突变是 not supported at this moment。该类应另外补充接口以抑制类型错误:

interface Car {
  printName: () => void;
}

@makePrintNameMixin(foo, bar, baz)
class Car {
    constructor() {...}
}

【讨论】:

  • 应该是=&gt; Function 而不是=&gt; void
  • 使用 Car.prototype.printName 是 ES5 语法 - 具体来说,我正在寻找 ES6/TS 版本。
  • 可用的选项都在那里。我添加了更复杂且需要更改 makePrintName 工作方式的 mixins 的概念,但它们对 TS 来说是自然的。它不仅仅是弃用的 'ES5' 语法。它是 JS,一种单一语言。如果它适合这种情况(并且适合这种情况),它是低级的,但在 ES6 和 TS 开发中完全有效。类是一个函数,因此您可以通过prototype 扩展它的原型,就这么简单。我猜 Felix Kling 已经在上面的 cmets 中解释了这一点。
  • @AlexanderMills 你为什么要使用Function 而不是() =&gt; void。使用Function 是不精确的......
  • @AlexanderMills 我列出了我认为可行的所有选项。如果您不喜欢与prototype 混淆,请选择最后一个。至于 jcalz 的回答,它会导致创建将被丢弃但仍需要为其维护正确签名的虚拟函数。像@setMethod(makePrintName(foo, bar, baz)) printName() {} 这样使用它并使用this.printName(1) 调用它会导致TS 中的类型错误,因为它不应该使用arg 调用。没有提到它,因为它看起来更像是装饰器滥用。
【解决方案4】:

只需将: 替换为=

printName = makePrintName()

: 用于类型表示法。

编辑
如 cmets 中所述,上述内容不会更改原型。相反,您可以使用 ES5 语法在类定义之外执行此操作:

// workaround: extracting function return type
const dummyPrintName = !true && makePrintName();
type PrintNameType = typeof dummyPrintName;

class Car {
    // ...
    printName: PrintNameType;
}
Car.prototype.printName = makePrintName();

Playground

【讨论】:

  • 啊啊啊啊啊啊啊啊啊啊啊好奇怪
  • 如果您认为这是一个好问题,也许我应该改写标题?
  • 对不起,这不会创建原型函数。我要删除答案
  • 是的,它看起来像是在顶级对象上,这很奇怪
  • 谢谢,是的,这是唯一对我有用的,它是 es5 和 es6 之间的混合...我会等着看是否有更好的结果,然后再选择答案谢谢
【解决方案5】:

你为什么不试试这样的东西

class Car {
    constructor(name) {
        this.kind = 'Car';
        this.name = name;
        this.printName = makePrintName(foo, bar, baz);

    }
}

【讨论】:

  • 是的,可以,但是使用原型更有效,我们清理了原型链中的顶级对象。
  • 是的原型智能它效率不高:)
猜你喜欢
  • 1970-01-01
  • 2018-10-26
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-06
  • 2021-12-25
相关资源
最近更新 更多