Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   XDR Class — реализация классов с аксессорами для IE8 (https://javascript.ru/forum/project/37074-xdr-class-%E2%80%94-realizaciya-klassov-s-aksessorami-dlya-ie8.html)

FINoM 06.04.2013 21:40

XDR Class — реализация классов с аксессорами для IE8
 
Всем привет. Решил показать очередную имплементацию классов для JS, но с поддержкой геттеров и сеттеров с помощью Object.defineProperty, работающую в IE8. Идея очень проста: для нормальных браузеров использовать тривиальное наследование, которое можно найти в любом учебнике по JS, а для IE8 возвращать экземпляр XDomainRequest (или просто XDR), расшрияя нужными свойствами (то есть протоип менять не рекомендуется, список свойств должен быть статичным, как в работе devote).

Для тех, кто не в курсе, IE8 поддерживает Object.defineProperty для DOM объектов, XDR, в этом случае, наиболее привлекателен, так как его экземпляр имеет всего 7 свойств + 3 у прототипа.

Object.defineProperty для IE8 поддерживается кое-как. Более подробно можно прочесть в этой теме: http://javascript.ru/forum/offtopic/...rty-v-ie8.html

Поэтому я настойчиво рекомендую использовать в качестве элементов дескриптора только get и set.

Документации никакой нет, но появится после успешного завершения работы над фреймворком (превью которого я покажу чуть позже). Код выложил в репозиторий: https://github.com/finom/xdr-class/b...r/xdr-class.js (для того, чтоб проверить, просто выкачайте репозиторий и запустите index.html, сюда и на jsFiddle вставить код не могу из-за политик безопасности в IE)

Функция требует underscore (хотя его можно легко убрать).
A = Class({
    a1: 'Aa1',
    a2: 'Aa2',
    aMethod1: function(arg) {
		Object.defineProperty( this, 'x', {
			get: function() {
				return 'X Property'
			},
			set: function( v ) {
				console.log( 'Message from "x" setter, value is: ', v );
			}
		});
        console.log( 'A.Method1 call, arg is: ', arg )
    }
});

B = Class({
    'extends': A, // кавычки обязательны (для подсветки и чтоб не вызвать ошибки в IE), но 'extends' можно заменить на extend без кавычек
    b1: 'Bb1',
    b2: 'Bb2',
    a2: 'Ba2',
    constructor: function() {
        console.log( 'B.constructor call' );
    }
});

C = Class({
    'extends': B,
    c1: 'Cc1',
	b1: 'Cb1',
    constructor: function() {
        this.aMethod1( 'BLAH' );
		this.x = 'New X Value';
        B.call( this, arguments );
        console.log( 'Properties: ', this.c1, this.b1, this.b2, this.a1, this.a2 );
    },
    aMethod1: function() {
        console.log( 'C.method call' );
        B.prototype.aMethod1.apply( this, arguments );
    }
});

new C;


Из todo: добавить метод .instanceOf и что-то придумать с вызовом родительских методов (B.prototype.aMethod1.call( this ) немного надоел).

devote 06.04.2013 22:22

Цитата:

Сообщение от FINoM
а для IE8 возвращать экземпляр XDomainRequest (или просто XDR)

любопытно сколько каждый экзепляр отжирает памяти :)

FINoM 06.04.2013 22:28

Цитата:

Сообщение от devote
любопытно сколько каждый экзепляр отжирает памяти

хз, не замерял, но после создания миллиона инстанцов, комп не глючил. Понимаю, что это не объективная оценка, чуть позже проверю (и скорость по сравнению с Хромом, и память).

FINoM 06.04.2013 23:21

Сделал небольшую пробу по памяти: перед запуском ИЕ отжирал 30 МБ, на пике съедал 120 МБ, после окончания работы скрипта таск менеджер показал 100МБ. То есть миллион инстанцов отжирают 70—90МБ. Да, опять же, это не глубокое тестирование:
console.log = function() {};
console.time( '1' );
for(var i = 0; i < 1000000; i++) new C;
console.timeEnd('1');

Небольшой замер скорости:
В Хроме 26195.000ms (просто для сравнения)
В ИЕ 10 15683ms
В ИЕ 8 112826ms (всего лишь в ~4 раза медленнее Хрома. Еще раз отмечу, в Хроме используется тривиальное наследование).

FINoM 07.04.2013 01:16

Дальше можно делать всё, что душе угодно, например:
MainClass = Class({
  define: function( key, descriptor ) {
    Object.defineProperty( this, key, descriptor );
  }
});

MyClass = Class({
  'extend': MainClass,
  constructor: function() {
    this.define( 'prop', { get: function() { return 'bla'; }, set: function(v) {alert( v )}} );
  }
});

melky 07.04.2013 08:46

Цитата:

Сообщение от FINoM (Сообщение 244585)
Сделал небольшую пробу по памяти: перед запуском ИЕ отжирал 30 МБ, на пике съедал 120 МБ, после окончания работы скрипта таск менеджер показал 100МБ. То есть миллион инстанцов отжирают 70—90МБ. Да, опять же, это не глубокое тестирование:
console.log = function() {};
console.time( '1' );
for(var i = 0; i < 1000000; i++) new C;
console.timeEnd('1');

Небольшой замер скорости:
В Хроме 26195.000ms (просто для сравнения)
В ИЕ 10 15683ms
В ИЕ 8 112826ms (всего лишь в ~4 раза медленнее Хрома. Еще раз отмечу, в Хроме используется тривиальное наследование).

похоже, что в тесте экземпляры сразу убиваются сборщиком мусора, и в следствие память освобождается. а остаточные 90 мб - это "удалить на потом", т.к. у IE свой тупанутый сборщик мусора, который может очистить память, когда фаза луны соблаговолит - либо через секунду, либо через минуту.

а если собрать экземпляры в массив?

tenshi 07.04.2013 09:36

почему нет стирания XDR свойств?

devote 07.04.2013 11:22

Цитата:

Сообщение от tenshi
почему нет стирания XDR свойств?

а ты знаешь способ как это сделать в ИЕ

tenshi 07.04.2013 16:35

ну так через defineProperty не работает?

devote 07.04.2013 17:12

Цитата:

Сообщение от tenshi
ну так через defineProperty не работает?

ну если бы работало, не было бы фраз на тему того что там есть свойства. И при всем этом FINoM, дал ссылку на предыдущее обсуждение http://javascript.ru/forum/offtopic/...rty-v-ie8.html где именно эта проблема и обсуждалась

tenshi 07.04.2013 20:46

а, не думал, что там про это, тогда вопрос исчерпан ^^'

FINoM 08.04.2013 06:40

Запилил метод .instsanceOf
a.instanceOf( B );
(думаю, можно не описывать, что он делает).

Сделал небольшую замену для MyClass.prototype.method.apply( this, arguments );

Она не так хорошо выглядит, как у devote (я так понимаю, ты как-то декорировал методы?), зато реализация выглядит сильно проще. Моего ума пока не хватило сделать нормальный, не зацикливающийся parent (this.parent.method()).

A = Class({
   method: function() {}
});

B = Class({
  method: function() {
    B.parent.method( this );
  }
});


Чтобы вызвать метод родительского класса пишем имя текущего класса (указывать в методах название родительского класса — совсем некрасиво, но своего — уже лучше), свойство parent, имя метода, аргументы:
1. this - обязательно
2. arguments или любые аргументы
Можно написать так:
B.parent.method( this, arguments );

а можно и так:
B.parent.method( this, 1, 2, 3, 4, 5 );

FINoM 08.04.2013 06:52

Цитата:

Сообщение от melky
а если собрать экземпляры в массив?

console.log = function() {}; x = {}; console.time( '1' ); for( var i = 0; i < 100000; i++ ) x[ i ] = new C; console.timeEnd( '1' )
Миллион инстанцов сожрали около гига, сто тысяч сожрали, ожидаемо, 100 мегабайт. Не знаю, хорошо это или плохо, но совет очевиден: не создавайте больше 100 тысяч инстанцов, 100 мегабайт для осла не так уж и много :)

FINoM 08.04.2013 06:53

Цитата:

Сообщение от tenshi
почему нет стирания XDR свойств?

На них можно повесить геттер и вернуть undefined, но они всё равно будут перечисляться.

tenshi 08.04.2013 10:10

FINoM, ну, лучше уж только перечисляться, чем еще и что-то возвращать

как тебе такой вариант?

var B= Class(function( proto ){
    proto.define( 'foo', function( ){ return 1 } )
    proto.override( 'bar', function( return 2 } )
})


define - создаёт свойство, но бросает исключение, если такое уже есть
override - перегружает свойство предка, но бросает исключение, если такого нет

ну и расширять тут можно:
defineGetter, overrideGetter
defineSetter, overrideSetter
и так далее

FINoM 08.04.2013 16:09

Цитата:

Сообщение от tenshi
как тебе такой вариант?

Предполагатся, что разработчик сделает всё что желает. В моей хернюшке только самый необходимый минимум. Если программист хочет расширить количество методов, он просто создаёт свой главный класс, от которого наследуются другие:
MainClass = Class({
  define: function() {/* ... */},
  override: function() {/* ... */}
  /* ... */
});

A = Class({
  'extends': MainClass 
   /* ... */
});

То же самое касается и этого:
Цитата:

Сообщение от tenshi
ну, лучше уж только перечисляться, чем еще и что-то возвращать

Навешивание аксессоров занимает, пускай не большое, количество ресурсов. Если разработчик хочет, может сам это сделать. Но если ему это не нужно, то и незачем тратить процессорное время и память (пускай и мизерные количества).
MainClass = Class({
  initMainClass: function() {
    // можно перечислить все семь свойств и вернуть в геттере undefined,
    // а можно и перечислить всего три (которые не null) и вернуть null
    var badProps = [ 'contentType', 'timeout', 'responseText' ];
    for(var i = 0; i < badProps.length; i++ ) {
      Object.defineProperty( this, badProps[ i ], { get: function() { return null; }});
    }
  }
})

tenshi 08.04.2013 17:19

не, ты не понял, у меня эти методы расширяют прототип, а не экземпляр

а пример, не о том, вот правильный:

var A= Class(function( proto ){
    proto.define( 'foo', function( ){ return 1 } )
})
var B= Class(function( proto ){
    proto.mixin( A )
    proto.override( 'foo', function( base, 1 ){
        return base() + 1
    } )
})

FINoM 09.04.2013 22:50

Цитата:

Сообщение от tenshi
не, ты не понял, у меня эти методы расширяют прототип, а не экземпляр

Прототип не наследуется, а копируется в XDR. Хотя я всё равно не очень понял, что ты хочешь этим самым получить.

tenshi 10.04.2013 12:12

вот это:
proto.override( 'foo', function( base, 1 ){
        return base() + 1
    } )

перегруженный метод передаётся в качестве первого параметра

FINoM 11.04.2013 01:32

Цитата:

Сообщение от tenshi
перегруженный метод передаётся в качестве первого параметра

Ты имеешь в виду вариант, как сделать parent?

tenshi 12.04.2013 10:32

именно


Часовой пояс GMT +3, время: 15:54.