Co robi „! -” w JavaScript?

376

Mam ten fragment kodu (wzięty z tego pytania ):

var walk = function(dir, done) {
    var results = [];

    fs.readdir(dir, function(err, list) {
        if (err)
            return done(err);

        var pending = list.length;

        if (!pending) 
            return done(null, results);

        list.forEach(function(file) {
            file = path.resolve(dir, file);
            fs.stat(file, function(err, stat) {
                if (stat && stat.isDirectory()) {
                    walk(file, function(err, res) {
                        results = results.concat(res);

                        if (!--pending)
                            done(null, results);
                    });
                } else {
                    results.push(file);

                    if (!--pending) 
                        done(null, results);
                }
            });
        });
    });
};

Staram się podążać za tym i myślę, że rozumiem wszystko, z wyjątkiem końca, gdzie jest napisane !--pending. W tym kontekście, co robi to polecenie?

Edycja: Doceniam wszystkie dalsze komentarze, ale na pytanie udzielono odpowiedzi wiele razy. W każdym razie dzięki!

Kieran E.
źródło
222
To wspaniały sposób na pomylenie kolejnej osoby z kodem.
Eric J.,
240
!~--[value] ^ trueNazywam taki kod „zabezpieczeniem pracy”
TbWill4321,
63
To mi przypomina Jaka jest nazwa -->operatora?
Soner Gönül,
36
@ TbWill4321 Gdybym robił przegląd kodu, byłoby to dokładnie przeciwne do bezpieczeństwa pracy.
corsiKa,
8
Operatory w połączeniu z nazwą zmiennej sprawia, że ​​nie jest tak źle. Każdy doświadczony programista Javascript nie potrzebuje więcej niż kilku sekund, aby wiedzieć, co robi.
Christiaan Westerbeek,

Odpowiedzi:

537

! odwraca wartość i daje przeciwną wartość logiczną:

!true == false
!false == true
!1 == false
!0 == true

--[value] odejmuje jeden (1) od liczby, a następnie zwraca tę liczbę do pracy z:

var a = 1, b = 2;
--a == 0
--b == 1

Tak, !--pendingodejmuje jeden z toku, a następnie powraca przeciwieństwo jego truthy / wartość falsy (czy nie jest 0).

pending = 2; !--pending == false 
pending = 1; !--pending == true
pending = 0; !--pending == false

I tak, postępuj zgodnie z ProTip. Może to być powszechny idiom w innych językach programowania, ale w przypadku większości deklaratywnych programów JavaScript wygląda to zupełnie obco.

TbWill4321
źródło
623
ProTip ™: Nigdy nie rób tego w swoim kodzie, to śmieszne.
Naftuli Kay,
17
Nie wspominając, że --działa tylko na zmienne; nie można zastosować go ogólnie do wartości.
deltab,
10
@ dokładniej: validSimpleAssignmentTargets. Obejmuje to odniesienia do identyfikatora i odwołania do właściwości.
Bergi,
19
--0i --1nie będzie działać. Literały liczbowe nie są prawidłowym wyrażeniem po lewej stronie
PSWai,
7
@Pharap: Uh, mam więcej problemów z analizowaniem tej i > -1rzeczy niż i--(i tak przy okazji, zapomniałeś i = init() - 1). Do tego służą idiomy… i każdy programista powinien się ich nauczyć.
Bergi,
149

To nie jest operator specjalny, to 2 standardowe operatory jeden po drugim:

  1. Dekrementacja przedrostka ( --)
  2. Logiczne not ( !)

Powoduje pendingto zmniejszenie wartości, a następnie sprawdzenie, czy jest równa zero.

Amit
źródło
8
Jestem zdecydowanie nowy, ale dlaczego jest tak lepszy w użyciu if(pending === 1)?
Kieran E.
46
@KieranE, to nie jest, to skrót, który całkowicie łamie zasadę „czytelność jest królem”.
Ryan
23
@KieranE nie chodzi o bycie lepszym, jest inaczej. Mutuje zmienną (zmniejsza ją o 1), a następnie używa nowej wartości do testowania
Amit
7
@KieranE, jest to równoważne z if( pending === 1 ) { done... } else { pending = pending - 1; }.
CompuChip,
7
@CompuChip Wygląda na to, że - czekanie byłoby zrobione w każdym przypadku, więc kod jest bardziej podobny do: if (! Pending) {--pending; itd.} else {- w oczekiwaniu; itp.} Opiera się na developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Paul Russell
109

Szereg odpowiedzi opisuje, co robi to polecenie, ale nie wyjaśnia , dlaczego tak się tutaj dzieje.

Pochodzę ze świata C i czytam !--pendingjako „odliczaj pendingi sprawdzam, czy wynosi zero”, nie myśląc o tym. Jest to idiom, który moim zdaniem powinien znać programiści w podobnych językach.

Ta funkcja służy readdirdo uzyskania listy plików i podkatalogów, które zbiorczo nazywam „wpisami”.

Zmienna pendingśledzi, ile z nich pozostało do przetworzenia. Zaczyna się od długości listy i odlicza w dół do zera podczas przetwarzania każdego wpisu.

Wpisy te mogą być przetwarzane poza kolejnością, dlatego konieczne jest odliczanie, a nie zwykła pętla. Po przetworzeniu wszystkich wpisów oddzwanianie donejest wywoływane w celu powiadomienia pierwotnego dzwoniącego o tym fakcie.

W pierwszym wywołaniu donejest poprzedzone znakiem return, nie dlatego, że chcemy zwrócić wartość, ale po prostu, aby funkcja przestała działać w tym momencie. Byłoby czystszym kodem upuścić returni umieścić alternatywę w else.

Stig Hemmer
źródło
13
@Bergi to dobrze znany idiom w świecie C, ale nie jest to idiomatyczny Javascript. Dla każdego podwyrażenia różne ekosystemy będą miały różnorodne różne, niekompatybilne idiomy, jak to się tam robi. Używanie idiomów z „obcych” języków jest raczej nieprzyjemnym zapachem kodu.
Peteris,
3
@Peteris: Jest to idiom we wszystkich językach podobnych do C, które mają operator dekrementacji. Oczywiście czasami istnieją inne, lepsze sposoby dostępne w języku, ale nie sądzę, że JS ma jakieś specjalne idiomy liczenia.
Bergi,
6
Znaczna część społeczności Javascript, w tym wpływowi liderzy, tacy jak Douglas Crockford, opowiadają się za unikaniem całkowicie operatorów unrecrement i decrement . Nie zamierzam tutaj kłócić się o to, czy są poprawne, czy nie; moim celem jest jedynie wskazanie, że ponieważ istnieje kontrowersja, kod podobny !--pendingzawodzi wiele linters i wytycznych dotyczących stylu. Wystarczy mi powiedzieć, że prawdopodobnie nie jest to idiomatyczne (niezależnie od tego, czy alternatywa jest „lepsza”).
GrandOpener,
3
@GrandOpener „Idiomatic” oznacza „wyrażenie powszechnie rozumiane przez native speakerów”. Predecrement jest zdecydowanie dobrze zrozumiany dla użytkowników języków podobnych do C, a więc i tak jest „! (- x)”. To, czy używanie czegoś jest dobrym pomysłem, jest całkowicie odrębnym pytaniem od tego, jak idiomatyczne jest. (Np. Bardzo wątpię, że spotkasz wielu programistów dowolnego języka, którzy nie rozumieją, co robi „goto”, pomimo często wyrażanej opinii, że umieszczenie go w kodzie mieści się między „Lust” a „Murder” na liście z ośmiu grzechów głównych).
jmbpiano,
3
@Pharap To dlatego, że C # i Java nie konwertują intsię booldomyślnie i nie ma absolutnie nic wspólnego z używaniem !--.
Agop
36

To jest skrót.

! nie jest".

-- zmniejsza wartość.

!--Sprawdza więc , czy wartość uzyskana z zanegowania wyniku zmniejszenia wartości jest fałszywa.

Spróbuj tego:

var x = 2;
console.log(!--x);
console.log(!--x);

Pierwszy jest fałszywy, ponieważ wartość x wynosi 1, drugi jest prawdziwy, ponieważ wartość x wynosi 0.

Uwaga dodatkowa: !x--najpierw sprawdza, czy x jest fałszem, a następnie go zmniejsza.

Lucas
źródło
RE notatka dodatkowa - jesteś pewien? Wygląda na to, że post-fix ma wyższy priorytet niż !, podczas gdy pre-fix ma niższy priorytet. JavaScript
Pierwszeństwo
2
Tak. (Spróbuj var x = 2; console.log(!x--); console.log(!x--); console.log(!x--);). Chociaż poprawka --po wykonaniu może zostać wykonana jako pierwsza, jej wartością zwracaną jest wartość zmiennej przed zmniejszeniem ( Decrement Opperator ).
Lucas,
Myślę, że chciałeś powiedzieć „ !--zwraca negację wyniku zmniejszenia wartości”. Tylko kod zewnętrzny, taki jak console.log(), sprawdza, czy jego zawartość jest zgodna z prawdą.
mareoraft,
31

!jest operatorem JavaScript NOT

--jest operatorem przedsprzedaży. Więc,

x = 1;
if (!x) // false
if (!--x) // becomes 0 and then uses the NOT operator,
          // which makes the condition to be true
Sterling Archer
źródło
8
WTH jest „stwierdzeniem fałszerstwa”?
Bergi,
5
@Bergi Zakładam, że tak naprawdę wiesz, co to znaczy, ale w przypadku, gdy nie wiesz (lub nikt inny nie wie), tutaj jest wyjaśnienie JavaScript, które również dobrze tłumaczy na inne języki z tą koncepcją (np. Python).
Dannnno,
1
@Pharap to nie moja odpowiedź, więc nie zamierzam jej dotykać.
Dannnno,
2
@Dannnno: Cóż, wiem, czym są wartości fałszowania i jakie są oświadczenia , i dlatego wiem, że w JS nie ma czegoś takiego jak „oświadczenie fałszu”. Zakładam, że to nie odnosi się do terminu logicznego .
Bergi,
1
Nie rozumiem, w jaki sposób --operator mógłby pracować ze stałą ... może miałeś na myśli --xzamiast --0?
SJuan76,
24
if(!--pending)

znaczy

if(0 == --pending)

znaczy

pending = pending - 1;
if(0 == pending)
James Turner
źródło
4
I to jest wyraźny sposób na napisanie kodu. Oczywiście wolę if(0 == pending)ze względu na potencjał literówek. if(0 = pending)jest błędem składni. if(pending = 0)to zadanie, które spowoduje mylące zachowanie gotowego kodu.
Theo Brinkman,
3
@TheoBrinkman: tak naprawdę if(0 = pending)nie jest to błąd składniowy - analizuje się dobrze - ale błąd referencyjny, ponieważ 0nie można go przypisać (zob. ECMA-262 (wydanie 6), sekcja 12.14.1).
hmakholm pozostawił Monikę
13

To nie operator, a za nim miejscowy środek do dekrementacji.

Więc jeśli pendingbył liczbą całkowitą o wartości 1:

val = 1;
--val; // val is 0 here
!val // evaluates to true
Brendan Abel
źródło
11

Po prostu maleje pendingo jeden i uzyskuje logiczne uzupełnienie (negację). Logicznym uzupełnieniem dowolnej liczby różnej od 0 jest false, dla 0 jest true.

MinusFour
źródło
Zauważ, że „negacja” ma inne znaczenie dla liczb niż na boolach
Bergi,
1
W porządku, użyję innego terminu. Nie jestem jednak pewien, czy tak jest lepiej.
MinusFour
11

Wyjaśnienie

To jest 2 operatorów, a !i a--

!--x 

Zatem --zmniejszenie x o 1, a następnie !zwraca true, jeśli x jest teraz 0 (lub NaN ...), false, jeśli nie jest. Możesz przeczytać ten idiom coś w stylu „zmniejszamy x, a jeśli to czyni zero…”

Jeśli chcesz, aby był bardziej czytelny, możesz:

var x = 1
x = x - 1   
if(!x){ //=> true
    console.log("I understand `!--` now!") 
}
x //=> 0

Wypróbuj to:

/* This is an example of the above, you can read this, but it is not needed for !-- */function interactive(a){$("span.code").keydown(function(e){if(13==(e.keyCode||e.which)){var t=$(this);t.clone().html("code").insertAfter(t.next().next()).show().focus().after(template.clone().removeClass("result-template").show()).next().after("<br>"),interactive(),e.preventDefault()}}).keyup(function(e){13!=(e.keyCode||e.which)&&run()})}var template=$(".result-template").hide(),code=$("span.code");code.attr("contenteditable","true").each(function(e,t){template.clone().removeClass("result-template").insertAfter(t)}),interactive(),$.fn.reduce=[].reduce;function run(){var b=!1,context={};$("span.code").each(function(){var a=$(this),res=a.next().show().removeClass("error");try{with(context)res.html(b?"":"  //=> "+eval(a.text()))}catch(e){b=e,res.html("  Error: "+b.message).addClass("error")}})};run();
/* This is an example of the above, you can read this, but it is not needed for !-- */span.result.error{display:block;color:red}.code{min-width:10px}body{font-family:Helvetica,sans-serif}
<!-- This is an example of the above, you can read this, but it is not needed for `!--` --><span class="result result-template"> //=> unknown </span> <h2>Edit This Code:</h2><code><span class="code">x = 1</span><br><span class="code">!--x</span><br><span class="code"> x </span><br></code> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Fiddle (kod wypróbowania)

Ben Aubin
źródło
8

Prawdziwym problemem jest tutaj brak odstępu między dwoma operatorami !i --.

Nie wiem, dlaczego ludzie mają w głowie, że nigdy nie można użyć spacji za !operatorem. Myślę, że wynika to ze sztywnego stosowania mechanicznych reguł białych znaków zamiast zdrowego rozsądku. Prawie każdy standard kodowania, który widziałem, zabrania spacji po wszystkich jednostkowych operatorach, ale dlaczego?

Jeśli zdarzały się przypadki, w których wyraźnie potrzebujesz tej przestrzeni, jest to jedna sprawa .

Rozważ ten fragment kodu:

if (!--pending)
    done(null, results);

Nie tylko są !i --zmiksowane razem, masz to( je również przeciwko niemu. Nic dziwnego, że trudno powiedzieć, co jest z tym związane.

Trochę więcej białych znaków sprawia, że ​​kod jest znacznie wyraźniejszy:

if( ! --pending )
    done( null, results );

Jasne, jeśli jesteś przyzwyczajony do reguł mechanicznych, takich jak „brak spacji w parenach” i „brak spacji po jednostkowym operatorze”, może to wydawać się nieco obce.

Ale spójrz, jak dodatkowe białe znaki grupują i oddzielają różne części ifwyrażenia i wyrażenia: Masz --pending, więc --jest to oczywiście jego własny operator i ściśle z nim związany pending. (Zmniejsza pendingi zwraca obniżony wynik.) Następnie zostałeś !oddzielony od tego, więc jest to oczywiście wyraźny operator, negujący wynik. Wreszcie, masz if(i )otaczasz całym wyrażeniem, aby uczynić to ifstwierdzeniem.

I tak, usunąłem spację między ifi (, ponieważ ( należy do if. Nie (jest to część jakiejś (!--składni, ponieważ wydaje się, że jest w oryginale, (jeśli jest częścią składni ifsamej instrukcji.

Biała spacja służy tutaj do przekazywania znaczenia , zamiast przestrzegać jakiegoś standardu kodowania mechanicznego.

Michael Geary
źródło
1
Wersja z białymi znakami jest dla mnie bardziej czytelna. Kiedy po raz pierwszy zobaczyłem pytanie, które zakładałem, !--to jakiś operator javascript, którego nie znałem. Dlaczego nie użyć nawiasów, aby uczynić je jeszcze bardziej wyraźnym? if(!(--pending))
emory
1
Brak prowadnice stylu Jestem świadomy zakazują korzystania ++lub --, ale wielu nie zakazują korzystania niż istotne pomieszczenia, takie jak po !operatora przedrostka i mniej istotnych nawiasach.
1
Odstępy po jednoargumentowych operatorach są odradzane, ponieważ oddziela je od wyrażenia, na którym operuje. Chciałbyś, żeby było czytane razem ... przynajmniej imho
aldrin