Javascript, obiekt klasy i foreach

0

Witam

Mam następujący problem, w tablicy o długości 3 elementów mam na różnych pozycjach (61,251,99) obiekty klasy zrobionej przez classy.js (popularny skrypt).

Teraz chciałbym przelecieć przez te wszystkie obiekty i coś z nimi zrobić, zrobiłem więc pętlę:

 
for (obj in engineFinalObjects) 
    {
        console.log(obj);
    }

Ale obj w tej pętli nie jest obiektem tylko pierwszym polem tego obiektu (WTF?)! W mojej klasie pierwszym polem jest "id" i zmienna obj zawiera właśnie to pole zamiast odniesienia do całej klasy, nie moge się dostać do innych pól.

Czy robię coś nie tak?

EDIT::
Sorry nie zauważyłem, zmienna obj zawiera pozycję elementu w tablicy. Problem nieaktualny.

1

NIE UŻYWAJ pętli for-in (nie foreach! JS nie ma obecnie pętli foreach!) do iterowania po tablicach. Użyj zwykłej pętli for.

for-in służy do iterowania po własnościach obiektu. Jak zauważyłeś, zmienna iterująca to kolejne nazwy własności (klucze) obiektu, a nie kolejne wartości. Język nie gwarantuje (!!!!) kolejności iterowania w pętli for-in. Specyfikacja mówi na ten temat:

"The mechanics and order of enumerating the properties [...] is not specified."
( http://es5.github.com/#x12.6.4 )

W praktyce, przeglądarki iterują zwykle po kolei, ale nie powinno się na tym polegać. Kolejność nie jest gwarantowana i w IE faktycznie bywają problemy, gdy z jakiegoś obiektu usunie się jakąś własność, a potem znów się doda.

Poza tym, for-in iteruje również po wszystkich tych własnościach odziedziczonych poprzez łańcuch prototypów, które mają [[Enumerable]] ustawione na true. Na szczęście, wbudowane funkcje (i pole length) mają to ustawione na false, przez co domyślnie wszystko niby działa, ale jeśli użytkownik rozszerzy Array.prototype, to w starszych przeglądarkach nawet nie będzie miał możliwości sprawienia, by rozszerzenia miały [[Enumerable]] ustawione na false, więc jego funkcje pojawią Ci się podczas iteracji w for-in.

Dlatego w przypadku tablic (a wspominasz o nich w Twoim poście) polecam nie cyrkować i używać zwykłej pętli for. Jeśli zaś chcesz iterować po własnościach obiektu (a nie po indeksach tablicy), to wtedy słusznie używasz for-in.

0

Ale nie mam pojęcia jak mam użyć zwykłej pętli for skoro elementy w tablicy są na różnych pozycjach. Jakby były 0,1,2,3 to pewnie, że nie bawiłbym się z pętlą for-in,

0

Brakuje mi tu kontekstu i boję się, że coś Ci źle podpowiem...

Czy engineFinalObjects to na pewno tablica? Bo może to zwykły obiekt (wtedy użycie for-in jest OK!).

Jeśli engineFinalObjects to tablica, to czemu są w niej dziury? Czasami świadczy to o tym, że coś jest źle zaplanowane -- ale też nie zawsze.

Jeśli iterujesz po rzadkiej (ang. sparse) tablicy, to możesz użyć zawsze czegoś takiego:

for (var i = 0; i < arr.length; i++) {
  if (arr.hasOwnProperty(i)) {
    // operuj na arr[i];
  }
}

Język gwarantuje, że length będzie ustawione na najwyższy indeks tablicy + 1 (ale działa to tylko dla tablic i indeksów liczbowych!). Z kolei wywołanie .hasOwnProperty() gwarantuje, że przetworzymy tylko te indeksy w tablicy, które istnieją -- więc pominiemy dziury.

Tyle że jeśli masz bardzo rzadką tablicę, to powyższe rozwiązanie będzie nieefektywne (dużo niepotrzebnych wywołań .hasOwnProperty()).

Może faktycznie być tak, że spotkałeś się z tą wyjątkową sytuacją, w której najlepszym praktycznym wyjściem będzie użycie for-in na tablicy. Jeśli chcesz ochronić taką pętlę przed odziedziczonymi własnościami, to także możesz użyć hasOwnProperty:

for (var key in engineFinalObjects) {
    if (engineFinalObjects.hasOwnProperty(key)) {
      var value = engineFinalObjects[key];
      console.log(key, value);
    }
}

Często się zdarza, że ludzie używają for-in tylko dlatego, że dla nich to to samo co odpowiednie dla tablic for-each -- a są w błędzie. Dlatego chciałem Cię przestrzec.

Jeśli pisałbyś tylko pod nowoczesne przeglądarki (czyli niestety nie IE8), mógłbyś użyć funkcji forEach, które mają tablice zgodnie ze specyfikacją ECMAScriptu 5:

engineFinalObjects.forEach(function(value) {
    console.log(value);
});

Czyli to coś takiego jak pętla foreach. Tak naprawdę, funkcja przekazana do forEach dostaje jeszcze za parametr indeks elementu tablicy i samą tablicę, ale nie musisz pisać tych parametrów gdy ich nie potrzebujesz. Pełna sygnatura wygląda jednak tak:

engineFinalObjects.forEach(function(value, index, arr) {
    console.log(value);
});

Ostrzegę Cię jeszcze przed dwoma rzeczami:

  1. Zawsze deklaruj zmienne używając var. W pierwszym poście masz to var pominięte. Powinno być for (var obj in...) lub obj powinno zostać zadeklarowane gdzieś wyżej. Jeśli pominiesz var, zmienna obj będzie globalna.
  2. Wiem, że to żałosne, ale w JavaScripcie istnieje "jedyny słuszny" sposób stawiania klamr otaczających bloki instrukcji warunkowych, pętli itp. Nie powinno się stawiać klamry otwierającej w nowej linii. W JS-ie działa automatyczne wstawianie średników (tm) i daje się ono we znaki, jeśli będziesz chciał zwrócić z funkcji literał obiektowy, ale postawisz klamrę w nowej linii. Napiszesz:
return
{
  foo: 123
};

...a po automatycznym wstawieniu średnika zostanie to zamienione na:

return;
{
  foo: 123
};

co jest równorzędne z:

return undefined;
{
  foo: 123
};

i funkcja zwróci Ci undefined ;). A dalsza część funkcji (to, co miało być literałem obiektowym) będzie potraktowana jako martwy kod. Tak piszę żebyś się nie zdziwił gdy na to natrafisz.

0

Dziękuję za kilka rad.

Tak engineFinalObjects jest tablicą. Tablicą o długości 3 elementów i ma na różnych pozycjach typu 3,99, 251 odniesienia do obiektów. Indeks tablicy jest jednocześnie ID obiektu, jest to po prostu wygodne więc jeśli mam obiekt o ID 512 i chcę pobrać z niego ID Modelu to robię engineFinalObjects[512].modelid i nie muszę się babrać w jakieś kosmiczne sortowania itd.

Jeżeli użyłbym twojego sposobu z for to słusznie zauważyłeś, że byłoby to nieefektywne.

Przeglądarki poniżej IE9 olewam totalnie bo to projekt z HTML5, a o forEach po prostu nie wiedziałem, jak wpisałem foreach to wywaliło mi syntax error i myśłałem, że tego składnika po prostu nie ma. Cóż człowiek uczy się całe życie.

Jeśli chodzi o automatyczne wstawianie średników to jest to jakaś funkcja, którą się włącza i można wyłączyć czy to element engine JS przeglądarki? średniki zawsze stawiam na końcu ręcznie.

0

Automatyczne wstawianie średników to element języka i nie da się go wyłączyć. Ja akurat też wstawiam je zawsze ręcznie, ale niektórzy ludzie uważają, że im mniej średników, nawiasów itp. tym lepiej. W sumie faktycznie średniki na końcu linii tylko zaśmiecają mi kod, bo i tak mam maksymalnie jedną instrukcję w jednej linii...

Niestety, jak widzisz, automatyczne wstawianie średników nie zawsze działa tak, jakbyśmy sobie tego oczekiwali.

BTW. tak sądząc po tym co piszesz, to na Twoim miejscu nie robiłbym chyba z engineFinalObjects tablicy -- użyłbym raczej mapy, czyli zwykłego obiektu. Bo mapowanie ID (nie tyle indeks, co identyfikator) -> obiekt to... mapowanie, a nie sekwencja elementów. Z drugiej strony, może chcesz mieć to jakoś posortowane, czy coś -- że to jednak sekwencja jakaś -- więc z powodu braku kontekstu nie będę się już mądrzył.

0

Hmm jeżeli użyłbym obiektu jako mapy to jeśli chciałbym odwołać się do konkretnego ID to musiałbym przelecieć przez wszystkie obiekty i sprawdzać, który ma ten właściwy ID i jest mi potrzebny co byłoby również nieefektywne ale może się mylę.

Wymyśliłem taki sposób odnoszenia się do konkretnych obiektów poprzez umieszczenie ich w tablicy w której numer elementu jest jego ID żebym nie musiał całej listy przelatywać aby odwołać się do konkretnego obiektu. Jestem otwarty na propozycje jeśli masz lepszy pomysł :)

Tych danych nie będę raczej sortował, to po prostu id obiektów i nie są mi potrzebne posortowane.

Pozdrawiam

0
Ezio napisał(a)

Hmm jeżeli użyłbym obiektu jako mapy to jeśli chciałbym odwołać się do konkretnego ID to musiałbym przelecieć przez wszystkie obiekty i sprawdzać, który ma ten właściwy ID i jest mi potrzebny co byłoby również nieefektywne ale może się mylę.

Jeśli dobrze rozumiem o co Ci chodzi to tak, mylisz się ;).

W JavaScripcie obiekty (tablice to zresztą też obiekty!) działają jak "hashmapy". Kluczami są stringi, wartości mogą być dowolne.

Załóżmy, że masz pusty obiekt obj. Jego własności możesz zarówno zapisywać, jak i odczytywać na różne sposoby. Również używając notacji tablicowej, tj. nawiasów kwadratowych:

var obj = {}; // obiekt, nie tablica!

// zapis: to wszystko działa zgodnie z oczekiwaniami
obj.red = "czerwony";
obj["green"] = "zielony";

var colorName = "blue";
obj[colorName] = "niebieski";

obj["666"] = "mroczny"; // a co, klucz może równie dobrze być ciągiem cyfr
obj[666] = "mroczny"; // to samo co wyżej -- JavaScript wywoła .toString() na liczbie 666


// odczyt
console.log( obj.red ); // "czerwony"
console.log( obj["red"] ); // "czerwony" -- po prostu zapis alternatywny

colorName = "blue";
console.log( obj[colorName] ); // niebieski


console.log( obj["666"] ); // mroczny
console.log( obj[666] ); // mroczny

// console.log( obj.666 ); // tak się nie da, bo w notacji kropkowej klucz musi być poprawnym identyfikatorem

Jak widzisz, w przypadku obiektów również możesz używać notacji z nawiasami kwadratowymi -- nazywanie jej "notacją tablicową" jest troszkę mylące, bo w JS-ie w nawiasach można podawać nie tylko indeksy (liczby), ale dowolne klucze. Tak naprawdę, jeśli poda się liczbę, to zostanie ona zamieniona na string.

Znajoma notacja kropkowa (obiekt.kucz) to w zasadzie to samo, tyle że wymaga, by klucz był poprawnym identyfikatorem. Liczba nie jest poprawnym identyfikatorem, podobnie jak np. słowo zastrzeżone (np. class). Dlatego jeśli chcesz dorzucić do obiektu własność class lub takową odczytać, nie możesz napisać obj.class tylko obj["class"].

W każdym razie, użycie ID w mapach (zwykłych obiektach) jest perfekcyjnie legalne i bardzo często używane. Możesz napisać:

var engineFinalObjects = {}; // obiekt, a nie tablica!

function addEngineFinalObject(obj) {
  var generatedId = Math.round(Math.random() * 1000); // cokolwiek
  engineFinalObjects[generatedId] = obj;
  return generatedId;
}

function getEngineFinalObjectById(id) {
  return engineFinalObjects[id];
}

var id = addEngineFinalObject("foo");
console.log( getEngineFinalObjectById(id) ); // foo
0

W takim razie to co robiłem od samego początku jest obiektem, nie tablicą (ale ten Javascript jest porąbany) i wszystko robię poprawnym sposobem i tak jak się powinno.

Już czasami człowiek może się pogubić. Znam C#, PHP, HTML, CSS, JS, w końcu to wszystko się zaczyna mieszać.

Dzięki za tak dogłębne wytłumaczenie tematu.

0

Porąbany? Bo nie znasz go jeszcze na tyle dobrze, by odróżnić tablicę od mapy?

To, że nie jest taki jak inne języki nie znaczy że jest porąbany. Jest w JavaScripcie trochę błędów projektowych, ale (z drobnymi wyjątkami) nie dotyczą one notacji zapisywania literałów obiektowych. W rzeczywistości, notacja ta jest na tyle udana, że została wprost zaadaptowana w znanym i lubianym formacie JSON.

Uprzedzę, że JavaScript nie ma dziedziczenia klasycznego, tylko prototypowe -- więc nie myśl o tamtejszym dziedziczeniu tak, jak to jest w C# czy PHP. I znacznie bardziej niż powyższe języki polega na funkcyjności.

To dość proste: albo zdecydujesz się nauczyć języka i wykorzystasz jego mocne strony, albo będziesz miał już totalną kaszanę, usiłując używać JavaScriptu tak jak Javy czy C#. JavaScript nigdy nie będzie tak dobrą Javą jak Java, to chyba oczywiste ;).

0

Jest moim zdaniem porąbany bo różni się horrendalnie od normalnych w moim pojęciu języków typu PHP, C#. I nie podoba mi się nie dlatego, że nie chce mi się go uczyć. Nie mam czasu żeby się uczyć jego specyficznych zagadnień, w czasie pisania tych postów miałem do napisania kolejną część engine i tylko upewniałem się czy moje rozwiązanie jest dobre.

1 użytkowników online, w tym zalogowanych: 0, gości: 1