【问题标题】:Does Javascript have something like Ruby's method_missing feature?Javascript 是否有类似 Ruby 的 method_missing 功能?
【发布时间】:2012-04-04 11:54:50
【问题描述】:

在 Ruby 中,我认为您可以调用尚未定义的方法,然后捕获被调用方法的名称并在运行时处理此方法。

Javascript 可以做同样的事情吗?

【问题讨论】:

标签: javascript ruby metaprogramming


【解决方案1】:

您可以使用Proxy 类。

var myObj = {
    someAttr: 'foo'
};

var p = new Proxy(myObj, {
    get: function (target, methodOrAttributeName) {
        // target is the first argument passed into new Proxy, aka. target is myObj

        // First give the target a chance to handle it
        if (Object.keys(target).indexOf(methodOrAttributeName) !== -1) {
            return target[methodOrAttributeName];
        }

        // If the target did not have the method/attribute return whatever we want

        // Explicitly handle certain cases
        if (methodOrAttributeName === 'specialPants') {
            return 'trousers';
        }

        // return our generic method_missing function
        return function () {
            // Use the special "arguments" object to access a variable number arguments
            return 'For show, myObj.someAttr="' + target.someAttr + '" and "'
                   + methodOrAttributeName + '" called with: [' 
                   + Array.prototype.slice.call(arguments).join(',') + ']';
        }
    }
});

console.log(p.specialPants);
// outputs: trousers

console.log(p.unknownMethod('hi', 'bye', 'ok'));
// outputs: 
// For show, myObj.someAttr="foo" and "unknownMethod" called with: [hi,bye,ok]

关于

您可以使用p 代替myObj

你应该小心get,因为它会拦截p的所有属性请求。因此,p.specialPants() 会导致错误,因为specialPants 返回的是字符串而不是函数。

unknownMethod 的实际情况等同于以下内容:

var unk = p.unkownMethod;
unk('hi', 'bye', 'ok');

这是因为函数是 javascript 中的对象。

奖金

如果您知道期望的参数数量,则可以在返回的函数中将它们声明为正常。
例如:

...
get: function (target, name) {
    return function(expectedArg1, expectedArg2) {
...

【讨论】:

    【解决方案2】:

    我创建了一个 javascript 库,让您可以在 javascript 中使用 method_missinghttps://github.com/ramadis/unmiss

    它使用 ES6 代理来工作。这是一个使用 ES6 类继承的示例。但是,您也可以使用装饰器来实现相同的结果。

    import { MethodMissingClass } from 'unmiss'
    
    class Example extends MethodMissingClass {
        methodMissing(name, ...args) {
            console.log(`Method ${name} was called with arguments: ${args.join(' ')}`);
        }
    }
    
    const instance = new Example;
    instance.what('is', 'this');
    
    > Method what was called with arguments: is this
    

    【讨论】:

      【解决方案3】:

      我之所以提出这个问题,是因为如果第一个对象上不存在该方法,我正在寻找一种方法来解决另一个对象。它不像您所要求的那样灵活 - 例如,如果两者都缺少一个方法,那么它将失败。

      我正在考虑为我拥有的一个小库执行此操作,该库有助于以一种使 extjs 对象更易于测试的方式配置它们。我有单独的调用来实际获取交互对象,并认为这可能是通过有效返回增强类型将这些调用粘在一起的好方法

      我可以想到两种方法:

      原型

      您可以使用原型来做到这一点 - 因为如果它不在实际对象上,那么东西就会落入原型。如果您希望通过使用 this 关键字的一组函数似乎不起作用 - 显然您的对象不会知道或关心其他人知道的东西。

      如果所有代码都是您自己的代码,并且您没有使用 this 和构造函数...这是一个好主意,原因有很多,那么您可以这样做:

          var makeHorse = function () {
              var neigh = "neigh";
      
              return {
                  doTheNoise: function () {
                      return neigh + " is all im saying"
                  },
                  setNeigh: function (newNoise) {
                      neigh = newNoise;
                  }
              }
          };
      
          var createSomething = function (fallThrough) {
              var constructor = function () {};
              constructor.prototype = fallThrough;
              var instance = new constructor();
      
              instance.someMethod = function () {
                  console.log("aaaaa");
              };
              instance.callTheOther = function () {
                  var theNoise = instance.doTheNoise();
                  console.log(theNoise);
              };
      
              return instance;
          };
      
          var firstHorse = makeHorse();
          var secondHorse = makeHorse();
          secondHorse.setNeigh("mooo");
      
          var firstWrapper = createSomething(firstHorse);
          var secondWrapper = createSomething(secondHorse);
          var nothingWrapper = createSomething();
      
          firstWrapper.someMethod();
          firstWrapper.callTheOther();
          console.log(firstWrapper.doTheNoise());
      
          secondWrapper.someMethod();
          secondWrapper.callTheOther();
          console.log(secondWrapper.doTheNoise());
      
          nothingWrapper.someMethod();
          //this call fails as we dont have this method on the fall through object (which is undefined)
          console.log(nothingWrapper.doTheNoise());
      

      这不适用于我的用例,因为 extjs 人不仅错误地使用了“this”,他们还在使用原型和“this”的原则上构建了一个完整的疯狂的经典继承类型系统。

      这实际上是我第一次使用原型/构造函数,我有点困惑你不能只设置原型——你还必须使用构造函数。对象(至少在 Firefox 中)调用 __proto 有一个魔法场,它基本上是真正的原型。似乎实际的原型字段仅在构建时使用......多么令人困惑!


      复制方法

      这种方法可能更昂贵,但对我来说似乎更优雅,并且也适用于使用this 的代码(例如,您可以使用它来包装库对象)。它也适用于使用函数式/闭包风格编写的东西——我刚刚用 this/constructors 说明了它,以表明它适用于类似的东西。

      这是模组:

          //this is now a constructor
          var MakeHorse = function () {
              this.neigh = "neigh";
          };
      
          MakeHorse.prototype.doTheNoise = function () {
              return this.neigh + " is all im saying"
          };
          MakeHorse.prototype.setNeigh = function (newNoise) {
              this.neigh = newNoise;
          };
      
          var createSomething = function (fallThrough) {
              var instance = {
                  someMethod : function () {
                      console.log("aaaaa");
                  },
                  callTheOther : function () {
                      //note this has had to change to directly call the fallThrough object
                      var theNoise = fallThrough.doTheNoise();
                      console.log(theNoise);
                  }
              };
      
              //copy stuff over but not if it already exists
              for (var propertyName in fallThrough)
                  if (!instance.hasOwnProperty(propertyName))
                      instance[propertyName] = fallThrough[propertyName];
      
              return instance;
          };
      
          var firstHorse = new MakeHorse();
          var secondHorse = new MakeHorse();
          secondHorse.setNeigh("mooo");
      
          var firstWrapper = createSomething(firstHorse);
          var secondWrapper = createSomething(secondHorse);
          var nothingWrapper = createSomething();
      
          firstWrapper.someMethod();
          firstWrapper.callTheOther();
          console.log(firstWrapper.doTheNoise());
      
          secondWrapper.someMethod();
          secondWrapper.callTheOther();
          console.log(secondWrapper.doTheNoise());
      
          nothingWrapper.someMethod();
          //this call fails as we dont have this method on the fall through object (which is undefined)
          console.log(nothingWrapper.doTheNoise());
      

      我实际上预计必须在某处使用bind,但似乎没有必要。

      【讨论】:

        【解决方案4】:

        您正在解释的 ruby​​ 功能称为“method_missing”http://rubylearning.com/satishtalim/ruby_method_missing.htm

        这是一项全新功能,仅存在于某些浏览器中,例如 Firefox(在蜘蛛猴 Javascript 引擎中)。在 SpiderMonkey 中,它被称为 "__noSuchMethod__" https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod

        请阅读 Yehuda Katz http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ 的这篇文章,了解有关即将实施的更多详细信息。

        【讨论】:

        • Yehuda Katz 的文章来自 2008 年。Brandon Eich 提倡使用代理 API since 2010。 Mozilla 提出的__noSuchMethod__ API 是非标准的,没有前途。
        【解决方案5】:

        method_missing 不适合 JavaScript,原因与它在 Python 中不存在的原因相同:在两种语言中,方法只是碰巧是函数的属性;并且对象通常具有不可调用的公共属性。与 Ruby 相比,对象的公共接口是 100% 的方法。

        JavaScript 中需要一个钩子来捕获对缺失属性的访问,无论它们是否是方法。 Python 有它:参见__getattr__ 特殊方法。

        Mozilla 的__noSuchMethod__ 提案在充满它们的语言中引入了另一个不一致之处。

        JavaScript 的前进方向是Proxy mechanism(也在ECMAscript Harmony 中),它更接近于customizing attribute access 的Python 协议,而不是Ruby 的method_missing

        【讨论】:

        • 请注意,Javascript 语义与 Python 中的语义有些不同且更棘手。在 Python 中,f=obj.m;f(x) 等价于 obj.m(x)。在 Javascript 中,obj.m(x)this 设置为 obj,而 f=obj.m;f(x) 没有。
        • 阿门:“Mozilla 的 noSuchMethod 提案在充满它们的语言中引入了另一个不一致之处。”
        • 嗯,我当时不知道,但是python现在有一个非常方便的__missing__方法。
        • __missing__ 方法仅用于映射,用于添加逻辑以处理映射中缺少键的情况。例如,实现与字符串键一起使用但不区分大小写的映射很有用。与method_missing无关。
        【解决方案6】:

        暂时没有,没有。有a proposal for ECMAScript Harmony, called proxies,它实现了一个类似的(实际上,更强大的)功能,但 ECMAScript Harmony 还没有推出,可能几年内都不会推出。

        【讨论】:

        • 代理目前在 Chrome 21 及更高版本中实现,带有实验性的 Javascript 标志。有关当前支持的 ECMAScript Harmony 功能的最新信息,请参阅此站点:kangax.github.io/es5-compat-table/es6
        • @jörg-w-mittag,未来即将来临 :)
        • @HappyHamburger:我对 ES6 感到非常兴奋,特别是正确的尾调用、letconst、简洁的函数语法和代理。
        • @jörg-w-mittag - 你如何看待新规范中类的实现?
        • @HappyHamburger:我更多地将 ES 用作 Scheme 和 Self 的混合体,而不是浏览器内的 Java 克隆,所以我不太关心类。然而,由于它们只是语法糖,它们根本不会改变 ES 的核心。这与 ES4 类非常不同,ES4 类基本上是除了原型之外的全新继承结构。
        【解决方案7】:

        不,javascript 中没有直接类似于 ruby​​ 的 method_missing 钩子的元编程功能。解释器只是引发一个错误,调用代码可以捕获但不能被正在访问的对象检测到。这里有一些关于在运行时定义函数的答案,但这不是一回事。你可以做很多元编程,改变对象的特定实例,定义函数,做一些功能性的事情,比如记忆和装饰器。但是没有像 ruby​​ 或 python 那样对缺失函数进行动态元编程。

        【讨论】:

          【解决方案8】:

          据我所知,您可以先将函数初始化为null,然后再替换实现来模拟它。

          var foo = null;
          var bar = function() { alert(foo()); } // Appear to use foo before definition
          
          // ...
          
          foo = function() { return "ABC"; } /* Define the function */
          bar(); /* Alert box pops up with "ABC" */
          

          此技巧类似于用于实现递归 lambda 的 C# 技巧,如 here 所述。

          唯一的缺点是,如果您确实在定义之前使用foo,您会在尝试调用null 时遇到错误,就好像它是一个函数,而不是一个更多描述性错误消息。但是您会期望在定义函数之前收到一些错误消息。

          【讨论】:

          • 我仍然不想要,因为它必须纯粹在运行时完成,而在您的示例中,您必须在设计时定义 foo 而当时我什至可能不知道名称 foo。
          • “在定义函数之前,你会期望得到一些错误信息”——不,你不会。这就是缺少方法的全部意义所在。
          • 您不需要在顶部将 foo 初始化为 null。无论如何,声明都会被吊起。只要在调用 bar 之前设置它。无论如何,这并不能真正回答 OP 的问题..
          猜你喜欢
          • 1970-01-01
          • 2015-11-21
          • 2013-04-22
          • 2016-04-06
          • 1970-01-01
          • 2016-10-12
          相关资源
          最近更新 更多