Aktywne Wpisy
zszargane-nerwy +907
Tak oto wygląda równouprawnienie w praktyce - panie wychodzą z pracy o godz. 11:00, panowie pracują do końca :) Dzięki temu inkluzywnemu rozwiązaniu panowie, zamiast zająć się obsługą swoich zadań, będą "ogarniać pracę urzędu" czyli odbierać telefony i tłumaczyć ludziom, że ich sprawy nie zostaną dziś załatwione, bo 85% urzędu zostało oddelegowane do mycia okien i gotowania żurku.
Pracownicy Urzędu Miasta i Gminy we Wrześni pozdrawiają pozostałych pracowników budżetówki, którzy przy okazji
Pracownicy Urzędu Miasta i Gminy we Wrześni pozdrawiają pozostałych pracowników budżetówki, którzy przy okazji
mirko_anonim +42
✨️ Obserwuj #mirkoanonim
Śmieszą mnie chłopy co chodzą na siłownie, oczywiście żaden się nie przyzna że robi to dla kobiet tylko "dla siebie" taa xD. Zwłaszcza popularność siłowni wystrzeliła w kosmos w ostatnich latach akurat jak na rynku matrymonialnym jest coraz gorzej. Nie znam żadnego chłopa, który by dla samego siebie: trzymał restrykcyjną dietę, chodził wcześnie spać żeby się wysypiać, unikał używek, regularnie ćwiczenia, itd. To tak jakby siedzieć w domu w
Śmieszą mnie chłopy co chodzą na siłownie, oczywiście żaden się nie przyzna że robi to dla kobiet tylko "dla siebie" taa xD. Zwłaszcza popularność siłowni wystrzeliła w kosmos w ostatnich latach akurat jak na rynku matrymonialnym jest coraz gorzej. Nie znam żadnego chłopa, który by dla samego siebie: trzymał restrykcyjną dietę, chodził wcześnie spać żeby się wysypiać, unikał używek, regularnie ćwiczenia, itd. To tak jakby siedzieć w domu w
Dynamic scoping
Przy okazji jednego z wpisów popełniłem już dość obszerny fragment odnośnie tego jak działa scoping w JSie. Uzupełnijmy go o informację, że w JavaScript dostępne są dwa typy scope'ów – statyczny oraz dynamiczny.
Statyczny (reprezentowany przez lexical environment record) jest tym dobrym - kompilator jest go w stanie zanalizować już w trakcie kompilacji kodu, co daje pewien zysk w postaci lepszej optymalizacji. Z drugiej zaś strony stoi scoping dynamiczny, tworzony dopiero w trakcie działania programu. Ten reprezentowany jest przez object environment record, i w praktyce sprowadza się do tego, że do scope chaina dodawany jest zwykły, JavaScriptowy obiekt. A że obiekty w JavaScript są bardzo dynamiczne, to dynamic scoping dość mocno utrudnia optymalizację. Takich dynamicznych scope’ów mamy w ES5 trzy: scope globalny, reprezentowany przez obiekt globalny (w przeglądarce to będzie
window
, w nodeglobal
, a ogólnie jest to obiekt dostępny podthis
w kontekście globalnym), blockcatch
orazwith
statement. Skupimy się na tym ostatnim, ponieważ to ono jest winowajcą całego zamieszania.With statement
W dużym skrócie, taki kod:
with(obj) { … }
spowoduje że przed wykonaniem kodu z bloku, do scope chain dodany zostanie dynamic scope reprezentowany przez obiekt
obj
. Oznacza to, że jeśli wewnątrz bloku odwołamy się do jakiejś zmiennej, powiedzmy o nazwiex
, to najpierw binding o tej nazwie zostanie wyszukany wśród własności obiektuobj
. Tym samym ten kod:var x = 1;
var obj = { x: 2 };
with(obj) {
console.log(x);
}
wypisze nam do konsoli
2
a nie1
. Gdyby natomiast własnośćx
nie istniała wewnątrz obiektuobj
, wtedy binding o tej nazwie zostałby znaleziony scope wyżej, a w konsoli ujrzelibyśmy1
.Bardzo dużym problemem w kontekście
with
statement są prototypy. Wszystko dlatego że zanim kod przejdzie od analizowania własności obiektu do analizowania otaczających go scope’ów, analizowany jest także cały łańcuch prototypów danego obiektu. A to utrudnia zarówno analizowanie go przez człowieka, jak i przez kompilator.Niestety zgodnie z regułą don’t break the web nie można było od tak sobie usunąć go z języka, w pewnym sensie został on zatem uznany za deprecated - od wersji ES5 jego użycie w trybie ścisłym powodowało rzucenie błędu
SyntaxError
.ExtJS, czyli jak napisać paskudną, jedną linijkę…
Niestety nie wszyscy programiści są świadomi konsekwencji, więc tworzą kod wątpliwej jakości. W jednym z modułów do popularnego frameworka ExtJS znalazł się taki oto fragment:
WITHVALUES = 'with(values){ ',
Przy czym interesuje nas tylko środek stringa (moduł ten odpowiada za kompilację szablonów), a dodatkową informacją niech będzie fakt, że
values
to może być tablica.Czaicie to?
Jedna prosta linijka, dwa wyrazy i dwa niezłe fuckupy. Nie dość, że użyto odradzanego
with
statement, to jeszcze użyto go na tablicy, a więc użyto tablicy w taki sposób w jaki używa się obiektu. Paskudne. Ten fragment kodu znalazł się w module ExtJS, skąd trafił do części postawionych na nim stron...która zepsuje internet
Jedną z ważniejszych rzeczy, które zostały dodane w ES6, są kolekcje danych wraz z mechanizmem iteracji po tych kolekcjach, czyli iteratorach. Tablica jest jedną z takich kolekcji i ona również ma zaimplementowane nowe metody, udostępniane przez interfejs iterable. Jedną z takich metod jest metoda
values()
, która zwraca po prostu kolejne wartości z kolekcji – w przypadku tablicy, kolejne jej elementy.5 czerwca 2013 do kodu Firefoxa Nightly [zostaje wysłany commit]( http://hg.mozilla.org/mozilla-central/rev/5d35dc039af7), który dodaje metodę
Array.prototype.values
. Zmiana jest niewielka, ponieważvalues()
zwraca dokładnie ten sam iterator, co metodaArray.prototype[Symbol.iterator]
. Aktualizacja zostaje rozesłana do wielu sadystów używających wersji Nightly i już kilka dni później na Bugzillę trafiają bugi informujące o wystąpieniu regresji.Dochodzenie nie trwa długo i dość szybko winnym zostaje wskazany wymieniony już przeze mnie kod z ExtJS, a dokładnie
with(values)
. Otóż jeślivalues
będzie tablicą, a wewnątrz tego blokuwith
znajdzie się odwołanie dovalues
, czyli przykładowo taki kod:var values = [];
…
with(values) {
…
var a = values[0];
to co stanie się teraz? Ano, z powodu użycia
with
binding o nazwievalues
będzie wyszukiwany od obiektuvalues
czyli od naszej tablicy. Jeśli nie zostanie znalezione wśród jej własności, to zgodnie z tym, co mówiłem wcześniej, zostanie sprawdzony prototyp tego obiektu, a więc w przypadku tablicy -Array.prototype
. A tam już czeka własność o nazwievalues
, której wcześniej nie było. Więc tak naprawdę próbujemy odczytać klucz0
nie z tablicy, a z funkcji której zadaniem jest zwrócić tablicowy iterator.TC39, mamy problem
Pojawia się problem. Jedną z fundamentalnych zasad przy tworzeniu nowych wersji EcmaScript, związanych z tym jak jest wydawany, jest „don’t break the web”. A tutaj mamy sytuację kiedy sieć została zepsuta, ponieważ jedna drobna zmiana sprawia, że część stron może z dnia na dzień przestać działać. Co prawda ExtJS nie ma zbyt dużego udziału w rynku (szacuje się go na 0.1%), jednak ta wartość została uznana za i tak zbyt dużą. Dyskusje nt. rozwiązań trwały aż w końcu wybrano jedno z nich. Jest ono moim zdaniem brzydkie, bo to bardziej takie hackowanie, ale lepszego chyba nie było.
Rozwiązanie
Aby rozwiązać problem, posłużono się symbolami. Tl;dr – symbole to nowy typ w EcmaScript, każdy ma unikalną wartość i mogą być używane jaki klucz w obiektach. Co więcej, bardzo dużo zachowań języka zależy teraz także od metod ukrytych pod specjalnymi symbolami (well-known symbols), które są umieszczone w konstruktorze
Symbol
. I takSymbol.hasInstance
steruje zachowaniem operatorainstanceof
,Symbol.iterator
mówi w jaki sposób dany obiekt ma być iterowany,Symbol.toStringTag
mówi co zwracaObject.prototype.toString
wykonany w kontekście danego obiektu (pełna lista tutej). W związku z problematycznymwith
został dodany takżeSymbol.unscopables
.Symbol.unscopables
służy do bezpośredniego ukrywania własności obiektu przed wyszukiwaniem bindingów w dynamic scope’ach. To znaczy ni mniej, ni więcej tyle, że używając tego symbolu jawnie wskazujemy, które własności w danym obiekcie mają być pominięte wewnątrz dynamic scope. A więc właśnie wwith
statement:var a = 0;
var objProto = { a: 1 };
var obj = Object.create(objProto);
obj.a = 2;
obj[Symbol.unscopables] = { 'a': true };
with(obj) {
console.log(a);
// --> 0, a nie 2 (bezpośrednia własność) czy 1 (własność dziedziczona z prototypu)
}
try-catch
oraz obiekt globalny, mimo że są zakresami dynamicznymi, nie obsługująSymbol.unscopables
- wynika to bezpośrednio z 8.1.1.2.1 (krok 6.)Konkluzja
Specyfika JavaScriptu nie pozwala na frywolne szastanie rzeczami, które są zawarte w języku. W takiej np. Javie czy PHP możemy coś oznaczyć jako deprecated żeby następnie za, powiedzmy, 2 wydania tę rzecz zupełnie wyrzucić. Ci, którzy zdecydują się na aktualizację, będą musieli dane fragmenty kodu przepisać. Ci, którzy tego nie zrobią, nie będą musieli nic robić a wszystko nadal będzie działać tak samo.
Z JS jest inaczej. Przy wprowadzaniu każdej nowej rzeczy trzeba mieć świadomość, że zostanie ona z tym językiem aż do jego śmierci. Trzeba także analizować czy nowe rzeczy nie stoją w jakimś konflikcie z tymi, które w języku już są. To jest trudne, zapewne dość mocno spowalnia rozwój samego JSa, a także grozi nieprzewidywanymi regresjami i koniecznością ich późniejszego łatania – także takimi brzydkimi fixami. Czy można było zrobić to lepiej? Ciężko powiedzieć. Rozważano zmianę nazwy metody, rozważano olanie problemu i podejście „śmierć frajerom którzy nie zaktualizują sobie ExtJS”. Ostatecznie zdecydowano się na taką wersję, a więc – prawdopodobnie – była to najlepsza, jaką wymyślono.
W razie czego zachęcam do zadawania pytań.
Źródła:
https://esdiscuss.org/topic/array-prototype-values-compatibility-hazard
https://esdiscuss.org/topic/removing-array-prototype-values
http://www.2ality.com/2011/06/with-statement.html
https://bugzilla.mozilla.org/show_bug.cgi?id=883914
https://bugzilla.mozilla.org/show_bug.cgi?id=881782
https://hg.mozilla.org/mozilla-central/rev/5d35dc039af7
http://docs.sencha.com/extjs/4.0.7/source/XTemplate.html
#programowanie #javascript #webdev #dlugipost #marmiteopowiadaojs
@Marmite: Nie przepraszaj za uƶywanie zrozumiałej terminologii. Pamiętaj ƶe framework to po polsku "zręb" :/
@KrzaQ2: Pierwsze słyszę to tłumaczenie :D
A przeprosiłem bo prywatnie nienawidzę i piętnuję zawsze jakiekolwiek formy ponglisha - a w takiej dziedzinie jak programowanie, to po prostu się nie da niestety. Więc musiałem to zrobić, żeby być w zgodzie ze swoim sumieniem.