javascript - CoffeeScript 类和 nosuchmethod

标签 javascript coffeescript metaprogramming ecmascript-harmony

假设存在Harmony Proxies在底层 Javascript 引擎中,如何构造一个 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),我们可以在代理处理程序中做类似的事情,但不是直接调用一个特殊的方法(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!'

关于javascript - CoffeeScript 类和 nosuchmethod,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14513509/

相关文章:

javascript - Firefox 不显示自定义错误消息

coffeescript - 无法在 Trigger.io 中使用预构建 Hook

c++ - 用于散列编译时字符串的延迟指针

javascript - Backbone 插入位置和移位模型

javascript - 使用 Android 硬件后退键导航时出现导航问题 [react-native]

coffeescript - gulp ,browserify, map ?

定义函数闭包的 Pythonic 方式

javascript - 将变量从js函数保存到代码后面的server/#C?

javascript - 使用 javascript(或 jQuery)选择和操作 CSS 伪元素,例如::before 和::after

ruby-on-rails - 如何设置 "dynamically"变量值?