Возобновить с ошибки - PullRequest
       40

Возобновить с ошибки

6 голосов
/ 01 февраля 2012

Прежде, чем меня орут за то, что я попробовал что-то настолько безрассудное, позвольте мне сказать вам, что я бы не стал этого делать в реальной жизни, и это академический вопрос.

Предположим, я пишу библиотеку и хочу, чтобы мой объект мог составлять методы по мере необходимости.

Например, если вы хотите вызвать метод .slice(), а у меня его нет, обработчик window.onerror запустит его для меня

В любом случае, я играл с этим здесь

window.onerror = function(e) {
    var method = /'(.*)'$/.exec(e)[1];
    console.log(method); // slice
    return Array.prototype[method].call(this, arguments); // not even almost gonna work 
};

var myLib = function(a, b, c) {
    if (this == window) return new myLib(a, b, c);
    this[1] = a; this[2] = b; this[3] = c;
    return this;
};

var obj = myLib(1,2,3);

console.log(obj.slice(1));

Также (может быть, мне следует начать новый вопрос), могу ли я изменить свой конструктор, чтобы принимать неопределенное количество аргументов?

var myLib = function(a, b, c) {
    if (this == window) return new myLib.apply(/* what goes here? */, arguments);
    this[1] = a; this[2] = b; this[3] = c;
    return this;
};

Кстати, я знаю, что могу загружать свои объекты с помощью

['slice', 'push', '...'].forEach(function() { myLib.prototype[this] = [][this]; });

Это не то, что я ищу

1 Ответ

4 голосов
/ 02 февраля 2012

Поскольку вы задавали академический вопрос, я полагаю, проблема совместимости браузера не является проблемой. Если это действительно не так, я бы хотел представить для этого прокси-серверы гармонии. onerror не очень хорошая практика, так как это просто событие, возникающее, если где-то происходит ошибка. Следует, если вообще, использовать только в качестве крайней меры. (Я знаю, что вы сказали, что не используете его в любом случае, но onerror просто не очень удобен для разработчиков.)

По сути, прокси-серверы позволяют вам перехватывать большинство основных операций в JavaScript - в первую очередь получить любое свойство, которое здесь полезно. В этом случае вы можете перехватить процесс получения .slice.

Обратите внимание, что прокси по умолчанию являются "черными дырами". Они не соответствуют никаким объектам (например, установка свойства на прокси-сервере просто вызывает ловушку set (перехватчик); фактическое хранение вы должны сделать самостоятельно). Но есть «обработчик пересылки», который направляет все через обычный объект (или, конечно, экземпляр), так что прокси-сервер ведет себя как обычный объект. Расширяя обработчик (в данном случае, часть get), вы можете довольно легко перенаправить методы Array.prototype следующим образом.

Таким образом, всякий раз, когда любое свойство (с именем name) выбирается, путь к коду выглядит следующим образом:

  1. Попробуйте вернуть inst[name].
  2. В противном случае попробуйте вернуть функцию, которая применяет Array.prototype[name] к экземпляру с заданными аргументами для этой функции.
  3. В противном случае просто верните undefined.

Если вы хотите поиграть с прокси, вы можете использовать последнюю версию V8, например, в ночной сборке Chromium (убедитесь, что она работает как chrome --js-flags="--harmony"). Опять же, прокси не доступны для «обычного» использования, потому что они относительно новые, меняют многие фундаментальные части JavaScript и фактически еще не определены официально (все еще в черновиках).

Это простая диаграмма того, как это происходит (inst на самом деле прокси, в который был обёрнут экземпляр). Обратите внимание, что это только иллюстрирует получение свойства; все остальные операции просто передаются через прокси из-за неизмененного обработчика переадресации.

proxy diagram

Код прокси может быть следующим:

function Test(a, b, c) {
  this[0] = a;
  this[1] = b;
  this[2] = c;

  this.length = 3; // needed for .slice to work
}

Test.prototype.foo = "bar";

Test = (function(old) { // replace function with another function
                        // that returns an interceptor proxy instead
                        // of the actual instance
  return function() {
    var bind = Function.prototype.bind,
        slice = Array.prototype.slice,

        args = slice.call(arguments),

        // to pass all arguments along with a new call:
        inst = new(bind.apply(old, [null].concat(args))),
        //                          ^ is ignored because of `new`
        //                            which forces `this`

        handler = new Proxy.Handler(inst); // create a forwarding handler
                                           // for the instance

    handler.get = function(receiver, name) { // overwrite `get` handler
      if(name in inst) { // just return a property on the instance
        return inst[name];
      }

      if(name in Array.prototype) { // otherwise try returning a function
                                    // that calls the appropriate method
                                    // on the instance
        return function() {
          return Array.prototype[name].apply(inst, arguments);
        };
      }
    };

    return Proxy.create(handler, Test.prototype);
  };
})(Test);

var test = new Test(123, 456, 789),
    sliced = test.slice(1);

console.log(sliced);               // [456, 789]
console.log("2" in test);          // true
console.log("2" in sliced);        // false
console.log(test instanceof Test); // true
                                   // (due to second argument to Proxy.create)
console.log(test.foo);             // "bar"

Обработчик переадресации доступен на официальной вики гармонии .

Proxy.Handler = function(target) {
  this.target = target;
};

Proxy.Handler.prototype = {
  // Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined
  getOwnPropertyDescriptor: function(name) {
    var desc = Object.getOwnPropertyDescriptor(this.target, name);
    if (desc !== undefined) { desc.configurable = true; }
    return desc;
  },

  // Object.getPropertyDescriptor(proxy, name) -> pd | undefined
  getPropertyDescriptor: function(name) {
    var desc = Object.getPropertyDescriptor(this.target, name);
    if (desc !== undefined) { desc.configurable = true; }
    return desc;
  },

  // Object.getOwnPropertyNames(proxy) -> [ string ]
  getOwnPropertyNames: function() {
    return Object.getOwnPropertyNames(this.target);
  },

  // Object.getPropertyNames(proxy) -> [ string ]
  getPropertyNames: function() {
    return Object.getPropertyNames(this.target);
  },

  // Object.defineProperty(proxy, name, pd) -> undefined
  defineProperty: function(name, desc) {
    return Object.defineProperty(this.target, name, desc);
  },

  // delete proxy[name] -> boolean
  delete: function(name) { return delete this.target[name]; },

  // Object.{freeze|seal|preventExtensions}(proxy) -> proxy
  fix: function() {
    // As long as target is not frozen, the proxy won't allow itself to be fixed
    if (!Object.isFrozen(this.target)) {
      return undefined;
    }
    var props = {};
    Object.getOwnPropertyNames(this.target).forEach(function(name) {
      props[name] = Object.getOwnPropertyDescriptor(this.target, name);
    }.bind(this));
    return props;
  },

  // == derived traps ==

  // name in proxy -> boolean
  has: function(name) { return name in this.target; },

  // ({}).hasOwnProperty.call(proxy, name) -> boolean
  hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); },

  // proxy[name] -> any
  get: function(receiver, name) { return this.target[name]; },

  // proxy[name] = value
  set: function(receiver, name, value) {
   this.target[name] = value;
   return true;
  },

  // for (var name in proxy) { ... }
  enumerate: function() {
    var result = [];
    for (var name in this.target) { result.push(name); };
    return result;
  },

  // Object.keys(proxy) -> [ string ]
  keys: function() { return Object.keys(this.target); }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...