【问题标题】:Is there an equivalent of the __noSuchMethod__ feature for properties, or a way to implement it in JS?是否有等效于属性的 __noSuchMethod__ 功能,或者在 JS 中实现它的方法?
【发布时间】:2010-02-15 15:07:38
【问题描述】:

在一些 javascript 实现(Rhino、SpiderMonkey)中有一个 noSuchMethod 功能

proxy = {
    __noSuchMethod__: function(methodName, args){
        return "The " + methodName + " method isn't implemented yet. HINT: I accept cash and beer bribes" ;
    },

    realMethod: function(){
     return "implemented" ;   
    }
}

js> proxy.realMethod()
implemented
js> proxy.newIPod()
The newIPod method isn't implemented yet. HINT: I accept cash and beer bribes
js>

我想知道,有没有办法为属性做类似的事情?我想编写可以在属性和方法上分派的代理类。

【问题讨论】:

  • 这个问题更多是出于好奇而不是出于需要,我试图将 Rhino 用作 Java 应用程序的脚本引擎,这涉及为主机对象及其方法和属性创建 js 包装器。最后我改用了 Clojure,因为它让与 Java 的对话变得容易多了,尽管顺便说一下,在 Clojure 中创建动态代理实际上比在 Javascript 中更难。
  • 相关:stackoverflow.com/q/11144589/1348195 我还使用新的代理 API 在那里发布了答案。

标签: javascript


【解决方案1】:

更新: ECMAScript 6 代理是 widely supported now。基本上,如果你不需要支持 IE11,你可以使用它们。

代理对象允许您为基本操作定义自定义行为,例如属性查找、赋值、枚举、函数调用等。

用 ES6 代理模拟 __noSuchMethod__

通过对属性访问实施陷阱,您可以模拟非标准__noSuchMethod__ 陷阱的行为:

function enableNoSuchMethod(obj) {
  return new Proxy(obj, {
    get(target, p) {
      if (p in target) {
        return target[p];
      } else if (typeof target.__noSuchMethod__ == "function") {
        return function(...args) {
          return target.__noSuchMethod__.call(target, p, args);
        };
      }
    }
  });
}

// Example usage:

function Dummy() {
  this.ownProp1 = "value1";
  return enableNoSuchMethod(this);
}

Dummy.prototype.test = function() {
  console.log("Test called");
};

Dummy.prototype.__noSuchMethod__ = function(name, args) {
  console.log(`No such method ${name} called with ${args}`);
  return;
};

var instance = new Dummy();
console.log(instance.ownProp1);
instance.test();
instance.someName(1, 2);
instance.xyz(3, 4);
instance.doesNotExist("a", "b");

2010 年原始答案

目前只有一个现有的东西可以真正做你想做的事,但遗憾的是没有被广泛实施:

目前只有两个可用的实现,在最新的 Firefox 4 测试版中(自 FF3.7 预发布以来一直存在)和用于服务器端 JavaScript 的 node-proxy -ChromeSafari 目前正在研究中-。

它是next version of ECMAScriptearly proposals 之一,它是一个API,允许您实现虚拟化 对象(代理),您可以在其中分配各种陷阱 -callbacks- 在不同情况下执行,您可以完全控制此时 - 在 ECMAScript 3/5 中 - 只有宿主对象可以做的事情。

要构建代理对象,你必须使用Proxy.create 方法,因为你对setget 陷阱感兴趣,我给你举一个非常简单的例子:

var p = Proxy.create({
  get: function(proxy, name) {        // intercepts property access
    return 'Hello, '+ name;
  },
  set: function(proxy, name, value) { // intercepts property assignments
    alert(name +'='+ value);
    return true;
  }
});

alert(p.world); // alerts 'Hello, world'
p.foo = 'bar';  // alerts foo=bar

试试看here

编辑: 代理 API 进化了,Proxy.create 方法被删除以支持使用 Proxy 构造函数,请参阅上面的代码更新到 ES6:

const obj = {};
const p = new Proxy(obj, {
  get(target, prop) {        // intercepts property access
    return 'Hello, '+ prop;
  },
  set(target, prop, value, receiver) { // intercepts property assignments
    console.log(prop +'='+ value);
    Reflect.set(target, prop, value, receiver)
    return true;
  }
});

console.log(p.world);
p.foo = 'bar';

代理 API 是如此新,甚至没有记录在 Mozilla 开发者中心,但正如我所说,自 Firefox 3.7 预发行版以来已经包含了一个有效的实现。

Proxy 对象在全局范围内可用,create 方法可以接受两个参数,一个 handler 对象,它只是一个包含命名为您要实现的陷阱的属性的对象,以及一个可选的proto 参数,使您能够指定代理继承的对象。

可用的陷阱有:

// TrapName(args)                          Triggered by
// Fundamental traps
getOwnPropertyDescriptor(name):           // Object.getOwnPropertyDescriptor(proxy, name)
getPropertyDescriptor(name):              // Object.getPropertyDescriptor(proxy, name) [currently inexistent in ES5]
defineProperty(name, propertyDescriptor): // Object.defineProperty(proxy,name,pd)
getOwnPropertyNames():                    // Object.getOwnPropertyNames(proxy) 
getPropertyNames():                       // Object.getPropertyNames(proxy) 
delete(name):                             // delete proxy.name
enumerate():                              // for (name in proxy)
fix():                                    // Object.{freeze|seal|preventExtensions}(proxy)

// Derived traps
has(name):                                // name in proxy
hasOwn(name):                             // ({}).hasOwnProperty.call(proxy, name)
get(receiver, name):                      // receiver.name
set(receiver, name, val):                 // receiver.name = val
keys():                                   // Object.keys(proxy)

除了提案本身之外,我看到的唯一资源是以下教程:

编辑:更多信息即将发布,Brendan Eich 最近在JSConf.eu 会议上发表了演讲,您可以在此处找到他的幻灯片:

【讨论】:

  • JSConf.eu 网站上似乎不再提供 Brendan Eich 的视频。 Luckily it is on YouTube now.
  • Chrome 也有代理。
  • 虽然范围很广,但这个答案完全忽略了这样一个事实,即问题的重点是调用 methods 而不是访问属性!不是一回事。
  • @ValeriuPaloş:我添加了一个示例,说明如何使用 ES6 代理模拟非标准 __noSuchMethod__ 陷阱。
  • 对于我的一种情况,我遇到了错误,我认为这是由于 enableNoSuchMethod else if return 返回一个函数而不是结果/值,因此通过将该函数变为 IIFE,这解决了这个问题。任何人都可以评论是否有意义?对代理还没有 100% 的信心。我认为它与丢失的东西是否被称为属性或函数有关,但在这一点上这是一个猜测。但是,对于其他人来说,它必须是原始的(而不是 IIFE)才能使 args 正确通过。这可能是由于传播运算符造成的吗?
【解决方案2】:

这是获得类似于 __noSuchMethod__ 的行为的方法

首先,这是一个带有一个方法的简单对象:

var myObject = {
    existingMethod: function (param) {
        console.log('existing method was called', param);
    }
}

现在创建一个代理,它将捕获对属性/方法的访问并将您现有的对象添加为第一个参数。

var myObjectProxy = new Proxy(myObject, {
   get: function (func, name) {
       // if property or method exists, return it
       if( name in myObject ) {
           return myObject[name];
       }
       // if it doesn't exists handle non-existing name however you choose
       return function (args) {
           console.log(name, args);
       }
    }
});

现在试试吧:

myObjectProxy.existingMethod('was called here');
myObjectProxy.nonExistingMethod('with a parameter');

适用于 Chrome/Firefox/Opera。在 IE 中不起作用(但已经在 Edge 中起作用)。还在移动 Chrome 上进行了测试。

代理的创建可以自动化且不可见,即如果您使用工厂模式来构建您的对象。我这样做是为了创建可以直接从主线程调用内部函数的工作人员。由于这个名为 Proxy 的酷炫新功能,现在使用 worker 变得如此简单。有史以来最简单的 worker 实现:

var testWorker = createWorker('pathTo/testWorker.js');
testWorker.aFunctionInsideWorker(params, function (result) {
    console.log('results from worker: ', result);
});

【讨论】:

    【解决方案3】:

    我不相信这种类型的元编程在 javascript 中是可能的(目前)。相反,请尝试使用 __noSuchMethod__ 功能来实现使用属性获取器的效果。不是跨浏览器,因为它是Mozilla extension

    var proxy = {
        __noSuchMethod__: function(methodName, args) {
           if(methodName.substr(0,3)=="get") {
              var property = methodName.substr(3).toLowerCase();                             
              if (property in this) {
                  return this[property];
              }
           }
        }, color: "red"
     };
     alert(proxy.getColor());           
    

    【讨论】:

      【解决方案4】:

      您可以使用Proxy 类。

      var myObj = {
          someAttr: 'foo'
      };
      
      var p = new Proxy(myObj, {
          get: function (target, propName) {
              // target is the first argument passed into new Proxy,
              // in this case target === myObj
              return 'myObj with someAttr:"' + target.someAttr 
                     + '" had "' + propName 
                     + '" called on it.';
          }
      });
      console.log(p.nonExsistantProperty);
      // outputs: 
      // myObj with someAttr:"foo" had "nonExsistantProperty" called on it
      

      【讨论】:

        【解决方案5】:

        SpiderMonkey 中除了__noSuchMethod__ 之外,还有__defineGetter____defineSetter____lookupGetter____lookupSetter__

        【讨论】:

          【解决方案6】:

          虽然这是一个老问题,但我今天正在研究这个问题。我希望能够无缝集成来自另一个上下文(可能是不同的网页或服务器)的代码。

          从长远来看,它是一种会破裂的东西,但我认为它仍然是一个有趣的概念。这些东西对于快速将代码混合在一起很有用,(然后存在多年,埋在某个地方)。

          var mod = modproxy();
          
          mod.callme.first.now('hello', 'world');
          
          mod.hello.world.plot = 555;
          
          var v = mod.peter.piper.lucky.john.valueOf;
          console.log(v);
          
          mod.hello.world = function(v) {
            alert(v);
            return 777;
          };
          
          var v = mod.hello.world('funky...');
          console.log(v);
          
          var v = mod.hello.world.plot.valueOf;
          console.log(v);
          
          mod.www.a(99);
          mod.www.b(98);
          
          function modproxy(__notfound__) {
          
            var mem = {};          
            return newproxy();
          
            function getter(target, name, receiver, lname) {
          
              if(name === 'valueOf') {
                lname=lname.slice(1);
          
                if(lname in mem) {
                  var v = mem[lname];
                  console.log(`rd : ${lname} - ${v}`);
                  return v;
                }
          
                console.log(`rd (not found) : ${lname}`);
                return;
              }
          
              lname += '.'+name;
              return newproxy(() => {}, lname);
          
            } // getter
          
            function setter(obj, prop, newval, lname) {
          
              lname += '.' + prop;
              lname = lname.slice(1);
              console.log(`wt : ${lname} - ${newval}`);
              mem[lname] = newval;
          
            } // setter
          
            function applyer(target, thisArg, args, lname) {
          
              lname = lname.slice(1);
          
              if(lname in mem) {
                var v = mem[lname];
          
                if(typeof v === 'function') {
                  console.log(`fn : ${lname} - [${args}]`);
                  return v.apply(thisArg,args);
                }
          
                return v;
          
              }
          
              console.log(`fn (not found): ${lname} - [${args}]`);
          
            } // applyer
          
            function newproxy(target, lname) {
          
              target = target || {};
              lname = lname || '';
          
              return new Proxy(target, {
                get: (target, name, receiver) => {
                  return getter(target, name, receiver, lname);
                },
                set: (target, name, newval) => {
                  return setter(target, name, newval, lname);
                },
                apply: (target, thisArg, args) => {
                  return applyer(target, thisArg, args, lname);
                }
              });
          
            } //proxy
          
          } //modproxy

          我实现了一个valueOf 步骤来读取值,因为似乎该属性首先是“get-ted”。

          我认为在“get-ted”属性时无法判断它是否会被调用或读取(或需要进一步链接)。

          单层属性没问题。该属性要么存在,要么不存在,要么是所需的类型,要么不存在。

          当我更熟悉代码的行为方式和最佳实现方式时,我将进一步研究它,研究异步/等待例程的承诺、代理现有对象以及更好地控制如何访问属性。

          我在 GitHub 上创建了一个仓库:modproxy.js/README.md

          代码笔:modproxy.js

          我原来的问题:

          does javascript have an equivalent to the php magic class __call

          【讨论】:

            猜你喜欢
            • 2021-12-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-09-19
            • 1970-01-01
            • 1970-01-01
            • 2010-11-28
            相关资源
            最近更新 更多