关于一次性自重定义函数模式

On a one-time self-re-defining function pattern

考虑以下模式:

function foo /* aka outer_foo */ () {

    // === stage 1 ===
    // declaration and initialization of closure variables

    var ... <CLOSURE_VARS> ...;

    // initialization of CLOSURE_VARS
    // (possibly expensive and/or depending on values known only at run-time)


    // === stage 2 ===
    // one-time redefinition of foo

    foo = function /* inner_foo */ ( arg0, ... ) {
        var results;

        // code computing value of results based on CLOSURE_VARS, arg0, ...

        // foo is never modified here

        return results;
    };


    // === stage 3 ===
    // invocation of inner function and returning of result

    return foo.apply( this, arguments );
}

为方便起见,我将分别使用术语outer_fooinner_foo来指定上面的外部和内部函数,即使代码不使用这些标识符。

outer_foo 的主体包括三个阶段(最后两个阶段各包含一个语句):

  1. 一些闭包变量的声明和初始化;
  2. 标识符的重新定义foo
  3. 将最初传递给outer_foo的参数传递给inner_foo,并返回结果。

outer_foo的主体最多执行一次,即第一次调用“名为foo的函数”,如果它曾经发生过。从今以后,outer_foo 的主体将无法访问,所有对“名为 foo 的函数”的后续调用将导致 的执行inner_foo.

一般来说,人们可能会设想这种模式的一些变体 1,但我在这里谈论的基本方案的基本限制是:

  1. fooouter_foo2;
  2. 的执行期间恰好被重新定义一次
  3. fooinner_foo.
  4. 的执行过程中永远不会重新定义

(如果违反了这些基本约束,则所有赌注均无效;这种情况不在本问题的范围内。)


我已经意识到这个方案的至少一个缺点:一些从业者认为自重定义函数令人困惑,不利于代码可读性,and/or 本质上品味低下,即使重定义仅发生以完全确定的方式一次,如上述方案中的情况。

我的问题是:

Does this scheme have any additional downsides, and if so, what are they?

I'm particularly interested in those downsides that are specific to JavaScript.


1 例如,

function foo /* aka outer_foo */ () {
    var inner_foo, ... <CLOSURE_VARS> ...;

    if ( some_deterministic_test() ) {
        inner_foo = function ( arg0, ... ) {
            // etc.
        }
    }
    else {
        inner_foo = function ( arg0, ... ) {
            // etc.
        }
    }


    foo = inner_foo;

    return foo.apply( this, arguments );
}

在此变体中,最终分配给 foo 的功能取决于在 运行 时执行的某些测试。

2 这里很容易规定 foo 不仅必须在 [=95 内重新定义一次=],但这是“确定性地”完成的。当然,在 foo 的最终设置中与“确定性”(无论选择如何定义它)的任何偏差只会增加代码的 运行 时间行为的复杂性。不幸的是,我不知道如何在不陷入律师细节的迷宫的情况下使这个规定精确。我能做的最好的事情就是添加一个狡猾的——而且几乎是不连贯的——短语“越确定性越好”,并希望读者明白我的意思。然而,这个附加规定的唯一用途是排除不正当但完全不现实的情况(例如 foo 的最终值取决于随机过程的结果),所以我将其排除在外。

我可以想到您应该注意的另外两点:

  • 性能。编译器就像你提到的那些人。他们中的一些人会对这种模式感到困惑,并且他们可能无法像使用其他模式时那样对其进行优化。这可能是问题,也可能不是问题,但您应该确保对其进行测试。
  • 违约。你说你希望 outer_foo 最多执行一次。事实可能并非如此!有人可能会传递它并在某处放置对它的引用,这样它在被调用时就不会变得无法访问。当然,这不太可能,但根据您的要求,您可能想要防范它。
    毕竟,它是一种损坏的设计,作为单纯的 别名 (var f = foo; f()) 不应改变功能。确保所有人 consumers/users 都知道你在做什么,这样他们就不会被绊倒。