Skip to content

Instantly share code, notes, and snippets.

@olehi94
Last active March 1, 2019 20:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save olehi94/ff721b542f0addd0af9b26d29a246ae1 to your computer and use it in GitHub Desktop.
Save olehi94/ff721b542f0addd0af9b26d29a246ae1 to your computer and use it in GitHub Desktop.

Асинхронные скрипты: defer/async

  • Специальные атрибуты async и defer используются для того, чтобы пока грузится внешний скрипт – браузер показал остальную (следующую за ним) часть страницы. Без них этого не происходит.
  • Разница между async и defer: атрибут defer сохраняет относительную последовательность скриптов, а async – нет. Кроме того, defer всегда ждёт, пока весь HTML-документ будет готов, а async – нет.

Директива use strict

  • Директива выглядит как строка "use strict"; или 'use strict'; и ставится в начале скрипта.

Например:

"use strict";

// этот код будет работать по современному стандарту ES5
...

Шесть типов данных, typeof

  • Число «number»
var n = 123;
n = 12.345;

Бесконечность Infinity получается при делении на ноль:

alert( 1 / 0 ); // Infinity

но,

alert( 0 / 0 ); // NaN
alert( null / 0 ); // NaN
alert( false / 0 ); // NaN
alert( 'text' / 0 ); // NaN
alert( [] / 0 ); // NaN
alert( underfined / 0 ); // NaN

Ошибка вычислений NaN будет результатом некорректной математической операции, например:

alert( "нечисло" * 2 ); // NaN, ошибка
  • Строка «string»
  • Булевый (логический) тип «boolean»
  • Специальное значение «null»

В JavaScript null не является «ссылкой на несуществующий объект» или «нулевым указателем», как в некоторых других языках. Это просто специальное значение, которое имеет смысл «ничего» или «значение неизвестно».

  • Специальное значение «undefined»

Если переменная объявлена, но в неё ничего не записано, то её значение как раз и есть undefined

  • Объекты «object» Он используется для коллекций данных и для объявления более сложных сущностей. Объявляются объекты при помощи фигурных скобок {...}, например:
var user = { name: "Вася" };
  • Оператор typeof
    • У него есть два синтаксиса: со скобками и без:

      • Синтаксис оператора: typeof x.
      • Синтаксис функции: typeof(x).
    • Результатом typeof является строка, содержащая тип:

      typeof undefined // "undefined"
      typeof 0 // "number"
      typeof Infinity // "number"
      typeof NaN // "number"
      typeof true // "boolean"
      typeof "foo" // "string"
      typeof {} // "object"
      typeof [] // "object"
      typeof null // "object"  (1)
      typeof function(){} // "function"  (2)

      Последние две строки помечены, потому что typeof ведет себя в них по-особому.

      1. Результат typeof null == "object" – это официально признанная ошибка в языке, которая сохраняется для совместимости. На самом деле null – это не объект, а отдельный тип данных.

      2. Заметим, что функции не являются отдельным базовым типом в JavaScript, а подвидом объектов. Но typeof выделяет функции отдельно, возвращая для них "function". На практике это весьма удобно, так как позволяет легко определить функцию.

  • Еще..
"" == 0 // true
" " == 0 // true
но, "" == " " // false (каждый символ иммет вес)
и "" < " " // true (каждый символ иммет вес)

Основные операторы

Операторы сравнения и логические значения

  • Сравнение с null и undefined

    Интуитивно кажется, что null/undefined эквивалентны нулю, но это не так.

    Они ведут себя по-другому.

    1. Значения null и undefined равны == друг другу и не равны чему бы то ни было ещё. Это жёсткое правило буквально прописано в спецификации языка.
    2. При преобразовании в число null становится 0, а undefined становится NaN.

    Посмотрим забавные следствия.

  • Некорректный результат сравнения null с 0

    Сравним null с нулём:

    alert( null > 0 ); // false
    alert( null == 0 ); // false

    Итак, мы получили, что null не больше и не равен нулю. А теперь…

    alert(null >= 0); // true

    Как такое возможно? Если нечто «больше или равно нулю», то резонно полагать, что оно либо больше, либо равно. Но здесь это не так.

    Дело в том, что алгоритмы проверки равенства == и сравнения >= > < <= работают по-разному.

    Сравнение честно приводит к числу, получается ноль. А при проверке равенства значения null и undefined обрабатываются особым образом: они равны друг другу, но не равны чему-то ещё.

    В результате получается странная с точки зрения здравого смысла ситуация, которую мы видели в примере выше.

  • Несравнимый undefined

    Значение undefined вообще нельзя сравнивать:

    alert( undefined > 0 ); // false (1)
    alert( undefined < 0 ); // false (2)
    alert( undefined == 0 ); // false (3)
    1. Сравнения (1) и (2) дают false потому, что undefined при преобразовании к числу даёт NaN. А значение NaN по стандарту устроено так, что сравнения ==, <, >, <=, >= и даже === с ним возвращают false.
    2. Проверка равенства (3) даёт false, потому что в стандарте явно прописано, что undefined равно лишь null или себе и ничему другому.
    Вывод: любые сравнения с undefined/null, кроме точного ===, следует делать с осторожностью.

    Желательно не использовать сравнения >= > < <= с ними, во избежание ошибок в коде.

Побитовые операторы

  • Вспомогательные функции parseInt, toString

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

    • parseInt("11000", 2) – переводит строку с двоичной записью числа в число.
    • n.toString(2) – получает для числа n запись в 2-ной системе в виде строки.

    Например:

    var access = parseInt("11000", 2); // получаем число из строки
    alert( access ); // 24, число с таким 2-ным представлением
    var access2 = access.toString(2); // обратно двоичную строку из числа
    alert( access2 ); // 11000
    • А таким способом можно привести число с любой системи числения.

    Например:

    0b100; // 4, 2-ичная, начинаеться на 0b
    0o100; // 64, , 8-ичная начинаеться на 0o
    0x100; // 256, 16-ичная, начинаеться на 0x
  • Округление

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

Например, двойное НЕ (~):

alert( ~~12.345 ); // 12

Подойдёт и Исключающее ИЛИ (^) с нулём:

alert( 12.345 ^ 0 ); // 12

Последнее даже более удобно, поскольку отлично читается:

alert(12.3 * 14.5 ^ 0); // (=178) "12.3 умножить на 14.5 и округлить"

У побитовых операторов достаточно низкий приоритет, он меньше чем у остальной арифметики:

alert( 1.1 + 1.2 ^ 0 ); // 2, сложение выполнится раньше округления
  • Проверки на равенство -1
if (~n) { n не -1 }

Условные операторы: if, '?'

  • Преобразование к логическому типу Оператор if (...) вычисляет и преобразует выражение в скобках к логическому типу.

    В логическом контексте:

    • Число 0, пустая строка "", null и undefined, а также NaN являются false,
    • Остальные значения – true.

Логические операторы

  • || (ИЛИ)

    Оператор || вычисляет операнды слева направо до первого «истинного» и возвращает его, а если все ложные – то последнее значение.

    Иначе можно сказать, что "|| запинается на правде".

  • && (И) Итак, оператор && вычисляет операнды слева направо до первого «ложного» и возвращает его, а если все истинные – то последнее значение.

    Иначе можно сказать, что "&& запинается на лжи".

    • Приоритет у && больше, чем у ||
  • ! (НЕ) Действия !:

    • Сначала приводит аргумент к логическому типу true/false.
    • Затем возвращает противоположное значение.

    В частности, двойное НЕ используют для преобразования значений к логическому типу:

    alert( !!"строка" ); // true
    alert( !!null ); // false
  • Nb

    alert(5) // underfined

Преобразование типов для примитивов

  • Строковое преобразование

    Можно осуществить преобразование явным вызовом String(val):

    alert( String(null) === "null" ); // true

    Также для явного преобразования применяется оператор "+", у которого один из аргументов строка. В этом случае он приводит к строке и другой аргумент, например:

    alert( true + "test" ); // "truetest"
    alert( "123" + undefined ); // "123undefined"
  • Численное преобразование

    Для преобразования к числу в явном виде можно вызвать Number(val), либо, что короче, поставить перед выражением унарный плюс "+":

    var a = +"123"; // 123
    var a = Number("123"); // 123, тот же эффект
    • Значение преобразуется в:

      undefined -> NaN

      null -> 0

      true / false -> 1 / 0

      Строка -> Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то 0, иначе из непустой строки "считывается" число, при ошибке результат NaN.

      // после обрезания пробельных символов останется "123"
      alert( +"   \n  123   \n  \n" ); // 123

      Ещё примеры:

      • Логические значения:
      alert( +true ); // 1
      alert( +false ); // 0
      • Сравнение разных типов – значит численное преобразование:
      alert( "\n0 " == 0 ); // true

      При этом строка "\n0" преобразуется к числу, как указано выше: начальные и конечные пробелы обрезаются, получается строка "0", которая равна 0.

      • С логическими значениями:
      alert( "\n" == false );
      alert( "1" == true );

      Здесь сравнение "==" снова приводит обе части к числу. В первой строке слева и справа получается 0, во второй 1.

  • Логическое преобразование

    • Преобразование к true/false происходит в логическом контексте, таком как if(value), и при применении логических операторов.

    • Все значения, которые интуитивно «пусты», становятся false. Их несколько: 0, пустая строка, null, undefined и NaN.

      Остальное, в том числе и любые объектыtrue.

    • Для явного преобразования используется двойное логическое отрицание !!value или вызов Boolean(value).

    • Обратите внимание: строка "0" становится true. В отличие от многих языков программирования (например PHP), "0" в JavaScript является true, как и строка из пробелов:

      alert( !!"0" ); // true
      alert( !!" " ); // любые непустые строки, даже из пробелов - true!
    • Логическое преобразование интересно тем, как оно сочетается с численным.

      Два значения могут быть равны, но одно из них в логическом контексте true, другое – false.

      Например, равенство в следующем примере верно, так как происходит численное преобразование:

      alert( 0 == "\n0\n" ); // true

      … А в логическом контексте левая часть (0) даст false, правая ("\n0\n") – true, так как любая не пустая строка в логическом контексте равна true:

      if ("\n0\n") {
      alert( "true, совсем не как 0!" );
      }

      С точки зрения преобразования типов в JavaScript это совершенно нормально. При сравнении с помощью «==»численное преобразование, а в ifлогическое, только и всего.

  • Немного практики

    "" + 1 + 0 = "10" // (1)
    "" - 1 + 0 = -1 // (2)
    true + false = 1
    6 / "3" = 2
    "2" * "3" = 6
    4 + 5 + "px" = "9px"
    "$" + 4 + 5
     = "$45"
    "4" - 2
     = 2
    "4px" - 2
     = NaN
    7 / 0
     = Infinity
    " -9\n" + 5 = " -9\n5"
    " -9\n" - 5 = -14
    5 && 2
     = 2
    2 && 5
     = 5
    5 || 0
     = 5
    0 || 5 = 5
    null + 1 = 1 // (3)
    undefined + 1 = NaN // (4)
    null == "\n0\n" = false // (5)
    +null == +"\n0\n" = true // (6)
    

    (1). Оператор "+" в данном случае прибавляет 1 как строку, и затем 0.

    (2). Оператор "-" работает только с числами, так что он сразу приводит "" к 0.

    (3). null при численном преобразовании становится 0

    (4). undefined при численном преобразовании становится NaN

    (5). При сравнении == с null преобразования не происходит, есть жёсткое правило: null == undefined и только.

    (6). И левая и правая часть == преобразуются к числу 0.

Циклы while, for

  • Следующая итерация: continue

    • Директива continue/break позволяет обойтись без скобок
    if (i % 2 == 0) continue;
    • Нельзя использовать break/continue справа от оператора „?“

    Синтаксические конструкции, которые не возвращают значений, нельзя использовать в операторе '?'.

  • Метки для break/continue

    Бывает нужно выйти одновременно из нескольких уровней цикла.

    Например, внутри цикла по i находится цикл по j, и при выполнении некоторого условия мы бы хотели выйти из обоих циклов сразу:

    outer: for (var i = 0; i < 3; i++) {
    
    for (var j = 0; j < 3; j++) {
    
      var input = prompt('Значение в координатах '+i+','+j, '');
    
      // если отмена ввода или пустая строка -
      // завершить оба цикла
      if (!input) break outer; // (*)
    
      }
    }
    alert('Готово!');

    Вызов break outer ищет ближайший внешний цикл с такой меткой и переходит в его конец.

    В примере выше это означает, что будет разорван самый внешний цикл и управление перейдёт на alert.

    Директива continue также может быть использована с меткой, в этом случае управление перепрыгнет на следующую итерацию цикла с меткой.

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

  • Вывести простые числа

    Натуральное число, большее 1, называется простым, если оно ни на что не делится, кроме себя и 1.

    Другими словами, n>1 – простое, если при делении на любое число от 2 до n-1 есть остаток.

    Создайте код, который выводит все простые числа из интервала от 2 до 10. Результат должен быть: 2,3,5,7.

    P.S. Код также должен легко модифицироваться для любых других интервалов.

    Решение с использованием метки:

    nextPrime:
      for (var i = 2; i <= 10; i++) {
    
        for (var j = 2; j < i; j++) {
          if (i % j == 0) continue nextPrime;
        }
    
        alert( i ); // простое
      }

    Решение без метки из записью в масив:

    var simpleArr = [];
    for (var number = 2; number <= 10; number++) {
      let isSimply = true;
      for (var j = 2; j <= Math.floor(number / 2); j++) {
        if (number % j) continue;
        isSimply = false;
        break;
      }
      if (isSimply === true) {
        simpleArr.push(number);
      }
    }
    alert(simpleArr);

Конструкция switch

  • Оператор switch предполагает строгое равенство ===

Функции

  • Локальные переменные

    Блоки if/else, switch, for, while, do..while не влияют на область видимости переменных.

    При объявлении переменной в таких блоках, она всё равно будет видна во всей функции.

    Например:

    function count() {
      // переменные i,j не будут уничтожены по окончании цикла
      for (var i = 0; i < 3; i++) {
        var j = i * 2;
      }
    
      alert( i ); // i=3, последнее значение i, при нём цикл перестал работать
      alert( j ); // j=4, последнее значение j, которое вычислил цикл
    }
  • Внешние переменные

    Переменные, объявленные на уровне всего скрипта, называют «глобальными переменными».

  • Аргументы по умолчанию

    Если параметр не передан при вызове функции – он считается равным undefined.

    Для указания значения «по умолчанию», то есть, такого, которое используется, если аргумент не указан, используется два способа(ES5) и один способ(ES6):

    • ES5 Можно проверить, равен ли аргумент undefined, и если да – то записать в него значение по умолчанию. Этот способ продемонстрирован в примере выше.

      function showMessage(from, text) {
        if (text === undefined) {
          text = 'текст не передан';
        }
      
        alert( from + ": " + text );
      }
      
      showMessage("Маша", "Привет!"); // Маша: Привет!
      showMessage("Маша"); // Маша: текст не передан
    • ES5 Использовать оператор ||:

      function showMessage(from, text) {
        text = text || 'текст не передан';
      
        ...
      }

      Второй способ считает, что аргумент отсутствует, если передана пустая строка, 0, или вообще любое значение, которое в логическом контексте является false.

    • ES6 Присвоить значение параметру функции

      function showMessage(from, text = 10) {
        ...
      }
  • Возврат значения

    • Значение функции без return и с пустым return

      В случае, когда функция не вернула значение или return был без аргументов, считается что она вернула undefined:

      function doNothing() { /* пусто */ }
      
      alert( doNothing() ); // undefined
      
      or
      
      function doNothing() {
        return;
      }
      
      alert( doNothing() === undefined ); // true
  • Выбор имени функции

    • Сверхкороткие имена функций

      Имена функций, которые используются очень часто, иногда делают сверхкороткими.

      Например, во фреймворке jQuery есть функция $, во фреймворке Prototype – функция $$, а в библиотеке LoDash очень активно используется функция с названием из одного символа подчеркивания _.

Функциональные выражения

Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.

  • Сравнение Function Declaration с Function Expression

    «Классическое» объявление функции вида function имя(параметры) {...}, называется в спецификации языка «Function Declaration».

    • Function Declaration – функция, объявленная в основном потоке кода.
    • Function Expression – объявление функции в контексте какого-либо выражения, например присваивания.

    Несмотря на немного разный вид, по сути две эти записи делают одно и то же:

    // Function Declaration
    function sum(a, b) {
      return a + b;
    }
    
    // Function Expression
    var sum = function(a, b) {
      return a + b;
    }

    Оба этих объявления говорят интерпретатору: "объяви переменную sum, создай функцию с указанными параметрами и кодом и сохрани её в sum".

    Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.

    Поэтому их можно вызвать до объявления, например:

    sayHi("Вася"); // Привет, Вася
    
    function sayHi(name) {
      alert( "Привет, " + name );
    }

    А если бы это было объявление Function Expression, то такой вызов бы не сработал:

    sayHi("Вася"); // ошибка!
    
    var sayHi = function(name) {
      alert( "Привет, " + name );
    }
  • Условное объявление функции

    В некоторых случаях «дополнительное удобство» Function Declaration может сослужить плохую службу.

    Например, попробуем, в зависимости от условия, объявить функцию sayHi по-разному:

    var age = +prompt("Сколько вам лет?", 20);
    
    if (age >= 18) {
      function sayHi() {
        alert( 'Прошу вас!' );
      }
    } else {
      function sayHi() {
        alert( 'До 18 нельзя' );
      }
    }
    
    sayHi();

    Function Declaration при use strict видны только внутри блока, в котором объявлены. Так как код в учебнике выполняется в режиме use strict, то будет ошибка.

    А что, если использовать Function Expression?

    var age = prompt('Сколько вам лет?');
    
    var sayHi;
    
    if (age >= 18) {
      sayHi = function() {
        alert( 'Прошу Вас!' );
      }
    } else {
      sayHi = function() {
        alert( 'До 18 нельзя' );
      }
    }
    
    sayHi();

    Или даже так:

    var age = prompt('Сколько вам лет?');
    
    var sayHi = (age >= 18) ?
      function() { alert('Прошу Вас!');  } :
      function() { alert('До 18 нельзя'); };
    
    sayHi();
  • Анонимные функции

    function ask(question, yes, no) {
      if (confirm(question)) yes()
      else no();
    }
    
    function showOk() {
      alert( "Вы согласились." );
    }
    
    function showCancel() {
      alert( "Вы отменили выполнение." );
    }
    
    // использование
    ask("Вы согласны?", showOk, showCancel);

    Здесь же обратим внимание на то, что то же самое можно написать более коротко:

    function ask(question, yes, no) {
    if (confirm(question)) yes()
    else no();
    }
    
    ask(
      "Вы согласны?",
      function() { alert("Вы согласились."); },
      function() { alert("Вы отменили выполнение."); }
    );

    Функциональное выражение, которое не записывается в переменную, называют анонимной функцией.

  • new Function

    Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.

    Он позволяет создавать функцию полностью «на лету» из строки, вот так:

    var sum = new Function('a,b', ' return a+b; ');
    
    var result = sum(1, 2);
    alert( result ); // 3

    То есть, функция создаётся вызовом new Function(params, code):

    Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.

    Пример использования – динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.

Рекурсия, стек

Рекурсия - когда функция вызывает сама себя.

  • Степень pow(x, n) через рекурсию

    pow(x, n) = x * pow(x, n - 1)

    Например, вычислим pow(2, 4), последовательно переходя к более простой задаче:

    1.pow(2, 4) = 2 * pow(2, 3)

    2.pow(2, 3) = 2 * pow(2, 2)

    3.pow(2, 2) = 2 * pow(2, 1)

    4.pow(2, 1) = 2

    Этот алгоритм на JavaScript:

    function pow(x, n) {
      if (n != 1) { // пока n != 1, сводить вычисление pow(x,n) к pow(x,n-1)
        return x * pow(x, n - 1);
      } else {
        return x;
      }
    }
    
    alert( pow(2, 3) ); // 8

    Говорят, что «функция pow рекурсивно вызывает сама себя» до n == 1.

    Значение, на котором рекурсия заканчивается, называют базисом рекурсии. В примере выше базисом является 1.

    Общее количество вложенных вызовов называют глубиной рекурсии. В случае со степенью, всего будет n вызовов.

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

    Итак, рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его – ещё к более простому, и так далее, пока значение не станет очевидно.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment