I have to upgrade a small private function in a 1500 LoC JS module, written in The Revealing Module Pattern.
Simplified task looks like this:
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
I have studied
Accessing variables trapped by closure
how to get an object from a closure?
questions already, which looks like most relevant to my task, but without success.
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.
But I don't go this way, because the method becomes not private anymore, and I can't predict what may become broken in the old 100000 LoC spaghetti monster app, written in old-style ECMAScript 5.
UPDATE
This is the answer on https://stackoverflow.com/a/65156282/7709003 answer.
First of all, thank you for the detailed answer, T.J. Crowder.
this is usually called monkeypatching
Holy true.
You can't, unless you expose that private in some way.
Right. I just don't want to expose the method right into the window
scope. I thinks the solution could be more elegant.
it appears you can change the source code
Well, as you already have noticed, this is confusing, but point is, I'm not yet sure myself. I have to keep the default module behavior, but I hope I can extend the module a bit, to help to apply this monkey patch.
If you want the full story, there is an IoT device, running the WebApp, which uses the module we discuss to translate the microcontroller registers state into a DOM view. Let's call it registers_into_view
module.
By copying the same module, a web-server was created, which:
registers_into_view
module creates a model View (which normally should happen on the Frontend)By extending the same WebApp, the "Cloud" web-app was created, which:
registers_into_view
module, but insteadNormally, I would refactor the whole architecture:
registers_into_view
module on the backendregisters_into_view
module on the frontendBut the company A, that uses the stack, refuse to do so. The existing schema works for 6 years for them. In fact, their managers avoid big changes, coz the company B, that had created this software, charge a lot of money for the work. So they have no motivation to refactor.
Well, I have.
I work for company C and we're going to sell same IoT devices.
We also want to use the existing Frontend WebApp.
For this needs, I extend our existing IoT server to support those devices and their frontend.
So I have to copy another server API contracts, using another language and framework.
Now, instead of recreating 1500 LoC registers_into_view
module, that runs on the backend server, I think to import already existing registers_into_view
module into a "Cloud" WebApp and monkey-patch it to retreive registers data from JSON instead of a memory dump (our private_method
).
The intrusion should be as little as possible, to higher chances to my patch to be merged.
Now I hope my motivation is clear enough. I find it not so interesting to everybody and so tried to clean the programming task from the context.
I cannot change the public_method
of the module, because in reality it runs ~50 other private methods, which all call retreive_data_method
just with different requests and put results in a different places in DOM.
Works like a charm. So simple and elegant. I will just simplify it a bit to my needs:
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()
Instead of direct replace, as in your solution, I've tried to save the module context in some exposed variable and find the private method via it. Which didn't work.
Thanks.
I have to upgrade a small private function in a 1500 LoC JS module, written in The Revealing Module Pattern.
I take it you mean you have to do this at runtime, from outside the "module" function. This is usually called "monkeypatching."
You can't, unless you expose that private in some way.
The only working solution I found is to rewrite
function private_method(){}
tothis.private_method = function(){...}
which binds it to the window, so I can change it in runtime.
If you can do that, then it appears you can change the source code (leading me to this question on the question).
But if you can change the source code, then you can do this (see ***
comments):
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() { /* ... */ });
You can generalize (and arguably simplify) that by putting all private methods on an object that you call them through, but it means either calling them with a different this
than they may be expecting or making those calls a bit more awkward:
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() { /* ... */ };