简体   繁体   English

CoffeeScript中的动态类生成

[英]Dynamic class generation in CoffeeScript

What is the best way to dynamically create classes in CoffeeScript, in order to later instantiate objects of them? 在CoffeeScript中动态创建类的最佳方法是什么,以便稍后实例化它们的对象?

I have found ways to do it, but I am not sure if there is maybe an even better (or simpler) way to achieve it. 我已经找到了实现它的方法,但我不确定是否有更好(或更简单)的方法来实现它。 Please let me know your thoughts on my code. 请让我知道您对我的代码的看法。

Let's start with simple non-dynamic classes: 让我们从简单的非动态类开始:

class Animal
  constructor: (@name) ->

  speak: ->
    alert "#{@name} says #{@sound}"

class Cat extends Animal
  constructor: (@name) ->
    @sound = "meow!"

garfield = new Cat "garfield"
garfield.speak()

As expected, garfield says meow! 正如所料,加菲尔德说喵!

But now we want to dynamically generate classes for more animals, which are defined as follows: 但现在我们想为更多动物动态生成类,其定义如下:

animalDefinitions = [
    kind:  'Mouse'
    sound: 'eek!'
  ,
    kind:  'Lion'
    sound: 'roar!'
  ]

The first naive attempt fails: 第一次天真的尝试失败了:

for animal in animalDefinitions
  animal.class = class extends Animal
    constructor: (@name) ->
      @sound = animal.sound

mutant = new animalDefinitions[0].class "mutant"
mutant.speak()

The animal we just created, mutant , should be a mouse. 我们刚创造的动物, mutant ,应该是一只老鼠。 However, it says roar! 然而,它说咆哮! This is because animal.sound only gets evaluated when we instantiate the class. 这是因为在我们实例化类时,只会对animal.sound进行求值。 Luckily, from JavaScript we know a proven way to solve this: a closure: 幸运的是,从JavaScript我们知道一种可靠的方法来解决这个问题:一个闭包:

for animal in animalDefinitions
  makeClass = (sound) ->
    class extends Animal
      constructor: (@name) ->
        @sound = sound
  animal.class = makeClass(animal.sound)

mickey = new animalDefinitions[0].class "mickey"
mickey.speak()

simba = new animalDefinitions[1].class "simba"
simba.speak()

Now it works as desired, mickey mouse says eek! 现在它按照需要工作,米老鼠说eek! and simba the lion says roar! 和狮子说咆哮! But it looks somewhat complicated already. 但它看起来有点复杂。 I am wondering if there is an easier way to achieve this result, maybe by accessing the prototype directly. 我想知道是否有更简单的方法来实现这个结果,可能是直接访问原型。 Or am I completely on the wrong track? 还是我完全走错了路?

Since sound is a default value for an Animal instance, you can set it as a property on class definition: 由于sound是Animal实例的默认值,因此您可以将其设置为类定义的属性:

class Cat extends Animal
    sound: 'meow!'

garfield = new Cat "garfield"
garfield.speak() # "garfield says meow!"

then 然后

for animal in animalDefinitions
    animal.class = class extends Animal
        sound: animal.sound

mutant = new animalDefinitions[0].class "mutant"
mutant.speak() # "mutant says eek!"

If you want sound to be overridable, you can do 如果你想要sound可以覆盖,你可以做到

class Animal
    constructor: (@name, sound) ->
        @sound = sound if sound? 
    speak: ->
        console.log "#{@name} says #{@sound}"

For your immediate problem (which is that the closure in the loop does not capture the current value, but the latest one), there is the do construct: 对于你的直接问题(循环中的闭包不捕获当前值,但是最新的闭包),有do构造:

for animal in animalDefinitions
  do (animal) ->
    animal.class = class extends Animal
      constructor: (@name) ->
        @sound = animal.sound

I somehow expected CoffeeScript to take care of that automatically, since this is a common error in JavaScript, but at least with do there is a concise way to write it. 不知何故,我预计的CoffeeScript自动照顾这一点,因为这是在JavaScript中一个常见的错误,但至少有do有把它写一个简洁的方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM