【问题标题】:CoffeeScript classes and nosuchmethodCoffeeScript 类和 nosuchmethod
【发布时间】:2013-01-08 22:15:04
【问题描述】:

假设在底层 Javascript 引擎中存在 Harmony Proxies,如何构造一个 CoffeeScript 超类,以便扩展它允许类定义 noSuchMethod 方法(或 methodMessing)?

如果类(或其超类)没有请求的方法,则将使用名称和参数列表调用该方法。

【问题讨论】:

    标签: javascript coffeescript metaprogramming ecmascript-harmony


    【解决方案1】:

    好问题! =D

    (注意:我只在 Firefox 中测试过,因为它似乎是唯一支持 Harmony 代理的浏览器。)

    这似乎适用于缺少的属性

    class DynamicObject
    
      propertyMissingHandler =
        get: (target, name) ->
          if name of target
            target[name] 
          else
            target.propertyMissing name
    
      constructor: ->
        return new Proxy @, propertyMissingHandler
    
      # By default return undefined like a normal JS object.
      propertyMissing: -> undefined
    
    class Repeater extends DynamicObject
      exited: no
      propertyMissing: (name) ->
        if @exited then "#{name.toUpperCase()}!" else name
    
    r = new Repeater
    console.log r.hi     # -> hi
    console.log r.exited # -> false. Doesn't print "exited" ;)
    r.exited = yes
    console.log r.omg    # -> OMG!
    

    现在,它可以工作了,但它有一个小大警告:它依赖于“其他类型”的构造函数。也就是说,DynamicObject 的构造函数返回 不是 DynamicObject 实例(它返回包装实例的代理)。其他类型的构造函数有微妙和不那么微妙的问题和they are not a very loved feature in the CoffeeScript community

    例如,上面的工作(在 CoffeeScript 1.4 中),但只是因为生成的 Repeater 构造函数返回调用超级构造函数的结果(因此返回代理对象)。如果Repeater 有不同的构造函数,它将无法工作:

    class Repeater extends DynamicObject
      # Innocent looking constructor.
      constructor: (exited = no) ->
        @exited = exited
      propertyMissing: (name) ->
        if @exited then "#{name.toUpperCase()}!" else name
    
    console.log (new Repeater yes).hello # -> undefined :(
    

    您必须显式返回调用超级构造函数的结果才能使其工作:

      constructor: (exited = no) ->
        @exited = exited
        return super
    

    因此,由于其他类型的构造函数有点令人困惑/损坏,我建议避免使用它们并使用类方法来实例化这些对象,而不是 new

    class DynamicObject
    
      propertyMissingHandler =
        get: (target, name) ->
          if name of target
            target[name] 
          else
            target.propertyMissing name
    
      # Use create instead of 'new'.
      @create = (args...) ->
        instance = new @ args...
        new Proxy instance, propertyMissingHandler
    
      # By default return undefined like a normal JS object.
      propertyMissing: -> undefined
    
    class Repeater extends DynamicObject
      constructor: (exited = no) ->
        @exited = exited
        # No need to worry about 'return'
      propertyMissing: (name) ->
        if @exited then "#{name.toUpperCase()}!" else name
    
    console.log (Repeater.create yes).hello # -> HELLO!
    

    现在,对于缺少的方法,为了和问题中要求的接口相同,我们可以在代理处理程序中做类似的事情,但不是直接调用特殊方法(propertyMissing)在目标上没有具有该名称的属性时,它返回一个函数,该函数又调用特殊方法(methodMissing):

    class DynamicObject2
    
      methodMissingHandler =
        get: (target, name) ->
          return target[name] if name of target
          (args...) ->
            target.methodMissing name, args
    
      # Use this instead of 'new'.
      @create = (args...) ->
        instance = new @ args...
        new Proxy instance, methodMissingHandler 
    
      # By default behave somewhat similar to normal missing method calls.
      methodMissing: (name) -> throw new TypeError "#{name} is not a function"
    
    class CommandLine extends DynamicObject2
      cd: (path) ->
        # Usually 'cd' is not a program on its own.
        console.log "Changing path to #{path}" # TODO implement me
      methodMissing: (name, args) ->
        command = "#{name} #{args.join ' '}"
        console.log "Executing command '#{command}'" 
    
    cl = CommandLine.create()
    cl.cd '/home/bob/coffee-example'  # -> Changing path to /home/bob/coffee-example
    cl.coffee '-wc', 'example.coffee' # -> Executing command 'coffee -wc example.coffee'
    cl.rm '-rf', '*.js'               # -> Executing command 'rm -rf *.js'
    

    不幸的是,我无法找到一种方法来区分代理处理程序中的属性访问和方法调用,以便 DynamicObject 可以更智能并相应地调用 propertyMissing 或 methodMissing(尽管如此,因为方法调用只是一个属性访问后跟函数调用)。

    如果我必须选择并使 DynamicObject 尽可能灵活,我会选择 propertyMissing 实现,因为子类可以选择他们希望如何实现 propertyMissing 并将缺少的属性视为方法或不。上面根据 propertyMissing 实现的 CommandLine 示例将是:

    class CommandLine extends DynamicObject
      cd: (path) ->
        # Usually 'cd' is not a program on its own.
        console.log "Changing path to #{path}" # TODO implement me
      propertyMissing: (name) ->
        (args...) ->
          command = "#{name} #{args.join ' '}"
          console.log "Executing command '#{command}'" 
    

    这样,我们现在可以混合继承自同一个基类的中继器和命令行(多么有用!=P):

    cl = CommandLine.create()
    r = Repeater.create yes
    cl.echo r['hello proxies'] # -> Executing command 'echo HELLO PROXIES!'
    

    【讨论】:

    • 我不知道 1.4 中的 Ctor 问题,感谢您指出。
    • 感谢您对此进行探索。它可能不被接受,但从构造函数返回一个代理是一个很好的技巧。
    • @koops 是的,它成功了,但我更希望看到一个能够正确扩展/继承代理的解决方案。扩展 Proxy(至少是 Firefox 提供的 Proxy)的问题在于 Proxy 的行为似乎不像普通的函数/构造函数;你可以做new Proxy(target, handler),但你不能做Proxy.apply(this, target, handler)(在DynamicObject构造函数中这样做会是一个更好的技巧IMO)。我还尝试让 DynamicObject.prototype 成为 Proxy 的实例(这听起来是一种扩展它们的合理方式),但这在整个地方都爆炸了,呵呵。
    猜你喜欢
    • 2011-12-07
    • 2011-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多