extendscript - Add function into the scope of another function JavaScript - Stack Overflow

I'm trying to get the example code below to work.Edit (Trying to be more clear what the aim is):I

I'm trying to get the example code below to work.
Edit (Trying to be more clear what the aim is):

I wan't to make all the functions and variables of the obj available in the functions setup and draw as they where globals. This is not for the browser. This is for Adobe ExtendScript so I can only use EcmaScript 3 and some polyfills. The lib.js file is provided by me and gets included before the the user-script.js file. The user has a reference what functions are availble and might use them within setup and draw. This is actually pretty similar to what P5.js does but I'm are trying to achieve this for InDesign. We can of course call the obj.foo(). The aim is though to get rid of the obj. to give the user the possibility to just call foo and get the result of obj.foo.

This is lib.js. It is just a small part of the library to illustrate what I have at hand.

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  },
  foobah:function(arg){
    return arg*2;
  },
  CONST:"Hello World",
  go:function(){
    // looks in the global scope if there is
    // a setup and draw function and should patch them.
    if(typeof glob.setup === 'function'){
      glob.setup();
    },
    if(typeof glob.draw === 'function'){
      glob.draw();
    }
  }
};

This could be the user-script.js. The structure we Provide is:

  • #include for the lib
  • function setup(){}
  • function draw(){}
  • obj.go() as execution of everything (might be removed later on)

I can not tell the user to write more additional code into setup and draw. The part for the user should be so reduced he can write this by hand and does not need to use a boilerplate or something like this.

#include lib.js

function setup() {
  foo(); // might be called or not
  console.log('in setup');
}

function draw() {
  bah();// might be called or not
  console.log('in draw');
}
obj.go()

Thanks to all your answers. I will review them and get back to report what was the final decision. All of them seem to solve the problem in different ways. I currently can't tell which is the "right" answer. Those we seemed be nearest to my question got an upvote from me.

I'm trying to get the example code below to work.
Edit (Trying to be more clear what the aim is):

I wan't to make all the functions and variables of the obj available in the functions setup and draw as they where globals. This is not for the browser. This is for Adobe ExtendScript so I can only use EcmaScript 3 and some polyfills. The lib.js file is provided by me and gets included before the the user-script.js file. The user has a reference what functions are availble and might use them within setup and draw. This is actually pretty similar to what P5.js does but I'm are trying to achieve this for InDesign. We can of course call the obj.foo(). The aim is though to get rid of the obj. to give the user the possibility to just call foo and get the result of obj.foo.

This is lib.js. It is just a small part of the library to illustrate what I have at hand.

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  },
  foobah:function(arg){
    return arg*2;
  },
  CONST:"Hello World",
  go:function(){
    // looks in the global scope if there is
    // a setup and draw function and should patch them.
    if(typeof glob.setup === 'function'){
      glob.setup();
    },
    if(typeof glob.draw === 'function'){
      glob.draw();
    }
  }
};

This could be the user-script.js. The structure we Provide is:

  • #include for the lib
  • function setup(){}
  • function draw(){}
  • obj.go() as execution of everything (might be removed later on)

I can not tell the user to write more additional code into setup and draw. The part for the user should be so reduced he can write this by hand and does not need to use a boilerplate or something like this.

#include lib.js

function setup() {
  foo(); // might be called or not
  console.log('in setup');
}

function draw() {
  bah();// might be called or not
  console.log('in draw');
}
obj.go()

Thanks to all your answers. I will review them and get back to report what was the final decision. All of them seem to solve the problem in different ways. I currently can't tell which is the "right" answer. Those we seemed be nearest to my question got an upvote from me.

Share Improve this question edited Jul 25, 2016 at 0:14 user6445533 asked Jul 1, 2016 at 13:47 fabianmoronzirfasfabianmoronzirfas 4,1294 gold badges26 silver badges42 bronze badges 16
  • 3 You cannot add functions to other functions, but you can pose two functions to produce a new one – elclanrs Commented Jul 1, 2016 at 13:49
  • 1 function pose(f, g){return function(){return f(g.apply(this, arguments))}}; var h = pose(f, g) – elclanrs Commented Jul 1, 2016 at 13:51
  • 1 why not to call fn1(obj.foo); while implementation of fn1 is like function fn1(foo){} – binariedMe Commented Jul 1, 2016 at 13:53
  • 1 To expand, your functions are not really functions persay, they don't take any arguments or return anything, they are more like procedures for side-effects, so you may want to look into something like function after(f, g){return function(){f(); g()}} or before where you'd execute in the opposite order. – elclanrs Commented Jul 1, 2016 at 13:54
  • 1 It sounds like you are looking for with (obj) { … }, but that of course requires cooperation of the user. – Bergi Commented Jul 4, 2016 at 17:58
 |  Show 11 more ments

11 Answers 11

Reset to default 1

This does as you ask using closures and a higher order function, but obj is in scope of fn1 and fn2 so the actual function doesn't need to be injected you could just call obj.foo from within each function.

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  }
};

function higherOrderF(f,name){
  return function(){
    name && console.log(name)
    return f.apply(null,arguments)
  }
}

var fn1 = higherOrderF(obj.foo, 'fn1')
var fn2 = higherOrderF(obj.bah, 'fn2')

fn1();
fn2();

with is an easy solution

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  },
  proto: null
}

//the frowned upon with statement use with caution!!!
with(obj){
function fn1() {
  foo();
  console.log('fn1');
}

function fn2() {
  bah();
  console.log('fn2');
}

function main() {
  fn1(); // Should output: foo and then fn1
  fn2(); // should output: bah and hen fn2
}
}
main()

//Dependency Injection
//es6 can do this a lot cleaner.
;(function(o){
  var foo = o.foo,
      bah = o.bah;
  
  function fn1() {
    foo();
    console.log('fn1');
  }
  
  function fn2() {
    bah();
    console.log('fn2');
  }
  
  function main() {
    fn1(); // Should output: foo and then fn1
    fn2(); // should output: bah and hen fn2
  }
  main() 
}(obj))


//es6 Dependency Injection 0 globals
;(({foo,bah} = obj)=>{  
  function fn1() {
    foo();
    console.log('fn1');
  }
  
  function fn2() {
    bah();
    console.log('fn2');
  }
  
  function main() {
    fn1(); // Should output: foo and then fn1
    fn2(); // should output: bah and hen fn2
  }
  
  main()
})()

As it already has been mentioned, the only pattern that helps one achieving the goal of changing/altering control flow of closed functions/methods (Thus, one does not own theirs code bases.) is function-position.

It got pointed too, that there are differently implemented aproaches to it.

The OP's altered example will make use of a prototypal implementation of Function.before. Because JavaScript already features a standardized bind, I'm firmly convinced that Function.prototype is the right place as well for some other method-modifiers like before, after, around, afterThrowing and afterFinally.

... sticking as close to the OP's example as possible:

var
    obj = {
        foo: function() {
            console.log('foo');
        },
        bah: function() {
            console.log('bah');
        }
    };

function fn1() {
    //foo();
    console.log('fn1');
}

function fn2() {
    //bah();
    console.log('fn2');
}

function main() {
    //fn1.add(obj.foo); // <= Should add function foo to fn1
    //fn2.add(obj.bah); // <= Should add function bah to fn2

    window.fn1 = window.fn1.before(obj.foo);
    window.fn2 = window.fn2.before(obj.bah);

    fn1(); // Should output: foo and then fn1
    fn2(); // should output: bah and then fn2

    //obj.foo = obj.foo.after(f1);
    //obj.bah = obj.bah.after(f2);
}

main();

... implementation of Function.prototype.before:

(function (Function) {
    var
        isFunction = function (type) {
            return (
                (typeof type == "function")
                && (typeof type.call == "function")
                && (typeof type.apply == "function")
            );
        },
        getSanitizedTarget = function (target) {
            //return (target == null) ? null : target;
            return ((target != null) && target) || null;
        };

    Function.prototype.before = function (handler, target) { // before
        target  = getSanitizedTarget(target);
        var proceed = this ;

        return (isFunction(handler) && isFunction(proceed) && function () {
            var args = arguments;

            //handler.apply(target, args);
            handler.call(target, args);
            return proceed.apply(target, args);

        }) || proceed;
    };
}(Function));

Taking into account the OP's answer ...

A: Yes That's right ...

... to this question ...

Q: ... Do I understand you right that you are going to leave the decision of whether to call foo derived from obj.foo or bah / obj.bah up to the logic implemented within fn1 respectively fn2?

... the former approach of mine changes from Function.before to Function.around.

However, personally I'm not pleased with this solution, for both globally provided methods fn1 and fn2 now need to anticipate the modification in advance, which in my eyes is not a clean approach.

Nevertheless, the next example es close to what the OP is looking for:

var
    obj = {
        foo: function() {
            console.log('foo');
        },
        bah: function() {
            console.log('bah');
        }
    };

// - both next function's arguments-signatures
//   do already anticipate the modification.
// - this is considered to be a dirty approach,
//   please check your architecture or your
//   concept of control flow.

function fn1(foo, fn1, args) {

    foo(); // does not necessarily need to be called.

    console.log('fn1');
}
function fn2(bah, fn2, args) {

    bah(); // does not necessarily need to be called.

    console.log('fn2');
}

function main() {
    //fn1.add(obj.foo); // <= Should add function foo to fn1
    //fn2.add(obj.bah); // <= Should add function bah to fn2

    window.fn1 = obj.foo.around(window.fn1);
    window.fn2 = obj.bah.around(window.fn2);

    fn1(); // Should output: foo and then fn1
    fn2(); // should output: bah and then fn2
}

main();

... implementation of Function.prototype.around:

(function (Function) {
    var
        isFunction = function (type) {
            return (
                (typeof type == "function")
                && (typeof type.call == "function")
                && (typeof type.apply == "function")
            );
        },
        getSanitizedTarget = function (target) {
            //return (target == null) ? null : target;
            return ((target != null) && target) || null;
        };

    Function.prototype.around = function (handler, target) { // around
        target  = getSanitizedTarget(target);

        var proceed = this ;
        return (isFunction(handler) && isFunction(proceed) && function () {

            return handler.call(target, proceed, handler, arguments);

        }) || proceed;
    };
}(Function));

OP: I don't get the idea of the module system. ...

With a (custom) module system one was going to provide a method to ones users via that they were able to register theirs code bases, encapsulated each into a function itself. Since there was control of the "registering", it was up to oneself of which additional state/behavior should be injected into such function based modules. One just has to make clear which arguments would be passed into those modules by ones system.

OP: ... I will have a look into your before prototype. This looks like it could work for us. ...

It won't. But another more straightforward approach will do. It will misuse the idea of a module system and turn things inside out. The OP's requiremnts are far from being ideal. But a possible solution, refactored from the OP's provided latest example code, that - within the given scenario - does what the OP might look for, could look similar to that ...

lib.js

// lib.js

(function () {
    // do not unnecessarily pollute the global namespace.

    function foo() {
        console.log('foo');
    }
    function bar() {
        console.log('bar');
    }

    function biz() {
        console.log('biz');
    }
    function baz() {
        console.log('baz');
    }

    function foobar(arg){
        return (arg * 2);
    }

    function executeGlobalCustomCode() {

        // does look for the existence of both globally
        // scoped methods `setup` and `draw` and, if so,
        // calls them with the right arguments.

        if (typeof global.setup === 'function') {

            global.setup(foo, bar);
        }
        if (typeof global.draw === 'function') {

            global.draw(biz, baz);
        }
    }

    var
        global = (function () {
            return this;
        }).call(null),

        CONST = "Hello World";

    // do only expose into global namespace what is really needed.
    global.util = {
        go: executeGlobalCustomCode
    };

}());

custom code with "lib.js" included.

// custom code with "lib.js" included.

function setup(foo, bar) {

    console.log('within SETUP :: call "foo" and "bar".');

    foo(); // might be called or not
    bar(); //
}

function draw(biz, baz) {

  //console.log('within DRAW :: call "biz" and "baz".');
    console.log('within DRAW :: call "baz" only.');

  //biz(); // might be called or not
    baz(); //
}

util.go(); // does trigger execution of `setup` and `draw`.

I think prototypes are a way to go. So, I am using Function.prototype in my solution. Please check if this works for you:

var obj = {
    foo: function() {
        console.log('food');
    },
    goo: function() {
        console.log('good');
    }
}

Function.prototype.add = function(fun) {
    if (!this.list) {
        this.list = [];
    }
    this.list.push(fun);
}

function a() {
    if (a.list) {
        a.list.forEach(function(f) {
            f();
        })
    }
    console.log('done at a');
}

function b() {
    if (b.list) {
        b.list.forEach(function(f) {
            f();
        })
    }
    console.log('done at b');
}

function main() {
    a.add(obj.foo);
    b.add(obj.goo);
    a();
    b();
    console.log('done at main');
}

main();

I have tried to use as much of how you would call (in terms of structure).

Do this solve your problem ?

OP: The problem here is that we have a lot of functions that need to be present in draw and setup. So passing them one by one, is not a solution. We could pass them as object but then we are back at square one. ... I will rethink the idea and maybe we have to go for a global scope.

Just out of curiosity and because I never ever was in need of the black magic of eval and with, I finally went for it, proving that Frankenstein's monster really can be created via those tools. There it is, this poor creature ...

... Victor ...

// lib.js

(function () {
    // do not unnecessarily pollute the global namespace.

    var toolkit = {

        foo : function foo() {
            console.log('foo');
        },
        bar : function bar() {
            console.log('bar');
        },

        biz : function biz() {
            console.log('biz');
        },
        baz : function baz() {
            console.log('baz');
        }
    };

    var globallyExecutablesNameList = [
        "setup",
        "draw"
    ];

    function badlyEvalPatchAndExecuteGlobalCustomCode() {

        // does look for the existence of listed globally
        // scoped methods and, if so, patches them with a
        // bad "eval" approach (using `new Function`) that
        // within this eval uses an `with (context)` which
        // is considered to be error prone.

        var
            fct,

            str,
            args,
            body,
            result,

            regXFctArgsAndBody  = (/function[^(]*\(([^)]*)\)\s*\{(.*)\}/);

        globallyExecutablesNameList.forEach(function (fctName) {
            fct = global[fctName];

            if (typeof fct === "function") {

                str     = fct.toString().replace((/\n/g), "###__NEW__LINE__###");
                result  = regXFctArgsAndBody.exec(str);

                if (result) {
                    body = [
                        "with (this) {",  // within new method's `this` context ...
                                          //    do stuff, that was derived from ...

                        result[2].split(/###__NEW__LINE__###/).join("\n"),

                                          // ... a (global) custom function's body.
                        "}"               //
                    ].join("");

                    args = result[1]
                        .replace((/###__NEW__LINE__###/g), "")
                        .replace((/\s+/g), "")
                        .split(",");

                    args.push(body);

                    fct = Function.apply(null, args); // frankenstein's monster.

                  //console.log("args : ", args);
                  //console.log("fct.toString() : ", fct.toString());
                }
                fct.call(toolkit); // provide `toolkit` as this function's context.
            }
        });
    }

    var
        global = (function () {
            return this;
        }).call(null);

    // do only expose into global namespace what is really needed.
    global.lib = {
        go: badlyEvalPatchAndExecuteGlobalCustomCode
    };

}());

... and it's victims ...

// custom code with "lib.js" included.

function setup(/*a, b, c*/) {

    console.log("SETUP");

    foo();
    bar();
}

function draw(/*x, y*/) {

    console.log("DRAW");

  //biz();
    baz();
}

lib.go(); // does look for `setup` + `draw` + processes them by 2 dirty techniques.

You can pass a function to a function.

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  }
};

function fn1( passedFoo ) {
  passedFoo();
  console.log('fn1');
}

function fn2( passedBah ) {
  passedBah();
  console.log('fn2');
}

function main() {
  fn1(obj.foo); // Should output: foo and then fn1
  fn2(obj.bah); // should output: bah and hen fn2
}

This is the monkey patched solution it is really replacing the original functions with new ones that still call the original function... so it works as though you had injected code into them. You could still use a higher order function to help pose these monkey patches.

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  }
};

function fn1() {
  //foo();
  console.log('fn1');
}

function fn2() {
  //bah();
  console.log('fn2');
}

main()
function main() {
  //monkey patched functions
  var originalFn1=fn1
  fn1 = function(){
     obj.foo()
     return originalFn1.apply(this,arguments)
  }
  
  var originalFn2=fn2
  fn2 = function(){
     obj.bah()
     return originalFn2.apply(this,arguments)
  }
  
  fn1(); // Should output: foo and then fn1
  fn2(); // should output: bah and hen fn2
}

I think for add function , need this global variable .So here I check for undefined condition for call function second times without parameter !

function fn1(f) {
  if(typeof f != "undefined") this.fn1.add = f;
  this.fn1.add();
  console.log('fn1');
}

function fn2(f) {
  if(typeof f != "undefined") this.fn2.add = f;
  this.fn2.add();
  console.log('fn2');
}
function main() {
  fn1(obj.foo); // <= Should add function foo to fn1
  fn2(obj.bah); // <= Should add function bah to fn2
  fn1(); // Should output: foo and then fn1
  fn2(); // should output: bah and hen fn2
}

main(); 

I see the problem of adding to global scope, here's a new way using eval

var obj = {
  foo: function() {
    console.log('foo');
  },
  bah: function() {
    console.log('bah');
  }
};

function fn1() {
  foo();
  console.log('fn1');
}

function fn2() {
  bah();
  console.log('fn2');
}

function main() {
  function myFunc(fn) {
    function foo() {
        obj.foo();
    }
    function bah() {
        obj.bah();
    }
    eval("this.exec = " + fn.toString());
  }

  var myNewFunc1 = new myFunc(fn1);
  myNewFunc1.exec(); //foo, fn1
  var myNewFunc2 = new myFunc(fn2);
  myNewFunc2.exec(); //bah, fn2
}

main();

Working fiddle

  1. Find out all of the names of the functions that you want to convert to globals from the library you are importing.
  2. Make them global. If you want to make some fancy AST parsing thing that finds them all for you, I suppose you could do that. Or you could just use the console and work it out. Nonetheless you actually want this to be static as that helps you reason about the code that is produced, If it is dynamically produced then future upgrades to the library could brake earlier code, if you have global names clashing.

But the code only needs to look like this

var foo = obj.foo,
  bar = obj.bar;
foo()
bar()

Alternatively, you could acknowledge that having all your functions namespaced isn't such a bad idea, and that it isn't a plicated even for a beginner programmer to make an alias of the function they want to use. It helps them understand that it can be a good idea to keep their code organised, and helps them understand that functions and objects aren't copied when they are assigned to a new variable name they are just referenced.

var map = reallyLongLibraryNameThatNoOneWantsToType.map
//alternatively
var lib = reallyLongLibraryNameThatNoOneWantsToType
lib.reduce([1,2,3],function(a,b){ return a +b }, 0)

This is the best answer that can be given for this case. I know that it is annoying. All the options given above create more problems then they solve. Really ask yourself why exactly do I want to do what I have asked?

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745150309a4613838.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信