包装函数和 function.length

发布于 2024-12-03 09:29:27 字数 828 浏览 1 评论 0原文

假设我有以下代码,

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

带注释的 ES5.1 规范声明 。 length 定义如下

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

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

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

Let's consider I have the following code

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

I have just lost the .length data on _fun because I tried to wrap it with some interception logic.

The following doesn't work

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

The annotated ES5.1 specification states that .length is defined as follows

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

Given that the logic inside fun requires .length to be accurate, how can I intercept and overwrite this function without destroying the .length data?

I have a feeling I will need to use eval and the dodgy Function.prototype.toString to construct a new string with the same number of arguments. I want to avoid this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

把人绕傻吧 2024-12-10 09:29:27

我知道您更喜欢其他方式,但我能想到的就是将一些东西与 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);
});

I know you'd prefer some other way, but all I can think of is to hack together something with the Function constructor. Messy, to say the least, but it seems to work:

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);
});
(り薆情海 2024-12-10 09:29:27

正确且一致地伪造长度是 javascript 的最终前沿,这几乎是它的开始和结束。在一种几乎可以伪造一切的语言中,长度仍然有些神奇。 ES6 将会交付,我们现在可以根据您使用的引擎和版本来或多或少地伪造它。对于一般的 Web 兼容性来说,这还有很长的路要走。 Proxies/noSuchMethod 在 Mozilla 中已经有一段时间了。代理和 WeakMap 已在 Chromium 和 Node 的 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

Faking length correctly and consistently is the final frontier in javascript and that's pretty much the beginning and end of it. In a language where you can fake just about everything, length is still somewhat magical. ES6 will deliver, and we can fake it now to greater and lesser degrees depending which engine and version you're in. For general web compatability it's a ways off. Proxies/noSuchMethod has been in Mozilla for a while. Proxies and WeakMaps have gotten to usable in V8 in Chromium and and node (requiring flags to enable) which provide the tool you need to fake length correctly.

In detail on "length": http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

The eventual solution: http://wiki.ecmascript.org/doku.php?id=harmony:proxies + http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps

2024-12-10 09:29:27

我为此目的使用以下函数;对于具有合理参数数量的函数来说,它确实很快,比公认的答案更灵活,并且适用于具有超过 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);
}

I use the following function for this purpose; it’s really fast for functions with reasonable parameter counts, more flexible than the accepted answer and works for functions with more than 26 parameters.

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);
}
忆依然 2024-12-10 09:29:27

如果您需要支持具有任意数量参数的函数,则只需沿着 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

You only need to go down the eval/Function route if you need to support functions with any number of parameters. If you can set a reasonable upper limit (my example is 5) then you can do the following:

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

This method of wrapping code is also extendable by creating different where switches. I've implemented before and after because they are the most useful for my own project — and around just because it reminds me of lisp. Using this set-up you could also hand off the wrapping (var f) to external code allowing you to develop a plugin like system for where keywords, meaning that you could easily extend or override what wrapFunction supported.

Obviously you can change how the code is actually wrapped however you like, the key really is just using a similar technique to 999 and AdrianLang, just without worrying about building strings and passing to new Function.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文