Czy istnieją uzasadnione zastosowania instrukcji „with” JavaScript?


369

Komentarze Alana Storma w odpowiedzi na moją odpowiedź dotyczącą withoświadczenia skłoniły mnie do myślenia. Rzadko znajdowałem powód do korzystania z tej konkretnej funkcji języka i nigdy nie zastanawiałem się, w jaki sposób może to powodować problemy. Teraz jestem ciekawy, w jaki sposób mogę skutecznie wykorzystać with, unikając pułapek.

Gdzie znalazłeś withstwierdzenie przydatne?


52
Nigdy tego nie używam. Łatwiej jest bez niego żyć, jeśli udaję, że nie istnieje.
Nosredna

6
Może kiedyś istniało wiele ważnych zastosowań. Ale to jest dyskusja. ES5 Strict usunięty, withwięc nie ma już czegoś takiego.
Thomas Aylott,

27
Warto tutaj zauważyć, że ES5 Strict jest nadal opcjonalny .
Shog9

5
Zamiast usuwać „z” w ES5 strict, czy nie lepiej byłoby zmienić standard, aby w przypadku braku znalezionej zmiennej przypisanie wewnątrz „z” było powiązane z obiektem argumentu?
JussiR,

2
@JussiR: Prawdopodobnie. Problem polega jednak na tym, że prawdopodobnie spowodowałoby to uszkodzenie starszych przeglądarek.
Sune Rasmussen

Odpowiedzi:


520

Dzisiaj przyszło mi do głowy inne zastosowanie, więc podekscytowałem się w Internecie i znalazłem wzmiankę o nim: Definiowanie zmiennych w zakresie zakresu .

tło

JavaScript, pomimo powierzchownego podobieństwa do C i C ++, nie obejmuje zmiennych do bloku, w którym są zdefiniowane:

var name = "Joe";
if ( true )
{
   var name = "Jack";
}
// name now contains "Jack"

Zadeklarowanie zamknięcia w pętli jest częstym zadaniem, które może prowadzić do błędów:

for (var i=0; i<3; ++i)
{
   var num = i;
   setTimeout(function() { alert(num); }, 10);
}

Ponieważ pętla for nie wprowadza nowego zakresu, to samo num- z wartością 2- będzie współdzielone przez wszystkie trzy funkcje.

Nowy zakres: letiwith

Wraz z wprowadzeniem letoświadczenia w ES6 łatwo jest wprowadzić nowy zakres, gdy jest to konieczne, aby uniknąć tych problemów:

// variables introduced in this statement 
// are scoped to each iteration of the loop
for (let i=0; i<3; ++i)
{
   setTimeout(function() { alert(i); }, 10);
}

Lub nawet:

for (var i=0; i<3; ++i)
{
   // variables introduced in this statement 
   // are scoped to the block containing it.
   let num = i;
   setTimeout(function() { alert(num); }, 10);
}

Dopóki ES6 nie będzie powszechnie dostępny, użycie to pozostaje ograniczone do najnowszych przeglądarek i programistów chętnych do korzystania z transpilerów. Możemy jednak łatwo zasymulować to zachowanie, używając with:

for (var i=0; i<3; ++i)
{
   // object members introduced in this statement 
   // are scoped to the block following it.
   with ({num: i})
   {
      setTimeout(function() { alert(num); }, 10);
   }
}

Pętla działa teraz zgodnie z przeznaczeniem, tworząc trzy oddzielne zmienne o wartościach od 0 do 2. Zauważ, że zmienne zadeklarowane w bloku nie mają do niego zasięgu , w przeciwieństwie do zachowania bloków w C ++ (w C, zmienne muszą być deklarowane na początku blok, więc w pewnym sensie jest podobny). To zachowanie jest w rzeczywistości dość podobne do letskładni blokowej wprowadzonej we wcześniejszych wersjach przeglądarek Mozilla, ale nie jest powszechnie stosowane gdzie indziej.


15
Nigdy nie myślałem o używaniu z literałem, wydaje się uzasadnione.
Matt Kantor,

81
To naprawdę bardzo martwe. Nigdy nie myślałem o graniu w ten sposób w JavaScript. Całkowicie rozszerzyłem nowe obszary mojego kodowania. Chciałbym móc głosować 10 razy!
kizzx2

27
Dla tych, którzy nadal się sprzeciwiali, zawsze można było użyć zamknięcia:for (var i = 0; i < 3; ++i) { setTimeout ((function () { var num = i; return function () { alert (num); }; }) (), 10);}
Thomas Eding

4
W rzeczywistości powyższy problem występuje w większości przeglądarek innych niż Mozilla (Chrome, Safari, Opera, IE).
Max Shawabkeh

24
niech wsparcie oświadczenie w IE naprawdę uratować moje boczek teraz, ja zmagam się z moim sumieniem o tym, czy do korzystania z zamiast. Prawdziwy problem polega na tym, że nawet przy let ze znakiem let należy zachować szczególną ostrożność ze względu na odziedziczone właściwości obiektu w łańcuchu prototypów. Na przykład var toString = function () { return "Hello"; }; with ({"test":1}) { console.log(toString()); };. W zakres tego z oświadczeniem toString () jest dziedziczną własność obiektu , tak wyraźnie określona funkcja nie jest tzw. Nadal świetna odpowiedź :-)
Andy E

161

Używam instrukcji with jako prostej formy importu o określonym zakresie. Powiedzmy, że masz jakiś konstruktor znaczników. Zamiast pisać:

markupbuilder.div(
  markupbuilder.p('Hi! I am a paragraph!',
    markupbuilder.span('I am a span inside a paragraph')
  )
)

Zamiast tego możesz napisać:

with(markupbuilder){
  div(
    p('Hi! I am a paragraph!',
      span('I am a span inside a paragraph')
    )
  )
}

W tym przypadku użycia nie wykonuję żadnego zadania, więc nie mam z tym problemu dwuznaczności.


5
Tak to widziałem w VB. (I jedyne, o czym wiedziałem.)
Mateen Ulhaq,

2
Minusem tego jest to, że jeśli odwołujesz się do zmiennej w bloku z, która znajduje się poza obiektem konstruktora znaczników, silnik js i tak najpierw szuka jej wewnątrz konstruktora znaczników, zmniejszając wydajność.
Adam Thomas

3
To naprawdę pomaga ograniczyć kod dla osób pracujących ze ścieżkami obszaru roboczego.
Brian McCutchon,

4
Wersja „z” tego kodu działa dosłownie ponad 240 razy wolniej na mojej maszynie niż wersja „nie z” tego samego. Dlatego ludzie twierdzą, że nie ma uzasadnionego zastosowania. Nie dlatego, że w niektórych miejscach kod nie jest ładniejszy. Zobacz test: jsfiddle.net/sc46eeyn
Jimbo Jonny

1
@McBrainy - To jest dokładnie ten rodzaj miejsca, w którym nie powinieneś używać kodu, który działa o wiele wolniej (zobacz komentarz, który właśnie napisałem powyżej tego). Jeśli potrzebujesz skrótów do super powtarzanego kodu, możesz je zadeklarować. Na przykład, jeśli używasz context.bezierCurveTosto razy prosto, możesz powiedzieć, var bc2 = context.bezierCurveTo;a następnie po prostu iść za bc2(x,x,etc);każdym razem, gdy chcesz to nazwać. To dość szybkie i jeszcze mniej gadatliwe, a jednocześnie withbardzo wolne.
Jimbo Jonny

83

Jak wskazały moje poprzednie komentarze, nie sądzę, abyś mógł withbezpiecznie korzystać z niego, bez względu na to, jak kuszące może być w danej sytuacji. Ponieważ problem nie jest tutaj bezpośrednio omawiany, powtórzę go. Rozważ następujący kod

user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);

with(user){
    name = 'Bob';
    age  = 20;
}

Bez dokładnego zbadania tych wywołań funkcji nie można w żaden sposób powiedzieć, jaki będzie stan programu po uruchomieniu tego kodu. Gdybyuser.name było już ustawione, teraz będzie Bob. Jeśli nie zostanie ustawiony, globalny namezostanie zainicjowany lub zmieniony na, Boba userobiekt pozostanie bez namewłaściwości.

Błędy się zdarzają. Jeśli użyjesz go razem , w końcu to zrobisz i zwiększysz szanse, że Twój program się nie powiedzie. Co gorsza, możesz napotkać działający kod, który ustawia globalny blok w bloku, celowo lub przez autora, który nie wie o tym dziwactwie konstrukcji. Przypomina to napotkanie przełącznika, nie masz pojęcia, czy autor tego zamierzał, i nie ma sposobu, aby dowiedzieć się, czy „naprawienie” kodu spowoduje regresję.

Nowoczesne języki programowania są pełne funkcji. Niektóre funkcje po latach użytkowania okazały się złe i należy ich unikać. Javascript withjest jednym z nich.


18
Ten problem pojawia się tylko wtedy, gdy przypisujesz wartości do atrybutu obiektu. Ale co, jeśli używasz go tylko do odczytu wartości? Uważam, że w takim przypadku można go użyć.
airportyh

10
Ten sam problem dotyczy odczytu wartości Toby. W powyższym fragmencie kodu nie wiesz, czy nazwa jest ustawiona na obiekcie użytkownika, więc nie wiedziałbyś, czy czytasz nazwę globalną, czy nazwę użytkownika.
Alan Storm

12
Przy odczytywaniu wartości istnieje wyraźna zasada pierwszeństwa: atrybuty obiektu są sprawdzane przed zmiennymi spoza zakresu. Nie różni się niczym od zakresu zmiennych w funkcjach. Rzeczywisty problem z przypisaniem i „z”, jak rozumiem, polega na tym, że to, czy przypisanie atrybutu nastąpi, zależy od tego, czy atrybut istnieje na bieżącym obiekcie, o którym mowa, który jest właściwością środowiska wykonawczego i nie można go łatwo wywnioskować patrząc na kod.
airportyh

1
Myślę, że możesz tam być, Toby. Problem z pisaniem wystarczy, że całkowicie unikam konstruktu.
Alan Storm

„To tak, jakby spotkać się z upadkiem na przełączniku, nie masz pojęcia ...” - Więc ban też ()? ;-p
Sz.

66

withOstatnio stwierdzenie to okazało się niezwykle przydatne. Ta technika nigdy tak naprawdę nie przyszła mi do głowy, dopóki nie rozpocząłem bieżącego projektu - konsoli wiersza poleceń napisanej w JavaScript. Próbowałem emulować interfejsy API Firebug / WebKit, w których można wprowadzać specjalne polecenia do konsoli, ale nie zastępują one żadnych zmiennych w zasięgu globalnym. Pomyślałem o tym, próbując przezwyciężyć problem, o którym wspomniałem w komentarzach do doskonałej odpowiedzi Shog9 .

Aby osiągnąć ten efekt, użyłem dwóch z instrukcjami do „warstwowania” zakresu za zasięgiem globalnym:

with (consoleCommands) {
    with (window) {
        eval(expression); 
    }
}

Wspaniałą rzeczą w tej technice jest to, że oprócz wad wydajnościowych nie odczuwa ona zwykłych obaw with wyrażenia, ponieważ i tak oceniamy w zakresie globalnym - nie istnieje niebezpieczeństwo, że zmienne poza naszym pseudo-zasięgiem będą zmodyfikowany.

Zainspirowałem się opublikowaniem tej odpowiedzi, gdy, ku mojemu zaskoczeniu, udało mi się znaleźć tę samą technikę, która była używana w innym miejscu - kod źródłowy Chromium !

InjectedScript._evaluateOn = function(evalFunction, object, expression) {
    InjectedScript._ensureCommandLineAPIInstalled();
    // Surround the expression in with statements to inject our command line API so that
    // the window object properties still take more precedent than our API functions.
    expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }";
    return evalFunction.call(object, expression);
}

EDYCJA: Właśnie sprawdziłem źródło Firebug, łączą 4 z instrukcjami razem, aby uzyskać jeszcze więcej warstw. Zwariowany!

const evalScript = "with (__win__.__scope__.vars) { with (__win__.__scope__.api) { with (__win__.__scope__.userVars) { with (__win__) {" +
    "try {" +
        "__win__.__scope__.callback(eval(__win__.__scope__.expr));" +
    "} catch (exc) {" +
        "__win__.__scope__.callback(exc, true);" +
    "}" +
"}}}}";

1
ale martwiąc się, że ecmascript5 powstrzymuje cię przed zrobieniem tego. Czy istnieje rozwiązanie ecmascript 5?
kybernetikos

@Adam: Nie jestem tego pewien. ES5 generuje błąd tylko w trybie ścisłym, więc nie jest to natychmiastowy problem, jeśli globalny tryb nie został zadeklarowany. ES Harmony może stanowić większy problem, ale można go naprawić za pomocą niektórych nowszych rzeczy, takich jak serwery proxy.
Andy E

@AndyE przepraszam, to nie jest temat, ale czy „konsola wiersza poleceń napisana w JavaScript” jest dostępna gdziekolwiek?
kybernetikos

@Adam: nie, to nie jest. Całość miała być zestawem narzędzi programistycznych dla gadżetów pulpitu Windows, ale nigdy go nie ukończyłem (chociaż konsola działa bardzo dobrze). W pewnym momencie mogę go dokończyć, mimo że WDG nie mają obecnie świetlanej przyszłości.
Andy E

3
Kilka tygodni temu przenieśliśmy naszą implementację konsoli w Chrome z bloku na magię symboli, ponieważ z blokiem zablokowano niektóre funkcje ES6 :)
Alexey Kozyatinskiy

54

Tak, tak i tak. Istnieje bardzo uzasadnione użycie. Zegarek:

with (document.getElementById("blah").style) {
    background = "black";
    color = "blue";
    border = "1px solid green";
}

Zasadniczo wszelkie inne haki DOM lub CSS są fantastyczne w użyciu. To nie jest tak, że „CloneNode” będzie niezdefiniowany i wróci do globalnego zasięgu, chyba że zejdziesz mu z drogi i nie zdecydujesz się na to.

Skarga prędkości Crockforda polega na tym, że za pomocą tworzy się nowy kontekst. Konteksty są na ogół drogie. Zgadzam się. Ale jeśli właśnie utworzyłeś div i nie masz pod ręką jakiegoś frameworka do ustawiania css i musisz ręcznie skonfigurować około 15 właściwości CSS, wówczas tworzenie kontekstu będzie prawdopodobnie tańsze niż tworzenie zmiennych i 15 dereferencji:

var element = document.createElement("div"),
    elementStyle = element.style;

elementStyle.fontWeight = "bold";
elementStyle.fontSize = "1.5em";
elementStyle.color = "#55d";
elementStyle.marginLeft = "2px";

itp...


5
+1, ponieważ uważam również, że istnieje wiele uzasadnionych zastosowań with. Jednak w tym konkretnym przypadku możesz po prostu:element.style.cssText="background: black ; color: blue ; border: 1px solid green"
GetFree

5
Można osiągnąć to samo w jednej linii za pomocą prostego extendsposobu z obu jQuery lub underscore.js: $.extend(element.style, {fontWeight: 'bold', fontSize: '1.5em', color: '#55d', marginLeft: '2px'}).
Trevor Burnham,

9
@TrevorBurnham - Jeśli masz zamiar założyć, że jQuery jest dostępny, po prostu skorzystaj z jego .css()metody ...
nnnnnn

4
Co dokładnie uniemożliwia przejście tych zmiennych do zasięgu globalnego? Czy to tylko dlatego, że wszystkie style CSS są zawsze zdefiniowane we wszystkich elementach, czy co?
mpen

1
@Zaznacz tak, są one zawsze zdefiniowane, z zerami lub pustymi łańcuchami jako wartości, jeśli nie ma niestandardowego stylu dla właściwości
Esailija 24.04.2013

34

Możesz zdefiniować funkcję małego pomocnika, aby zapewnić korzyści withbez niejasności:

var with_ = function (obj, func) { func (obj); };

with_ (object_name_here, function (_)
{
    _.a = "foo";
    _.b = "bar";
});

8
OMG, MOJA GŁOWA WYBUCHANA! bez niejasności? Musisz to zagłosować, stary!
Jarrod Dixon

@Jarrod: co jest w tym zabawnego ( is.gd/ktoZ )? Większość osób korzystających z tej witryny jest mądrzejsza ode mnie, więc wybacz mi, jeśli się mylę, ale wydaje się, że to zła informacja.
kruk

14
Ale to tylko dłuższy i trudniejszy do zrozumienia sposób działania: var _ = obj_name_here; _.a = "foo"; _.b = "bar;
Rene Saarsoo

3
Rene: Dzięki temu udostępnisz zmienną „_” zewnętrznemu zakresowi, co spowoduje potencjalne błędy. Ujawni również wszelkie zmienne tymczasowe użyte do obliczenia parametrów obiektu.
John Millikin,

30
Dostaniesz więcej błędów, with_będąc błotnistą podwójną wersją (function(_){ _.a="foo"; })(object_here);(standardowego sposobu symulowania bloków w stylu c / java). Użyj tego zamiast tego.
mk.


18

Nigdy nie używam, nie widzę powodu i nie polecam tego.

Problem withpolega na tym, że zapobiega wielu optymalizacjom leksykalnym może wykonać implementacja ECMAScript. Biorąc pod uwagę rozwój szybkich silników opartych na JIT, problem ten prawdopodobnie stanie się jeszcze ważniejszy w najbliższej przyszłości.

Może to wyglądać na withdopuszczenie czystszych konstrukcji (np. Wprowadzenie nowego zakresu zamiast zwykłego anonimowego opakowania funkcji lub zastąpienie pełnego aliasingu), ale tak naprawdę nie jest tego warte . Oprócz zmniejszonej wydajności zawsze istnieje niebezpieczeństwo przypisania do właściwości niewłaściwego obiektu (gdy właściwość nie zostanie znaleziona na obiekcie we wstrzykiwanym zakresie) i być może błędnego wprowadzenia zmiennych globalnych. IIRC, ta ostatnia kwestia motywuje Crockforda do zalecania unikania go with.


6
Wydajność straszydła często się psuje, prawie tak często jak globals ... Zawsze wydaje mi się dziwny, biorąc pod uwagę, że mówimy o JavaScript . Można by założyć, że poprawa wydajności jest naprawdę dramatyczna, aby zagwarantować tak dużą uwagę, ale ... Jeśli masz jakieś twarde liczby na temat kosztów with(){}konstrukcji takich jak te podane w innych odpowiedziach tutaj, w nowoczesnych przeglądarkach, chciałbym zobaczyć im!
Shog9

6
Dlaczego jest to dziwne w kontekście Javascript? :) I tak, to jest dramatyczne. Pomyśl o tym - implementacja musi ocenić wyrażenie w nawiasie, przekonwertować je na obiekt, wstawić z przodu bieżącego łańcucha zakresu, ocenić instrukcję wewnątrz bloku, a następnie przywrócić normalny łańcuch zakresu. To dużo pracy. To coś więcej niż proste wyszukiwanie właściwości, które można przekształcić w wysoce zoptymalizowany kod niskiego poziomu. Oto bardzo prosty wzorzec, który właśnie wykonałem (daj mi znać, jeśli znajdziesz jakieś błędy), pokazujący różnicę - gist.github.com/c36ea485926806020024
kangax

5
@kangax: Pochodzę ze środowiska C ++, w którym wielu programistów ma obsesję na punkcie niewielkiej wydajności w kodzie, nawet jeśli nie mają zauważalnego wpływu na wydajność większej procedury lub programu. Wydaje mi się to dziwne w kontekście JavaScript, gdzie tak duża część wydajności procedury może zależeć od implementacji maszyny wirtualnej. Widziałem kilka przypadków, w których programiści JS unikają, powiedzmy, anonimowej funkcji z powodu obaw związanych z kosztami instalacji, ale wydaje się, że jest to wyjątek, a nie reguła, zarezerwowana dla bardzo wrażliwych obszarów kodu.
Shog9

5
To powiedziawszy, masz całkowitą rację, jeśli chodzi o koszt with(){}: skonfigurowanie nowego zakresu za pomocą withjest niezwykle drogie w każdej testowanej przeze mnie przeglądarce. Chciałbyś tego uniknąć w każdym często wywoływanym kodzie. Ponadto Chrome okazał się dramatycznym hitem dla każdego kodu wykonywanego w with()zakresie. Co ciekawe, IE miał najlepszą charakterystykę wydajności kodu w with()blokach: uwzględnienie kosztu instalacji, with()zapewnia najszybszy sposób dostępu członka do maszyn wirtualnych IE6 i IE8 (choć ogólnie te maszyny wirtualne są najwolniejsze). Dobre rzeczy, dzięki ...
Shog9

5
FWIW: oto ten sam zestaw testów z uwzględnieniem kosztów konfiguracji: jsbin.com/imidu/edit Zmienny dostęp dla with()Chrome jest prawie o rząd wielkości wolniejszy i ponad dwukrotnie szybszy w IE ...!
Shog9

13

Visual Basic.NET ma podobną Withinstrukcję. Jednym z bardziej powszechnych sposobów, w jaki go używam, jest szybkie ustawienie szeregu właściwości. Zamiast:

someObject.Foo = ''
someObject.Bar = ''
someObject.Baz = ''

, Umiem pisać:

With someObject
    .Foo = ''
    .Bar = ''
    .Baz = ''
End With

To nie jest tylko kwestia lenistwa. Dzięki temu kod jest znacznie bardziej czytelny. I w przeciwieństwie do JavaScript, nie ma w tym dwuznaczności, ponieważ musisz poprzedzić wszystko, na co wpływa instrukcja, .kropką. Tak więc następujące dwa są wyraźnie odrębne:

With someObject
    .Foo = ''
End With

vs.

With someObject
    Foo = ''
End With

Pierwsza to someObject.Foo; ten drugi jest Fooobjęty zakresem zewnętrznym someObject .

Uważam, że brak rozróżnienia w JavaScript sprawia, że ​​jest on znacznie mniej przydatny niż wariant Visual Basic, ponieważ ryzyko niejednoznaczności jest zbyt wysokie. Poza tym withnadal jest potężnym pomysłem, który może poprawić czytelność.


2
Prawda prawda Nie odpowiada jednak na jego pytanie. Więc to nie temat.
Allain Lalonde

6
To również działało mi w głowie. ktoś musiał to powiedzieć . Dlaczego JavaScript nie może mieć tylko kropki.
Carson Myers

Zgadzam się, notacja kropkowa jest lepsza, szkoda, że ​​JavaScript jej nie używa. +1


7

Użycie „with” może sprawić, że kod będzie bardziej suchy.

Rozważ następujący kod:

var photo = document.getElementById('photo');
photo.style.position = 'absolute';
photo.style.left = '10px';
photo.style.top = '10px';

Możesz go wysuszyć w następujący sposób:

with(document.getElementById('photo').style) {
  position = 'absolute';
  left = '10px';
  top = '10px';
}

Myślę, że zależy to od tego, czy wolisz czytelność czy ekspresję.

Pierwszy przykład jest bardziej czytelny i prawdopodobnie zalecany dla większości kodów. Ale i tak większość kodu jest dość oswojona. Drugi jest nieco bardziej niejasny, ale wykorzystuje ekspresyjną naturę języka, aby zmniejszyć rozmiar kodu i zbędne zmienne.

Wyobrażam sobie, że ludzie, którzy lubią Javę lub C #, wybiorą pierwszy sposób (object.member), a ci, którzy wolą Ruby lub Python, wybiorą ten drugi sposób.


Ups, nie zdawałem sobie sprawy, że ktoś już rok temu napisał ten sam przykład. Pomijając problemy z wydajnością, „with” sprawia, że ​​kod DRY jest przyjemny kosztem nieco trudniejszego do odczytania. Myślę, że w przypadku współpracy z innymi programistami lub większością kodu produkcyjnego dobrą praktyką jest unikanie słowa kluczowego „z”. Ale jeśli pracujesz z programistami na poziomie eksperckim i rozumiesz, jak uniknąć potencjalnych nieefektywności, to na pewno idź do miasta z „z”.
Jonah

6

Myślę, że oczywistym zastosowaniem jest skrót. Jeśli np. Inicjujesz obiekt, po prostu zapisujesz dużo „ObjectName”. Coś w rodzaju „with-slots” lisp, które pozwalają pisać

(with-slots (foo bar) objectname
   "some code that accesses foo and bar"

co jest tym samym, co pisanie

"some code that accesses (slot-value objectname 'foo) and (slot-value objectname 'bar)""

Bardziej oczywiste jest, dlaczego jest to skrót, kiedy twój język pozwala na „Objectname.foo”, ale nadal.


1
wspaniale widzieć kod lisp! Myślę, że „z” w javascript jest oczywiście zainspirowane jego schematycznymi korzeniami jako językiem, ale, niestety, jesteś zwolennikiem publikowania LISP na pytanie javascript.
Fire Crow

1
Głosowanie na podstawie podstawowej zasady. „z” to fenomenalnie potężny konstrukt. Ale większość JS nie rozumie zamknięć i pisze na JS absurdalnie skomplikowane systemy dziedziczenia klas Java - więc skąd mieliby wiedzieć o potencjale metaprogramowania oferowanym przez „z”?
jared

Oczywiście with-slotswymaga określenia, których gniazd używasz, podczas gdy withużyjesz tych gniazd, które zostaną powiązane w czasie wykonywania.
Samuel Edwin Ward

6

Mając doświadczenie z Delphi, powiedziałbym, że używanie z powinno być ostateczną optymalizacją rozmiaru, prawdopodobnie wykonaną przez pewnego rodzaju algorytm minimalizujący javascript z dostępem do statycznej analizy kodu w celu weryfikacji jego bezpieczeństwa.

Problemy z określaniem zakresu, które możesz napotkać przy swobodnym używaniu instrukcji with , mogą być ogromnym bólem w ** i nie chciałbym, aby ktokolwiek przeżył sesję debugowania, aby dowiedzieć się, co on .. dzieje się w twoim kodzie , aby dowiedzieć się, że przechwycił element członkowski lub niepoprawną zmienną lokalną zamiast zamierzonej zmiennej globalnej lub zewnętrznej.

VB z instrukcją jest lepszy, ponieważ potrzebuje kropek, aby ujednoznacznić zakres, ale Delphi z instrukcją to naładowana broń z wyzwalaczem do włosów i wydaje mi się, że javascript jest wystarczająco podobny, aby uzasadnić to samo ostrzeżenie.


5
Javascript z instrukcją jest gorszy niż Delphi. W Delphi, z wykonuje tak samo szybko (jeśli nie szybciej) niż notacja object.member. W javascript, z musi przejść zakres, aby sprawdzić pasujące elementy, dzięki czemu zawsze jest wolniejszy niż notacja object.member.
Martijn

5

Używanie z nie jest zalecane i jest zabronione w trybie ścisłym ECMAScript 5. Zalecaną alternatywą jest przypisanie obiektu, którego właściwości chcesz uzyskać dostęp do zmiennej tymczasowej.

Źródło: Mozilla.org


4

Za pomocą instrukcji with można zmniejszyć rozmiar kodu lub dla członków klasy prywatnej, na przykład:

// demo class framework
var Class= function(name, o) {
   var c=function(){};
   if( o.hasOwnProperty("constructor") ) {
       c= o.constructor;
   }
   delete o["constructor"];
   delete o["prototype"];
   c.prototype= {};
   for( var k in o ) c.prototype[k]= o[k];
   c.scope= Class.scope;
   c.scope.Class= c;
   c.Name= name;
   return c;
}
Class.newScope= function() {
    Class.scope= {};
    Class.scope.Scope= Class.scope;
    return Class.scope;
}

// create a new class
with( Class.newScope() ) {
   window.Foo= Class("Foo",{
      test: function() {
          alert( Class.Name );
      }
   });
}
(new Foo()).test();

Instrukcja with jest bardzo przydatna, jeśli chcesz zmodyfikować zakres, co jest niezbędne do posiadania własnego zakresu globalnego, którym możesz manipulować w czasie wykonywania. Możesz nałożyć stałe lub niektóre często używane funkcje pomocnicze, takie jak np. „ToUpper”, „toLower” lub „isNumber”, „clipNumber” aso ..

O złej wydajności, którą często czytam: Określanie zakresu funkcji nie będzie miało żadnego wpływu na wydajność, w rzeczywistości w moim FF funkcja o zasięgu działa szybciej niż nieskalowana:

var o={x: 5},r, fnRAW= function(a,b){ return a*b; }, fnScoped, s, e, i;
with( o ) {
    fnScoped= function(a,b){ return a*b; };
}

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnRAW(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnScoped(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

Tak więc w wyżej wspomniany sposób użyta instrukcja with nie ma negatywnego wpływu na wydajność, ale jest dobra, ponieważ zmniejsza rozmiar kodu, co wpływa na użycie pamięci na urządzeniach mobilnych.


3

Używanie z powoduje również spowolnienie kodu w wielu implementacjach, ponieważ wszystko jest teraz zawinięte w dodatkowy zakres wyszukiwania. Nie ma uzasadnionego powodu do używania w JavaScript.


5
Przedwczesna optymalizacja. Nie rób pretensji „wolniej”, chyba że zmiażdżysz liczby; wszelkie koszty ogólne są prawdopodobnie trywialne zarówno dla współczesnych, jak i starożytnych implikacji js.
mk.

2
Zdecydowanie nie zgadzam się z twoją konkluzją, opartą na tym, co może nie stanowić problemu dla programistów innych niż ty.
Dave Van den Eynde

4
@mk: ok, tutaj liczba crunchingu: var obj={a:0,b:0,c:0};var d=+new Date;with(obj){for(var i=0;i<1000000;++i){a+=1;b+=1;c+=1}}+new Date-d;daje średnio 2500, podczas gdy var obj={a:0,b:0,c:0};var d=+new Date;for(var i=0;i<1000000;++i){obj.a+=1;obj.b+=1;obj.c+=1}+new Date-d;daje średnio 750, co sprawia, że ​​ten używa ponad 3 razy wolniej.
yorick

3
Po prostu uruchomiłem je w Chrome 23 w konsoli, kiedy to zobaczyłem. Otrzymałem wyniki 1138 dla withkodu i 903 bez. Z tą niewielką różnicą, nawet w ciasnej pętli, dokonałbym wyboru opartego na prostocie kodowania i łatwości refaktoryzacji dla poszczególnych przypadków, zanim martwię się o wydajność.
Plynx

3

Myślę, że instrukcja with może się przydać podczas konwersji języka szablonów na JavaScript. Na przykład JST w base2 , ale widziałem to częściej.

Zgadzam się, że można to zaprogramować bez oświadczenia o rezygnacji. Ponieważ jednak nie powoduje żadnych problemów, jest to zgodne z prawem użycie.


3

Jest dobry do umieszczania kodu, który działa w stosunkowo skomplikowanym środowisku, w kontenerze: używam go do tworzenia lokalnego powiązania dla „okna” i do uruchamiania kodu przeznaczonego dla przeglądarki internetowej.


3

Myślę, że dosłowne użycie obiektu jest interesujące, jak drop-in zamiast użycia zamknięcia

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       (function(info)
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       })(data[i]);
}

lub z oświadczeniem równoważnym zamknięcia

for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       with({info: data[i]})
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       }        
}

Myślę, że prawdziwym ryzykiem jest przypadkowe zminimalizowanie zmiennych, które nie są częścią instrukcji with, dlatego podoba mi się przekazanie dosłowności obiektu, możesz zobaczyć dokładnie, co będzie w dodanym kontekście w kodzie.


3

Stworzyłem funkcję „scalania”, która eliminuje niektóre z tych dwuznaczności w withinstrukcji:

if (typeof Object.merge !== 'function') {
    Object.merge = function (o1, o2) { // Function to merge all of the properties from one object into another
        for(var i in o2) { o1[i] = o2[i]; }
        return o1;
    };
}

Mogę go używać podobnie with, ale wiem, że nie wpłynie to na żaden zakres, na który nie mam wpływu.

Stosowanie:

var eDiv = document.createElement("div");
var eHeader = Object.merge(eDiv.cloneNode(false), {className: "header", onclick: function(){ alert("Click!"); }});
function NewObj() {
    Object.merge(this, {size: 4096, initDate: new Date()});
}

3

Dla niektórych krótkich kawałków kodu, chciałbym skorzystać z funkcji trygonometrycznych jak sin, cositd. W trybie stopni zamiast w trybie promienistej. W tym celu używam AngularDegreeobiektu:

AngularDegree = new function() {
this.CONV = Math.PI / 180;
this.sin = function(x) { return Math.sin( x * this.CONV ) };
this.cos = function(x) { return Math.cos( x * this.CONV ) };
this.tan = function(x) { return Math.tan( x * this.CONV ) };
this.asin = function(x) { return Math.asin( x ) / this.CONV };
this.acos = function(x) { return Math.acos( x ) / this.CONV };
this.atan = function(x) { return Math.atan( x ) / this.CONV };
this.atan2 = function(x,y) { return Math.atan2(x,y) / this.CONV };
};

Następnie mogę używać funkcji trygonometrycznych w trybie stopniowym bez dalszego szumu językowego w withbloku:

function getAzimut(pol,pos) {
  ...
  var d = pos.lon - pol.lon;
  with(AngularDegree) {
    var z = atan2( sin(d), cos(pol.lat)*tan(pos.lat) - sin(pol.lat)*cos(d) );
    return z;
    }
  }

Oznacza to: Używam obiektu jako zbioru funkcji, które włączam w ograniczonym regionie kodu dla bezpośredniego dostępu. Uważam to za przydatne.


nie jest dobrym pomysłem używanie withinstrukcji w ten sposób, po prostu utrudnia odczytanie kodu, ponieważ nie wiesz, która funkcja jest globalna, a która funkcja jest wywoływana w zakresie z obiektem, więc jeśli jakakolwiek funkcja nie została zdefiniowana w zakres obiektu, a następnie spróbuje uzyskać do niego dostęp w globalnej przestrzeni nazw
Saket Patel,

Zdając sobie sprawę z problemu określania zakresu, nadal uważam to za przydatne. Matematyk czytający kod chce bezpośrednio zobaczyć, że powyższa formuła jest zastosowaniem „prawa 4 kolejnych części” w trygonometrii sferycznej. Surowa alternatywa zaciemnia formułę: z = Math.atan2( Math.sin(d * Math.PI / 180), Math.cos( pol.lat * Math.PI / 180) * Math.tan( pos.lat * Math.PI / 180 ) - Math.sin( pol.lat * Math.PI / 180 ) * Math.cos( d * Math.PI / 180) ) * 180 / Math.PI;dałaby ten sam rezultat, ale to horror.
rplantiko

@rplantiko Należy pamiętać, że większość ludzi nie czuje się z tym dobrze. Więc jeśli nie piszesz kodu, którego nikt inny nigdy nie dotknie. Poza tym jestem prawie pewien, że pokażę ci kilka zastosowań, withktóre by Cię zaskoczyły.
Juan Mendes

2

Myślę, że użyteczność withmoże zależeć od tego, jak dobrze napisany jest twój kod. Na przykład, jeśli piszesz kod, który wygląda następująco:

var sHeader = object.data.header.toString();
var sContent = object.data.content.toString();
var sFooter = object.data.footer.toString();

możesz argumentować, że withpoprawi to czytelność kodu, wykonując następujące czynności:

var sHeader = null, sContent = null, sFooter = null;
with(object.data) {
    sHeader = header.toString();
    sContent = content.toString();
    sFooter = content.toString();
}

I odwrotnie, można argumentować, że naruszasz Prawo Demeter , ale z drugiej strony może nie. I dygresję =).

Przede wszystkim wiedz, że Douglas Crockford nie zaleca używania with. Zachęcam do zapoznania się z jego postem na blogu withi jego alternatywami tutaj .


Dzięki za odpowiedź, Tom. Przeczytałem zalecenie Crockforda i choć ma to sens, to idzie tak daleko. Podchodzę do pomysłu - poruszonego pośrednio przez doekmana - że prawdziwą mocą {} jest sposób, w jaki można go wykorzystać do manipulowania zasięgiem ...
Shog9

2

Po prostu nie rozumiem, w jaki sposób używanie z jest bardziej czytelne niż pisanie object.member. Nie sądzę, że jest mniej czytelny, ale nie sądzę, żeby był bardziej czytelny.

Jak powiedział lassevk, zdecydowanie mogę zobaczyć, w jaki sposób używanie z byłoby bardziej podatne na błędy niż używanie bardzo wyraźnej składni „object.member”.


1

Sprawdzanie poprawności formularza w javascript na W3schools http://www.w3schools.com/js/js_form_validation.asp, gdzie formularz obiektu jest „skanowany” w celu znalezienia danych wejściowych o nazwie „email”

Ale zmodyfikowałem go, aby uzyskać z KAŻDEJ formy wszystkie pola sprawdzane jako niepuste, niezależnie od nazwy lub ilości pól w formularzu. Cóż, przetestowałem tylko pola tekstowe.

Ale with () uprościło sprawę. Oto kod:

function validate_required(field)
{
with (field)
  {
  if (value==null||value=="")
    {
    alert('All fields are mandtory');return false;
    }
  else
    {
    return true;
    }
  }
}

function validate_form(thisform)
{
with (thisform)
  {
    for(fiie in elements){
        if (validate_required(elements[fiie])==false){
            elements[fiie].focus();
            elements[fiie].style.border='1px solid red';
            return false;
        } else {elements[fiie].style.border='1px solid #7F9DB9';}
    }

  }
  return false;
}

1

Widelec Coco w CoffeeScript ma withsłowo kluczowe, ale po prostu ustawia this(także zapisywalny jak @w CoffeeScript / Coco) na obiekt docelowy w bloku. Eliminuje to niejednoznaczność i osiąga zgodność z trybem ścisłym ES5:

with long.object.reference
  @a = 'foo'
  bar = @b

0

Oto dobre zastosowanie with: dodawanie nowych elementów do literału obiektu, na podstawie wartości przechowywanych w tym obiekcie. Oto przykład, którego właśnie użyłem dzisiaj:

Miałem zestaw możliwych płytek (z otworami skierowanymi do góry, dołu, lewej lub prawej), które można wykorzystać, i chciałem szybko dodać listę płytek, które zawsze będą umieszczane i blokowane na początku gry . Nie chciałem pisać dalej types.tbrdla każdego typu na liście, więc po prostu użyłem with.

Tile.types = (function(t,l,b,r) {
  function j(a) { return a.join(' '); }
  // all possible types
  var types = { 
    br:  j(  [b,r]),
    lbr: j([l,b,r]),
    lb:  j([l,b]  ),  
    tbr: j([t,b,r]),
    tbl: j([t,b,l]),
    tlr: j([t,l,r]),
    tr:  j([t,r]  ),  
    tl:  j([t,l]  ),  
    locked: []
  };  
  // store starting (base/locked) tiles in types.locked
  with( types ) { locked = [ 
    br,  lbr, lbr, lb, 
    tbr, tbr, lbr, tbl,
    tbr, tlr, tbl, tbl,
    tr,  tlr, tlr, tl
  ] } 
  return types;
})("top","left","bottom","right");

0

Możesz użyć z, aby uniknąć konieczności jawnego zarządzania arity podczas korzystania z pliku.j.j:

var modules = requirejs.declare([{
    'App' : 'app/app'
}]);

require(modules.paths(), function() { with (modules.resolve(arguments)) {
    App.run();
}});

Wdrożenie Requirej.declare:

requirejs.declare = function(dependencyPairs) {
    var pair;
    var dependencyKeys = [];
    var dependencyValues = [];

    for (var i=0, n=dependencyPairs.length; i<n; i++) {
        pair = dependencyPairs[i];
        for (var key in dependencyPairs[i]) {
            dependencyKeys.push(key);
            dependencyValues.push(pair[key]);
            break;
        }
    };

    return {
        paths : function() {
            return dependencyValues;
        },

        resolve : function(args) {
            var modules = {};
            for (var i=0, n=args.length; i<n; i++) {
                modules[dependencyKeys[i]] = args[i];
            }
            return modules;
        }
    }   
}

0

Jak zauważył Andy E. w komentarzach do odpowiedzi Shog9, to potencjalnie nieoczekiwane zachowanie występuje podczas używania withz literałem obiektu:

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with ({num: i}) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "[object Object]"
  }
}

Nie to, że nieoczekiwane zachowanie nie było już znakiem rozpoznawczymwith .

Jeśli nadal chcesz używać tej techniki, przynajmniej użyj obiektu z zerowym prototypem.

function scope(o) {
  var ret = Object.create(null);
  if (typeof o !== 'object') return ret;
  Object.keys(o).forEach(function (key) {
    ret[key] = o[key];
  });
  return ret;
}

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with (scope({num: i})) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "a"
  }
}

Ale to zadziała tylko w ES5 +. Nie używaj też with.


0

Pracuję nad projektem, który umożliwi użytkownikom przesyłanie kodu w celu zmodyfikowania zachowania części aplikacji. W tym scenariuszu używam withklauzuli, aby ich kod nie modyfikował niczego poza zakresem, z którym chciałbym, aby się z nimi bałaganili. (Uproszczona) część kodu, której używam do tego celu:

// this code is only executed once
var localScope = {
    build: undefined,

    // this is where all of the values I want to hide go; the list is rather long
    window: undefined,
    console: undefined,
    ...
};
with(localScope) {
    build = function(userCode) {
        eval('var builtFunction = function(options) {' + userCode + '}');
        return builtFunction;
    }
}
var build = localScope.build;
delete localScope.build;

// this is how I use the build method
var userCode = 'return "Hello, World!";';
var userFunction = build(userCode);

Ten kod zapewnia (w pewnym stopniu), że kod zdefiniowany przez użytkownika nie ma dostępu do żadnych obiektów o globalnym zasięgu, takich jak window żadna z moich lokalnych zmiennych poprzez zamknięcie.

Mówiąc krótko, wciąż muszę przeprowadzać statyczne sprawdzanie kodu przesłanego przez użytkownika, aby upewnić się, że nie używają innych podstępnych sposobów uzyskiwania dostępu do zasięgu globalnego. Na przykład następujący kod zdefiniowany przez użytkownika pobiera bezpośredni dostęp do window:

test = function() {
     return this.window
};
return test();


0

Mój

switch(e.type) {
    case gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED: blah
    case gapi.drive.realtime.ErrorType.CLIENT_ERROR: blah
    case gapi.drive.realtime.ErrorType.NOT_FOUND: blah
}

sprowadza się do

with(gapi.drive.realtime.ErrorType) {switch(e.type) {
    case TOKEN_REFRESH_REQUIRED: blah
    case CLIENT_ERROR: blah
    case NOT_FOUND: blah
}}

Czy możesz zaufać tak niskiej jakości kodowi? Nie, widzimy, że stało się absolutnie nieczytelne. Ten przykład niezaprzeczalnie udowadnia, że ​​nie ma potrzeby deklaracji rezygnacji, jeśli poprawię czytelność;)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.