改变闭包中函数的行为

Change the behavior of a function in closure

我要升级一个1500 LoC JS模块中的一个小私有函数,写在The Revealing Module Pattern.

简化后的任务如下所示:

var module = module || function (){

    function init(){
        // you can upgrade init anyhow to be able to replace private_method later in runtime

        (function not_important(){console.log("I do some other stuff")})()
    }

    function public_method(){
        private_method()
    }

    function private_method(){
        console.log("original private method")
    }

    // you can add any methods here, too

    return {
        init: init,
        public_method: public_method,
        // and expose them
    }
}()


/** this is another script on the page, that uses original module */
module.public_method()  // returns "original private method"

/** the third script, that runs only in some environment, and requires the module to work a bit different */
// do any JS magic here to replace private_method
// or call module.init(method_to_replace_private_method)
module.public_method()  // I want to see some other behavior from private_method here

我学过
Accessing variables trapped by closure
how to get an object from a closure?
已经有问题,看起来与我的任务最相关,但没有成功。

我找到的唯一可行的解​​决方案是重写 function private_method(){}this.private_method = function(){...} 绑定到 window,所以我可以在运行时更改它。

但我不会这样做,因为该方法不再是私有的,而且我无法预测用旧式 ECMAScript 5 编写的旧的 100000 LoC 意大利面条怪物应用程序可能会损坏什么。

更新
这是 答案上的答案。

首先感谢T.J的详细解答。克劳德.

this is usually called monkeypatching

神圣真实。

You can't, unless you expose that private in some way.

没错。我只是不想将方法直接公开到 window 范围内。我认为解决方案可能更优雅。

it appears you can change the source code

好吧,正如您已经注意到的,这令人困惑,但重点是,我自己也不确定。 我必须保留默认的模块行为,但我希望我可以稍微扩展一下模块,以帮助应用这个猴子补丁。

如果您想了解完整的故事,可以使用物联网设备 运行 WebApp,它使用我们讨论的模块将微控制器寄存器状态转换为 DOM 视图。我们称它为 registers_into_view 模块。

通过复制相同的模块,创建了一个网络服务器,其中:

通过扩展同一个 WebApp,创建了“云”Web 应用程序,它:

通常,我会重构整个架构:

但是使用堆栈的公司A拒绝这样做。现有模式对他们有效 6 年。事实上,他们的经理避免大的改变,因为创建这个软件的公司 B 为这项工作收取了很多钱。所以他们没有动力去重构。

嗯,我有。 我在 C 公司工作,我们打算销售相同的物联网设备。
我们还想使用现有的 Frontend WebApp。
为此,我扩展了现有的物联网服务器以支持这些设备及其前端。
所以我不得不复制另一个服务器 API 合同,使用另一种语言和框架。

现在,与其重新创建在后端服务器上运行的 1500 LoC registers_into_view 模块,我想将现有的 registers_into_view 模块导入“云”WebApp 并将其猴子修补到从 JSON 而不是内存转储(我们的 private_method)检索寄存器数据。

入侵越少越好,这样我的补丁被合并的几率就大。

现在我希望我的动机足够明确。我发现它对每个人来说都不是那么有趣,因此尝试从上下文中清除编程任务。

让我们开始寻找解决方案。

你的解决方案 2

我无法更改模块的 public_method,因为实际上它运行了约 50 个其他私有方法,它们都调用 retreive_data_method 只是根据不同的请求并将结果放在 DOM.

解决方案 1

很有魅力。如此简单和优雅。我将根据我的需要对其进行一些简化:

var module = module || function (){
    function public_method(){private_method()}
    function private_method(){console.log("original private method")}
    return {
        public_method: public_method,
        
        // this function allows to update the private_method in runtime
        replace_private_method: function(fn) {
                // Function declarations effectively create variables;
                // you can simply write to them:
                private_method = fn;
        }
    }
}()

// original behavior
module.public_method()

// patching
module.replace_private_method(function() {console.log("I've been monkey patched")});
// new behavior
module.public_method()

我没有像您的解决方案那样直接替换,而是尝试将模块上下文保存在一些公开的变量中,并通过它找到私有方法。哪个没用。

谢谢。

I have to upgrade a small private function in a 1500 LoC JS module, written in The Revealing Module Pattern.

我认为你的意思是你必须在运行时从“模块”函数外部执行此操作。这通常称为“monkeypatching”。

你不能,除非你以某种方式公开那个私人。

The only working solution I found is to rewrite function private_method(){} to this.private_method = function(){...} which binds it to the window, so I can change it in runtime.

如果你能做到这一点,那么你似乎可以更改源代码(引导我 关于这个问题)。

但是如果你可以更改源代码,那么你可以这样做(参见 *** 评论):

var module = module || function (){
    function init(){
        (function not_important(){console.log("I do some other stuff")})()
    }

    function public_method(){
        private_method()
    }

    function private_method(){
        console.log("original private method")
    }

    return {
        init: init,
        public_method: public_method,
        // *** Provide yourself functions to get `private_method` (and any
        // others you may want) and update it
        __privates__: {
            private_method: {
                get: function() {
                    return private_method;
                },
                set: function(fn) {
                    // *** Function declarations effectively create variables;
                    // you can write to them:
                    private_method = fn;
                }
            }
        }
    }
}()

// *** Where you want to make your change
module.__privates__.private_method.set(function() { /* ... */ });

您可以通过将所有私有方法放在您调用它们的对象上来概括(并且可以说是简化),但这意味着要么使用与他们预期不同的 this 调用它们,要么使那些调用有点尴尬:

var module = module || function (){
    /*** An object with the private functions you need to do this for
    var privates = {};

    function init(){
        (function not_important(){console.log("I do some other stuff")})()
    }

    function public_method(){
        // *** Calling it via that object, which has an effect on `this`
        privates.private_method()
        // *** If you want `this` to be the same as it would have been
        // with the raw call above (the global object or `undefined` if
        // you're in strict mode), you can use the comma trick:
        // (0,privates.private_method)()
    }

    privates.private_method = function private_method(){
        console.log("original private method")
    };

    return {
        init: init,
        public_method: public_method,
        // *** Expose that object with the private functions
        __privates__: privates
    }
}()

// *** Where you want to make your change
module.__privates__.private_method = function() { /* ... */ };