2014-10-18 12 views
6

(Załóżmy, że istnieje dobry powód, aby tego chcieć.) Zobacz koniec pytania, jeśli chcesz przeczytać dobry powód .)Uzyskaj taki sam wynik, jak dla pętli for..in, bez pętli for..in

Chciałbym uzyskać ten sam wynik co pętla for in, ale bez przy użyciu tej konstrukcji językowej. Przez wynik rozumiem tylko tablicę nazw właściwości (nie muszę odtwarzać zachowania, które by się stało, gdybym zmodyfikował obiekt podczas iteracji nad nim).

Aby umieścić pytanie do kodu, chciałbym wdrożyć tę funkcję bez for in:

function getPropertiesOf(obj) { 
    var props = []; 
    for (var prop in obj) 
    props.push(prop); 
    return props; 
} 

z mojego zrozumienia specyfikacji około the for in statement i the Object.keys method ECMAScript 5.1, wydaje się następujące realizacja powinny być poprawne :

function getPropertiesOf(obj) { 
    var props = []; 
    var alreadySeen = {}; 

    // Handle primitive types 
    if (obj === null || obj === undefined) 
    return props; 
    obj = Object(obj); 

    // For each object in the prototype chain: 
    while (obj !== null) { 
    // Add own enumerable properties that have not been seen yet 
    var enumProps = Object.keys(obj); 
    for (var i = 0; i < enumProps.length; i++) { 
     var prop = enumProps[i]; 
     if (!alreadySeen[prop]) 
     props.push(prop); 
    } 

    // Add all own properties (including non-enumerable ones) 
    // in the alreadySeen set. 
    var allProps = Object.getOwnPropertyNames(obj); 
    for (var i = 0; i < allProps.length; i++) 
     alreadySeen[allProps[i]] = true; 

    // Continue with the object's prototype 
    obj = Object.getPrototypeOf(obj); 
    } 

    return props; 
} 

Chodzi o to, aby chodzić wyraźnie łańcuch prototypów i używać Object.keys uzyskać własne właściwości w każdym obiekcie łańcucha. Wykluczamy nazwy obiektów już widziane w poprzednich obiektach w łańcuchu, w tym także wtedy, gdy były postrzegane jako nieprzeliczalne. Sposób ten powinien również przestrzegać the additional guarantee mentioned on MDN:

Sposób Object.keys() zwraca tablicę własnych właściwości przeliczalnych danego obiektu, w takiej samej kolejności jak przewidziany przez for...in pętli [...].

(podkreślenie moje)

grałem trochę z tej realizacji, a ja nie byłem w stanie go złamać.

Więc pytanie:

Czy moja analiza jest prawidłowy? Czy mogę przeoczyć szczegół specyfikacji, która spowodowałaby, że ta implementacja byłaby niepoprawna?

Czy znasz inny sposób, aby to zrobić, który byłby zgodny z konkretną kolejnością implementacji for in we wszystkich przypadkach?

Uwagi:

  • Nie dbam o ECMAScript < 5.1.
  • Nie dbam o wydajność (może to być katastrofalne).

Edycja: aby zaspokoić ciekawość @ lexicore'S (ale naprawdę nie część pytania), to dobry powód jest następujący. Opracowuję kompilator do JavaScript (od Scala), a konstrukcja językowa for in nie jest częścią rzeczy, które chcę wspierać bezpośrednio w pośredniej reprezentacji mojego kompilatora. Zamiast tego mam "wbudowaną" funkcję getPropertiesOf, która jest zasadniczo tym, co pokazuję jako pierwszy przykład. Próbuję pozbyć się jak największej liczby wbudowanych, zastępując je implementacjami "przestrzeni użytkownika" (napisanymi w Scali).Aby uzyskać wydajność, nadal mam optymalizator, który czasami "intrinsifies" niektórych metod, i w tym przypadku byłoby to intrinsify getPropertiesOf z wydajnym pierwszym wdrożeniem. Jednak aby pośrednia reprezentacja była dobra i działała, gdy optymalizator jest wyłączony, potrzebuję prawdziwej implementacji tej funkcji, bez względu na koszt wydajności, o ile jest on poprawny. I w tym przypadku nie mogę użyć for in, ponieważ mój IR nie może reprezentować tego konstruktu (ale mogę wywoływać dowolne funkcje JavaScript na dowolnych obiektach, np. Object.keys).

+0

'(obj === null || obj === undefined)' jest takie samo jak 'obj == null'. Czy z ciekawości jest jakiś powód, dla którego używasz bardziej gadatliwej postaci? – gilly3

+0

@ gilly3 Ponieważ '==' jest złe. Dla mniej upartej odpowiedzi: z tego samego powodu, dla którego nie mogę użyć 'for in': IR intencjonalnie nie obsługuje' == '(ponieważ jest złe ...). – sjrd

+0

Interesujące. Czuję, że '==' jest faktycznie * siłą * języka, który powinien być używany rozważnie. – gilly3

Odpowiedz

3

Z punktu specyfikacji widzenia, twój analiza poprawne tylko przy założeniu, że dana implementacja określa konkretną kolejność wyliczenia dla za-w oświadczeniu:

Jeśli implementacja określa konkretną kolejność wyliczenia dla dla instrukcji wejścia-in, ta sama kolejność wyliczeń musi być użyta w kroku 5 tego algorytmu.

Zobacz ostatnie zdanie here.

Tak więc, jeśli implementacja nie zapewnia dostarczenia takiej określonej kolejności, wówczas dla wejścia i dla Object.keys mogą być zwracane różne rzeczy. Cóż, w tym przypadku nawet dwa różne dodatki mogą zwrócić różne rzeczy.

Całkiem interesująca, cała historia ogranicza się do pytania, czy dwie insiny da takie same wyniki, jeśli obiekt nie został zmieniony. Ponieważ, jeśli tak nie jest, to w jaki sposób możesz przetestować "ten sam"?

W praktyce najprawdopodobniej będzie to prawdą, ale mogę też łatwo wyobrazić sobie, że obiekt mógłby dynamicznie odbudować swoją wewnętrzną strukturę, między wezwaniami. Na przykład, jeśli pewna właściwość jest dostępna bardzo często, implementacja może zrestrukturyzować tabelę mieszającą, aby dostęp do tej właściwości był bardziej wydajny. O ile widzę, specyfikacja nie zabrania tego. A także nie jest to takie nierozsądne.

Więc odpowiedź na twoje pytanie brzmi: nie, nie ma gwarancji zgodnie ze specyfikacją, ale nadal będzie prawdopodobnie działać w praktyce.

Aktualizacja

myślę, że jest inny problem. Gdzie jest zdefiniowany, jaka jest kolejność właściwości między członkami prototypowego łańcucha? Możesz uzyskać właściwości "własne" we właściwej kolejności, ale czy są scalone dokładnie tak, jak robisz to? Na przykład, dlaczego najpierw są to właściwości podrzędne i następne?

+0

Racja, rozumiem to.Ale jeśli podstawowa implementacja nie * definiuje * określonej kolejności, to moja funkcja spełnia także specyfikację, prawda? Ponieważ nie ma nic do spełnienia pod względem porządku, wszelkie zamówienia, w tym te, których używam, są w porządku; wymóg jest zasadniczo nieważny. – sjrd

+0

@sjrd Masz rację, po prostu nie ma sposobu, aby udowodnić, że jest inaczej. Które wymaganie jest dokładnie nieważne? – lexicore

+2

Wymóg zwracania nazw w tej samej kolejności, co zrobiłby wpis "za". Jeśli podstawowa realizacja nie gwarantuje żadnego rodzaju zamówienia, to wymóg ten jest nieważny. – sjrd