Що робить "! -" у 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

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віднімаємо одне від очікуваного, а потім повертаємо протилежне його значення truthy / falesy (незалежно від того, чи ні 0).

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

І так, дотримуйтесь ProTip. Це може бути поширеною фразою в інших мовах програмування, але для більшості декларативних програм програмування JavaScript це виглядає досить чужо.


623
ProTip ™: Ніколи цього не робіть у своєму коді, це смішно.
Naftuli Kay

17
Це не згадує, що --діє лише на змінних; ви не можете застосувати його до значень загалом.
дельтаб

10
@deltab: validSimpleAssignmentTargets, якщо бути точним. Це включає посилання на ідентифікатор та посилання на властивості.
Бергі

19
--0і --1не буде працювати. Числові літерали не є дійсним виразом лівої сторони
PSWai

7
@Pharap: Ага, у мене більше проблем з тим, i > -1що розбирати цю річ, ніж i--(і все-таки ти забув i = init() - 1). Ось для чого ідіоми ... і кожен програміст повинен їх вивчити.
Бергі

149

Це не спеціальний оператор, це два стандартних оператора один за одним:

  1. Декремент префікса ( --)
  2. Логічно не ( !)

Це призводить pendingдо зменшення, а потім тестування, щоб перевірити, чи не дорівнює нулю.


8
Я безумовно новачок у цьому, але чому це перевершує використання if(pending === 1)?
Кіран Е

46
@KieranE, це не так, це скорочення, яке повністю порушує правило "читабельність - це король".
Райан

23
@KieranE справа не в тому, щоб бути вищим, це інше. Він вимикає змінну (зменшує її на 1), а потім використовує нове значення для тестування
Amit

7
@KieranE, це рівнозначно if( pending === 1 ) { done... } else { pending = pending - 1; }.
CompuChip

7
@CompuChip Насправді це виглядає так: - очікування буде зроблено в будь-якому випадку, тому код більше схожий на: if (! Очікує) {--pending; пр.} інше {--завершується; тощо} Це засновано на developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Пол Рассел

109

У ряді відповідей описано, що ця команда робить, але не чому вона робиться саме так.

Я родом із світу С, і читаю !--pendingяк "відлічуй pendingі перевіряю, чи дорівнює нулю", не думаючи про це. Я вважаю, що програмісти подібними мовами - це ідіома.

Функція використовує readdirдля отримання списку файлів і підкаталогів, які я спільно називатиму "записами".

Змінна pendingвідслідковує, скільки з них залишається обробити. Він починається як довжина списку і відраховується вниз до нуля під час обробки кожного запису.

Ці записи можуть бути оброблені поза порядком, тому необхідно відлік часу, а не просто простий цикл. Коли всі записи були оброблені, doneвикликається зворотний дзвінок , щоб сповістити початкового абонента про цей факт.

У першому дзвінку до done, що передує return, не тому, що ми хочемо повернути значення, а просто для того, щоб функція припинила виконання у цій точці. Було б більш чистим кодом скинути returnта поставити альтернативу в else.


13
@Bergi - це відома ідіома у світі С, але це не ідіоматичний Javascript. Для будь-якої субекспресії, різні екосистеми матимуть безліч різних, несумісних ідіом, як "це робиться" там. Використання ідіом із «чужих» мов - досить поганий запах коду.
Петерсіс

3
@ Петерис: це ідіома в усіх мовах, подібних С, які мають оператор декременту. Звичайно, іноді існують різні, кращі способи, доступні в мові, але я не думаю, що JS має особливі ідіоми підрахунку.
Бергі

6
Є значна частина спільноти Javascript, включаючи впливових лідерів, таких як Дуглас Крокфорд, які виступають за те, щоб цілком уникати одинарних операторів збільшення та зменшення . Я не збираюся тут сперечатися про те, правильно вони чи ні; мій намір полягає лише в тому, щоб зазначити, що оскільки суперечка існує, подібний код !--pendingне зможе отримати багато ліній і стильових рекомендацій. Цього мені достатньо, щоб сказати, що це, мабуть, не ідіоматично (незалежно від того, альтернатива "краща").
GrandOpener

3
@GrandOpener "Ідіоматичний" означає "вираження, яке зазвичай розуміють носії мови". Попередній вираз, безумовно, добре зрозумілий користувачам мов, подібних до С, і, з розширенням, так є "! (- x)". Чи використовувати щось чи ні - це хороша ідея® - це окреме питання, повністю від того, наскільки це ідіоматично. (Наприклад, я дуже сумніваюся, що ви зіткнетеся з багатьма програмістами будь-якої мови, які не розуміють, що робить "goto", незважаючи на часто висловлювану думку, що включення його у свій код потрапляє десь між списком "Похоть" та "Вбивство". з восьми смертельних гріхів.)
jmbpiano

3
@Pharap Це тому , що C # і Java не конвертувати intв boolнеявному, і не має абсолютно нічого спільного з використанням !--.
Агоп

36

Це стенограма.

! не".

-- зменшення значення.

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

Спробуйте це:

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

Перше - хибне, оскільки значення х дорівнює 1, друге - істинне, оскільки значення х дорівнює 0.

Бічна примітка: !x--спочатку перевірить, чи x помилково, а потім декрементує.


ПОЗНАЧИТИ бічну ноту - ви впевнені? Схоже, пост-виправлення має більшу перевагу, ніж у !той час як попереднє виправлення має нижчий пріоритет. Пріоритет JavaScript для оператора
Dannnno

2
Так. (Спробуйте var x = 2; console.log(!x--); console.log(!x--); console.log(!x--);). Хоча пост-виправлення --може виконуватися спочатку, його поверненим значенням є значення змінної перед декрементацією ( Оператор зменшення ).
Лукас

Я думаю, ти мав на увазі сказати " !--повертає заперечення результату зменшення значення". Тільки зовнішній код, наприклад console.log(), перевіряє, чи є його вміст правдивим.
мареокрафт

31

!є оператором JavaScript NOT

--є оператором до декременту. Тому,

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).
Даннно

1
@Pharap це не моя відповідь, тому я не збираюся її торкатися.
Даннно

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-е видання), сек. 14.14.1).
hmakholm залишився над Монікою

13

Це не оператор, за яким слід встановити попередній декремент.

Отже, якщо pendingбуло ціле число зі значенням 1:

val = 1;
--val; // val is 0 here
!val // evaluates to true

11

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


Зауважте, що "негатив" має інше значення щодо цифр, ніж для булевих
Бергі

1
Добре, я буду використовувати інший термін. Не впевнений, чи краще це.
MinusFour

11

Пояснення

Це 2 оператори, a !і a--

!--x 

Отже, --декременти x на 1, тоді !повертається істина, якщо 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>

Fiddle (Спробуйте код)


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.