Пользователь
0,0
рейтинг
21 апреля 2011 в 18:53

Разработка → ES5 Harmony Proxy — меняем семантику JavaScript внутри самого JavaScript

Прокси — это новые объекты JavaScript для которых программист должен определить своё поведение. Стандартное поведение всех объектов определено в движке JavaScript, который чаще всего написан на C++. Прокси позволяют программисту определить практически любое поведение объекта JavaScript, они полезны для написания базовых объектов или оберток функций или для создания абстракций виртуальных объектов и предоставляют API для мета-программирования. Сейчас Прокси не входит в стандарт, но его стандартизация запланирована в ECMAScript Harmony. Чтобы избежать путаницы уточню, что эти Прокси не имеют ничего общего с прокси серверами.

Где их можно использовать


1. Общие промежуточные абстракции
2. Создание виртуальных объектов: обертки существующих объектов, удаленные(от слова далекий) объекты, ленивое создание объектов (Пример ORM — Ruby ActiveRecord, Groovy GORM)
3. Прозрачное ведение логов, трассировки, профилирования
4. Внедрение предметно-ориентированных языков
5. Динамический перехват несуществующих методов, создание отсутствующих методов (__noSuchMethod__)
6. База для специфичных итераторов

Понятия


1. Эта особенность языка называется «всеобъемлющий механизм»(ориг. catch-all mechanism) — это имя используется для описания этой особенности. В тексте будет использоваться оригинальное название.
2. Другое название понятию «всеобъемлющий механизм» — посредник (ориг. intercession API)
3. Объект, который обрабатывает свойство называется обработчик — handler
4. Объект, свойства которого заменяются называется прокси — proxy
5. Объект/метод, который создает прокси объекты называется фабрика прокси — proxy factory
6. Методы, входящие в обработчик, которые обрабатывают какое-либо поведение называются ловушки/перехватчики traps (по аналогии с операционными системами)
7. Прокси может быть захватывающим/активным (ориг. trapping) или быть распущенным (ориг. fixed)

Как с ними работать — API


Существует два вида фабрик прокси. Одна для для объектов другая для функций.

Конструктор прокси-объекта:
var proxy = Proxy.create(handler, proto);

Конструктор прокси-функции:
var proxy = Proxy.createFunction(handler, callTrap, constructTrap);

proto — не обязательный параметр, определяющий прототип прокси
callTrap — функция, которая будет замещать оригинальную функцию при прямом вызове прокси-функции (пример ниже все объяснит). Важно: this замещающей функции (callTrap) совпадает с this замещаемой.
constructTrap — не обязательный параметр — функция, которая будет заменять оригинальный конструктор функции при вызове через new. Важно: this замещающей функции (constructTrap) всегда undefined. Если constructTrap не передан, то используется callTrap в котором this делегирует от proxy.prototype (обычное поведение конструкторов ES5 Глава 13.2.2)
handler — объект, которые определяет поведение прокси. Этот объект должен всегда содержать Базовые ловушки (traps)

Базовые ловушки/перехватчики (Fundamental traps)

Как это читать: имяПерехватчика: function(переменные, которые_передаются_перехватчику) -> {Тип возвращаемых данных} // Что заменяет
{
  getOwnPropertyDescriptor: function(name) -> {PropertyDescriptor | undefined} 
  // Object.getOwnPropertyDescriptor(proxy, name)

  getPropertyDescriptor:    function(name) -> {PropertyDescriptor | undefined} 
  // Object.getPropertyDescriptor(proxy, name)   (not in ES5)

  getOwnPropertyNames:      function() -> {String[]}                           
  // Object.getOwnPropertyNames(proxy) 

  getPropertyNames:         function() -> {String[]}                           
  // Object.getPropertyNames(proxy)              (not in ES5)

  defineProperty:           function(name, propertyDescriptor) -> {Mixed}      
  // Object.defineProperty(proxy,name,pd)

  delete:                   function(name) -> {Boolean}                        
  // delete proxy.name
  
  fix:                      function() -> {{String:PropertyDescriptor}[]|undefined}
  // Object.{freeze|seal|preventExtensions}(proxy)
}


Производные ловушки/перехватчики (Derived traps)

Эти перехватчики не обязательны, если они не будут определены, то будет использоваться логика по умолчанию.
{
  has:       function(name) -> {Boolean}                
  // if (name in proxy) ...

  hasOwn:    function(name) -> {Boolean}               
  // ({}).hasOwnProperty.call(proxy, name)

  get:       function(receiver, name) -> {Mixed}        
  // receiver.name;

  set:       function(receiver, name, val) -> {Boolean} 
  // receiver.name = val;

  enumerate: function() -> {String[]}                   
  // for (name in proxy) Возвращает массив имен своих и унаследованные перечисляемых объектов

  keys:      function() -> {String[]}                   
  // Object.keys(proxy)  Возвращает массив своих перечисляемых объектов
}

По умолчанию будет выполняться следующая логика
В виде таблицы можно посмотреть тут

Производными ловушки называются «производными» потому, что они могут быть определены базовыми ловушками. Например, ловушка has может быть определена, используя ловушку getPropertyDescriptor (проверить возвращает undefined или нет). С помощью производных перехватчиков можно с меньшими затратами эмулировать свойства, поэтому они и были определены. Ловушка fix была введена для того, чтобы позволить прокси взаимодействовать с Object.preventExtensions, Object.seal и Object.freeze. Нерасширеямый, запечатанный или замороженный объект (non-extensible, sealed, frozen) должен каким-либо образом ограничить свободу обработчика (handler) т.е. то, что он должен возвратить в будущих вызовах set, get итп. Например, если предыдущий вызов handler.get(p, “foo”) возвратил не undefined, то будущие вызовы handler.get(p, "foo") обязаны возвращать такое же значение, если объект заморожен (frozen). Каждый раз, когда прокси пытаются заморозить, запечатать, заблокировать (non-extensible, sealed or frozen) вызывается ловушка "fix" обработчика.
Обработчик fix имеет 2 варианта:
1. отклонить запрос (fix должен возвратить undefined), тогда будет вызвано исключение TypeError.
2. выполнить запрос и возвратить описание объекта в виде {String:PropertyDescriptor}[] в этом случае «всеобъемлющий механизм» создаст новый объект, основываясь на описании объекта. В этот момент удаляются все ссылки на обработчик (он может быть удален сборщиком мусора). В этом случае прокси называется распущенным.

Примеры



Hello, Proxy!

Следующий кусок кода создает прокси, который прерывает доступ к свойствам и возвращает для каждого свойства «p» значение «Hello, p»:
var p = Proxy.create({
  get: function(proxy, name) {
    return 'Hello, '+ name;
  }
});

document.write(p.World); // Напечатает 'Hello, World'

Живой пример

Простой профайлер

Создадим простую обертку, которая считает сколько раз какое свойство было получено:
function makeSimpleProfiler(target) {
  var forwarder = new ForwardingHandler(target);
  var count = Object.create(null);
  forwarder.get = function(rcvr, name) {
    count[name] = (count[name] || 0) + 1;
    return this.target[name];
  };
  return {
    proxy: Proxy.create(forwarder,
                        Object.getPrototypeOf(target)),
    get stats() { return count; }
  };
}

Функция makeSimpleProfiler получает в качестве аргумента объект, который мы хотим мониторить. Она возвращает объект, имеющий 2 свойства: сам прокси и stats — количество вызовов.
Живой пример

Функция ForwardingHandler в строке два функции makeSimpleProfiler создает простой переадресующий проки, который прозрачно делегирует все операции, выполняемые над прокси, целевому объекту. Вот как она выглядит:
function ForwardingHandler(obj) {
  this.target = obj;
}
ForwardingHandler.prototype = {
  has: function(name) { return name in this.target; },
  get: function(rcvr,name) { return this.target[name]; },
  set: function(rcvr,name,val) { this.target[name]=val;return true; },
  delete: function(name) { return delete this.target[name]; }
  enumerate: function() {
    var props = [];
    for (name in this.target) { props.push(name); };
    return props;
  },
  iterate: function() {
    var props = this.enumerate(), i = 0;
    return {
      next: function() {
        if (i === props.length) throw StopIteration;
        return props[i++];
      }
    };
  },
  keys: function() { return Object.keys(this.target); },
  ...
};
Proxy.wrap = function(obj) {
  return Proxy.create(new ForwardingHandler(obj),
                      Object.getPrototypeOf(obj));
}

С полной версией этой функции можно ознакомиться тут. Этот переаресующий обработчик (forwarding handler) скорее всего станет частью стандарта.

Удаленные объекты

Прокси позволяют вам создавать виртуальные объекты, которые могут эмулировать удаленные объекты или существующие объекты. Для демонстрации давайте создадим обертку вокруг существующей библиотеки для удаленной коммуникации в JavaScript. Библиотека web_send Tyler Close'а может быть использована для создания удаленных связей с объектами, расположенными на сервере. Теперь мы можем вызывать методы по HTTP POST запросам, используя эту удаленную связь. К сожалению, удаленные связи не могут быть использованы как объекты.
Сравним. Для вызова удаленной функции изначально нужно было вызывать:
Q.post(ref, 'foo', [a,b,c]);

Используя прокси мы можем сделать этот вызов более естественным, напишем нашу обертку:
function Obj(ref) {
  return Proxy.create({
    get: function(rcvr, name) {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return Q.post(ref, name, args);
      };
    }
  });
}

Теперь мы можем делать вот так Obj(ref).foo(a,b,c).

Эмуляция __noSuchMethod__

Используя Прокси возможно эмулировать хук __noSuchMethod__ в тех браузерах, которые его не поддерживают (но сейчас это не актуально).
function MyObject() {};
MyObject.prototype = Object.create(NoSuchMethodTrap);
MyObject.prototype.__noSuchMethod__ = function(methodName, args) {
  return 'Hello, '+ methodName;
};

new MyObject().foo() // returns 'Hello, foo'

Этот объект использует NoSuchMethodTrap-прокси в котором ловушка get заменяет оригинальный __noSuchMethod__.
var NoSuchMethodTrap = Proxy.create({
  get: function(rcvr, name) {
    if (name === '__noSuchMethod__') {
      throw new Error("receiver does not implement __noSuchMethod__ hook");
    } else {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return this.__noSuchMethod__(name, args);
      }
    }
  }
});

Живой пример

Сообщения высшего порядка

Сообщения высшего порядка это сообщения, получающие другие сообщения в качестве аргумента, как описано тут.
Сообщения высшего порядка похожи на функции высшего порядка, но они более емкие в коде. Используя прокси очень просто создавать сообщения высшего порядка. Рассмотрим специальный объект "_", который переделывает сообщения в функции:
var msg = _.foo(1,2)
msg.selector; // "foo"
msg.args; // [1,2]
msg(x); // x.foo(1,2)

msg это функция, которая использует один аргумент, как если бы она была определена как function(z) { return z.foo(1,2); }. Следующий пример это прямая интерпретация СВП из вышеупомянутых документов, но написанная более емким кодом:
var words = "higher order messages are fun and short".split(" ");
String.prototype.longerThan = function(i) { return this.length > i; };
// используем СВП для обработки сообщения как функции
document.write(words.filter(_.longerThan(4)).map(_.toUpperCase()));
// Без СВП этот код был бы таким:
// words.filter(function (s) { return s.longerThan(4) })
//       .map(function (s) { return s.toUpperCase() })

Вот код объекта "_":
// превращает сообщение в функцию
var _ = Proxy.create({
  get: function(_, name) {
    return function() {
      var args = Array.prototype.slice.call(arguments);
      var f = function(rcvr) {
        return rcvr[name].apply(rcvr, args);
      };
      f.selector = name;
      f.args = args;
      return f;
    }
  }
});

Живой пример

Эмуляция базовых объектов

Прокси дают Javascript программистам возможность эмулировать странности базовых объектов, таких как DOM. Это позволяет авторам библиотек оборачивать базовые объекты для того, чтобы их «приручить» (прим. Песочницы) или исправить их для сокращения кросс-браузерной несовместимости.

Прокси-функции

Предыдущие примеры использовали объекты. Вот простой пример прокси-функции:
var simpleHandler = {
  get: function(proxy, name) {
    // can intercept access to the 'prototype' of the function
    if (name === 'prototype') return Object.prototype;
    return 'Hello, '+ name;
  }
};
var fproxy = Proxy.createFunction(
  simpleHandler,
  function() { return arguments[0]; }, // call trap
  function() { return arguments[1]; }); // construct trap

fproxy(1,2); // 1
new fproxy(1,2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'Hello, foo'

Живой пример

Прокси-функции открывают возможности для написания особых идиом, которые были нам не доступны в чистом JavaScript. В первую очередь функции-прокси могут создать из любого объекта функцию (callable object):
function makeCallable(target, call, construct) {
  return Proxy.createFunction(
    new ForwardingHandler(target),
    call,
    construct || call);
}

Второе, функции-прокси можно использовать для создания псевдо-классов, сущности которых функции(instances are callable).
function Thing() {
  /* initialize state, etc */
  return makeCallable(this, function() {
    /* actions to perform when instance
       is called like a function */
  });
}

Эксперименты с новой семантикой


Для любителей языка: Прокси могут быть использованы для создания «стандартных» объектов и функций JavaScript в самом JavaScript. Возможность менять семантику объектов JavaScript внутри JavaScript значительно упрощает механизм внесения мелких изменений в семантику для проведения экспериментов. Частичную реализацию семантик внутри JavaScript можно наблюдать в значениях ловушек по умолчанию. Другой пример: Массивы JavaScript внутри JavaScript, используя Прокси.

Советы по использованию прокси


Избегайте рекурсий

Избегайте неявных вызовов toString внутри ловушек. Будьте аккуратны с аргументом receiver ловушек get и set. receiver представляет ссылку на прокси, поэтому неявно вызвав get и set приведет к бесконечной рекурси. Например вызов console.log(receiver) для дебага внутри сеттера вызовет метод toString, который приведет к бесконечной рекурсии.
get: function(receiver, name) {
  print(receiver);
  return target[name];
}

Если p это прокси, который использует ловушку выше, тогда вызов p.foo приведет к бесконечному циклу: Сперва ловушка get будет вызвана с name="foo", которая печатает receiver (т.е. p). Это приводит к вызову p.toString(), который приведет к вызову ловушки ещё раз в этот раз с name="toString". И так далее.

Прокси как обработчики

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

image

Прокси трейсер/Прокси зонд

Трейсер просто печатает описание всех операций, который он обрабатывал. Это очень полезно для дебага или для изучения работы прокси.
Живой пример

Зонд имеет схожую с трейсером логику он логирует все мета-уровневые операции, примененные к нему.
Живой пример

Когда это можно будет использовать?


Сейчас только Firefox 4.0 поддерживает прокси. Есть реализация прокси для Node.js в виде расширения: node-overload (частичная поддержка) node-proxy (практически полная поддержка). В любом случае Прокси будут внесены в стандарт так, что скоро он появится и в вашем браузере!

Дополнительные ресурсы


1. ECMAScript Harmony
2. Документация на The Mozilla Developer Network
3. Разработка стандарта: первая часть этого Google Tech Talk и вот эта бумага, показанная на DLS 2010.
4. Brendan Eich, в своем блоге кратко объясняет основы Прокси.
5. Частичный список открытых проблем Прокси в Firefox 4.
6. Слайды Brendan Eich из его оклада на jsconf

Использованные в статье ресурсы


1. MDC Proxy (DRAFT)
2. ES5 Catch-all Proxies
3. Proxy Inception (Brendan Eich)
4. Tutorial: Harmony Proxies (Tom Van Cutsem)

Если вам что-то не понятно, пожалуйста, задавайте свои вопросы или посмотрите слайды. Предложения, пожелания, критика приветствуется!
Mikhail Davydov @azproduction
карма
449,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (39)

  • 0
    Интересная вещь. Спасибо. Особенно порадовал профайлер.
  • +8
    Молодец, что переводишь статьи / создаешь русскоязычные версии. Чтобы русское JS-сообщество не отставало — это полезное и хорошее дело.

    По поводу прокси, от себя могу добавить несколько примеров, когда я экспериментировал с ними в самых первых имплементациях SpiderMonkey:

    Из интересного:

    1. Массивы с отрицательными индексами — как в Python:

    var a = Array.new(1, 2, 3);
    
    console.log(a[-1]); // 3
    console.log(a[-2]); // 2
    
    a[-1] = 10;
    console.log(a); // 1,2,10
    console.log(a[-1]); // 10
    

    2. Мета-программирование на уровне объекта — фишки с __count__, __noSuchProperty__, __delete__, __get__, __set__ и т.д. Это только ради академического интереса (вообще, прокси и создавались, чтобы разделить мета- и прикладной уровни).

    Можно менять трэп обычным присваиванием функции:

    var foo = Object.new({
      __get__: function (name) {
        console.log('__get__:' + name);
      }
    });
    
    foo.bar; // __get__: bar
    foo.baz; // __get__: baz
    
    foo.__get__ = function (name) {
      console.log("New get: " + name);
    };
    
    foo.bar; // New get: bar

    3. Сахар для разделенного мета-уровня

    var foo = Object.new({
      // normal object level
      data: {
        x: {
          value: 10,
          writable: true
        }
      },
      // meta-level
      meta: {
        noSuchProperty: function fooNoSuchProperty(name) {
          console.log("noSuchProperty: " + name);
        },
        noSuchMethod: function fooNoSuchMethod(name, args) {
          console.log("noSuchMethod: " + name + ", args: " + args);
        }
      },
      // a prototype of an object
      proto: Object.prototype
    });
    
    foo.bar; // noSuchProperty "bar"
    foo.bar(1, 2, 3); // noSuchProperty: "bar" -> noSuchMethod: "bar", args: 1, 2, 3
    
    // normal-level doesn't disturb meta-level
    foo.noSuchProperty = 10; // assign to normal-level, but not to meta-level
    // still noSuchProperty of the meta-level is called
    foo.baz; // noSuchProperty: "baz"
    
    // the same with noSuchMethod
    foo.noSuchMethod = 20; // assign to normal-level, but not to meta-level
    // still the meta-level noSuchMethod is activated
    foo.baz(10, 20, 30); // noSuchMethod: "baz", args: 10, 20, 30

    4. Оптимизированный __noSuchMethod__ — функция-активатор реюзается.

    5. Делегирующие примеси (как в Ruby) или множественное наследование. Используется именно реюз и делегация, а не просто копирование в собственные свойства.

    И т.д. — больше можно найти здесь.

    P.S.: был длинный тред с дебатами, где мы обсуждали, нужно ли добавить флаг isCall в get трэп — чтобы различать выражение вызова: foo.bar от foo.bar(), но пока решили не делать (что затрудняет имплементацию __noSuchMethod__ — нужна функция-активатор, которая является значением любого свойства).
    • 0
      Особо радует пункт 3 =)
    • 0
      Спасибо за интересные дополнения, которые можно реально применять. Часть из них я видел в твоем комментарии в блоге Brendan Eich, но поленился дописать. Стараюсь продвигать, но как видно, в силу тех или иных причин, нововведения пока не особо интересны. Твой es-laboratory я просмотрю на днях.
      • 0
        На самом деле, скажу субъективно, оно достаточно тяжело. Если не разбираться с этим на высоком уровне — не осилишь. И количество плюсов за такую сильную статью это только доказывают. Высококлассных Javascript-прогеров, в т.ч. на Хабре — единицы.

        Мне кажется, намного лучше было бы выкладывать подобные вещи малыми порциями. Даже тему, освещенную в статье — разложить штук на 5 мелких (хоть и объекмных) статтей, с примерами и разжовыванием.
        • 0
          >Высококлассных Javascript-прогеров, в т.ч. на Хабре — единицы.
          Тогда вам надо собраться и напиться с горя. Правда такую толпу не каждый бар выдержит
          • 0
            Лишь бы на столик хватило))
    • 0
      ну и зря не добавили.

      хотел сделать автопривязку методов к объектам при получении на них ссылки, но обломался.
      хочется простую логику: если метод «вызывается», то и пусть себе вызывается. а вот если «получается» и берётся из прототипа, то в объекте создаётся привязанный к нему прокси и возвращается он.

      var c= function(){
      this.a= function(){}
      }
      c.prototype.b= function(){ this.a() }

      var o= new c

      addEventListener( 'load', o.a, false ) // a вызовется контексте window
      addEventListener( 'load', o.b, false ) // b вызовется в контексте o
      removeEventListener( 'load', o.b, false ) // o.b не меняется при каждом обращении

      сейчас можно делать такие финты лишь для всех методов без разбора, а не только для колбэков. isCall сильно бы в этом помог.
      • 0
        Основной протест был в поддержке не только «функций-вызовов», но и функций, как объектов. I.e.: не только так: foo.bar(), но и foo.bar.apply(...). Записи должны (?) быть семантически эквивалентны. Достичь этого можно лишь всегда возвращая функцию-активатор. Но я тоже ратовал за isCall флаг.
        • 0
          ну, они тут вовсе не должны. другое дело, что по другому передать параметры функции Одной переменной никак не получится. лучше бы сделали синтаксис для вызова метода с недетерминированным числом параметров без отрыва от объекта.

          foo.bar( arguments: [1,2,3] ) // foo.bar.apply( foo, [1,2,3] )

          и вот этот вызов уже должен быть полностью эквивалентен foo.bar( 1, 2, 3 )
          • 0
            Да, похожий сахар будет. И называется это rest и spread:

            function foo(...arguments) {
              console.log(arguments);
            }
            
            foo(1, 2, 3); // [1, 2, 3]

            И обратно (spread):

            function foo(a, b, c) {
              console.log(a, b, c);
            }
            
            var args = [1, 2, 3];
            
            foo(...args); // 1, 2, 3

            Слайд 23 — здесь.
            • 0
              выглядит как-то слишком костыльно. передать можно только переменную, нельзя заинплейсить какое-нибудь выражение. но таки да, с такой фичей использовать obj.func.apply будет бессмысленно кроме случаев где надо именно переопределить контекст. мой вариант как-то удобней, наглядней и расширяемей

              напомнило e4x — расширили синтаксис для работы xml, но работает он исключительно с xml. в результате чего никакой сторонний объект не может реализовать тот же интерфейс.

              [a][b]1[/b][/a]..b // 1
              ({a: {b: 1 } })..b // неизлечимая рантайм ошибка

              так что к нему даже прокси не прикрутить.
              а всего-то надо было, чтобы оператор ".." вызывал метод __descendants__
              • 0
                Помоему, rest и spread — элегантный сахар для foo.bar.apply( foo, [1,2,3] )
                В «каноничном» JavaScript нет оператора .. — это расширение Mozilla, как вы уже написали, поэтому ждать чего-то ( __descendants__) стоит только от разработчика расширения. Прочие браузеры реагируют на .. как на ошибку вроде «expected identifier, got '.'» т.к. парсят код «по стандарту» и врятли будут что-то переделывать.
                А вот перегрузки операторов, на сколько я знаю, в JavaScript пока не планируется.
                • 0
                  вот именно что каждый разработчик расширения копошится в своём болоте ибо нет единого механизма расширения синтаксиса. все эти расширения выглядят как костыли, прилепленные сбоку и постоянно спотыкающиеся о костыли соседних расширений.

                  ну вот почему не планируется-то? сделали же перегрузку «точки» с помощью гетеров. сделали перегрузку «присваивания» с помощью сеттеров. вроде даже с помощью прокси-функции можно перегрузить «вызов» и «инстанцирование». почему не дать перегружать и остальные операторы?

                  пока что мы видим робкие попытки решить частные случаи, вместо того чтобы решать проблему комплексно. язык всё усложняется и усложняется, вместо того, чтобы упрощаться и унифицироваться.
  • 0
    Второе, функции-прокси можно использовать для создания псевдо-классов, сущности которых функции(instances are callable).

    function Thing() {
      /* initialize state, etc */
      return makeCallable(this, function() {
        /* actions to perform when instance
           is called like a function */
      });
    }


    Между прочим, это не хватало. На примере jquery синтаксический сахар:
    var $table = $('table');
    $table('td');
    // вместо
    $table.find('td');
    
  • 0
    Уху… все жду проксей чтобы создать класс Nothing, который всегда будет возвращать сам себя вместо вываливания object property/method doesn't exist и заменить-таки null.
    • 0
      var Nothing = function () { return Nothing; }
      
      var NewNothing = new new new new new Nothing();
      
      console.log(NewNothing == Nothing); // true
      
      • 0
        Не, смысл был в том, чтобы, к примеру

        a = something(); // если метод вываливается с ошибкой, то возвращается Nothing
        
        console.log(a.split(1, 15).length ) // a любые последующие манипуляции c медотами/полями возвращают Nothing
        


        В мозилке можно использовать _noSuchMethod_, но только в мозилке.
        • +2
          Понял. Типа, как в JQuery ошибки глушаться. А зачем? Чтобы усложнить отладку?
  • 0
    использую уже около месяца в node.js c node-proxy… ощущения как будто развязали руки
    • 0
      О! Только хотел сказать что нам за такие «технологии» на клиенте могут коечто открутить — а тут оказывается выход есть в сторону сервера
  • +1
    ну и хрень. зачем разделять проксиметоды и проксиобъекты? почему не разрешить любому объекту быть прокси? почему не сделают наконец, чтобы не только функции были объектами, но и объекты могли быть функциями? зачем на каждый паттерн придумывают свою кривую нативную реализацию вместо того, чтобы расширить возможности метапрограммирования?

    достаточно сделать поддержку __call__, __proto__, __get__, __set__, __cast__ и этого хватит для всего.
    • +2
      Прокси создавались, чтобы разделить мета- и прикладной уровни
      • +2
        это какое-то словоблудие. афайк прокси будут использоваться в обоих уровнях. просто потому что по другому никак.
    • 0
      Я с вами полностью согласен. Несостоятельность новых (порой очень странных) паттернов создает предпосылки к созданию сложных и таких же странных механизмов. Особенно дико выглядит пример с поддержкой нерабочего __noSuchMethod__ через нерабочую и запутанную конструкцию с прокси. Чтобы починить нашу машину, нам нужно починить нашу машину через выхлопную трубу.
      • 0
        Вам дают инструмент для создания инструментов, а вы против. Proxy открывают широчайшие возможности для JavaScript. Теперь JavaScript может, как никогда раньше, поглотить любые языковые парадигмы.
        • 0
          Я прекрасно пользуюсь хорошими продуманными инструментами. Увы, история развития JS такова, что он пока всё ещё то там, то сям, но «поглощать любые языковые парадигмы» — это далеко не то, что я лично хочу видеть в коде, созданный кем-то другим и пытаясь понять, чем автора не устроил достаточный набор существующих правил. Я не против развития Ecma, даже в парадигму OO, геттеры-сеттеры и т.п., но имхо уже через-чур глядеть в код и видеть отрицательный индекс массива (которого никогда не могло быть) и задавать себе вопрос «wtf?!». К тому же, «теперь js может» — сильно сказано )
          • 0
            У меня когда-то была такая же позиция. Но судьба JavaScript такова, что он является дополнительным языком, далеко не все знают его в совершенстве, не все компании могут позволить себе держать выделенного JavaScript программиста.
            Довольно часто пишут на JavaScript как на Ruby, Python, PHP — Prototype.js, переход RoR на CoffeeScript отличный тому пример. И как бы мы не хотели заставить всех писать на JavaScript как на JavaScript (как учили деды) у нас ничего не получится.
            Для упрощения внедрения семантик и были созданы прокси. К тому же все эти нововведения не так уж и плохи, помоему, отрицательный индекс это хорошо. Сейчас моя позиция более толерантна: «Я пишу на JavaScript как на JavaScript и все проекты над которыми я работаю пишут аналогично, а что делают остальные мне не важно потому, что я на них не могу повлиять».
            • 0
              Спорить о развитии JS смысла нет, это просто моё личное мнение. Будет так будет.
              Только писать фиговый код можно на любом языке, а тут выглядит как дополнительный стимул к изобретению ещё более мутантных вещей, написанного программистом из вселенной другого языка, пытающегося адаптировать под свою парадигму JS. Когда есть ограниченный набор правил, это ещё хоть как-то сдерживает, а когда правила устанавливаются динамически кем попало и как попало, это где-то на пути к хаосу.
          • 0
            > но имхо уже через-чур глядеть в код и видеть отрицательный индекс массива (которого никогда не могло быть) и задавать себе вопрос «wtf?!»

            Да ну, всего лишь сахар для более удобного обращения к индексам с конца: a[1] вместо a[a.length - 1].

            И такая парадигма используется в разных языках — те же Ruby, Python. Хотите используйте (с сахаром), хотите не используйте (без сахара, с шумом).
            • 0
              > a[1]

              a[-1]
            • +1
              Скажем так, отрицательный индекс массива сам себе — сладкий сахар, я за. Я также за то, чтобы были стандартизированы и другие полезные вещи, уменьшающие «шум». Но только как общее ненарушаемое правило, а не кастомное решение от 3ей стороны, когда индекс -1 может значит length-1, а может значить и совершенно иное, если кому-то захотелось.
              • 0
                Предлагалось, обсуждалось — не будет принято, т.к. backward compatibilities рулят развитием JS и основной «лейтмотив» — «не ломать WEB» (т.е. то, что уже написано). Некоторые вещи все равно будут необратимо сломаны, но это уже в зависимости от «migration tax», т.е. что достойно, чтобы поломать, а что не так и важно (как например отрицательные индексы, без которых, в принципе, можно прожить; но с ними слаще, да ;)).
                • 0
                  Единственное, почему мне видится полезным технология проксей и о чем было сказано, это песочницы. Но складывается ощущение, что эта функциональность становится параллельной концепции языка (т.е. сам язык тут ни при чём, что-то просто изолировано), поэтому, может быть, стоит её (песочницу) рассматривать отдельно как дополняющую технологию, не затрагивающую сам js и не нарушающую back-compatibility.
    • 0
      Именно поэтому я и создал свою удобную обертку для работы с объектами, где все эти хуки можно навешивать простыми присваиванием в рантайме. См. выше п. 2 «Метапрограммирование на уровне объекта». Такая модель отражает, например, подход Python (откуда ES много чего позаимствовал).

      Основная задача (по мнению TC-39), чтобы foo.get = 10 не означало, что ты «испортил» мета-get-хук. С другой стороны, почему «испортил», если ты сам за свои объектов и следишь — так и появляются обертки типа приведенных выше.

      P.S.: кстати, уже ES5 подход с их статическими методами, типа Object.getPrototypeOf, Object.keys и прочее тоже именно из-за разделения мета- и прикладного уровня. Мне в свое время (год назад) этот подход тоже не понравился, но что сделано — то сделано, и с точки зрения разделения мета-уровня и «чтобы не испортить» это осмысленное решение и в целом правильное. Хотя, повторю, синтаксически получается много «шума», отступов и т.д.

      P.S.[2]: ты еще не видел как будут value-proxy себя вести (это те, что для перегрузки операторов). Опять же — в Python это все на __magic__ методах, в ES куча синтаксического шума и телодвижений для этого. Причины — вновь разделение мета-уровня и оптимизация по памяти.
      • 0
        не шибко веская причина. скорее они просто знатные велосипедисты.
  • –1
    Хе… JS становится все больше и больше похожим на Python… Хорошо!
    • 0
      На самом деле он впитывает из всех по чуть-чуть, например Прокси пришли, по большому счету, из никому неизвестного AmbientTalk
      • 0
        … но лучше бы они впитывали Python :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.