Борьба с 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" Можно поиграться в консоли. |
Так-с, почти всё поборол. Если заставить свойство с аццессорами перечисляться через for..in, то, при использовании hasOwnProperty (а его ведь все используют, не так ли?) может даже показаться, что мы работаем с обычным объектом.
Не пинайте за говнокод, это лишь прототип. |
Цитата:
Object.prototype.hasOwnProperty.call( obj, prop );уж простите но привычка))) |
Поддерживать всякое убожество аля IE6789, в бесплодной попытке продлить ему жизнь?! Всё равно оно никогда не дорастет до нынешних FF, Chrome, Opera. Во фронтэнде использовать новые фичи клепая миллион заплаток?
ИМХО я пишу в двух стилях: совместимый(библиотека+) и нормальный код. А так идея конечно интересная, буду ждать продолжения. |
Цитата:
|
Цитата:
|
Цитата:
(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 ); |
Запустил код из поста в хроме, получил ошибку. Почему при создании объекта через Object.create, метод hasOwnProperty отсутствует? Мы ведь наследуем прототип Object.
x = Object.create( null ) x.hasOwnProperty( 'a' ) |
FINoM, можно комментарий?
Цитата:
Цитата:
|
nerv_, этот код сперт с MDN.
|
FINoM, это я понял. Ты мне скажи, зачем конкретно та строчка :)
|
Цитата:
|
Кажется вспомнил: он вроде как превращает число в int32
|
Цитата:
Цитата:
Это обычная битовая операция, сдвиг битов в право... Но в данном случае ничего не двигает, ибо сдвиг указан в ноль, но тут именно что и делает просто переводит нечто в число... Это сделано для случая если свойство length не существует, но нужно вернуть число.. пример: var a = {} var b = {length: 2} alert(a.length >>> 0); // получим ноль alert(b.length >>> 0); // получим 2 |
|
Цитата:
Лучше скажи чего эта хрень не пашет: http://javascript.ru/forum/offtopic/...tml#post202667 |
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 |
Во, блин, перепутал, затем поленился снова прочесть :D
|
Цитата:
|
Цитата:
|
Enumerable с сеттером можно реализовать через onpropertychange (придется вставлять элемент в документ), но, как реализовать геттер — задача.
|
Цитата:
|
Цитата:
|
Цитата:
|
devote, да, ты прав, вставил в боди.
|
Цитата:
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 штук лишних. |
Цитата:
А заодно еще раз поудалять свойтсва. |
Цитата:
|
Жаль onpropertychange не пашет.
|
Буду 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 |
Очень жаль, что нельзя так сделать:
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 |
Так-с, продолжаю раскопки. 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 конструктор? |
Судя по всему таки нет никакого конструктора, а такая штука не катит и не наследует свойства:
F = function() {} F.prototype = XDomainRequest.prototype; f = new F; |
Продолжайте, продолжайте, весьма увлекательно.))
|
Совсем немного отрефакторил код. Теперь итератором служит кастомный метод 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" |
Чего еще не хватает:
1. Копирования не-перечисляемых свойств прототипа в Object.create (хрен его знает, как это сделать). Для Object.prototype их можно прописать вручную. 2. Удалять дескриптор при удалении свойства (тоже хер знает как). |
И да, хотел потом на основе этой куйни запилить небольшую библиотеку с наследованием конструкторов, но хрен его знает, как наследовать XDomainRequest.
|
FINoM,
мда... много хочешь от ИЕ, радуйся хотя бы этому... Я подкинул небольшую идейку с реквестом, а тестить мне влом, да и времени нет. Пробуй, ищи, авось чего и найдешь))) Будешь первооткрывателем))) |
Цитата:
|
Цитата:
|
Часовой пояс GMT +3, время: 01:37. |