【问题标题】:Wrapping functions and function.length包装函数和function.length
【发布时间】:2011-11-11 15:50:15
【问题描述】:

假设我有以下代码

/*...*/
var _fun = fun;
fun = function() {
  /*...*/
  _fun.apply(this, arguments);
}

我刚刚丢失了_fun 上的.length 数据,因为我试图用一些拦截逻辑来包装它。

以下不起作用

var f = function(a,b) { };
console.log(f.length); // 2
f.length = 4;
console.log(f.length); // 2

.lengthannotated ES5.1 specification states定义如下

Object.defineProperty(fun, "length", {
  value: /*...*/,
  writable: false,
  configurable: false,
  enumerable: false
}

鉴于fun 内部的逻辑要求.length 是准确的,如何在不破坏.length 数据的情况下拦截和覆盖该函数?

我觉得我需要使用eval 和狡猾的Function.prototype.toString 来构造一个具有相同数量参数的新字符串。我想避免这种情况。

【问题讨论】:

标签: javascript function overwrite interception


【解决方案1】:

我知道你更喜欢其他方式,但我能想到的就是用Function 构造函数来破解一些东西。乱七八糟,至少可以说,但它似乎工作:

var replaceFn = (function(){
    var args = 'abcdefghijklmnopqrstuvwxyz'.split('');
    return function replaceFn(oldFn, newFn) {
        var argSig = args.slice(0, oldFn.length).join(',');
        return Function(
            'argSig, newFn',
            'return function('
                + argSig +
            '){return newFn.apply(this, arguments)}'
        )(argSig, newFn);
    };
}());

// Usage:
var _fun = fun;

fun = replaceFn(fun, function() {
  /* ... */
  _fun.apply(this, arguments);
});

【讨论】:

    【解决方案2】:

    正确且始终如一地伪造长度是 javascript 的最终边界,这几乎是它的开始和结束。在一种几乎可以伪造所有内容的语言中,长度仍然有些神奇。 ES6 将提供,我们现在可以根据您所使用的引擎和版本,或多或少地伪造它。对于一般的网络兼容性,它还有很长的路要走。 Proxies/noSuchMethod 已经在 Mozilla 中使用了一段时间。 Proxies 和 WeakMaps 已经在 Chromium 和节点(需要标志才能启用)的 V8 中可用,它们提供了正确伪造长度所需的工具。

    “长度”详解:http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

    最终解决方案:http://wiki.ecmascript.org/doku.php?id=harmony:proxies + http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps

    【讨论】:

    • 您能解释一下代理如何帮助伪造长度吗?弱地图怎么可能有帮助呢?此外,您对“长度”的引用指的是数组而不是函数
    • 使用代理,您可以为索引创建一个包罗万象的 getter,并复制所有必需的行为以匹配本机长度属性的作用。 WeakMaps 是您用来为不存在的属性提供访问器的工具,代理在类型/未定义检查完成之前会遇到这些属性。当某些东西要访问您的假数组上的 401042 的 getter 时,您首先使用 Wea​​kMap 定义该属性,然后更改您的假长度属性的值。所有本机长度属性都是指向 JavaScript 领域之外的实际数据结构的内部指针,因此可以显示“神奇”的行为。
    • 在函数上更改长度似乎不如数组有用,但它仍然具有魔力,在这种情况下,它只是在浏览器之间表现得不可预测。 Mozilla改变了大小,显然我没有看到其他任何东西。 V8 会在设置时返回您指定的数量,但不会实际更改值,并且无论您做什么都会返回实际的参数数量。长度应该是直接映射到存储它所代表的任何内容的内存。
    • 这很好,但这无助于创建与旧函数长度相同的新函数
    【解决方案3】:

    我为此目的使用以下功能;对于具有合理参数计数的函数来说,它真的很快,比公认的答案更灵活,并且适用于超过 26 个参数的函数。

    function fakeFunctionLength(fn, length) {
        var fns = [
            function () { return fn.apply(this, arguments); },
            function (a) { return fn.apply(this, arguments); },
            function (a, b) { return fn.apply(this, arguments); },
            function (a, b, c) { return fn.apply(this, arguments); },
            function (a, b, c, d) { return fn.apply(this, arguments); },
            function (a, b, c, d, e) { return fn.apply(this, arguments); },
            function (a, b, c, d, e, f) { return fn.apply(this, arguments); }
        ], argstring;
    
        if (length < fns.length) {
            return fns[length];
        }
    
        argstring = '';
        while (--length) {
            argstring += ',_' + length;
        }
        return new Function('fn',
            'return function (_' + argstring + ') {' +
                'return fn.apply(this, arguments);' +
            '};')(fn);
    }
    

    【讨论】:

      【解决方案4】:

      如果您需要支持带有任意数量参数的函数,您只需要走eval/Function 路线即可。如果你能设置一个合理的上限(我的例子是5)那么你可以做到以下几点:

      var wrapFunction = function( func, code, where ){
        var f;
        switch ( where ) {
          case 'after':
            f = function(t,a,r){ r = func.apply(t,a); code.apply(t,a); return r; }
          break;
          case 'around':
            f = function(t,a){ return code.call(t,func,a); }
          break;
          default:
          case 'before':
            f = function(t,a){ code.apply(t,a); return func.apply(t,a); }
          break;
        }
        switch ( func.length ) {
          case 0: return function(){return f(this, arguments);}; break;
          case 1: return function(a){return f(this, arguments);}; break;
          case 2: return function(a,b){return f(this, arguments);}; break;
          case 3: return function(a,b,c){return f(this, arguments);}; break;
          case 4: return function(a,b,c,d){return f(this, arguments);}; break;
          case 5: return function(a,b,c,d,e){return f(this, arguments);}; break;
          default:
            console.warn('Too many arguments to wrap successfully.');
          break;
        }
      }
      

      这种包装代码的方法也可以通过创建不同的where 开关来扩展。我实现了beforeafter,因为它们对我自己的项目最有用——而around 只是因为它让我想起了lisp。使用此设置,您还可以将包装 (var f) 移交给外部代码,从而允许您为 where 关键字开发类似系统的插件,这意味着您可以轻松扩展或覆盖 wrapFunction 支持的内容。

      显然,您可以随意更改代码的实际包装方式,关键在于使用与 999 和 AdrianLang 类似的技术,而无需担心构建字符串并传递给 new Function

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-02-17
        • 1970-01-01
        • 1970-01-01
        • 2018-03-07
        • 2017-11-30
        • 1970-01-01
        • 1970-01-01
        • 2014-02-19
        相关资源
        最近更新 更多