将函数添加到另一个函数JavaScript的范围内

时间:2016-07-01 13:47:25

标签: javascript extendscript control-flow method-modifier

我试图让下面的示例代码工作。
编辑(试图更明确目标是什么):

我不想在函数objsetup中使用draw的所有函数和变量作为全局变量。这不适用于浏览器。这适用于Adobe ExtendScript,因此我只能使用EcmaScript 3和一些polyfill。 lib.js文件由我提供,并包含在之前 user-script.js文件。用户可以参考哪些功能可用,并可以在setupdraw中使用它们。这实际上与P5.js非常相似,但我正试图为InDesign实现这一目标。我们当然可以打电话给obj.foo()。我们的目的是摆脱obj.,让用户只需拨打foo并获得obj.foo的结果即可。

这是lib.js.它只是图书馆的一小部分,用于说明我手边的内容。

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();
    }
  }
};

这可以是user-script.js。我们提供的结构是:

  • #include for lib
  • function setup(){}
  • function draw(){}
  • obj.go()执行所有事情(可能会在以后删除)

我无法告诉用户在设置和绘图中编写更多其他代码。用户的部分应该减少,他可以手工编写,不需要使用样板或类似的东西。

#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()

感谢您的所有答案。我将对它们进行审核并回过头来报告最终的决定。所有这些似乎都以不同的方式解决了这个问题。我目前无法分辨出哪个是"对"回答。那些我们似乎离我最近的问题得到了我的支持。

11 个答案:

答案 0 :(得分:1)

这就像你要求使用闭包和更高阶函数一样,但是obj在fn1和fn2的范围内,所以不需要注入实际函数你可以从每个函数中调用obj.foo。

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();

答案 1 :(得分:1)

我认为原型是一种可行的方式。所以,我在我的解决方案中使用Function.prototype。请检查这是否适合您:

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();

我试图尽可能多地使用(就结构而言)。

这样可以解决您的问题吗?

答案 2 :(得分:1)

正如已经提到的那样,有助于实现改变/改变封闭函数/方法的控制流的目标的唯一模式(因此,一个人不拥有他们的代码库。)是函数组合。

它也得到了指出,它有不同的实现方法。

OP改变的例子将使用Function.before的原型实现。因为JavaScript已经具有标准化的bind,所以我坚信 对于其他一些方法修饰符Function.prototype也是正确的位置 像beforeafteraroundafterThrowingafterFinally

......尽可能贴近OP的例子:

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();

...... 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));

考虑到OP的答案......

  

A:   是的,没错......

...对这个问题......

  

问:   ...我是否理解您是否要决定是否致电fooobj.foobah / obj.bah派生出来的决定分别fn1 fn2

......我之前的方法从Function.before更改为Function.around

但是,我个人对此解决方案并不满意,因为全球提供的方法fn1fn2现在需要提前预测修改,这在我看来并不干净方法

尽管如此,下一个例子接近OP正在寻找的内容:

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();

...... 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));

答案 3 :(得分:1)

with是一个简单的解决方案

&#13;
&#13;
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()
})()
&#13;
&#13;
&#13;

答案 4 :(得分:1)

  OP:我不明白模块系统。 ...

通过(自定义)模块系统,人们可以通过他们能够注册他们的代码库来为用户提供一种方法, 将每个封装成一个函数本身。 由于存在“注册”的控制,因此应该将其他状态/行为注入到这种基于功能的模块中。 只需要弄清楚哪些参数将被一个系统传递给那些模块。

  

OP:......我将查看你的before原型。这看起来对我们有用。 ...

不会。但另一种更直接的方法是可行的。它会滥用模块系统的想法并将内容彻底改变。 OP的要求远非理想。但是,从OP提供的最新示例代码重构的可能解决方案 - 在给定方案中 - OP可能会寻找什么,看起来可能类似......

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
    };

}());

包含“lib.js”的自定义代码。

// 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`.

答案 5 :(得分:1)

  

OP:这里的问题是我们在绘制和设置中需要有很多功能。   所以一个接一个地传递它们不是解决方案。我们可以将它们作为对象传递但是我们又回到了第一个方向。   ......我将重新考虑这个想法,也许我们必须走向全球范围。

出于好奇,因为我从未需要evalwith的黑魔法,我终于走了 为此,证明弗兰肯斯坦的怪物真的可以通过这些工具创造出来。在那里,这个可怜的生物......

...... 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
    };

}());

......它是受害者......

// 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.

答案 6 :(得分:0)

我看到了添加到全局范围的问题,这是使用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();

工作fiddle

答案 7 :(得分:0)

您可以将函数传递给函数。

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
}

答案 8 :(得分:0)

这是猴子修补的解决方案,它实际上是用原来的函数替换原来的函数,它们仍然调用原始函数...所以它就像你已经注入了代码一样。你仍然可以使用更高阶的函数来帮助组成这些猴子补丁。

&#13;
&#13;
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
}
&#13;
&#13;
&#13;

答案 9 :(得分:0)

我认为对于添加函数,需要这个全局变量。所以这里我没有参数检查第二次调用函数的未定义条件!

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(); 

答案 10 :(得分:0)

  1. 找出要导入的库中要转换为全局变量的所有函数名称。
  2. 让它们成为全球性的。如果你想做一些花哨的AST解析的东西,找到它们全部为你,我想你可以做到这一点。或者你可以使用控制台并解决它。尽管如此,您实际上希望这是静态的,因为它可以帮助您推断生成的代码,如果它是动态生成的,那么如果您的全局名称发生冲突,将来升级到库可能会导致更早的代码崩溃。
  3. 但代码只需看起来像这样

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

    或者,您可以确认将所有函数命名为命名空间并不是一个坏主意,并且即使初学者程序员创建他们想要使用的函数的别名也不是很复杂。它有助于他们理解保持代码组织是一个好主意,并帮助他们理解当函数和对象被分配给刚刚引用的新变量名时,不会复制它们。

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

    这是本案的最佳答案。我知道这很烦人。上面给出的所有选项都会产生更多问题然后解决。真的问自己为什么我想做我所要求的呢?