Jak mogę używać goto w JavaScript?


127

Mam kod, który absolutnie muszę zaimplementować przy użyciu goto. Na przykład chcę napisać taki program:

start:
alert("RINSE");
alert("LATHER");
repeat: goto start

Czy można to zrobić w Javascript?


goto byłby wygodny dla skompilowanego javascript. Mam JVM napisane w JavaScript. Byłoby znacznie wydajniejsze i krótsze z instrukcją goto.
neoexpert

Odpowiedzi:


151

Absolutnie! Istnieje projekt o nazwie Summer of Goto, który pozwala na wykorzystanie pełnego potencjału JavaScript i zrewolucjonizuje sposób pisania kodu.

To narzędzie do wstępnego przetwarzania JavaScript umożliwia utworzenie etykiety, a następnie przejście do niej przy użyciu następującej składni:

[lbl] <label-name>
goto <label-name>

Na przykład przykład w pytaniu można zapisać w następujący sposób:

[lbl] start:
alert("LATHER");
alert("RINSE");
[lbl] repeat: goto start;

Zwróć uwagę, że nie jesteś ograniczony tylko do prostych, trywialnych programów, takich jak niekończący się LATHER RINSEcykl powtarzania - możliwości, jakie daje, gotosą nieskończone i możesz nawet Hello, world!wysłać wiadomość do konsoli JavaScript 538 razy, na przykład:

var i = 0;
[lbl] start:
console.log("Hello, world!");
i++;
if(i < 538) goto start;

Możesz przeczytać więcej o implementacji goto , ale zasadniczo wykonuje on pewne wstępne przetwarzanie JavaScript, które wykorzystuje fakt, że możesz symulować goto za pomocą oznaczonej whilepętli . Tak więc, pisząc „Hello, world!” program powyżej, zostanie przetłumaczony na coś takiego:

var i = 0;
start: while(true) {
  console.log("Hello, world!");
  i++;
  if(i < 538) continue start;
  break;
}

Istnieją pewne ograniczenia tego procesu wstępnego przetwarzania, ponieważ pętle while nie mogą rozciągać się na wiele funkcji lub bloków. Nie jest to jednak wielka sprawa - jestem pewien, że korzyści płynące z możliwości korzystania z gotoJavaScript absolutnie cię przytłoczy.

Cały powyższy link prowadzący do biblioteki goto.js to ALL DEAD, tutaj potrzebne są linki:

goto.js (nieskompresowany) --- parseScripts.js (nieskompresowany)

Z Goto.js :

PS Dla każdego, kto się zastanawia (jak dotąd w sumie zero osób), Summer of Goto to termin spopularyzowany przez Paula Irisha podczas omawiania tego skryptu i decyzji PHP o dodaniu goto do ich języka.

A tym, którzy nie od razu rozpoznają, że cała ta sprawa to żart, proszę mi wybaczyć. <- (ubezpieczenie).


10
@SurrealDreams To może być żart, ale w rzeczywistości działa. Możesz kliknąć linki jsFiddle i zobaczyć, że faktycznie działają.
Peter Olson

22
Artykuł, do którego
utworzyłeś

6
@PeterOlson, Ale stackoverflow ma pomóc ludziom w nauce programowania. Pytania i odpowiedzi powinny się przydać. Nikomu to nie pomaga.
GoldenNewby

5
@ShadowWizard Ta odpowiedź jest już otoczona wieloma zastrzeżeniami i ludźmi, którzy mówią o tym, dlaczego nie należy tego używać. Nie wstydzę się pozwalać ludziom, którzy świadomie to ignorują, ponieść konsekwencje takiego postępowania.
Peter Olson

8
+1 dla @AlexMills. Szczerze mówiąc, myślę, że gotoprawdopodobnie jest niewykorzystany. To tworzy bardzo ładne wzorce obsługi błędów. Cholera, używamy switch, co jest gotow ogóle poza nazwą i nikt nie boli brzucha.
0x1mason

122

Nie. Nie uwzględnili tego w ECMAScript:

ECMAScript nie zawiera instrukcji goto.


1
Zastanawiałem się, czy GOTO będzie przydatne podczas debugowania JavaScript. Afaik, tylko IE zapewnia GOTO w swoim debugerze ... i faktycznie znalazłem dla niego przypadek użycia, ale nie jestem pewien, czy może być przydatny ogólnie ... do przeskakiwania podczas debugowania JavaScript. Co myślisz?
Šime Vidas

3
@ Šime Vidas: Nie jestem pewien, czy debugowanie przy użyciu funkcji goto jest przydatne. Zasadniczo bawiłbyś się ze ścieżką kodu w sposób, który i tak nigdy nie nastąpiłby bez debugowania.
pimvdb

12
Szkoda ... IMHO gotoidealnie pasowałoby do koktajlu głupich "cech" javascript :)
Yuriy Nakonechnyy

4
gotojest jednak zastrzeżonym słowem kluczowym do użytku w przyszłości. Możemy tylko mieć nadzieję :)
Azmisov

4
gotoprzydałoby się, gdy chcesz powrócić z funkcji zagnieżdżonej. Na przykład, używając podkreślenia underscore.js, udostępniasz anonimową funkcję podczas iteracji po tablicach. Nie możesz wrócić z wnętrza takiej funkcji, więc goto end;byłoby przydatne.
Hubro

31

Właściwie widzę, że ECMAScript (JavaScript) CZY INDEED ma instrukcję goto. Jednak JavaScript goto ma dwa smaki!

Dwie odmiany goto w JavaScript są nazywane etykietami kontynuacji i przerwania. W JavaScript nie ma słowa kluczowego „goto”. Goto jest wykonywane w JavaScript przy użyciu słów kluczowych break i continue.

Jest to mniej więcej wyraźnie określone na stronie w3schools tutaj http://www.w3schools.com/js/js_switch.asp .

Uważam, że dokumentacja oznaczona jako kontynuacja i oznaczona jako przerwa jest nieco niezręcznie wyrażona.

Różnica między oznaczoną kontynuacją a oznaczoną przerwą polega na tym, gdzie można ich użyć. Oznaczony ciąg dalszy może być używany tylko w pętli while. Zobacz w3schools po więcej informacji.

===========

Innym podejściem, które zadziała, jest gigantyczne oświadczenie while z gigantycznym oświadczeniem przełącznika w środku:

while (true)
{
    switch (goto_variable)
    {
        case 1:
            // some code
            goto_variable = 2
            break;
        case 2:
            goto_variable = 5   // case in etc. below
            break;
        case 3:
            goto_variable = 1
            break;

         etc. ...
    }

}

9
„Oznaczony ciąg dalszy może być używany tylko w pętli while”. - Nie, oznakowane breaki continuemogą być używane również w forpętlach. Ale tak naprawdę nieone równoważne gotoz założeniem, że są zamknięte w strukturze powiązanej pętli (pętli), w porównaniu z gotoktórą oczywiście - w językach, które ją mają - przenoszą się gdziekolwiek.
nnnnnn

31

W klasycznym JavaScript do osiągnięcia tego typu kodu potrzebne są pętle do-while. Zakładam, że być może generujesz kod do czegoś innego.

Sposobem na zrobienie tego, podobnie jak przy zapisywaniu kodu bajtowego w JavaScript, jest umieszczenie każdego celu etykiety w „oznaczonym” do-while.

LABEL1: do {
  x = x + 2;
  ...
  // JUMP TO THE END OF THE DO-WHILE - A FORWARDS GOTO
  if (x < 100) break LABEL1;
  // JUMP TO THE START OF THE DO WHILE - A BACKWARDS GOTO...
  if (x < 100) continue LABEL1;
} while(0);

Każda oznaczona pętla do-while, której używasz w ten sposób, w rzeczywistości tworzy dwa punkty etykiety dla jednej etykiety. Jeden na górze i jeden na końcu pętli. Skakanie do tyłu używa kontynuacji, a skok do przodu używa przerwy.

// NORMAL CODE

MYLOOP:
  DoStuff();
  x = x + 1;
  if (x > 100) goto DONE_LOOP;
  GOTO MYLOOP;


// JAVASCRIPT STYLE
MYLOOP: do {
  DoStuff();
  x = x + 1;
  if (x > 100) break MYLOOP;
  continue MYLOOP;// Not necessary since you can just put do {} while (1) but it     illustrates
} while (0)

Niestety nie ma innej możliwości.

Normalny przykładowy kod:

while (x < 10 && Ok) {
  z = 0;
  while (z < 10) {
    if (!DoStuff()) {
      Ok = FALSE;
      break;
    }
    z++;
  }
  x++;
} 

Powiedzmy, że kod zostaje zakodowany do kodu bajtowego, więc teraz musisz umieścić kody bajtowe w JavaScript, aby symulować swój backend w jakimś celu.

Styl JavaScript:

LOOP1: do {
  if (x >= 10) break LOOP1;
  if (!Ok) break LOOP1;
  z = 0;
  LOOP2: do {
    if (z >= 10) break LOOP2;
    if (!DoStuff()) {
      Ok = FALSE;
      break LOOP2;
    }
    z++;
  } while (1);// Note While (1) I can just skip saying continue LOOP2!
  x++;
  continue LOOP1;// Again can skip this line and just say do {} while (1)
} while(0)

Więc użycie tej techniki działa dobrze w prostych celach. Poza tym niewiele więcej możesz zrobić.

W przypadku normalnego Javacript nie powinieneś używać goto ever, więc prawdopodobnie powinieneś unikać tej techniki tutaj, chyba że tłumaczysz konkretnie inny kod stylu, aby działał w JavaScript. Zakładam, że w ten sposób na przykład pobierają jądro Linuksa w JavaScript.

UWAGA! To wszystko jest naiwne wyjaśnienie. Aby uzyskać poprawne zaplecze kodu bajtowego w języku Js, należy również rozważyć sprawdzenie pętli przed wyprowadzeniem kodu. Wiele prostych pętli while można wykryć jako takich, a następnie możesz raczej użyć pętli zamiast goto.


1
continuew do ... whilepętli kontynuuje warunek sprawdzenia . W ten sposób gotoużycie wstecznego tutaj do ... while (0)nie działa. ecma-international.org/ecma-262/5.1/#sec-12.6.1
ZachB

1
Nie działa. Muszę, żeby let doLoopto zadziałało. I główna pętla: let doLoop = false; do { if(condition){ doLoop = true; continue; } } while (doLoop) github.com/patarapolw/HanziLevelUp/blob/ ...
Polv

15

To stare pytanie, ale skoro JavaScript jest ruchomym celem - w ES6 jest to możliwe w przypadku implementacji obsługującej właściwe wywołania ogonowe. W implementacjach z obsługą poprawnych wywołań ogonowych, możesz mieć nieograniczoną liczbę aktywnych wywołań ogonowych (tj. Wywołania ogonowe nie „powiększają stosu”).

ZA gotoMożna traktować jako wezwanie ogonowej bez parametrów.

Przykład:

start: alert("RINSE");
       alert("LATHER");
       goto start

można zapisać jako

 function start() { alert("RINSE");
                    alert("LATHER");
                    return start() }

Tutaj call do startjest na pozycji taila, więc nie będzie przepełnienia stosu.

Oto bardziej złożony przykład:

 label1:   A
           B
           if C goto label3
           D
 label3:   E
           goto label1

Najpierw podzieliliśmy źródło na bloki. Każda etykieta wskazuje początek nowego bloku.

 Block1
     label1:   A
               B
               if C goto label3
               D

  Block2    
     label3:   E
               goto label1

Musimy połączyć bloki razem za pomocą goto. W przykładzie blok E następuje po D, więc dodajemy a goto label3po D.

 Block1
     label1:   A
               B
               if C goto label2
               D
               goto label2

  Block2    
     label2:   E
               goto label1

Teraz każdy blok staje się funkcją, a każde goto staje się wywołaniem końcowym.

 function label1() {
               A
               B
               if C then return( label2() )
               D
               return( label2() )
 }

 function label2() {
               E
               return( label1() )
 }

Aby uruchomić program, użyj label1().

Przepisanie jest czysto mechaniczne i można je w razie potrzeby wykonać za pomocą systemu makr, takiego jak sweet.js.


„w ES6 jest to możliwe w przypadku implementacji obsługującej właściwe wywołania ogonowe”. Połączenia końcowe AFAIK są wyłączone we wszystkich głównych przeglądarkach.
JD,

Wydaje mi się, że Safari obsługuje prawidłowe wywołania ogona. W momencie udzielenia odpowiedzi możliwe było włączenie odpowiednich wywołań końcowych w Chrome za pomocą przełącznika wiersza poleceń. Miejmy nadzieję, że ponownie to rozważą - a przynajmniej zaczną wspierać wyraźnie zaznaczone wywołania ogonowe.
soegaard

Wyraźnie zaznaczone wołania ogonem prawdopodobnie uszczęśliwiłyby wszystkich.
JD

14
const
    start = 0,
    more = 1,
    pass = 2,
    loop = 3,
    skip = 4,
    done = 5;

var label = start;


while (true){
    var goTo = null;
    switch (label){
        case start:
            console.log('start');
        case more:
            console.log('more');
        case pass:
            console.log('pass');
        case loop:
            console.log('loop');
            goTo = pass; break;
        case skip:
            console.log('skip');
        case done:
            console.log('done');

    }
    if (goTo == null) break;
    label = goTo;
}

8

Co powiesz na forpętlę? Powtarzaj tyle razy, ile chcesz. Lub whilepętla, powtarzaj, aż warunek zostanie spełniony. Istnieją struktury kontrolne, które pozwolą Ci powtórzyć kod. Pamiętam, że GOTOw Basicu ... zrobił taki zły kod! Nowoczesne języki programowania zapewniają lepsze opcje, które możesz w rzeczywistości obsługiwać.


Nieskończona pętla produkcyjna: prototyp, zarysowanie, lepszy prototyp, zarysowanie, lepszy lepszy prototyp, zarysowanie. Utrzymanie często jest błędem. Niewiele kodu wymaga utrzymania. Większość kodu jest przepisywana, a nie konserwowana.
Pacerier

7

Można to zrobić, ale należy to starannie zaplanować. Weźmy na przykład następujący program QBASIC:

1 A = 1; B = 10;
10 print "A = ",A;
20 IF (A < B) THEN A = A + 1; GOTO 10
30 PRINT "That's the end."

Następnie utwórz JavaScript, aby najpierw zainicjować wszystkie zmienne, a następnie wykonaj początkowe wywołanie funkcji, aby rozpocząć przewijanie piłki (wykonujemy to początkowe wywołanie funkcji na końcu) i skonfiguruj funkcje dla każdego zestawu linii, o których wiesz, że zostaną wykonane w jedna jednostka.

Postępuj zgodnie z tym z początkowym wywołaniem funkcji ...

var a, b;
function fa(){
    a = 1;
    b = 10;
    fb();
}
function fb(){
    document.write("a = "+ a + "<br>");
    fc();
}
function fc(){
    if(a<b){
        a++;
        fb();
        return;
    }
    else
    {
    document.write("That's the end.<br>");
    }
}
fa();

Wynik w tym przypadku to:

a = 1
a = 2
a = 3
a = 4
a = 5
a = 6
a = 7
a = 8
a = 9
a = 10
That's the end.

@JonHarrop Czy istnieje maksymalny rozmiar stosu, który JavaScript może obsłużyć, zanim stos się przepełni?
Eliseo d'Annunzio

1
Tak i wydaje się być bardzo mały. Dużo mniejszy niż jakikolwiek inny język, jakiego kiedykolwiek używałem.
JD

7

Generalnie wolałbym nie używać GoTo ze względu na słabą czytelność. Dla mnie jest to zła wymówka dla programowania prostych funkcji iteracyjnych zamiast konieczności programowania funkcji rekurencyjnych, a nawet lepiej (jeśli obawiamy się takich rzeczy jak przepełnienie stosu), ich prawdziwych iteracyjnych alternatyw (które czasami mogą być złożone).

Coś takiego zrobiłoby:

while(true) {
   alert("RINSE");
   alert("LATHER");
}

To właśnie jest nieskończona pętla. Wyrażenie („prawda”) w parantezach klauzuli while jest tym, co silnik JavaScript będzie sprawdzał - a jeśli wyrażenie jest prawdziwe, pętla będzie działać. Zapisanie tutaj „prawda” zawsze daje wartość „prawda”, stąd nieskończona pętla.


7

Jasne, używając switchkonstrukcji, którą możesz zasymulować gotow JavaScript. Niestety język nie zapewnia goto, ale jest to wystarczająca wymiana.

let counter = 10
function goto(newValue) {
  counter = newValue
}
while (true) {
  switch (counter) {
    case 10: alert("RINSE")
    case 20: alert("LATHER")
    case 30: goto(10); break
  }
}

5

Powinieneś przeczytać kilka tutoriali JS jak ten jeden .

Nie jestem pewien, czy gotow ogóle istnieje w JS, ale tak czy inaczej, zachęca to do złego stylu kodowania i należy go unikać.

Mógłbyś:

while ( some_condition ){
    alert('RINSE');
    alert('LATHER');
}

4

Możesz w prosty sposób użyć funkcji:

function hello() {
    alert("RINSE");
    alert("LATHER");
    hello();
}

5
To naprawdę zły pomysł, ponieważ będzie przekazywał adres zwrotny na stos wywołań, dopóki systemowi nie zabraknie pamięci.
Paul Hutchinson

1
Rzeczywiście, użytkownik będzie miał CTRL-ALT-DELETEd na długo przed niekończącymi się modalnymi dialogami RINSE-LATHER!
Shayne

4

Aby uzyskać funkcjonalność podobną do goto, zachowując czystość stosu wywołań, używam tej metody:

// in other languages:
// tag1:
// doSomething();
// tag2:
// doMoreThings();
// if (someCondition) goto tag1;
// if (otherCondition) goto tag2;

function tag1() {
    doSomething();
    setTimeout(tag2, 0); // optional, alternatively just tag2();
}

function tag2() {
    doMoreThings();
    if (someCondition) {
        setTimeout(tag1, 0); // those 2 lines
        return;              // imitate goto
    }
    if (otherCondition) {
        setTimeout(tag2, 0); // those 2 lines
        return;              // imitate goto
    }
    setTimeout(tag3, 0); // optional, alternatively just tag3();
}

// ...

Należy pamiętać, że ten kod działa wolno, ponieważ wywołania funkcji są dodawane do kolejki limitów czasu, która jest oceniana później, w pętli aktualizacji przeglądarki.

Pamiętaj również, że możesz przekazywać argumenty (używając setTimeout(func, 0, arg1, args...)w przeglądarce nowszej niż IE9 lub setTimeout(function(){func(arg1, args...)}, 0)w starszych przeglądarkach.

AFAIK, nigdy nie powinieneś natrafiać na przypadek, który wymaga tej metody, chyba że musisz wstrzymać nierównoległą pętlę w środowisku bez obsługi async / await.


1
Paskudny. Kocham to. FWIW, ta technika nazywa się trampolinem.
JD

Mogę zweryfikować, że to działa. Ręcznie transpiluję niektóre HP RPN z instrukcjami GTO, co prowadzi do głębokiej rekurencji, gdy są implementowane jako wywołania funkcji; pokazana powyżej metoda setTimeout () zwija stos i rekurencja nie jest już problemem. Ale jest znacznie wolniejszy. Aha, i robię to w Node.js.
Jeff Lowery,

3

zaczynam i kończą wszystkie zamknięcia rodziców

var foo=false;
var loop1=true;
LABEL1: do {var LABEL1GOTO=false;
    console.log("here be 2 times");
    if (foo==false){
        foo=true;
        LABEL1GOTO=true;continue LABEL1;// goto up
    }else{
        break LABEL1; //goto down
    }
    console.log("newer go here");
} while(LABEL1GOTO);

3
// example of goto in javascript:

var i, j;
loop_1:
    for (i = 0; i < 3; i++) { //The first for statement is labeled "loop_1"
        loop_2:
            for (j = 0; j < 3; j++) { //The second for statement is labeled "loop_2"
                if (i === 1 && j === 1) {
                    continue loop_1;
                }
                console.log('i = ' + i + ', j = ' + j);
            }
        }

2

Innym alternatywnym sposobem osiągnięcia tego samego jest użycie wywołań ogonowych. Ale nie mamy czegoś takiego w JavaScript. Ogólnie rzecz biorąc, goto jest wykonywane w JS przy użyciu poniższych dwóch słów kluczowych. przerwij i kontynuuj , odniesienie: Goto Statement w JavaScript

Oto przykład:

var number = 0;
start_position: while(true) {
document.write("Anything you want to print");
number++;
if(number < 100) continue start_position;
break;
}
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.