假设底层Javascript引擎中存在Harmony Proxies,如何构造一个CoffeeScript父类(super class),以便对其进行扩展将允许类定义noSuchMethod方法(或methodMessing)?

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

最佳答案

好问题! = 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生成的构造函数返回了调用 super 构造函数的结果(并因此返回了代理对象)。如果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 :(

您必须显式返回调用 super 构造函数的结果才能使其工作:
  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!

现在,对于缺少的方法,为了与问题中请求的接口(interface)具有相同的接口(interface),我们可以在代理处理程序中执行类似的操作,但是当目标没有属性时,无需在目标上直接调用特殊方法(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}'"

这样,我们现在可以混合从相同基类继承的Repeaters和CommandLines(多么有用!= P):
cl = CommandLine.create()
r = Repeater.create yes
cl.echo r['hello proxies'] # -> Executing command 'echo HELLO PROXIES!'

09-25 17:05