Javascript-форум (https://javascript.ru/forum/)
-   Оффтопик (https://javascript.ru/forum/offtopic/)
-   -   Борьба с Object.defineProperty в IE8 (https://javascript.ru/forum/offtopic/31305-borba-s-object-defineproperty-v-ie8.html)

FINoM 02.09.2012 20:01

Борьба с Object.defineProperty в IE8
 
Мне сегодня пришла такая мысль: почему мы не хотим использовать ноды в качестве объектов? Потому что в объекте будут иметься левые свойства. Чем это чревато?
1. При перечислении с помощью for..in будем получать кучу левых свойств.
2. При сериализации стандартными методами JSON будем получать undefined.

Первое решается созданием функции-итератора (например, какой-нибудь each), второе — написанием собственной функции для сериализации.

Что было выяснено, во время экспериментов:
1. Если дескриптор содержит value, enumerable не может равняться false.
2. И наоборот, если дескриптор содержит get или set, enumerable: true не работает.

3. configurable: false не работает при любом раскладе.
4. writable: false не работает при наличии value (ну и, конечно с аццессорами, так как не может работать по спецификации, хотя к чему я тут упоминаю спецификации...).

Удалось побороть всё, кроме второго (такое свойство не попадает в for..in, но работает с функцией each).

Вот, что сделал на скорую руку (см., начиная с 154 строки):

http://javascript.ru/forum/offtopic/...tml#post202743 — версия по-красивее
( function() {
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
			"use strict";
			if (this == null) {
				throw new TypeError();
			}
			var t = Object(this);
			var len = t.length >>> 0;
			if (len === 0) {
				return -1;
			}
			var n = 0;
			if (arguments.length > 1) {
				n = Number(arguments[1]);
				if (n != n) { // shortcut for verifying if it's NaN
					n = 0;
				} else if (n != 0 && n != Infinity && n != -Infinity) {
					n = (n > 0 || -1) * Math.floor(Math.abs(n));
				}
			}
			if (n >= len) {
				return -1;
			}
			var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
			for (; k < len; k++) {
				if (k in t && t[k] === searchElement) {
					return k;
				}
			}
			return -1;
		}
	}
	
	var extend = function( o1, o2 ) {
		for( var i in o2 ) {
			o1[ i ] = o2[ i ];
		}
	};
	
	
	
	var initIeObject = function() {
		JSON._stringify = JSON.stringify;
		JSON.stringify = function( object ) {
			var props = object._enumerableProps,	
				o;
			if( props ) {
				o = {}
				for( var i = 0; i < props.length; i++ ) {
					o[ props[ i ] ] = object[ props[ i ] ];
				}
			} else {
				o = object;
			}
			return JSON._stringify( o );
		};
		Object.defineProperties = function( object, descriptors ) {
			for( var key in descriptors ) {
				Object.defineProperty( object, key, descriptors[ key ] );
			}
		};
		Object._defineProperty = Object.defineProperty;
		Object.defineProperty = function( object, key, descriptor ) {
			var isConfugurable = descriptor.configurable !== undefined ? descriptor.configurable : false ;
			
			object._enumerableProps = object._enumerableProps || [];
			object._notEnumerableProps = object._notEnumerableProps || [];
			
			if( object._notConfigutable && ~object._notConfigutable.indexOf( key ) ) {
				throw TypeError( 'Cannot redefine property: ' + key );
			}
			
			
			delete descriptor.configurable;
			if( descriptor.enumerable === false ) {
				if( 'value' in descriptor ) {
					// фикс
					object[ '_' + key ] = descriptor.value;
					Object._defineProperty( object, key, {
						get: function() {
							return this[ '_' + key ];
						},
						set: function( value ) {
							this[ '_' + key ] = value;
						}
					});
					if( descriptor.writable === false ) {
						Object._defineProperty( object, key, {
							get: function() {
								return descriptor.value;
							}
						});
					}
				} else {
					Object._defineProperty( object, key, descriptor );
				}
				object._notEnumerableProps.push( key );
				
			} else {
				if( 'get' in descriptor || 'set' in descriptor ) {
					// что здесь можно сделать, чтоб свойство перечислялось с помощью for..in?
					descriptor.enumerable === false;
					Object._defineProperty( object, key, descriptor );
				} else {
					Object._defineProperty( object, key, descriptor );
				}
				
				object._enumerableProps.push( key );
			}
			
			if( isConfugurable === false ) {
				object._notConfigutable = object._notConfigutable || [];
				object._notConfigutable.push( key );
			}
		};
		
		Object.getOwnPropertyNames = function( object ) {
			return object._enumerableProps.concat( object._notEnumerableProps );
		};
		
		Object.keys = function( object ) {
			return object._enumerableProps;
		};
		
		Object.create = function( prototype, descriptors ) {
			var object = document.createComment( '' );
			document.body.appendChild( object );
			prototype && extend( object, prototype );
			Object.defineProperties( object, descriptors );
			object.hasOwnProperty = function( key ) { // можно эту штуку использовать в for..in, но свойства с аццессорами перечисляться не будут
				return !!~Object.getOwnPropertyNames( object ).indexOf( key );
			}
			return object;
		}
	}
	if( !Object.create && Object.defineProperty ) { 
		initIeObject();
	}
})();

var each = function( object, callback ) {
		var props = object._enumerableProps;
		if( props ) {
			for( var i = 0; i < props.length; i++ ) {
				callback.call( object[ props[ i ] ], props[ i ], object[ props[ i ] ]  );
			}
		} else {
			for( var i in object ) if( object.hasOwnProperty( i ) ) {
				callback.call( object[ i ], i, object[ i ] );
			}
		}
	}

x = Object.create( Object.prototype, {a: { value: 1, enumerable: true } });
console.log( x.a );
for( var i in x ) if( x.hasOwnProperty( i ) ){ console.log( i ) };


//Журнал: 1 
//Журнал: a 

y = Object.create( Object.prototype );
Object.defineProperties( y, {
	b: {
		get: function() {
			return 2;
		},
		set: function( value ) {
			alert( 'y.b setter, value = ' + value )
		},
		configurable: true
	},
	c: {
		get: function() {
			return 3;
		},
		set: function( value ) {
			alert( 'y.c setter, value = ' + value )
		}
	},
	d: {
		value: 4,
		configurable: false
	},
	e: {
		value: 5,
		enumerable: false,
		writable: false
	}
});

each( y, function( key, value ) {
	console.log( key,  ' ', value )
});

//Журнал: b 2 
//Журнал: c 3 
//Журнал: d 4 

console.log( JSON.stringify( y ) );
//Журнал: {"b":2,"c":3,"d":4} 

Object.defineProperty( y, 'b', { get: function() { return 'changed B' }});
console.log( y.b );
//Журнал: changed B 

console.log( 'e ', y.e );
//Журнал: e 5 
y.e = 123;
console.log( 'e ', y.e );

//Журнал: e 5 
Object.defineProperty( y, 'd', { get: function() { return 'changed D' }});
console.log( y.d );
//Error "Cannot redefine property: d"


Можно поиграться в консоли.

FINoM 02.09.2012 20:38

Так-с, почти всё поборол. Если заставить свойство с аццессорами перечисляться через for..in, то, при использовании hasOwnProperty (а его ведь все используют, не так ли?) может даже показаться, что мы работаем с обычным объектом.

Не пинайте за говнокод, это лишь прототип.

devote 02.09.2012 20:45

Цитата:

Сообщение от FINoM
hasOwnProperty (а его ведь все используют, не так ли?)

все верно, но я обычно юзаю его так:
Object.prototype.hasOwnProperty.call( obj, prop );
уж простите но привычка)))

Gozar 02.09.2012 20:51

Поддерживать всякое убожество аля IE6789, в бесплодной попытке продлить ему жизнь?! Всё равно оно никогда не дорастет до нынешних FF, Chrome, Opera. Во фронтэнде использовать новые фичи клепая миллион заплаток?

ИМХО я пишу в двух стилях: совместимый(библиотека+) и нормальный код.

А так идея конечно интересная, буду ждать продолжения.

FINoM 02.09.2012 20:51

Цитата:

Сообщение от devote
все верно, но я обычно юзаю его так

Эм, а смысл?

FINoM 02.09.2012 20:53

Цитата:

Сообщение от Gozar
Поддерживать всякое убожество аля IE6789, в бесплодной попытке продлить ему жизнь?!

Лично у меня задача перенести через несколько месяцев проект, в котором очень активно используется defineProperty с гетерами и сеттерами. Как это сделать, не переписывая полностью, от начала до конца, я не знаю.

devote 02.09.2012 20:55

Цитата:

Сообщение от FINoM
Эм, а смысл?

что бы GCC сжал лучшее, объявляю один раз:
(function( global ){
    var hasOwnProperty = Object.prototype.hasOwnProperty;

    // много кода

    // и где то
    for( ... ) {
        if ( hasOwnProperty.call( o, prop ) ) {
            // ....
        }
    }

    // и еще где то
    for( ... ) {
        if ( hasOwnProperty.call( o, prop ) ) {
            // ....
        }
    }

})( window );
GCC такой код сожмет лучшее превратив его примерно в:
(function( g ){
    var h = Object.prototype.hasOwnProperty;

    // много кода

    // и где то
    for( ... ) {
        if ( h.call( o, p ) ) {
            // ....
        }
    }

    // и еще где то
    for( ... ) {
        if ( h.call( o, p ) ) {
            // ....
        }
    }

})( window );

FINoM 02.09.2012 21:19

Запустил код из поста в хроме, получил ошибку. Почему при создании объекта через Object.create, метод hasOwnProperty отсутствует? Мы ведь наследуем прототип Object.
x = Object.create( null )
x.hasOwnProperty( 'a' )

nerv_ 02.09.2012 21:25

FINoM, можно комментарий?
Цитата:

Сообщение от FINoM
t.length >>> 0;

видел где-то, но не помню, что это (кажется какой-то хак)

Цитата:

Сообщение от Gozar
Поддерживать всякое убожество аля IE6789, в бесплодной попытке продлить ему жизнь?! Всё равно оно никогда не дорастет до нынешних FF, Chrome, Opera. Во фронтэнде использовать новые фичи клепая миллион заплаток?

мое мнение: лучше их убить побыстрей

FINoM 02.09.2012 21:27

nerv_, этот код сперт с MDN.

nerv_ 02.09.2012 21:29

FINoM, это я понял. Ты мне скажи, зачем конкретно та строчка :)

FINoM 02.09.2012 21:31

Цитата:

Сообщение от nerv_
Ты мне скажи, зачем конкретно та строчка

Не в курсе, лень гуглить.

nerv_ 02.09.2012 21:32

Кажется вспомнил: он вроде как превращает число в int32

devote 02.09.2012 21:46

Цитата:

Сообщение от nerv_
видел где-то, но не помню, что это (кажется какой-то хак)

Цитата:

Сообщение от FINoM
nerv_, этот код сперт с MDN.

мда ответ исчерпывающий)))

Это обычная битовая операция, сдвиг битов в право... Но в данном случае ничего не двигает, ибо сдвиг указан в ноль, но тут именно что и делает просто переводит нечто в число... Это сделано для случая если свойство length не существует, но нужно вернуть число..
пример:
var a = {}
var b = {length: 2}
alert(a.length >>> 0); // получим ноль
alert(b.length >>> 0); // получим 2

nerv_ 02.09.2012 21:51

нашел

devote, спасибо

FINoM, тоже спасибо :)

FINoM 02.09.2012 21:58

Цитата:

Сообщение от nerv_
FINoM, тоже спасибо

Хэх, не за что.
Лучше скажи чего эта хрень не пашет: http://javascript.ru/forum/offtopic/...tml#post202667

nerv_ 02.09.2012 22:17

FINoM, скажу
// create an object with null as prototype
o = Object.create( null );
alert( o.__proto__ );

// create an object with object as prototype
o = Object.create( Object.prototype );
alert( o.__proto__ );

Object.create

FINoM 02.09.2012 22:20

Во, блин, перепутал, затем поленился снова прочесть :D

devote 02.09.2012 22:20

Цитата:

Сообщение от FINoM
Лучше скажи чего эта хрень не пашет

Object.create создает объект унаследованный от первого параметра, но если первым параметром стоит null то создается совершенно пустой объект ни от чего не унаследованный.

FINoM 02.09.2012 22:24

Цитата:

Сообщение от FINoM
И наоборот, если дескриптор содержит get или set, enumerable: true не работает.

Господа, присоединяйтесь к поиску решения. Только этот момент отделяет от более-менее полноценной работы с defineProperty в IE8.

FINoM 02.09.2012 23:15

Enumerable с сеттером можно реализовать через onpropertychange (придется вставлять элемент в документ), но, как реализовать геттер — задача.

devote 02.09.2012 23:35

Цитата:

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

я вообще удивлен что у тебя аксессоры работают на не вставленном элементе в DOM объект... Ты точно уверен что вставлять его в DOM не надо? потому что насколько я помню, от defineProperty в ИЕ8 мало толку если элемент не вставлен в DOM

FINoM 02.09.2012 23:37

Цитата:

Сообщение от devote
я вообще удивлен что у тебя аксессоры работают на не вставленном элементе в DOM объект... Ты точно уверен что вставлять его в DOM не надо? потому что насколько я помню, от defineProperty в ИЕ8 мало толку если элемент не вставлен в DOM

Работает без вставки в ие9, режиме ие8. Надо еще затестить в настоящем ие8, но, думаю, это не принципиально.

devote 02.09.2012 23:38

Цитата:

Сообщение от FINoM
Надо еще затестить в настоящем ие8, но, думаю, это не принципиально.

вот именно, затести... потому как эмуляция в ИЕ9 это совсем не ИЕ8

FINoM 02.09.2012 23:52

devote, да, ты прав, вставил в боди.

devote 03.09.2012 00:36

Цитата:

Сообщение от FINoM
devote, да, ты прав, вставил в боди.

забей на DOMElement юзай XMLHttpRequest
var s = new XMLHttpRequest;
Object.defineProperty( s, "testProp", {
    set: function( value ) {
        alert( "va: " + value );
    },
    get: function() {
        return "hehe";
    }
});

s.testProp = 1;
alert( s.testProp );
и лишних свойств меньше, их там всего 9 штук лишних.

FINoM 03.09.2012 01:06

Цитата:

Сообщение от devote
забей на DOMElement юзай XMLHttpRequest

Ого, клас, щас попробую.
А заодно еще раз поудалять свойтсва.

devote 03.09.2012 01:08

Цитата:

Сообщение от FINoM
А заодно еще раз поудалять свойтсва.

ну это вряд ли получится, но зато в DOM ничего вставлять не надо и лишних свойств не 50 как у элемента а всего 9 штук

FINoM 03.09.2012 01:59

Жаль onpropertychange не пашет.

FINoM 03.09.2012 02:20

Буду XDomainRequest юзать, он реже используется, поэтому меньше вероятности что-то поломать. Плюс там всего 7 свойств.

Пока что получилось только переопределить эти свойства:
var s = new XDomainRequest;

for( var i in s ) {
	Object.defineProperty( s, i, {get: function() {return undefined}, enumerable: false} );
}

Но они всё равно перечисляются, не зависимо от enumerable:
Журнал: contentType undefined 
Журнал: onerror undefined 
Журнал: timeout undefined 
Журнал: ontimeout undefined 
Журнал: onprogress undefined 
Журнал: responseText undefined 
Журнал: onload undefined

FINoM 03.09.2012 02:27

Очень жаль, что нельзя так сделать:
var s = new XDomainRequest;
F = function() {}
F.prototype = s;
f = new F;
Object.defineProperty( s, 'x', {get: function() {return 123}, set: function() { alert() }, enumerable: false} );

console.log( f.x ); // ← undefined

FINoM 03.09.2012 03:16

Так-с, продолжаю раскопки. XDomainRequest.prototype содержит всего три свойства и так же позволяет использовать дескрипторы:
var s = XDomainRequest.prototype;
Object.defineProperty( s, 'blahblah', {value: 1} );
for( var i in s ) {
	console.log( i );
}
console.log( s );
console.log( s.constructor );

Журнал: open 
Журнал: send 
Журнал: abort 
Журнал: blahblah 
Журнал: [Interface prototype object] 
Журнал: null
Как видно, свойство constructor === null. Мот кто в курсе, где нарыть это самый Interface prototype конструктор?

FINoM 03.09.2012 03:38

Судя по всему таки нет никакого конструктора, а такая штука не катит и не наследует свойства:
F = function() {}
F.prototype = XDomainRequest.prototype;
f = new F;

Aetae 03.09.2012 04:26

Продолжайте, продолжайте, весьма увлекательно.))

FINoM 03.09.2012 05:46

Совсем немного отрефакторил код. Теперь итератором служит кастомный метод Object.each, который работает для всех браузеров, у которых есть defineProperty (то есть целевых браузеров).

В итоге у нас есть методы:
Object.defineProperties
Object.getOwnPropertyNames
Object.keys
Object.create
Object.getOwnPropertyDescriptor
Object.each — нестандартный

for..in всё еще не рекомендуется использовать.
( function() {
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
			"use strict";
			if (this == null) {
				throw new TypeError();
			}
			var t = Object(this);
			var len = t.length >>> 0;
			if (len === 0) {
				return -1;
			}
			var n = 0;
			if (arguments.length > 1) {
				n = Number(arguments[1]);
				if (n != n) { // shortcut for verifying if it's NaN
					n = 0;
				} else if (n != 0 && n != Infinity && n != -Infinity) {
					n = (n > 0 || -1) * Math.floor(Math.abs(n));
				}
			}
			if (n >= len) {
				return -1;
			}
			var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
			for (; k < len; k++) {
				if (k in t && t[k] === searchElement) {
					return k;
				}
			}
			return -1;
		}
	}
	if( !Object.create && Object.defineProperty ) { 
		initIeObject();
	}
	
	
	function initIeObject() {
		var Constructor = XDomainRequest,
			defineProperty = Object.defineProperty,
			getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
			
		Constructor.prototype.hasOwnProperty = function( key ) {
			return !!~Object.getOwnPropertyNames( this ).indexOf( key );
		};
		Object.defineProperties = function( object, descriptors ) {
			for( var key in descriptors ) {
				Object.defineProperty( object, key, descriptors[ key ] );
			}
		};
		JSON._stringify = JSON.stringify;
		JSON.stringify = function( object ) {
			var props = object._settings_ && object._settings_.enumerable,	
				o;
			if( props ) {
				o = {};
				for( var i = 0; i < props.length; i++ ) {
					o[ props[ i ] ] = object[ props[ i ] ];
				}
			} else {
				o = object;
			}
			return JSON._stringify( o );
		};
		
		Object.defineProperty = function( object, key, descriptor ) {
			if( !( object instanceof Constructor ) ) return defineProperty( object, key, descriptor ); // if another dom object
			
			var isConfugurable = descriptor.configurable !== undefined ? descriptor.configurable : false,
				_settings_ = object._settings_ = object._settings_ || {
					enumerable: [],
					notEnumerable: [],
					notConfigurable: [],
					descriptors: {}
				};
			
			delete descriptor.configurable;
			
			_settings_.descriptors[ key ] = descriptor;
			
			if( ~_settings_.notConfigurable.indexOf( key ) ) {
				throw TypeError( 'Cannot redefine property: ' + key );
			}			
			
			if( descriptor.enumerable === false ) {
				if( 'value' in descriptor ) {
					object[ '___' + key ] = descriptor.value;
					defineProperty( object, key, {
						get: function() {
							return this[ '___' + key ];
						},
						set: function( value ) {
							this[ '___' + key ] = value;
						}
					});
					if( descriptor.writable === false ) {
						defineProperty( object, key, {
							get: function() {
								return descriptor.value;
							}
						});
					}
				} else {
					defineProperty( object, key, descriptor );
				}
				
				_settings_.notEnumerable.push( key );
				
			} else {
				if( 'get' in descriptor || 'set' in descriptor ) {
					// что здесь можно сделать, чтоб свойство перечислялось с помощью for..in?
					descriptor.enumerable === false;
				} 
				
				defineProperty( object, key, descriptor );
				
				_settings_.enumerable.push( key );
			}
			
			if( isConfugurable === false ) {
				_settings_.notConfigurable.push( key );
			}
			
			_settings_.descriptors[ key ].configurable = isConfugurable;
		};
		
		Object.getOwnPropertyNames = function( object ) {
			return object._settings_.enumerable.concat( object._settings_.notEnumerable );
		};
		
		Object.keys = function( object ) {
			return object._settings_.enumerable;
		};
		
		Object.create = function( prototype, descriptors ) {
			var object = new Constructor;
			
			prototype && extend( object, prototype );
			Object.defineProperties( object, descriptors );
			
			return object;
		};
		
		Object.getOwnPropertyDescriptor = function( object, key ) {
			if( !( object instanceof Constructor ) ) return getOwnPropertyDescriptor( object, key ); // if another dom object
			return object._settings_ && object._settings_.descriptors[ key ];
		};
	};
	
	function extend( o1, o2 ) {
		for( var i in o2 ) {
			o1[ i ] = o2[ i ];
		}
	}
	
	if( Object.defineProperty ) {
		Object.each = function( object, callback ) {
			var props = object._settings_ && object._settings_.enumerable;
			if( props ) {
				for( var i = 0; i < props.length; i++ ) {
					callback.call( object[ props[ i ] ], props[ i ], object[ props[ i ] ]  );
				}
			} else {
				for( var i in object ) if( object.hasOwnProperty( i ) ) {
					callback.call( object[ i ], i, object[ i ] );
				}
			}
		};
	}
})();

x = Object.create( Object.prototype, {a: { value: 1, enumerable: true } });
console.log( x.a );
for( var i in x ) if( x.hasOwnProperty( i ) ){ console.log( i ) };


//Журнал: 1 
//Журнал: a 

y = Object.create( Object.prototype );
Object.defineProperties( y, {
	b: {
		get: function() {
			return 2;
		},
		set: function( value ) {
			alert( 'y.b setter, value = ' + value )
		},
		configurable: true
	},
	c: {
		get: function() {
			return 3;
		},
		set: function( value ) {
			alert( 'y.c setter, value = ' + value )
		}
	},
	d: {
		value: 4,
		configurable: false
	},
	e: {
		value: 5,
		enumerable: false,
		writable: false
	}
});

Object.each( y, function( key, value ) {
	console.log( key,  ' ', value )
});

//Журнал: b 2 
//Журнал: c 3 
//Журнал: d 4 

console.log( JSON.stringify( y ) );
//Журнал: {"b":2,"c":3,"d":4} 

Object.defineProperty( y, 'b', { get: function() { return 'changed B' }});
console.log( y.b );
//Журнал: changed B 

console.log( 'e ', y.e );
//Журнал: e 5 
y.e = 123;
console.log( 'e ', y.e );

//Журнал: e 5 
Object.defineProperty( y, 'd', { get: function() { return 'changed D' }});
console.log( y.d );
//Error "Cannot redefine property: d"

FINoM 03.09.2012 06:09

Чего еще не хватает:
1. Копирования не-перечисляемых свойств прототипа в Object.create (хрен его знает, как это сделать). Для Object.prototype их можно прописать вручную.
2. Удалять дескриптор при удалении свойства (тоже хер знает как).

FINoM 03.09.2012 06:18

И да, хотел потом на основе этой куйни запилить небольшую библиотеку с наследованием конструкторов, но хрен его знает, как наследовать XDomainRequest.

devote 03.09.2012 06:58

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

FINoM 03.09.2012 15:38

Цитата:

Сообщение от devote
много хочешь от ИЕ, радуйся хотя бы этому...

Ну блин, элементарное наследование ведь должно работать. А, нет, майкрософт вперде.

Gozar 03.09.2012 16:18

Цитата:

Сообщение от FINoM
нет, майкрософт

:)


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