¿Qué hace "! -" en JavaScript?

376

Tengo este código (tomado de esta pregunta ):

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

Estoy tratando de seguirlo, y creo que entiendo todo, excepto cerca del final donde dice !--pending . En este contexto, ¿qué hace ese comando?

Editar: agradezco todos los comentarios adicionales, pero la pregunta ha sido respondida muchas veces. ¡Gracias de cualquier manera!

Kieran E
fuente
222
Es una forma maravillosa de confundir a la siguiente persona para mantener el código.
Eric J.
240
!~--[value] ^ trueLlamo a un código como este "seguridad laboral"
TbWill4321
63
Esto me recuerda ¿ Cuál es el nombre del -->operador?
Soner Gönül
36
@ TbWill4321 Si estuviera haciendo la revisión del código, sería exactamente lo contrario de la seguridad laboral.
corsiKa
8
Los operadores combinados con el nombre de la variable hace no es tan malo. Cualquier programador experimentado de Javascript no necesitará más de un par de segundos para saber lo que hace.
Christiaan Westerbeek

Respuestas:

537

! invierte un valor y le da el booleano opuesto:

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

--[value] resta uno (1) de un número y luego devuelve ese número para trabajarlo:

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

Entonces, !--pendingresta uno de pendiente y luego devuelve lo opuesto a su valor de verdad / falsedad (ya sea que sea o no 0).

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

Y sí, sigue la ProTip. Esto puede ser un idioma común en otros lenguajes de programación, pero para la mayoría de la programación declarativa de JavaScript esto parece bastante extraño.

TbWill4321
fuente
623
ProTip ™: nunca haga esto en su código, es ridículo.
Naftuli Kay
17
Esto no menciona que --actúa solo en variables; No se puede aplicar a los valores en general.
deltab
10
@deltab: validSimpleAssignmentTargets, para ser exactos. Eso incluye referencias de identificador y referencias de propiedad.
Bergi
19
--0y --1no funcionará Los literales numéricos no son expresiones válidas del lado izquierdo
PSWai
77
@Pharap: Uh, tengo más problemas para analizar esa i > -1cosa que i--(y por cierto lo olvidaste i = init() - 1). Para eso están los modismos ... y cada programador debería aprenderlos.
Bergi
149

Ese no es un operador especial, son 2 operadores estándar uno tras otro:

  1. Una disminución de prefijo ( --)
  2. Un no lógico ( !)

Esto hace pendingque se disminuya y luego se pruebe para ver si es cero.

Amit
fuente
8
Definitivamente soy nuevo en esto, pero ¿por qué es esto superior al uso if(pending === 1)?
Kieran E
46
@KieranE, no es, es una taquigrafía que rompe completamente la regla de "la legibilidad es el rey".
Ryan
23
@KieranE no se trata de ser superior, es diferente. Muta la variable (la disminuye en 1) y luego usa el nuevo valor para probar
Amit
77
@KieranE, es equivalente a if( pending === 1 ) { done... } else { pending = pending - 1; }.
CompuChip
77
@CompuChip En realidad, parece que, en cualquier caso, se haría un pendiente, por lo que el código es más parecido a: if (! Pendiente) {- pendiente; etc.} else {- pendiente; etc.} Esto se basa en developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Paul Russell
109

Varias respuestas describen lo que hace este comando, pero no por qué se hace así aquí.

Vengo del mundo C, y leo !--pendingcomo "cuenta atráspending y verifica si es cero" sin pensar realmente en ello. Es un idioma que creo que los programadores en lenguajes similares deberían saber.

La función se utiliza readdirpara obtener una lista de archivos y subdirectorios, que colectivamente llamaré "entradas".

La variable pendingrealiza un seguimiento de cuántos de estos quedan por procesar. Comienza como la longitud de la lista y cuenta hacia abajo hacia cero a medida que se procesa cada entrada.

Estas entradas pueden procesarse fuera de servicio, por lo que es necesario realizar una cuenta regresiva en lugar de simplemente usar un bucle simple. Cuando se han procesado todas las entradas, donese llama a la devolución de llamada para notificar al llamador original de este hecho.

En la primera llamada a donese antepone return, no porque queramos devolver un valor, sino simplemente para que la función deje de ejecutarse en ese punto. Hubiera sido un código más limpio descartar returny poner la alternativa en un else.

Stig Hemmer
fuente
13
@Bergi es un lenguaje conocido en C mundo, pero es no idiomática Javascript. Para cualquier subexpresión dada, los diferentes ecosistemas tendrán una variedad de expresiones idiomáticas diferentes e incompatibles, cómo 'se hace' allí. El uso de expresiones idiomáticas de idiomas "extranjeros" es un olor a código bastante malo.
Peteris
3
@Peteris: es un modismo en todos los lenguajes tipo C que tienen el operador de decremento. Por supuesto, a veces hay diferentes y mejores formas disponibles en un idioma, pero no creo que JS tenga modismos especiales para contar.
Bergi
66
Hay una parte significativa de la comunidad Javascript, incluidos líderes influyentes como Douglas Crockford, que abogan por evitar por completo los operadores de incremento y decremento unarios . No voy a discutir aquí si son correctos o no; mi intención es solo señalar que, debido a que existe la controversia, el código como !--pendingfallará muchas linters y pautas de estilo. Eso es suficiente para decir que probablemente no sea idiomático (independientemente de si la alternativa es "mejor").
GrandOpener
3
@GrandOpener "Idiomático" significa "una expresión comúnmente entendida por hablantes nativos". Predecremento es definitivamente bien entendido por los usuarios de lenguajes tipo C y, por extensión, también lo es "! (- x)". Si usar o no algo es una Buena Idea® es una pregunta completamente diferente de lo idiomático que es. (Por ejemplo, dudo mucho que te encuentres con muchos programadores de cualquier idioma que no entiendan lo que hace "goto", a pesar de la opinión frecuentemente expresada de que incluirlo en tu código se encuentra entre "Lust" y "Murder" en la lista de los ocho pecados capitales.)
jmbpiano
3
@Pharap Esto se debe a C # y Java no convertir inta la boolforma implícita, y no tiene absolutamente nada que ver con el uso de !--.
Agop
36

Es una taquigrafía.

! no es".

-- disminuye un valor.

Por lo tanto, !--comprueba si el valor obtenido al negar el resultado de disminuir un valor es falso.

Prueba esto:

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

El primero es falso, ya que el valor de x es 1, el segundo es verdadero, ya que el valor de x es 0.

Nota al margen: !x--primero verificaría si x es falso y luego lo disminuiría.

Lucas
fuente
RE la nota al margen - ¿estás seguro? Parece que la corrección posterior tiene mayor prioridad que !, mientras que la corrección previa tiene menor prioridad. Precedencia del operador de JavaScript
Dannnno
2
Si. (Intenta var x = 2; console.log(!x--); console.log(!x--); console.log(!x--);) Si bien la corrección posterior --puede ejecutarse primero, su valor de retorno es el valor de la variable antes de disminuir ( Operador de decremento ).
Lucas
Creo que querías decir " !--devuelve la negación del resultado de decrementar un valor". Solo el código externo, como por ejemplo console.log(), verifica si su contenido es verdadero.
mareoraft
31

!es el JavaScript NO operador

--es un operador de pre-decremento. Entonces,

x = 1;
if (!x) // false
if (!--x) // becomes 0 and then uses the NOT operator,
          // which makes the condition to be true
Sterling Archer
fuente
8
¿Qué es una "declaración falsa"?
Bergi
55
@Bergi Asumo que realmente sabes lo que significa, pero en caso de que no lo hagas (o alguien más no lo sabe) aquí hay una explicación para JavaScript que también se traduce bastante bien a otros idiomas con este concepto (como Python).
Dannnno
1
@Pharap no es mi respuesta, así que no voy a tocarlo.
Dannnno
2
@ Dannnno: Bueno, sé qué son los valores falsos y qué son las declaraciones , y sé que no hay tal cosa como una "declaración falsa" en JS. Supongo que esto no se refiere al término lógico .
Bergi
1
No entiendo cómo el --operador podría trabajar con una constante ... ¿quizás quiso decir en --xlugar de --0?
SJuan76
24
if(!--pending)

medio

if(0 == --pending)

medio

pending = pending - 1;
if(0 == pending)
James Turner
fuente
44
Y esa es la forma clara de escribir el código. Por supuesto, prefiero if(0 == pending)debido a la posibilidad de errores tipográficos. if(0 = pending)es un error de sintaxis if(pending = 0)es una tarea que causará un comportamiento confuso en el código terminado.
Theo Brinkman
3
@TheoBrinkman: en realidad if(0 = pending)no es un error de sintaxis, se analiza bien, sino un error de referencia porque 0no es asignable (ref ECMA-262 (6ª ed) sec 12.14.1).
hmakholm dejó a Mónica el
13

Es el operador no seguido por el pre-decrementador en el lugar.

Entonces, si pendingfuera un entero con un valor de 1:

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

Simplemente disminuye pendingen uno y obtiene su complemento lógico (negación). El complemento lógico de cualquier número diferente de 0 es false, para 0 es true.

MenosCuatro
fuente
Tenga en cuenta que "negar" tiene un significado diferente en los números que en los booleanos
Bergi
1
Muy bien, usaré un término diferente. Sin embargo, no estoy seguro de si esto es mejor.
MinusFour
11

Explicación

Esto es 2 operadores, a !y a--

!--x 

Entonces, los --decrementos x por 1, entonces el! devuelve verdadero si x es ahora 0 (o NaN ...), falso si no lo es. Puede leer este modismo algo así como "decrementamos xy si eso lo hace cero ..."

Si desea que sea más legible, puede:

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

Pruébalo:

/* 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 (código de prueba)

Ben Aubin
fuente
8

El verdadero problema aquí es la falta de espacio entre los dos operadores !y --.

No sé por qué las personas piensan que no se puede usar un espacio después del !operador. Creo que proviene de la aplicación rígida de las reglas mecánicas de espacios en blanco en lugar del sentido común. Casi todos los estándares de codificación que he visto prohíben espacios después de todos los operadores unarios, pero ¿por qué?

Si alguna vez hubo un caso en el que claramente necesita ese espacio, este es uno.

Considere este bit de código:

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

No solo son !y se --mezclan, tienes eso( golpeado contra ellos también. No es de extrañar que sea difícil saber qué está conectado a qué.

Un poco más de espacio en blanco hace que el código sea mucho más claro:

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

Claro, si estás acostumbrado a las reglas mecánicas como "sin espacio dentro de parens" y "sin espacio después de un operador unario", esto puede parecer un poco extraño.

Pero observe cómo el espacio en blanco adicional agrupa y separa las diversas partes de la ifdeclaración y la expresión: lo tiene --pending, por lo --que claramente es su propio operador y está estrechamente vinculado pending. (Disminuye pendingy devuelve el resultado decrementado). Entonces tienes el !separado de eso, por lo que obviamente es un operador distinto, que niega el resultado. Finalmente, tienes if(y )rodea toda la expresión para que sea una ifdeclaración.

Y sí, eliminé el espacio entre ify (, porque ( pertenece al if. Esto (no es parte de algún tipo de (!--sintaxis, ya que parece estar en el original, (si es parte de la sintaxis de la ifdeclaración en sí.

El espacio en blanco aquí sirve para comunicar el significado , en lugar de seguir algún estándar de codificación mecánica.

Michael Geary
fuente
1
La versión en blanco es más legible para mí. Cuando vi por primera vez la pregunta, supuse que !--era un operador de JavaScript que no conocía. ¿Por qué no usar paréntesis para hacerlo aún más explícito? if(!(--pending))
emory
1
No tengo conocimiento de que las guías de estilo prohíban usar ++o --, pero muchas prohíben el uso de espacios no esenciales, como después del !operador de prefijo, y paréntesis no esenciales.
1
Se desaconseja el espacio en blanco después de operadores unarios porque lo separa de la expresión en la que opera. Lo que quiere que se pueda leer juntos ... al menos en mi humilde opinión
aldrin