Pull to refresh

Comments 39

Интересная вещь. Спасибо. Особенно порадовал профайлер.
Молодец, что переводишь статьи / создаешь русскоязычные версии. Чтобы русское 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__ — нужна функция-активатор, которая является значением любого свойства).
Спасибо за интересные дополнения, которые можно реально применять. Часть из них я видел в твоем комментарии в блоге Brendan Eich, но поленился дописать. Стараюсь продвигать, но как видно, в силу тех или иных причин, нововведения пока не особо интересны. Твой es-laboratory я просмотрю на днях.
На самом деле, скажу субъективно, оно достаточно тяжело. Если не разбираться с этим на высоком уровне — не осилишь. И количество плюсов за такую сильную статью это только доказывают. Высококлассных Javascript-прогеров, в т.ч. на Хабре — единицы.

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

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

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

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

и вот этот вызов уже должен быть полностью эквивалентен foo.bar( 1, 2, 3 )
Да, похожий сахар будет. И называется это 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 — здесь.
выглядит как-то слишком костыльно. передать можно только переменную, нельзя заинплейсить какое-нибудь выражение. но таки да, с такой фичей использовать obj.func.apply будет бессмысленно кроме случаев где надо именно переопределить контекст. мой вариант как-то удобней, наглядней и расширяемей

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

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

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

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

пока что мы видим робкие попытки решить частные случаи, вместо того чтобы решать проблему комплексно. язык всё усложняется и усложняется, вместо того, чтобы упрощаться и унифицироваться.
Второе, функции-прокси можно использовать для создания псевдо-классов, сущности которых функции(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');
Уху… все жду проксей чтобы создать класс Nothing, который всегда будет возвращать сам себя вместо вываливания object property/method doesn't exist и заменить-таки null.
var Nothing = function () { return Nothing; }

var NewNothing = new new new new new Nothing();

console.log(NewNothing == Nothing); // true
Не, смысл был в том, чтобы, к примеру

a = something(); // если метод вываливается с ошибкой, то возвращается Nothing

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


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

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

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

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

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

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

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

Articles