Что делает «! -» в JavaScript?

376

У меня есть этот кусок кода (взят из этого вопроса ):

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

Я пытаюсь следовать этому, и я думаю, что понимаю все, кроме как в конце, где говорится !--pending. В этом контексте, что делает эта команда?

Изменить: Я ценю все дальнейшие комментарии, но вопрос был дан ответ много раз. Спасибо, в любом случае!

Киран Э
источник
222
Это замечательный способ запутать следующего человека, чтобы он поддерживал код.
Эрик Дж.
240
!~--[value] ^ trueЯ называю такой код "безопасность работы"
TbWill4321
63
Это напоминает мне, как зовут -->оператора?
Soner Gönül
36
@ TbWill4321 Если бы я делал обзор кода, это было бы полной противоположностью безопасности работы.
CorsiKa
8
Операторы в сочетании с именем переменной делает не так уж плохо. Любому опытному программисту на Javascript не понадобится больше пары секунд, чтобы понять, что он делает.
Кристиан Вестербик

Ответы:

537

! инвертирует значение и дает противоположное логическое значение:

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

--[value] вычитает одно (1) из числа, а затем возвращает то число, с которым нужно работать:

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

Таким образом, !--pendingвычитает один из ожидающих, а затем возвращает противоположность его истинности / ложные значения (независимо от того, является ли он 0).

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

И да, следуйте ProTip. Это может быть распространенной идиомой в других языках программирования, но для большинства декларативного программирования JavaScript это выглядит совершенно чуждо.

TbWill4321
источник
623
ProTip ™: никогда не делайте этого в своем коде, это смешно.
Нафтули Кей
17
Это не упоминает, что --действует только на переменные; Вы не можете применить его к значениям в целом.
Deltab
10
@deltab: validSimpleAssignmentTargets, чтобы быть точным. Это включает ссылки на идентификаторы и ссылки на свойства.
Берги
19
--0и --1не будет работать. Числовые литералы недопустимы в выражении левой стороны
PSWai
7
@Pharap: У меня больше проблем с парсингом, i > -1чем i--(и, кстати, ты забыл i = init() - 1). Вот для чего идиомы ... и каждый программист должен учить их.
Берги
149

Это не специальный оператор, это два стандартных оператора один за другим:

  1. Префикс декремент ( --)
  2. Логическая нет ( !)

Это приводит pendingк уменьшению и проверке на нулевое значение.

Amit
источник
8
Я определенно новичок в этом, но почему это лучше, чем использовать if(pending === 1)?
Киран Э
46
@KieranE, это не так, это сокращение, которое полностью нарушает правило «удобочитаемость - король».
Райан
23
@ KieranE дело не в превосходстве, а в другом. Он изменяет переменную (уменьшает ее на 1), а затем использует новое значение для проверки
Amit
7
@KieranE, это эквивалентно if( pending === 1 ) { done... } else { pending = pending - 1; }.
CompuChip
7
@CompuChip На самом деле это выглядит так: --pending будет выполняться в любом случае, поэтому код больше похож на: if (! Pending) {--pending; и т. д.} else {--pending; и т.д.} Это основано на developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Пол Рассел
109

Ряд ответов описывает, что делает эта команда, но не почему она выполняется именно здесь.

Я родом из мира C, и я читаю !--pendingкак «Обратный отсчетpending и проверить, равен ли он нулю», не задумываясь об этом. Это идиома, которую, я думаю, должны знать программисты на похожих языках.

Функция использует readdir для получения списка файлов и подкаталогов, которые я буду называть «записи».

Переменная pendingотслеживает, сколько из них осталось обработать. Он начинается как длина списка и ведет к нулю при обработке каждой записи.

Эти записи могут быть обработаны не по порядку, поэтому необходимо вести обратный отсчет, а не просто использовать простой цикл. Когда все записи обработаны, doneвызывается обратный вызов, чтобы уведомить первоначального абонента об этом факте.

В первом обращении к doneпредваряется returnне потому, что мы хотим вернуть значение, а просто чтобы остановить выполнение функции в этой точке. Это был бы более чистый код, чтобы опустить returnи поместить альтернативу в else.

Стиг Хеммер
источник
13
@ Bergi это хорошо известная идиома в мире Си, но это не идиоматический Javascript. Для любого данного подвыражения разные экосистемы будут иметь различные, несовместимые идиомы, как «это делается» там. Использование идиом из «иностранных» языков - довольно неприятный запах кода.
Петерис
3
@Peteris: это идиома во всех C-подобных языках, которые имеют оператор декремента. Конечно, иногда в языке есть разные, лучшие способы, но я не думаю, что у JS есть какие-то особые идиомы подсчета.
Берги
6
Существует значительная часть сообщества Javascript, в том числе влиятельных лидеров , таких как Дуглас Crockford, которые выступают избегая унарные операторы инкремента и декремента полностью . Я не собираюсь спорить здесь о том, правильны они или нет; Я намерен лишь указать, что, поскольку противоречие существует, подобный код !--pendingне справится со многими строчками и рекомендациями по стилю. Мне достаточно сказать, что это, вероятно, не идиоматично (независимо от того, является ли альтернатива «лучшей»).
GrandOpener
3
@GrandOpener «Идиоматический» означает «выражение, обычно понимаемое носителями языка». Предопределение определенно хорошо понято для пользователей C-подобных языков и, соответственно, "! (- x)". Является ли использование чего-либо хорошей идеей или нет, это отдельный вопрос, полностью от того, насколько это идиоматично. (Например, я очень сомневаюсь, что вы столкнетесь со многими программистами любого языка, которые не понимают, что делает «goto», несмотря на часто высказываемое мнение, что включение его в ваш код находится где-то между «Lust» и «Murder» в списке из восьми смертных грехов.)
jmbpiano
3
@Pharap Это потому, что C # и Java не конвертируются intв boolнеявно и не имеют абсолютно никакого отношения к использованию !--.
Агоп
36

Это стенография.

! не является".

-- уменьшает значение.

Таким образом, !--проверяется, является ли значение, полученное в результате отрицания результата уменьшения значения, ложным.

Попробуй это:

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

Первое ложно, так как значение x равно 1, второе истинно, так как значение x равно 0.

Примечание: !x--сначала проверит, является ли x ложным, а затем уменьшит его.

Лукас
источник
RE примечание - вы уверены? Похоже, что пост-исправление имеет более высокий приоритет, чем !, в то время как предварительное исправление имеет более низкий приоритет. Приоритет JavaScript-оператора
Dannnno
2
Да. (Попробуй var x = 2; console.log(!x--); console.log(!x--); console.log(!x--);). Хотя пост-исправление --может выполняться первым, его возвращаемое значение - это значение переменной перед уменьшением ( Decrement Opperator ).
Лукас
Я думаю, что вы хотели сказать « !--возвращает отрицание результата уменьшения значения». Только внешний код, например console.log(), проверяет, является ли его содержимое достоверным.
mareoraft
31

!оператор JavaScript НЕ

--является оператором перед декрементом. Так,

x = 1;
if (!x) // false
if (!--x) // becomes 0 and then uses the NOT operator,
          // which makes the condition to be true
Стерлинг Арчер
источник
8
WTH это "ложное утверждение"?
Берги
5
@ Bergi Я предполагаю, что вы действительно знаете, что это значит, но на случай, если вы этого не сделаете (или кто-то еще не знает), приведено объяснение JavaScript, которое также неплохо переводится на другие языки с этой концепцией (например, Python).
Dannnno
1
@ Фарап, это не мой ответ, поэтому я не буду его трогать.
Dannnno
2
@Dannnno: Ну, я знаю, что такое ложные значения и что такое заявления , и поэтому я знаю, что в JS нет такого понятия, как «ложное утверждение». Я предполагаю, что это не относится к логическому термину .
Берги
1
Я не понимаю, как --оператор мог работать с константой ... может, вы имели ввиду --xвместо --0?
SJuan76
24
if(!--pending)

средства

if(0 == --pending)

средства

pending = pending - 1;
if(0 == pending)
Джеймс Тернер
источник
4
И это четкий способ написания кода. Конечно, я предпочитаю if(0 == pending)из-за потенциальных опечаток. if(0 = pending)это синтаксическая ошибка if(pending = 0)является присваиванием, которое приведет к запутанному поведению в готовом коде.
Тео Бринкман
3
@TheoBrinkman: на самом деле if(0 = pending)это не синтаксическая ошибка - она ​​хорошо разбирается - это ссылочная ошибка, потому что 0она не присваивается (см. ECMA-262 (6-е изд.) С. 12.14.1).
Hmakholm оставил Монику
13

Это оператор not, за которым следует предварительный декремент на месте.

Так что, если pendingбыло целое число со значением 1:

val = 1;
--val; // val is 0 here
!val // evaluates to true
Брендан Абель
источник
11

Он просто уменьшается pendingна единицу и получает свое логическое дополнение (отрицание). Логическое дополнение любого числа, отличного от 0 false, для 0 это true.

MinusFour
источник
Обратите внимание, что «отрицание» имеет другое значение для чисел, чем для логических
Берги
1
Хорошо, я буду использовать другой термин. Не уверен, что это лучше, хотя.
MinusFour
11

объяснение

Это 2 оператора, а !и--

!--x 

Таким образом, --x уменьшается на 1, а затем !возвращает true, если x теперь равно 0 (или NaN ...), и false, если это не так. Вы могли бы прочитать эту идиому что-то вроде "мы уменьшаем x и если это делает его нулевым ..."

Если вы хотите сделать его более читабельным, вы можете:

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

Попробуйте это:

/* 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>

Скрипка (Попробуйте код)

Бен Обин
источник
8

Настоящая проблема здесь заключается в отсутствии пробела между двумя операторами !и --.

Я не знаю, почему люди думают, что после !оператора нельзя использовать пробел . Я думаю, что это происходит из-за строгого применения механических правил пробела вместо здравого смысла. Почти все стандарты кодирования, которые я видел, запрещают пробелы после всех унарных операторов, но почему?

Если когда-либо был случай, когда вам явно нужно это место, это одно.

Рассмотрим этот фрагмент кода:

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

Мало того, что !и --пюрешься, ты тоже это (ударил против них. Неудивительно, что сложно сказать, с чем это связано.

Чуть больше пробелов делает код намного более понятным:

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

Конечно, если вы привыкли к механическим правилам, таким как «без пробела внутри паренсов» и «без пробела после унарного оператора», это может показаться немного чуждым.

Но посмотрите на то, как лишние пробелы группируют и разделяют различные части ifоператора и выражения: у вас есть --pending, так что --, очевидно, он является собственным оператором и тесно связан с ним pending. (Это уменьшает pendingи возвращает уменьшенный результат.) Затем вы получили !отделенное от этого, так что это, очевидно, отдельный оператор, сводящий на нет результат. Наконец, вы получили if(и )окружите все выражение, чтобы сделать его ifзаявлением.

И да, я удалил пространство между ifи (, так как ( принадлежит к if. Это (не часть какого-то (!--синтаксиса, как кажется в оригинале, (если часть синтаксиса самого ifоператора.

Пробелы здесь служат для передачи смысла , а не следуют некоторому стандарту механического кодирования.

Майкл Гири
источник
1
Пробельная версия более читабельна для меня. Когда я впервые увидел вопрос, я предположил, что !--это какой-то оператор javascript, которого я не знал. Почему бы не использовать скобки, чтобы сделать его еще более явным? if(!(--pending))
Эмори
1
Нет руководств по стилю, о которых я знаю, которые запрещают использовать ++или --, но многие действительно запрещают использовать несущественные пробелы, такие как !оператор префикса, и несущественные скобки.
1
Пробел после унарных операторов не рекомендуется, потому что он отделяет его от выражения, с которым он работает. Вы хотели бы, чтобы это было прочитано вместе ... по крайней мере, imho
aldrin