5

我无法弄清楚为什么以下代码不起作用:

var os = new Proxy(require('os'), {});
console.log( os.cpus() ); // TypeError: Illegal invocation

然而

var os = require('os');
console.log(Reflect.apply(os.cpus, os, []));

或者

var os = new Proxy(require('os'), {});
console.log( os.platform() );

按预期工作。

4

2 回答 2

5

刚刚浏览了 Node 存储库中os的源代码后,似乎cpus()从中导出的binding.getCPUs是 Node 运行时环境中的 C 挂钩。

cpus()因此,将binding对象作为函数上下文,然后通过代理丢失,给你IllegalInvocation错误,因为当你调用它时函数没有上下文——尽管我对细节很模糊。

platform()另一方面,导出为function () { return process.platform; },因此它只是一个返回对象的函数,不需要在特定上下文下运行,因为 Node 函数上下文将具有process默认指定的变量(除非它已被覆盖)。

以下行为表明,将os用作函数的上下文cpus将起作用——函数对象上的代理在调用属性时显然会丢失函数上下文。

const os = require('os');
const proxy = new Proxy(os, {});  // proxy of object, functions called get proxy context rather than os context
const cpus = new Proxy(os.cpus, {});  // proxy of function, still has os context

console.log(os.cpus());  // works (duh)
console.log(cpus());     // works
console.log(proxy.cpus.apply(os, []));  // works
console.log(proxy.cpus());  // fails with IllegalInvocation

注意:如果有人可以清除 JS 函数上下文的详细信息以获得答案,我也很乐意阅读它。

于 2017-02-27T22:16:41.540 回答
2

组成如何:

const os = require('os');
const proxy = new Proxy(os, {});
Object.getOwnPropertyNames(os).forEach(k => {
    var v = os[k];
    if(typeof v === "function") proxy[k] = v.bind(os);
});

//the `!!` because I don't want the actual print
//only a `true` or an `Error`
console.log(!!os.cpus());
console.log(!!proxy.cpus());
console.log(!!proxy.cpus.apply(proxy, []));

而这一切都作为一个实用函数来“替换” new Proxy(),哪里handler.bindTargetFunctions可以

  • 要绑定的 keyNames 数组(所以你可以具体)
  • 或任何真值或假值,以确定目标上的所有功能是否应绑定

编码:

function proxy(target, handler){
    const _proxy = new Proxy(target, handler);
    if(handler.bindTargetFunctions){
        let bindTargetFunctions = handler.bindTargetFunctions;
        if(!Array.isArray(bindTargetFunctions)){
            bindTargetFunctions = Object.getOwnPropertyNames(target)
                .filter(key => typeof target[key] === "function");
        }
        bindTargetFunctions.forEach(key => {
            _proxy[key] = target[key].bind(target);
        });
    }
    return _proxy;
}

const os = proxy(require('os'), { bindTargetFunctions: true });
//or
//const os = proxy(require('os'), { bindTargetFunctions: ["cpus"] });

console.log(os.cpus());

编辑:

目前,我尝试直接在我的 get 处理程序中绑定函数(请参阅 github.com/FranckFreiburger/module-invalidate/blob/master/…),我的解决方案的缺点是每次访问函数都会返回一个新绑定。

我在评论中提到了缓存。这就是这个缓存的样子:

function createProxy(mod){
    var cache = Object.create(null);

    return new Proxy(function(){}, {
        get(target, property, receiver) {
            var val = Reflect.get(mod._exports, property, receiver);
            if(typeof val === "function"){
                if(!(property in cache) || cache[property].original !== val){
                    cache[property] = {
                        original: val,
                        bound: bal.bind(mod._exports)
                    }
                }
                val = cache[property].bound;
            }else if(property in cache){
                delete cache[property];
            }

            return val;
        }
    });
}

不,我不认为这个缓存是一个普通的对象。不是因为它继承自 null,而是因为从逻辑上讲,对我来说这是一个字典/地图。而且我不知道您为什么要扩展或代理特定字典的任何原因。

于 2017-03-04T11:50:50.360 回答