Обтекание функций и функций. Длина - PullRequest
6 голосов
/ 07 сентября 2011

Давайте рассмотрим, у меня есть следующий код

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

Я только что потерял данные .length на _fun, потому что я пытался обернуть их некоторой логикой перехвата.

следующее не работает

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построить новую строку с тем же количеством аргументов.Я хочу избежать этого.

Ответы [ 4 ]

3 голосов
/ 07 сентября 2011

Я знаю, что вы бы предпочли другой путь, но все, что я могу придумать, это взломать что-то вместе с конструктором 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 голосов
/ 19 ноября 2011

Я использую следующую функцию для этой цели;он действительно быстр для функций с разумным количеством параметров, более гибок, чем принятый ответ, и работает для функций с более чем 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);
}
2 голосов
/ 07 сентября 2011

Длина фальшивки правильно и последовательно является последней границей в javascript, и это в значительной степени начало и конец этого. На языке, где вы можете подделать что угодно, длина все равно остается волшебной. ES6 будет работать, и мы можем подделать его сейчас в большей и меньшей степени в зависимости от того, какой у вас движок и версия. Для общей веб-совместимости это далеко. Proxies / noSuchMethod уже давно в Mozilla. Прокси и WeakMaps стали доступными для использования в V8 в Chromium и узлах (требующих флагов для включения), которые предоставляют инструмент, необходимый для правильной подделки длины.

Подробно о "длине": 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

0 голосов
/ 15 февраля 2013

Вам нужно идти по маршруту 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. Я реализовал before и after, потому что они наиболее полезны для моего собственного проекта & mdash; и around только потому, что это напоминает мне о лиспе. Используя эту настройку, вы также можете передать перенос (var f) на внешний код, что позволит вам разработать систему, подобную плагину, для ключевых слов where, что означает, что вы можете легко расширять или переопределять то, что поддерживает wrapFunction.

Очевидно, вы можете изменить способ, которым код фактически обернут, как вам угодно, ключ на самом деле просто использует технику, аналогичную 999 и AdrianLang, просто не беспокоясь о построении строк и переходе на new Function.

...