Que fait «! -» en JavaScript?

376

J'ai ce morceau de code (tiré de cette question ):

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);
                }
            });
        });
    });
};

J'essaie de le suivre, et je pense que je comprends tout, sauf vers la fin où il est dit !--pending. Dans ce contexte, que fait cette commande?

Edit: J'apprécie tous les autres commentaires, mais la question a été répondue à plusieurs reprises. Merci quand même!

Kieran E
la source
222
C'est une merveilleuse façon de confondre la prochaine personne à maintenir le code.
Eric J.
240
!~--[value] ^ trueJ'appelle un code comme celui-ci "sécurité d'emploi"
TbWill4321
63
Cela me rappelle Quel est le nom de l' -->opérateur?
Soner Gönül
36
@ TbWill4321 Si je faisais la révision du code, ce serait exactement l'opposé de la sécurité d'emploi.
corsiKa
8
Les opérateurs combinés avec le nom de variable fait n'est pas trop mal. Tout programmeur Javascript chevronné n'aura pas besoin de plus de quelques secondes pour savoir ce qu'il fait.
Christiaan Westerbeek

Réponses:

537

! inverse une valeur et vous donne le booléen opposé:

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

--[value] soustrait un (1) d'un nombre, puis renvoie ce nombre avec lequel travailler:

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

Donc, !--pendingsoustrait un de l'attente, puis retourne le contraire de sa valeur de vérité / fausse (que ce soit ou non 0).

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

Et oui, suivez le ProTip. Cela peut être un idiome courant dans d'autres langages de programmation, mais pour la plupart des programmes JavaScript déclaratifs, cela semble assez étranger.

TbWill4321
la source
623
ProTip ™: Ne faites jamais cela dans votre code, c'est ridicule.
Naftuli Kay
17
Cela ne mentionne pas que --n'agit que sur les variables; vous ne pouvez pas l'appliquer aux valeurs en général.
deltab
10
@deltab: validSimpleAssignmentTargets, pour être exact. Cela inclut les références d'identifiant et les références de propriété.
Bergi
19
--0et --1ne fonctionnera pas. Les littéraux numériques ne sont pas une expression valide du côté gauche
PSWai
7
@Pharap: Euh, j'ai plus de mal à analyser cette i > -1chose que i--(et d'ailleurs vous avez oublié i = init() - 1). C'est à cela que servent les idiomes… et chaque programmeur devrait les apprendre.
Bergi
149

Ce n'est pas un opérateur spécial, c'est 2 opérateurs standard l'un après l'autre:

  1. Un préfixe décrément ( --)
  2. Un pas logique ( !)

Cela provoque pendingune décrémentation, puis un test pour voir s'il est nul.

Amit
la source
8
Je suis définitivement nouveau dans ce domaine, mais pourquoi est-ce supérieur à l'utilisation if(pending === 1)?
Kieran E
46
@KieranE, ce n'est pas le cas, c'est un raccourci qui enfreint complètement la règle de "la lisibilité est reine".
Ryan
23
@KieranE ce n'est pas d'être supérieur, c'est différent. Il mute la variable (la diminue de 1), puis utilise la nouvelle valeur pour tester
Amit
7
@KieranE, c'est équivalent à if( pending === 1 ) { done... } else { pending = pending - 1; }.
CompuChip
7
@CompuChip Il semble que --pending soit fait dans tous les cas, donc le code ressemble plus à: if (! Pending) {--pending; etc.} else {--pending; etc.} Ceci est basé sur developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Paul Russell
109

Un certain nombre de réponses décrivent ce que fait cette commande, mais pas pourquoi elle est effectuée de cette façon ici.

Je viens du monde C, et je lis !--pendingcomme "compte à rebourspending et vérifie si c'est zéro" sans vraiment y penser. C'est un idiome que je pense que les programmeurs dans des langages similaires devraient connaître.

La fonction utilise readdirpour obtenir une liste de fichiers et de sous-répertoires, que j'appellerai collectivement «entrées».

La variable pendinggarde la trace du nombre de ceux qui restent à traiter. Il commence comme la longueur de la liste et compte vers le bas vers zéro lorsque chaque entrée est traitée.

Ces entrées peuvent être traitées dans le désordre, c'est pourquoi il est nécessaire de décompter plutôt que de simplement utiliser une simple boucle. Lorsque toutes les entrées ont été traitées, le rappel doneest appelé pour informer l'appelant d'origine de ce fait.

Dans le premier appel à doneest précédé de return, non pas parce que nous voulons retourner une valeur, mais simplement pour arrêter l'exécution de la fonction à ce point. Il aurait été préférable de supprimer le code returnet de mettre l'alternative dans un else.

Stig Hemmer
la source
13
@Bergi c'est un idiome bien connu dans le monde C, mais ce n'est pas du Javascript idiomatique. Pour une sous-expression donnée, différents écosystèmes auront une variété d'idiomes différents et incompatibles, comment «cela se fait» là-bas. L'utilisation d'idiomes de langues «étrangères» est une odeur de code plutôt mauvaise.
Peteris
3
@Peteris: C'est un idiome dans tous les langages de type C qui ont l'opérateur de décrémentation. Bien sûr, il existe parfois de meilleurs moyens différents dans une langue, mais je ne pense pas que JS ait des idiomes de comptage spéciaux.
Bergi
6
Il y a une partie importante de la communauté Javascript, y compris des leaders influents tels que Douglas Crockford, qui préconisent d'éviter complètement les opérateurs d'incrémentation et de décrémentation unaires . Je ne vais pas discuter ici de leur exactitude ou non; mon intention est seulement de souligner que parce que la controverse existe, le code comme !--pendingéchouera de nombreux linters et directives de style. Cela me suffit pour dire que ce n'est probablement pas idiomatique (que l'alternative soit "meilleure").
GrandOpener
3
@GrandOpener "Idiomatic" signifie "une expression couramment comprise par les locuteurs natifs". La prédécrémentation est certainement bien comprise des utilisateurs de langages de type C et, par extension, il en va de même pour "! (- x)". Que ce soit ou non en utilisant quelque chose est une bonne IDEA® est une question entièrement distincte de la façon dont il est idiomatiques. (Par exemple, je doute fortement que vous rencontrerez de nombreux programmeurs de n'importe quelle langue qui ne comprennent pas ce que fait "goto", malgré l'opinion fréquemment exprimée selon laquelle l'inclure dans votre code se situe quelque part entre "Lust" et "Murder" sur la liste des huit péchés capitaux.)
jmbpiano
3
@Pharap C'est parce que C # et Java ne se convertissent intpas boolimplicitement, et n'a absolument rien à voir avec l'utilisation de !--.
Agop
36

C'est un raccourci.

! n'est pas".

-- décrémente une valeur.

!--Vérifie donc si la valeur obtenue en annulant le résultat de la décrémentation d'une valeur est fausse.

Essaye ça:

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

La première est fausse, car la valeur de x est 1, la seconde est vraie, car la valeur de x est 0.

Remarque: !x--vérifierait d'abord si x est faux, puis le décrémenterait.

Lucas
la source
RE la note latérale - êtes-vous sûr? Il semble que le post-correctif ait une priorité plus élevée que !, tandis que le préfixe a une priorité plus faible.
Priorité de l'
2
Oui. (Essayez var x = 2; console.log(!x--); console.log(!x--); console.log(!x--);). Alors que le post-fix --peut s'exécuter en premier, sa valeur de retour est la valeur de la variable avant décrémentation ( Decrement Opperator ).
Lucas
Je pense que vous vouliez dire " !--renvoie la négation du résultat de la décrémentation d'une valeur". Seul le code extérieur, tel que console.log(), vérifie si son contenu est véridique.
mareoraft
31

!est l' opérateur JavaScript NOT

--est un opérateur de pré-décrémentation. Donc,

x = 1;
if (!x) // false
if (!--x) // becomes 0 and then uses the NOT operator,
          // which makes the condition to be true
Sterling Archer
la source
8
WTH est une "fausse déclaration"?
Bergi
5
@Bergi Je suppose que vous savez réellement ce que cela signifie, mais si vous ne le faites pas (ou que personne d'autre ne le sait), voici une explication de JavaScript qui se traduit également assez bien dans d'autres langues avec ce concept (comme Python).
Dannnno
1
@Pharap ce n'est pas ma réponse, donc je ne vais pas y toucher.
Dannnno
2
@Dannnno: Eh bien, je sais ce que sont les valeurs de falsification et quelles sont les déclarations , et je sais donc qu'il n'y a rien de tel qu'une "déclaration de falsification" dans JS. Je suppose que cela ne fait pas référence au terme logique .
Bergi
1
Je ne comprends pas comment l' --opérateur pourrait travailler avec une constante ... peut-être que vous vouliez dire à la --xplace de --0?
SJuan76
24
if(!--pending)

veux dire

if(0 == --pending)

veux dire

pending = pending - 1;
if(0 == pending)
James Turner
la source
4
Et c'est la façon claire d'écrire le code. Bien sûr, je préfère en if(0 == pending)raison du potentiel de fautes de frappe. if(0 = pending)est une erreur de syntaxe. if(pending = 0)est une affectation qui provoquera un comportement déroutant dans le code fini.
Theo Brinkman
3
@TheoBrinkman: en fait ce if(0 = pending)n'est pas une erreur de syntaxe - ça analyse bien - mais une erreur de référence car elle 0n'est pas attribuable (réf ECMA-262 (6e éd) sec 12.14.1).
hmakholm a quitté Monica
13

C'est l'opérateur not suivi du pré-décrémenteur en place.

Donc, si pendingétait un entier avec une valeur de 1:

val = 1;
--val; // val is 0 here
!val // evaluates to true
Brendan Abel
la source
11

Il diminue simplement pendingde un et obtient son complément logique (négation). Le complément logique de tout nombre différent de 0 est false, pour 0, il l'est true.

MinusFour
la source
Veuillez noter que "nier" a une signification différente sur les nombres que sur les booléens
Bergi
1
D'accord, je vais utiliser un terme différent. Je ne sais pas si c'est mieux cependant.
MinusFour
11

Explication

Il s'agit de 2 opérateurs, a !et a--

!--x 

Ainsi, le --décrémente x de 1, puis le !retourne vrai si x est maintenant 0 (ou NaN ...), faux s'il ne l'est pas. Vous pourriez lire cet idiome quelque chose comme "nous décrémentons x et si cela le rend nul ..."

Si vous souhaitez le rendre plus lisible, vous pouvez:

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

Essaye le:

/* 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 (Try Out Code)

Ben Aubin
la source
8

Le vrai problème ici est le manque d'espace entre les deux opérateurs !et --.

Je ne sais pas pourquoi les gens pensent que vous ne pouvez jamais utiliser un espace après l' !opérateur. Je pense que cela vient d'une application rigide des règles mécaniques des espaces blancs au lieu du bon sens. Presque toutes les normes de codage que j'ai vues interdisent les espaces après tous les opérateurs unaires, mais pourquoi?

S'il y a jamais eu un cas où vous avez clairement besoin de cet espace, celui-ci en est un.

Considérez ce morceau de code:

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

Non seulement sont !et --purée ensemble, vous avez que( a claqué contre eux aussi. Pas étonnant qu'il soit difficile de dire ce qui est connecté à quoi.

Un peu plus d'espace rend le code beaucoup plus clair:

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

Bien sûr, si vous êtes habitué aux règles mécaniques comme "pas d'espace à l'intérieur des parens" et "pas d'espace après un opérateur unaire", cela peut sembler un peu étranger.

Mais regardez comment les espaces blancs supplémentaires regroupent et séparent les différentes parties de l' ifinstruction et de l'expression: vous avez --pending, donc --c'est clairement son propre opérateur et est étroitement lié à pending. (Il décrémente pendinget renvoie le résultat décrémenté.) Ensuite, vous avez le !séparé de cela, c'est donc évidemment un opérateur distinct, annulant le résultat. Enfin, vous avez if(et )entourez toute l'expression pour en faire une ifdéclaration.

Et oui, j'ai supprimé l'espace entre ifet (, car le ( appartient au if. Cela (ne fait pas partie d'une sorte de (!--syntaxe, comme il semble être dans l'original, la (partie if de la syntaxe de l' ifinstruction elle-même.

L'espace blanc ici sert à communiquer la signification , au lieu de suivre une norme de codage mécanique.

Michael Geary
la source
1
La version blanche est plus lisible pour moi. Quand j'ai vu la question pour la première fois, je supposais qu'il !--s'agissait d'un opérateur javascript que je ne connaissais pas. Pourquoi ne pas utiliser des parenthèses pour le rendre encore plus explicite? if(!(--pending))
emory
1
Aucun guide de style que je suis au courant d' interdire l' utilisation ++ou --, mais beaucoup n'interdisent l' utilisation des espaces non essentiels tels que après l' opérateur préfixe et les parenthèses non essentiels. !
1
L'espace après les opérateurs unaires est déconseillé car il le sépare de l'expression sur laquelle il opère. Vous voudriez qu'il soit lu ensemble ... au moins à mon humble avis
aldrin