在闭包中改变函数的行为

德米特罗·吉尔曼

我必须升级一个用The Revealing Module Pattern编写的 1500 LoC JS 模块中的一个小型私有函数

简化的任务如下所示:

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

我研究了
访问被闭包捕获的变量
如何从闭包中获取对象?
已经提出问题,这看起来与我的任务最相关,但没有成功。

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

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

更新
这是https://stackoverflow.com/a/65156282/7709003 上的答案。

首先,感谢TJ Crowder 的详细回答。

这通常称为monkeypatching

神圣真实。

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

对。我只是不想将方法直接公开到window范围中。我认为解决方案可以更优雅。

看来您可以更改源代码

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

如果你想要完整的故事,有一个运行 WebApp 的 IoT 设备,它使用我们讨论的模块将微控制器寄存器状态转换为 DOM 视图。我们称之为registers_into_view模块。

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

  • 接收微控制器内存转储
  • 复制registers_into_view模块创建模型视图(通常应在前端发生)
  • 向 View 发送 JSON 格式

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

  • 否定现有registers_into_view模块,而是
  • 从后端接收View数据
  • 直接将其应用到 DOM 中

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

  • 删除registers_into_view后端模块的副本
  • registers_into_view在前端重用现有模块

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

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

现在,registers_into_view我认为不是重新创建在后端服务器上运行的1500 LoC模块,而是将现有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()

与您的解决方案中的直接替换不同,我尝试将模块上下文保存在一些公开的变量中,并通过它找到私有方法。这没有用。

谢谢。

TJ克劳德

我必须升级一个用 The Revealing Module Pattern 编写的 1500 LoC JS 模块中的一个小型私有函数。

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

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

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

如果您可以这样做,那么您似乎可以更改源代码(将我引向有关该问题的这个问题)。

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

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() { /* ... */ };

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章