Български
Тема
Български
Тема
Днес ще се научите да:
===, !==, <, >= ... и да избягвате капаните на ==;"4.10" < "4.2" е true и какво означава това за версиите;&&, || и !;|| и ??;if / else, switch и тернарния оператор ? :;for, while и do...while и да управлявате loops с break и continue;var, ==, == false).Къде е мястото на тази тема в примерното правило за проверка - след днешното занятие можете да го четете и пишете цялото. Всяка част използва някоя от днешните теми - числото в коментара е разделът от тази лекция:
function rule(doc) {
// 6. guard clauses: stop early when data is missing
if (!doc.overview || !doc.inventory) return;
// 1, 3. ===, &&, ||, ! - "is it one of our two models?"
const model = doc.overview.model;
if (model !== "XR-210" && model !== "XR-220") return;
// 5. defaults: || also replaces an empty name, ?? only a missing value
const name = doc.inventory.extended_info?.json?.data?.system?.name
|| doc.status.device_alias;
const level = doc.inventory.details.firmware_level ?? "";
// 10. a loop with break: the version is the text before the first space
let version = "";
for (const ch of level) {
if (ch === " ") break;
version += ch;
}
if (version === "") return;
// 2, 6. versions are compared part by part; the strictest condition first
let status;
if (versionBelow(version, "4.2.0.0")) {
status = "Below minimum";
} else if (versionBelow(version, "4.2.1.7")) {
status = "Below recommended";
} else {
return; // OK - no alert
}
// 7. switch: one value, several fixed answers
let action;
switch (status) {
case "Below minimum":
action = "Upgrade as soon as possible.";
break;
case "Below recommended":
action = "Plan an upgrade.";
break;
}
// 4, 8. "no" is truthy, so compare; the ternary returns a value
const login = doc.inventory.details.remote_login_enabled === "yes" ? "on" : "off";
emit(name, { "Model": model, "Version": version, "Status": status,
"Action": action, "Remote login": login });
}versionBelow(a, b) засега е готова helper функция - собствена ще напишем в занятие 5.?. (optional chaining) е поглед напред: спира при липсващо ниво, вместо да хвърли грешка. Повече в раздел 5 и в занятие 6.Почти всяко правило е верига от решения като тези: "има ли данни?", "това ли е моделът?", "твърде стар ли е firmware-ът?". Точните условия са най-важното умение при писането на правила.
Сравнението задава въпрос за две стойности. Отговорът е винаги boolean: true или false.
| Оператор | Значение | Пример | Резултат |
|---|---|---|---|
=== | строго равно (същият тип и стойност) | 5 === 5 | true |
!== | строго различно | 5 !== "5" | true |
== | нестрого равно (първо преобразува типовете) | 5 == "5" | true |
!= | нестрого различно | 5 != 3 | true |
> | по-голямо от | 10 > 5 | true |
< | по-малко от | 3 < 5 | true |
>= | по-голямо или равно | 5 >= 5 | true |
<= | по-малко или равно | 4 <= 5 | true |
console.log(2 == 2); // true
console.log(2 == "2"); // true - the string is converted to a number
console.log(2 === "2"); // false - different types, no conversion
// booleans are converted to numbers: true is 1, false is 0
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true > false); // true
const model = doc.overview.model;
console.log(model === "XR-220"); // true
console.log(model !== "XR-210"); // trueconsole.log(2 == 2); // true
console.log(2 == "2"); // true - the string is converted to a number
console.log(2 === "2"); // false - different types, no conversion
// booleans are converted to numbers: true is 1, false is 0
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true > false); // true
const model = doc.overview.model;
console.log(model === "XR-220"); // true
console.log(model !== "XR-210"); // true=== срещу == MDN: Strict equality (===), Equality (==)
== първо преобразува двете стойности до един и същ тип и след това ги сравнява. Правилата за преобразуване са дълги и изненадващи:
console.log("" == 0); // true - "" becomes 0
console.log("0" == false); // true - both become 0
console.log("no" == false); // false - "no" becomes NaN, false becomes 0
console.log(" \n " == 0); // true - a string of spaces becomes 0
console.log(null == 0); // false
console.log(null > 0); // false
console.log(null >= 0); // true - but >= converts null to 0!console.log("" == 0); // true - "" becomes 0
console.log("0" == false); // true - both become 0
console.log("no" == false); // false - "no" becomes NaN, false becomes 0
console.log(" \n " == 0); // true - a string of spaces becomes 0
console.log(null == 0); // false
console.log(null > 0); // false
console.log(null >= 0); // true - but >= converts null to 0!Използвайте === и !==
Те никога не преобразуват. Сравнява се точно това, което виждате. Всички сравнения в курса използват === / !==. Единственото ==, което си струва да знаете, е по-долу.
Анимация
Типове, сравнения и логика: 3. == и ===
==: null и undefined null == undefined е true, а null / undefined не са нестрого равни на нищо друго. Затова value == null означава "value е null или undefined":
let value; // undefined
console.log(value === undefined || value === null); // true - the long way
console.log(value == null); // true - the short way
value = 0;
console.log(value == null); // false - 0 is a real valuelet value; // undefined
console.log(value === undefined || value === null); // true - the long way
console.log(value == null); // true - the short way
value = 0;
console.log(value == null); // false - 0 is a real valueВ съществуващите правила ще видите и двете форми, и двете са правилни. В нов код изберете едната форма и я използвайте навсякъде.
NaN не е равно на нищо const slots = Number("abc");
console.log(slots); // NaN
console.log(slots === NaN); // false - even NaN !== NaN
console.log(Number.isNaN(slots)); // true - the right checkconst slots = Number("abc");
console.log(slots); // NaN
console.log(slots === NaN); // false - even NaN !== NaN
console.log(Number.isNaN(slots)); // true - the right checkconsole.log(0.1 + 0.2 === 0.3); // false (see session 2)
console.log(Math.abs(0.1 + 0.2 - 0.3) < 1e-9); // true - "close enough"
// Math.abs(n) is n without its sign: the distance between the two numbersconsole.log(0.1 + 0.2 === 0.3); // false (see session 2)
console.log(Math.abs(0.1 + 0.2 - 0.3) < 1e-9); // true - "close enough"
// Math.abs(n) is n without its sign: the distance between the two numbersОбектите се сравняват по референция
{} === {} и [] === [] са false: два обекта са равни само ако са един и същ обект. Повече в занятие 6.
Анимации
Числата в паметта: 4. 0.1 + 0.2, 6. NaN и Infinity
Два strings се сравняват символ по символ, по кодовете на символите (Unicode). Това се нарича лексикографски ред - като в речник:
console.log("b" > "azzzz"); // true - "b" > "a", the rest does not matter
console.log("a" > "A"); // true - lowercase letters have bigger codes
console.log("Z" < "a"); // true - all uppercase letters come first
console.log("1" < "11"); // true - "1" is a prefix of "11"
console.log("2" > "19999"); // true - "2" > "1"
console.log("1199" > "12"); // false - "11" < "12"
console.log("1" == "12"); // falseconsole.log("b" > "azzzz"); // true - "b" > "a", the rest does not matter
console.log("a" > "A"); // true - lowercase letters have bigger codes
console.log("Z" < "a"); // true - all uppercase letters come first
console.log("1" < "11"); // true - "1" is a prefix of "11"
console.log("2" > "19999"); // true - "2" > "1"
console.log("1199" > "12"); // false - "11" < "12"
console.log("1" == "12"); // falseАко единият string е начало на другия, по-късият е по-малък.
"4.10" < "4.2" е true Версиите са strings. Сравнени като strings, те дават грешни отговори:
console.log("4.10" < "4.2"); // true - "1" < "2" at position 2
console.log("10.1.0" < "9.0.0"); // true - "1" < "9" at position 0
console.log("4.2.1.7" < "4.2.1.10"); // false - "7" > "1"console.log("4.10" < "4.2"); // true - "1" < "2" at position 2
console.log("10.1.0" < "9.0.0"); // true - "1" < "9" at position 0
console.log("4.2.1.7" < "4.2.1.10"); // false - "7" > "1"Версията трябва да се сравнява част по част, като числа: 4 = 4, после 10 > 2. Такава helper функция, versionBelow(a, b), ще напишем в занятие 5. Дотогава задачите за упражнение ви я дават готова.
Смесени типове
Когато едната страна е number, string-ът се преобразува в number: "10" > 9 е true, но "10" > "9" е false. А "abc" < 5 и "abc" >= 5 са false и двете (NaN). Първо преобразувайте, после сравнявайте.
=== различава малки и главни букви. Стойностите в JSON не винаги са написани по един и същ начин, затова сравнявайте в един регистър. toLowerCase() дава същия текст с малки букви (повече методи на strings в занятие 8):
const raised = "Yes";
console.log(raised === "yes"); // false
console.log(raised.toLowerCase() === "yes"); // trueconst raised = "Yes";
console.log(raised === "yes"); // false
console.log(raised.toLowerCase() === "yes"); // trueЕстествен ред (поглед напред)
"4.10".localeCompare("4.2", undefined, { numeric: true }) дава 1 (по-голямо), защото чете групите от цифри като числа. Полезно е за сортиране (занятие 7), но нашите формати на версиите са твърде различни (4.2.1.7-B00, 42107-C00), затова за правилата пишем собствено сравнение.
Анимация
Типове, сравнения и логика: 4. Сравняване на strings
MDN: Logical AND (&&), Logical OR (||), Logical NOT (!)
Логическите оператори комбинират условия:
| Оператор | Име | true, когато |
|---|---|---|
a && b | И (AND) | и двете са истина |
a || b | ИЛИ (OR) | поне едното е истина |
!a | НЕ (NOT) | a е неистина |
Таблицата на истинност (T - true, F - false):
a | b | a && b | a || b | !a |
|---|---|---|---|---|
| T | T | T | T | F |
| T | F | F | T | F |
| F | T | F | T | T |
| F | F | F | F | T |
&& и || имат два операнда; ! има един (той е унарен).
console.log(true && false); // false
console.log(true || false); // true
console.log(!true); // false
const userAge = 19;
const userCountry = "BG";
console.log(userAge >= 18 && userCountry === "UK"); // false (true && false)
// the same with the report
const model = doc.overview.model;
const enabled = doc.inventory.details.remote_login_enabled;
console.log(model === "XR-220" && enabled === "yes"); // true
console.log(model === "XR-210" || model === "XR-220"); // true
console.log(!(model === "XR-210" || model === "XR-220")); // falseconsole.log(true && false); // false
console.log(true || false); // true
console.log(!true); // false
const userAge = 19;
const userCountry = "BG";
console.log(userAge >= 18 && userCountry === "UK"); // false (true && false)
// the same with the report
const model = doc.overview.model;
const enabled = doc.inventory.details.remote_login_enabled;
console.log(model === "XR-220" && enabled === "yes"); // true
console.log(model === "XR-210" || model === "XR-220"); // true
console.log(!(model === "XR-210" || model === "XR-220")); // falseMDN: Operator precedence
Първо се прилага !, после &&, после || - както * преди +:
const a = true, b = false, c = false;
console.log(a || b && c); // true - same as a || (b && c)
console.log((a || b) && c); // falseconst a = true, b = false, c = false;
console.log(a || b && c); // true - same as a || (b && c)
console.log((a || b) && c); // falseКогато едно условие смесва && и ||, винаги слагайте скоби - заради читателя, не само заради JavaScript:
if (env === "hw" || (platform === "virtual" && version !== "")) { ... }За да напишете "не (A и B)", обърнете всяка част и сменете && с ||:
| Това | е същото като |
|---|---|
!(a && b) | !a || !b |
!(a || b) | !a && !b |
Това ви трябва за guard clauses (раздел 6): "спри, ако моделът не е нито XR-210, нито XR-220" е if (model !== "XR-210" && model !== "XR-220") return;.
if (...), !, && и || приемат всякаква стойност, не само booleans. Тогава стойността се третира като true или false. Тези осем стойности са falsy:
| Тип | Falsy стойности |
|---|---|
| boolean | false |
| number | 0, -0, NaN (и 0n, който е bigint) |
| string | "" (празният string) |
| - | undefined, null |
Всичко останало е truthy - включително "0", "false", "no", " ", [] и {}.
console.log(Boolean("")); // false
console.log(Boolean("0")); // true - a non-empty string
console.log(Boolean("no")); // true
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean({})); // true
console.log(!!"text"); // true - !! is a short way to write Boolean()console.log(Boolean("")); // false
console.log(Boolean("0")); // true - a non-empty string
console.log(Boolean("no")); // true
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean({})); // true
console.log(!!"text"); // true - !! is a short way to write Boolean()"no" е truthy
Отчетът пази настройките да / не като текст: "remote_login_enabled": "yes", "remote_login_pwd_set": "no". if (doc.inventory.details.remote_login_pwd_set) е true и за "no" - всеки непразен string е truthy. Сравнявайте със стойността, която имате предвид: === "yes".
const enabled = "no";
if (enabled) {
console.log("this runs - wrong!");
}
if (enabled === "yes") {
console.log("this does not run - right");
}const enabled = "no";
if (enabled) {
console.log("this runs - wrong!");
}
if (enabled === "yes") {
console.log("this does not run - right");
}Truthy проверките са добри за един въпрос: "има ли изобщо стойност?" if (doc.overview) означава "документът има част overview".
true / false В съществуващите правила често има редове като if (present == false) или if (list.includes(x) !== true) (includes за arrays идва в занятие 4). Те работят, когато стойността е истински boolean, но == false преобразува: съвпада и с 0, "" и дори с "0" - който е truthy!
console.log(Boolean("0")); // true - "0" is truthy
console.log("0" == false); // true - but == false matches it
console.log(null == false); // false - and does NOT match null
console.log(!null); // trueconsole.log(Boolean("0")); // true - "0" is truthy
console.log("0" == false); // true - but == false matches it
console.log(null == false); // false - and does NOT match null
console.log(!null); // trueПишете го по-кратко и по-безопасно:
| Вместо | Пишете |
|---|---|
if (present == false) | if (!present) или if (present === false) |
if (isAffected == true) | if (isAffected) |
if (list.includes(x) !== true) | if (!list.includes(x)) |
Когато модернизирате правило, проверете какво може да бъде стойността. !present е true и за null и undefined, а present == false не е. Обикновено !present е това, което правилото е имало предвид ("липсва или не е зададено"), но все пак е промяна.
Анимация
Типове, сравнения и логика: 5. Truthy и falsy
&& и || спират веднага щом отговорът е ясен - това се нарича short-circuit evaluation. И те не връщат true / false, а единия от двата операнда:
| Израз | Връща |
|---|---|
a || b | a, ако a е truthy, иначе b |
a && b | a, ако a е falsy, иначе b |
console.log("XR-220" || "Unknown"); // XR-220
console.log("" || "Unknown"); // Unknown
console.log(undefined || "Unknown"); // Unknown
console.log("XR-220" && "OK"); // OK
console.log(null && "OK"); // null
let userName;
console.log(userName || "Anonymous"); // Anonymousconsole.log("XR-220" || "Unknown"); // XR-220
console.log("" || "Unknown"); // Unknown
console.log(undefined || "Unknown"); // Unknown
console.log("XR-220" && "OK"); // OK
console.log(null && "OK"); // null
let userName;
console.log(userName || "Anonymous"); // Anonymous|| за стойности по подразбиране Примерното правило използва || за fallback: ако името на системата липсва или е празно, използва alias-а на устройството:
const name = doc.inventory.extended_info?.json?.data?.system?.name
|| doc.status.device_alias;
console.log(name); // LAB-DEV-01
const noName = "" || doc.status.device_alias;
console.log(noName); // the aliasconst name = doc.inventory.extended_info?.json?.data?.system?.name
|| doc.status.device_alias;
console.log(name); // LAB-DEV-01
const noName = "" || doc.status.device_alias;
console.log(noName); // the aliasСъществуващите правила го използват постоянно:
var current = String(version || ""); // never undefined, always a string
var count = parseInt(text, 10) || 0; // NaN becomes 0
var customer = String(alias || "Unknown");&& като пазач (guard) Дясната страна се изпълнява само ако лявата е truthy. По-старите правила го използват, за да проверят всяко ниво на един път, преди да прочетат следващото:
const report = { overview: { model: "XR-220" } };
console.log(report.overview && report.overview.model); // XR-220
console.log(report.status && report.status.device_alias); // undefined - no error
// the modern form (ES2020), more in session 6:
console.log(report.status?.device_alias); // undefinedconst report = { overview: { model: "XR-220" } };
console.log(report.overview && report.overview.model); // XR-220
console.log(report.status && report.status.device_alias); // undefined - no error
// the modern form (ES2020), more in session 6:
console.log(report.status?.device_alias); // undefined|| заменя и 0, и "" || заменя всяка falsy стойност. Понякога 0, "" или false е истински отговор:
const failedDrives = 0;
console.log(failedDrives || "Not reported"); // Not reported - wrong!
console.log(failedDrives ?? "Not reported"); // 0 - rightconst failedDrives = 0;
console.log(failedDrives || "Not reported"); // Not reported - wrong!
console.log(failedDrives ?? "Not reported"); // 0 - right?? - nullish coalescing операторът (ES2020) MDN: Nullish coalescing operator (??)
a ?? b връща b само когато a е null или undefined:
a | a || "x" | a ?? "x" |
|---|---|---|
undefined | "x" | "x" |
null | "x" | "x" |
"" | "x" | "" |
0 | "x" | 0 |
false | "x" | false |
"no" | "no" | "no" |
??, когато 0, "" или false са валидни стойности.||, когато и празна стойност трябва да получи стойността по подразбиране.ВНИМАНИЕ
a || b ?? c е синтактична грешка. Смесването на ?? с || или && изисква скоби: (a || b) ?? c.
MDN: Logical OR assignment (||=), Nullish coalescing assignment (??=)
let status;
status ??= "Not reported"; // status = status ?? "Not reported"
console.log(status);
let name = "";
name ||= "Unknown"; // name = name || "Unknown"
console.log(name);let status;
status ??= "Not reported"; // status = status ?? "Not reported"
console.log(status);
let name = "";
name ||= "Unknown"; // name = name || "Unknown"
console.log(name);ES2021+
Проверете дали вашият rule engine поддържа ??=, ||= и &&=, преди да ги използвате в правило. Самият ?? е ES2020, като ?..
Анимация
Типове, сравнения и логика: 6. Short-circuit: && и ||
MDN: if...else
if if (condition) {
// runs only if condition is truthy
}const age = 19;
let status = "unknown";
if (age > 18) {
status = "adult";
}
console.log(status);
const version = doc.overview.version;
if (version === "4.2.1.7") {
console.log("exactly the recommended level");
}const age = 19;
let status = "unknown";
if (age > 18) {
status = "adult";
}
console.log(status);
const version = doc.overview.version;
if (version === "4.2.1.7") {
console.log("exactly the recommended level");
}if...else if (condition) {
// runs if condition is truthy
} else {
// runs otherwise
}const x = 5;
if (x % 2) { // 1 is truthy, 0 is falsy
console.log("Odd");
} else {
console.log("Even");
}
const enabled = doc.inventory.details.remote_login_enabled;
if (enabled === "yes") {
console.log("remote login is on");
} else {
console.log("remote login is off");
}const x = 5;
if (x % 2) { // 1 is truthy, 0 is falsy
console.log("Odd");
} else {
console.log("Even");
}
const enabled = doc.inventory.details.remote_login_enabled;
if (enabled === "yes") {
console.log("remote login is on");
} else {
console.log("remote login is off");
}x % 2 работи, но x % 2 !== 0 казва какво имаме предвид. Предпочитайте явни условия. (Не x % 2 === 1: за отрицателни числа -3 % 2 е -1.)
if...else if...else if (condition1) {
// condition1 is true
} else if (condition2) {
// condition1 is false, condition2 is true
} else {
// none of the above
}if.else if.else е последен. Не е задължителен, но обикновено е добра идея: хваща случая, за който не сте се сетили.Намерете бъга - кодът е валиден JavaScript, но дава грешни отговори:
const age = 25;
let status;
if (age > 12) {
status = "teen";
} else if (age > 18) {
status = "adult";
} else {
status = "child";
}
console.log(status);const age = 25;
let status;
if (age > 12) {
status = "teen";
} else if (age > 18) {
status = "adult";
} else {
status = "child";
}
console.log(status);Редът. 25 е > 12, затова се изпълнява първият блок и до "adult" никога не се стига. Проверявайте първо най-строгото условие: age > 18, после age > 12.
Същото важи и за firmware levels. Ниво под минималното е и под препоръчителното, затова първо се проверява минималното:
// a ready-made helper for today - compares part by part, as numbers.
// You will understand how it works in sessions 4-5.
function versionBelow(a, b) {
const pa = String(a).split(".").map(Number);
const pb = String(b).split(".").map(Number);
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
const x = pa[i] || 0;
const y = pb[i] || 0;
if (x !== y) return x < y;
}
return false;
}
const current = "4.1.9.3";
const minimum = "4.2.0.0";
const recommended = "4.2.1.7";
let status;
if (versionBelow(current, minimum)) {
status = "Below minimum";
} else if (versionBelow(current, recommended)) {
status = "Below recommended";
} else {
status = "OK";
}
console.log(status);// a ready-made helper for today - compares part by part, as numbers.
// You will understand how it works in sessions 4-5.
function versionBelow(a, b) {
const pa = String(a).split(".").map(Number);
const pb = String(b).split(".").map(Number);
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
const x = pa[i] || 0;
const y = pb[i] || 0;
if (x !== y) return x < y;
}
return false;
}
const current = "4.1.9.3";
const minimum = "4.2.0.0";
const recommended = "4.2.1.7";
let status;
if (versionBelow(current, minimum)) {
status = "Below minimum";
} else if (versionBelow(current, recommended)) {
status = "Below recommended";
} else {
status = "OK";
}
console.log(status);Сменете current на "4.2.0.5" и на "4.10.0.0" и изпълнете отново.
Анимация
Loops стъпка по стъпка: 1. if / else if
Чест случай: два факта да / не дават четири съобщения. Проверете първо "и двете":
const hasTimeServer = doc.inventory.details.ntp_ip_address !== "";
const hasBandwidth = doc.inventory.details.bandwidth !== "";
let message;
if (hasTimeServer && hasBandwidth) {
message = "time server and bandwidth reported";
} else if (hasTimeServer) {
message = "time server reported / bandwidth not reported";
} else if (hasBandwidth) {
message = "bandwidth reported / time server not reported";
} else {
message = "time server and bandwidth not reported";
}
console.log(message);const hasTimeServer = doc.inventory.details.ntp_ip_address !== "";
const hasBandwidth = doc.inventory.details.bandwidth !== "";
let message;
if (hasTimeServer && hasBandwidth) {
message = "time server and bandwidth reported";
} else if (hasTimeServer) {
message = "time server reported / bandwidth not reported";
} else if (hasBandwidth) {
message = "bandwidth reported / time server not reported";
} else {
message = "time server and bandwidth not reported";
}
console.log(message);Липсващ ключ не е ""
Ако в отчета изобщо няма ключ ntp_ip_address, стойността е undefined, а undefined !== "" е true - кодът по-горе казва "reported". Помислете кои стойности означават "не е докладвано" (undefined, null, ""?) и проверете всички. Домашното пита точно това.
{ } групира statements в блок. При един statement скобите могат да се пропуснат и ще го видите в правилата:
if (!doc) return;
if (version === "") return;Това е добре за кратък return на същия ред. Навсякъде другаде пишете скобите - добавянето на втори ред по-късно без тях е класически бъг:
if (isAffected)
console.log("affected");
emit(alias, details); // NOT part of the if - always runs!Едно правило не прави нищо за повечето документи. Вместо да влагате if в if, проверете първо какво не е наред и направете return (спрете правилото) веднага:
// nested - the real work is deep inside
function rule(doc) {
if (doc.overview) {
if (doc.overview.model === "XR-220") {
if (doc.overview.version !== "") {
// ... the real work
}
}
}
}
// guard clauses - flat and easy to read
function rule(doc) {
if (!doc.overview) return;
if (doc.overview.model !== "XR-220") return;
if (doc.overview.version === "") return;
// ... the real work
}return приключва функцията. Функциите идват в занятие 5 - засега четете return в правило като "спри тук, без alert".
function emit(id, details) { // a fake emit, to see the result here
console.log("ALERT for " + id + ": " + JSON.stringify(details));
}
function rule(doc) {
if (!doc || !doc.overview) return;
const model = doc.overview.model;
if (model !== "XR-210" && model !== "XR-220") return;
const enabled = doc.inventory.details.remote_login_enabled;
if (enabled !== "yes") return;
emit(doc.status.device_alias, { model: model, check: "remote login enabled" });
}
rule(doc); // the example JSON document: an alert
rule({ overview: { model: "XR-100" } }); // other model: nothing
rule({}); // no overview: nothing, and no errorfunction emit(id, details) { // a fake emit, to see the result here
console.log("ALERT for " + id + ": " + JSON.stringify(details));
}
function rule(doc) {
if (!doc || !doc.overview) return;
const model = doc.overview.model;
if (model !== "XR-210" && model !== "XR-220") return;
const enabled = doc.inventory.details.remote_login_enabled;
if (enabled !== "yes") return;
emit(doc.status.device_alias, { model: model, check: "remote login enabled" });
}
rule(doc); // the example JSON document: an alert
rule({ overview: { model: "XR-100" } }); // other model: nothing
rule({}); // no overview: nothing, and no errorswitch MDN: switch
switch сравнява една стойност с няколко фиксирани стойности. Това е алтернатива на дълга верига if...else if от проверки с ===:
const fruit = "apple";
switch (fruit) {
case "apple":
console.log("Apple is red.");
break;
case "banana":
console.log("Banana is yellow.");
break;
default:
console.log("Unknown fruit.");
}const fruit = "apple";
switch (fruit) {
case "apple":
console.log("Apple is red.");
break;
case "banana":
console.log("Banana is yellow.");
break;
default:
console.log("Unknown fruit.");
}case чрез === (строго).break.default се изпълнява, когато нито един case не съвпада. Пишете го последен.const loginType = doc.inventory.details.remote_login_type;
let description;
switch (loginType) {
case "central":
case "domain": // two cases, one block
description = "central user accounts";
break;
case "local":
description = "local users only";
break;
case "":
case "none":
description = "no remote login";
break;
default:
description = "unknown type: " + loginType;
}
console.log(description);const loginType = doc.inventory.details.remote_login_type;
let description;
switch (loginType) {
case "central":
case "domain": // two cases, one block
description = "central user accounts";
break;
case "local":
description = "local users only";
break;
case "":
case "none":
description = "no remote login";
break;
default:
description = "unknown type: " + loginType;
}
console.log(description);Забравен break
Без break кодът "пропада" в следващия case:
const status = "Below minimum";
switch (status) {
case "Below minimum":
console.log("Upgrade as soon as possible.");
// no break!
case "Below recommended":
console.log("Plan an upgrade.");
break;
default:
console.log("No action required.");
}const status = "Below minimum";
switch (status) {
case "Below minimum":
console.log("Upgrade as soon as possible.");
// no break!
case "Below recommended":
console.log("Plan an upgrade.");
break;
default:
console.log("No action required.");
}switch е строг
switch ("1") { case 1: ... } не съвпада: "1" === 1 е false. Първо преобразувайте string-а от JSON: switch (Number(count)).
Анимация
Loops стъпка по стъпка: 7. switch без break (advanced)
MDN: Conditional (ternary) operator
condition ? valueIfTrue : valueIfFalseЕдинственият оператор с три операнда. За разлика от if, той връща стойност, затова може да се присвои, да се подаде или да се сложи в template string. Тези два варианта са еднакви:
let x = 42;
let numType = x % 2 === 0 ? "even" : "odd";
console.log(numType);
let numType2;
if (x % 2 === 0) {
numType2 = "even";
} else {
numType2 = "odd";
}
console.log(numType2);let x = 42;
let numType = x % 2 === 0 ? "even" : "odd";
console.log(numType);
let numType2;
if (x % 2 === 0) {
numType2 = "even";
} else {
numType2 = "odd";
}
console.log(numType2);С отчета:
const env = "hw";
const recommended = env === "hw" ? "4.2.1.7" : "4.1.0.0";
console.log(recommended);
const enabled = doc.inventory.details.remote_login_enabled === "yes";
console.log(`remote login: ${enabled ? "on" : "off"}`);const env = "hw";
const recommended = env === "hw" ? "4.2.1.7" : "4.1.0.0";
console.log(recommended);
const enabled = doc.inventory.details.remote_login_enabled === "yes";
console.log(`remote login: ${enabled ? "on" : "off"}`);Един тернарен оператор е добре, два са твърде много
a ? b : c ? d : e е валидно, но трудно за четене. Вместо това използвайте if...else if.
Анимация
Типове, сравнения и логика: 7. Тернарен оператор (advanced)
MDN: Statements and declarations, Expressions and operators
Кодът на JavaScript се състои от два вида части:
if, switch, return и loops от следващия раздел (for, while).let x, y; // statement (declaration)
if (x) { x = 0; } // statement
switch (x) { } // statement
2 + 3; // expression: 5
x = 5; // expression: assignment has a value too (5)
x ** 6 - x; // expression
x = y = 5; // (y = 5) is an expression, its value 5 goes into x
model === "XR-220"; // expression: true or false
enabled ? "on" : "off"; // expressionЕто защо тернарният оператор работи в template string, а if - не:
const enabled = true;
console.log(`auth: ${enabled ? "on" : "off"}`); // fine
// console.log(`auth: ${if (enabled) {"on"}}`); // syntax errorconst enabled = true;
console.log(`auth: ${enabled ? "on" : "off"}`); // fine
// console.log(`auth: ${if (enabled) {"on"}}`); // syntax errorАнимация
Как се изчислява един израз: 6. Statement срещу expression (advanced)
MDN: Loops and iteration, for, while
Loop повтаря блок от код. В JavaScript за loops използваме:
for (най-често)while и do...while (рядко)for...of (от ES2015) - за arrays и strings; ще го разгледаме в занятие 4, раздел 8.for for (init; condition; update) {
// repeated while condition is true
}Стъпките:
init се изпълнява веднъж, най-напред. Обикновено декларира variable, например let i = 0, с която loop-ът итерира.condition: ако е true, блокът се изпълнява; ако е false, loop-ът приключва.update се изпълнява след блока (обикновено i++).for (let i = 0; i < 5; i++) {
console.log(`i=${i}`);
}for (let i = 0; i < 5; i++) {
console.log(`i=${i}`);
}Анимация
Loops стъпка по стъпка: 2. Loop-ът for
Четните числа от 2 до 10:
for (let i = 2; i <= 10; i += 2) {
console.log(`i=${i}`);
}for (let i = 2; i <= 10; i += 2) {
console.log(`i=${i}`);
}Съставяне на имена на слотове или дискове:
for (let slot = 1; slot <= 4; slot++) {
console.log(`drive_f01_slot_${slot}`);
}for (let slot = 1; slot <= 4; slot++) {
console.log(`drive_f01_slot_${slot}`);
}Обратно броене:
for (let tries = 5; tries > 0; tries--) {
console.log(`Try: ${tries}`);
}for (let tries = 5; tries > 0; tries--) {
console.log(`Try: ${tries}`);
}var в стари правила
Съществуващите правила често пишат for (var i = 0; ...). Тук работи по същия начин, но i продължава да съществува след loop-а. В нов код пишете let; разликата е обяснена в занятие 5.
Един string има дължина (length) и всеки символ има позиция (индекс), като се брои от 0:
const version = "4.2.1.7";
console.log(version.length); // 7
console.log(version[0]); // 4
console.log(version[1]); // .
console.log(version[version.length - 1]); // 7 - the last character
for (let i = 0; i < version.length; i++) {
console.log(i, version[i]);
}const version = "4.2.1.7";
console.log(version.length); // 7
console.log(version[0]); // 4
console.log(version[1]); // .
console.log(version[version.length - 1]); // 7 - the last character
for (let i = 0; i < version.length; i++) {
console.log(i, version[i]);
}String-ът все пак е примитив
В лекция 2 string беше примитив - една проста стойност, а не обект. А тук има .length и [0], като обект с properties. JavaScript прави това вместо вас, зад кулисите; как работи е обяснено в лекция 8 ("Обвивки на примитивите"). Засега: можете да четете символите по индекс, но не можете да ги променяте - version[0] = "5" не прави нищо.
Пребройте частите на една версия (точките + 1):
const version = "11.7.23.0";
let dots = 0;
for (let i = 0; i < version.length; i++) {
if (version[i] === ".") {
dots++;
}
}
console.log(`${dots + 1} parts`);const version = "11.7.23.0";
let dots = 0;
for (let i = 0; i < version.length; i++) {
if (version[i] === ".") {
dots++;
}
}
console.log(`${dots + 1} parts`);for...of (ES2015) дава директно символите, когато не ви трябва индексът:
let digits = 0;
for (const ch of "4.2.1.7") {
if (ch >= "0" && ch <= "9") { // string comparison: is it a digit?
digits++;
}
}
console.log(digits);let digits = 0;
for (const ch of "4.2.1.7") {
if (ch >= "0" && ch <= "9") { // string comparison: is it a digit?
digits++;
}
}
console.log(digits);Loop в loop. "*".repeat(n) дава n звездички:
// half tree
const treeDeep = 5;
for (let i = 1; i <= treeDeep; i++) {
console.log("*".repeat(i));
}// half tree
const treeDeep = 5;
for (let i = 1; i <= treeDeep; i++) {
console.log("*".repeat(i));
}// full tree
const crownHeight = 6;
const boleHeight = 2;
const boleWidth = 3;
let spaceCount;
// the crown:
for (let i = 1; i <= crownHeight; i++) {
spaceCount = crownHeight - i;
console.log(" ".repeat(spaceCount) + "*".repeat(i * 2 - 1));
}
// the bole:
spaceCount = Math.floor((crownHeight * 2 - 1 - boleWidth) / 2);
for (let i = 1; i <= boleHeight; i++) {
console.log(" ".repeat(spaceCount) + "*".repeat(boleWidth));
}// full tree
const crownHeight = 6;
const boleHeight = 2;
const boleWidth = 3;
let spaceCount;
// the crown:
for (let i = 1; i <= crownHeight; i++) {
spaceCount = crownHeight - i;
console.log(" ".repeat(spaceCount) + "*".repeat(i * 2 - 1));
}
// the bole:
spaceCount = Math.floor((crownHeight * 2 - 1 - boleWidth) / 2);
for (let i = 1; i <= boleHeight; i++) {
console.log(" ".repeat(spaceCount) + "*".repeat(boleWidth));
}Същото с два истински вложени loops - всеки ред съставя звездичките си една по една:
for (let row = 1; row <= 4; row++) {
let line = "";
for (let col = 1; col <= row; col++) {
line += "*";
}
console.log(line);
}for (let row = 1; row <= 4; row++) {
let line = "";
for (let col = 1; col <= row; col++) {
line += "*";
}
console.log(line);
}Анимация
Loops стъпка по стъпка: 6. Вложени loops (advanced)
while while (condition) {
// repeated while condition is true
}Условието се проверява преди всеки кръг. Нещо в блока трябва да го промени, иначе loop-ът никога не свършва:
let userTries = 5;
while (userTries > 0) {
console.log(`Try: ${userTries}`);
userTries--; // without this line the loop is endless!
}let userTries = 5;
while (userTries > 0) {
console.log(`Try: ${userTries}`);
userTries--; // without this line the loop is endless!
}Този пример е по-добър като for (вижте по-горе): броят на кръговете е известен. Използвайте while, когато не знаете предварително колко кръга ще трябват:
// Math.random() gives a random number from 0 to 1 (Math: session 8).
// The machine "thinks of" a number:
const machineNumber = Math.round(Math.random() * 10);
console.log(`machineNumber: ${machineNumber}`);
// The user "enters" numbers until they guess it:
let userNumber = Math.round(Math.random() * 10);
console.log(`userNumber: ${userNumber}`);
while (userNumber !== machineNumber) {
userNumber = Math.round(Math.random() * 10);
console.log(`new userNumber: ${userNumber}`);
}// Math.random() gives a random number from 0 to 1 (Math: session 8).
// The machine "thinks of" a number:
const machineNumber = Math.round(Math.random() * 10);
console.log(`machineNumber: ${machineNumber}`);
// The user "enters" numbers until they guess it:
let userNumber = Math.round(Math.random() * 10);
console.log(`userNumber: ${userNumber}`);
while (userNumber !== machineNumber) {
userNumber = Math.round(Math.random() * 10);
console.log(`new userNumber: ${userNumber}`);
}Проверете се: бихте ли написали същата логика с for loop?
Да. Първото предположение отива в init, новото предположение - в update:
for (let n = Math.round(Math.random() * 10);
n !== machineNumber;
n = Math.round(Math.random() * 10)) {
console.log(`new userNumber: ${n}`);
}Всеки while може да се напише като for с празни init и update: for (; condition; ) { ... }. Изберете този, който се чете по-добре - тук това е while.
С отчета - намерете всички точки с indexOf, който връща позицията на текст в string или -1, ако текстът го няма:
const version = doc.overview.version;
let pos = version.indexOf(".");
let count = 0;
while (pos !== -1) {
count++;
console.log(`dot at position ${pos}`);
pos = version.indexOf(".", pos + 1); // search again after this dot
}
console.log(`${count} dots`);const version = doc.overview.version;
let pos = version.indexOf(".");
let count = 0;
while (pos !== -1) {
count++;
console.log(`dot at position ${pos}`);
pos = version.indexOf(".", pos + 1); // search again after this dot
}
console.log(`${count} dots`);indexOf(...) !== -1 в стари правила
platform.indexOf("virtual") !== -1 означава "platform съдържа virtual". Модерната форма е platform.includes("virtual") (ES2015). А model.indexOf("XR-") === 0 означава "започва с" - днес model.startsWith("XR-").
Анимация
Loops стъпка по стъпка: 3. while и безкрайният loop
do...while Като while, но условието се проверява след блока - затова блокът се изпълнява поне веднъж:
do {
// runs once, then again while condition is true
} while (condition);const machineNumber = Math.round(Math.random() * 10);
console.log(`machineNumber: ${machineNumber}`);
let userNumber;
do {
userNumber = Math.round(Math.random() * 10);
console.log(`userNumber: ${userNumber}`);
} while (userNumber !== machineNumber);const machineNumber = Math.round(Math.random() * 10);
console.log(`machineNumber: ${machineNumber}`);
let userNumber;
do {
userNumber = Math.round(Math.random() * 10);
console.log(`userNumber: ${userNumber}`);
} while (userNumber !== machineNumber);do...while се среща рядко в правилата; разпознавайте го, когато го видите.
Анимация
Loops стъпка по стъпка: 4. do...while
break и continue break - излез от loop-а веднага.continue - прескочи останалата част от този кръг и мини към следващия.Изрежете версията от firmware level - спрете при първия интервал:
const level = doc.inventory.details.firmware_level; // "4.2.1.7 (build ...)"
let version = "";
for (let i = 0; i < level.length; i++) {
if (level[i] === " ") {
break; // the version ends here
}
version += level[i];
}
console.log(level);
console.log(version);const level = doc.inventory.details.firmware_level; // "4.2.1.7 (build ...)"
let version = "";
for (let i = 0; i < level.length; i++) {
if (level[i] === " ") {
break; // the version ends here
}
version += level[i];
}
console.log(level);
console.log(version);Запазете само цифрите и точките - прескочете всичко останало:
const text = "v4.2-1.7";
let clean = "";
for (const ch of text) {
const isDigit = ch >= "0" && ch <= "9";
if (!isDigit && ch !== ".") {
continue; // skip this character
}
clean += ch;
}
console.log(clean); // 4.21.7 - careful, "-" was a separator!const text = "v4.2-1.7";
let clean = "";
for (const ch of text) {
const isDigit = ch >= "0" && ch <= "9";
if (!isDigit && ch !== ".") {
continue; // skip this character
}
clean += ch;
}
console.log(clean); // 4.21.7 - careful, "-" was a separator!Резултатът показва, че почистването на данни иска внимание: прескачането на - слепи две части. В занятие 8 ще направим това както трябва, с regular expressions.
Безкрайни loops
Ако условието никога не стане false, страницата или правилото увисва. В браузъра затворете таба; в терминала натиснете Ctrl+C. Преди да пуснете while, запитайте се: какво променя условието?
Анимация
Loops стъпка по стъпка: 5. break и continue
Много съществуващи правила са написани в по-стар стил (отпреди ES2015). Те използват само това, което знаете от тази лекция и от лекция 2 - просто написано по друг начин. Ето едно кратко правило в този стил. Тук emit е фалшив: само отпечатва alert-а.
function emit(id, text) { // fake: prints instead of creating an alert
console.log("ALERT for " + id + ": " + text);
}
function rule(doc) {
if (!doc.overview || !doc.inventory || !doc.inventory.details) return;
var model = doc.overview.model;
if (model != "XR-210" && model != "XR-220") return;
var details = doc.inventory.details;
var loginOn = details.remote_login_enabled == "yes";
if (loginOn == false) return;
var level = details.firmware_level || "";
var version = "";
for (var i = 0; i < level.length; i++) {
if (level[i] == " ") break;
version = version + level[i];
}
var name = doc.status && doc.status.device_alias || "unknown device";
emit(name, "remote login is on, firmware " + version);
}
rule(doc);function emit(id, text) { // fake: prints instead of creating an alert
console.log("ALERT for " + id + ": " + text);
}
function rule(doc) {
if (!doc.overview || !doc.inventory || !doc.inventory.details) return;
var model = doc.overview.model;
if (model != "XR-210" && model != "XR-220") return;
var details = doc.inventory.details;
var loginOn = details.remote_login_enabled == "yes";
if (loginOn == false) return;
var level = details.firmware_level || "";
var version = "";
for (var i = 0; i < level.length; i++) {
if (level[i] == " ") break;
version = version + level[i];
}
var name = doc.status && doc.status.device_alias || "unknown device";
emit(name, "remote login is on, firmware " + version);
}
rule(doc);Старите форми и какво пишете днес:
| Стар стил | Означава | Днес |
|---|---|---|
var x = ... | variable | const (или let, ако се променя) |
a != b, a == b | сравнение с преобразуване на типа | !==, === |
if (loginOn == false) | "не е включено" | if (!loginOn) |
x || "" | стойност по подразбиране за всяка falsy стойност | x ?? "" (само за null / undefined) |
a && a.b | a.b, само ако a съществува | a?.b |
s = s + x | добавя в края | s += x |
"text " + x | съединява strings | `text ${x}` |
Същото правило в модерен стил:
function emit(id, text) { // fake: prints instead of creating an alert
console.log(`ALERT for ${id}: ${text}`);
}
function rule(doc) {
if (!doc.overview || !doc.inventory?.details) return;
const model = doc.overview.model;
if (model !== "XR-210" && model !== "XR-220") return;
const details = doc.inventory.details;
if (details.remote_login_enabled !== "yes") return;
const level = details.firmware_level ?? "";
let version = "";
for (let i = 0; i < level.length; i++) {
if (level[i] === " ") break;
version += level[i];
}
const name = doc.status?.device_alias ?? "unknown device";
emit(name, `remote login is on, firmware ${version}`);
}
rule(doc);function emit(id, text) { // fake: prints instead of creating an alert
console.log(`ALERT for ${id}: ${text}`);
}
function rule(doc) {
if (!doc.overview || !doc.inventory?.details) return;
const model = doc.overview.model;
if (model !== "XR-210" && model !== "XR-220") return;
const details = doc.inventory.details;
if (details.remote_login_enabled !== "yes") return;
const level = details.firmware_level ?? "";
let version = "";
for (let i = 0; i < level.length; i++) {
if (level[i] === " ") break;
version += level[i];
}
const name = doc.status?.device_alias ?? "unknown device";
emit(name, `remote login is on, firmware ${version}`);
}
rule(doc);Проверете се:
version за примерния JSON документ и защо loop-ът спира там?device_alias е "" (празен). Кое име използва всяко от правилата?remote_login_enabled липсва. Създава ли някое от правилата alert?"4.2.1.7". Нивото на firmware е "4.2.1.7 (build ...)", а break прекратява loop-а при първия интервал."unknown device" - || заменя всяка falsy стойност, също и "". Модерното правило: "" - ?? заменя само null и undefined. Когато модернизирате, решете кое от двете е нужно на правилото (раздел 5).undefined == "yes" е false, затова loginOn е false и старото правило спира. undefined !== "yes" е true, затова и модерното правило спира.Във вашето копие на Homework repository първо вземете новите задачи:
git pull --no-rebase --no-edit upstream main
node 03-conditions-and-loops/practice/task1-predict.jsВсеки файл обяснява задачата в началото си. Пишете кода си между редовете your code; проверките най-долу извеждат OK или FAIL.
| Задача | Файл | Какво правите |
|---|---|---|
| 1 | task1-predict.js | Предвидете 18 сравнения и логически изрази, после проверете |
| 2 | task2-firmware-status.js | Класифицирайте firmware level (if...else if) и изберете текста на действието (switch) |
| 3 | task3-rule-condition.js | Условието на примерното правило, с guard clauses за липсващи данни |
| 4 | task4-version-loops.js | Изрязване, броене и почистване на версии с loops, break и continue |
| 5 | task5-defaults.js | || или ?? - показване на липсващи стойности, без да се губят 0 и false |
| Тема | Запомнете |
|---|---|
| Сравнения | Винаги boolean. Използвайте === / !==; == null само за "null или undefined" |
| Strings | Сравняват се символ по символ: "4.10" < "4.2" е true. Сравнявайте версиите част по част |
| Логика | &&, ||, !; скоби при смесване; !(a && b) е !a || !b |
| Truthy / falsy | Falsy: false, 0, NaN, "", null, undefined. "no" и "0" са truthy |
| Стойности по подразбиране | a || b заменя всяка falsy стойност; a ?? b - само null / undefined |
if | Първо най-строгото условие; изпълнява се само първото съвпадение; guard clauses с return |
switch | Една стойност, ===, break след всеки case, default накрая |
| Тернарен оператор | cond ? a : b връща стойност; само едно ниво |
| Loops | for, когато броят е известен, while, когато не е, do...while - поне веднъж |
break / continue | Излизане от loop-а / прескачане към следващия кръг |
| Стар стил | var, ==, == false, a && a.b - четете го, пишете const / let, ===, !, ?. |
03-conditions-and-loops/homework.js (в homework repository): текст за състоянието на две настройки и първата позиция, на която две версии се различават.OK.==, || "" или == false, които биха могли да ви изненадат???, Loops and iterationСледващото занятие: arrays - списъци от модели, дискове и записи. model !== "XR-210" && model !== "XR-220" от днешното правило става allow-list: !["XR-210", "XR-220"].includes(model).