Skip to content

Latest commit

 

History

History
603 lines (387 loc) · 21.4 KB

JS_8.md

File metadata and controls

603 lines (387 loc) · 21.4 KB

Пример всплытия и погружения

<!DOCTYPE html>
<html lang="ru">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body * {
            margin: 10px;
            border: 1px solid blue;
        }
    </style>
</head>

<body>
    <form>FORM
        <div>DIV
            <p>P</p>
        </div>
    </form>
    <script>
        for (let elem of document.querySelectorAll('*')) {
            elem.addEventListener("click", e => alert(`Погружение: ${elem.tagName}`), true);
            elem.addEventListener("click", e => alert(`Всплытие: ${elem.tagName}`));
        }
    </script>
</body>

</html>

Прекращение всплытия событий

Всплытие идёт с «целевого» элемента прямо наверх.

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

Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.

Для этого нужно вызвать метод event.stopPropagation().

Например, здесь при клике на кнопку обработчик body.onclick не сработает:

<body onclick="alert(`сюда всплытие не дойдёт`)">
  <button onclick="event.stopPropagation()">Кликни меня</button>
</body>

event.stopImmediatePropagation()

Для того, чтобы полностью остановить обработку, существует метод event.stopImmediatePropagation().

Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.

Планирование: setTimeout и setInterval

Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».

Для этого существуют два метода:

  • setTimeout позволяет вызвать функцию один раз через определённый интервал времени.
  • setInterval позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени.

setTimeout

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

Параметры:

  • func|code

Функция или строка кода для выполнения. Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется.

  • delay

Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0.

  • arg1, arg2…

Аргументы, передаваемые в функцию (не поддерживается в IE9-)

Например, данный код вызывает sayHi() спустя одну секунду:

function sayHi() {
  alert('Привет');
}

setTimeout(sayHi, 1000);

function sayHi(phrase, who) {
  alert( phrase + ', ' + who );
}

setTimeout(sayHi, 1000, "Привет", "Джон"); // Привет, Джон

Если первый аргумент является строкой, то JavaScript создаст из неё функцию.

Это также будет работать:

setTimeout("alert('Привет')", 1000);

Отмена через clearTimeout

Вызов setTimeout возвращает «идентификатор таймера» timerId, который можно использовать для отмены дальнейшего выполнения.

Синтаксис для отмены:

let timerId = setTimeout(...);
clearTimeout(timerId);

setInterval

Метод setInterval имеет такой же синтаксис как setTimeout:

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

Все аргументы имеют такое же значение.

Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.

// повторить с интервалом 2 секунды
let timerId = setInterval(() => alert('tick'), 2000);

// остановить вывод через 5 секунд
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);

Рекурсивный setTimeout

Есть два способа запускать что-то регулярно.

Один из них setInterval. Другим является рекурсивный setTimeout. Например:

/** вместо:
let timerId = setInterval(() => alert('tick'), 2000);
*/

let timerId = setTimeout(function tick() {
  alert('tick');
  timerId = setTimeout(tick, 2000); // (*)
}, 2000);

Замыкание

Что такое замыкание?

Замыкание это функция у которой есть доступ к своей внешней функции по области видимости, даже после того, как внешняя функция прекратилась.

Замыкание это функция, захватывающая лексическое окружение того контекста, где она создана.

Что такое лексическая область видимости?

Лексическая область видимости это статическая область в JavaScript, имеющая прямое отношение к доступу к переменным, функциям и объектам, основываясь на их расположении в коде.

Вот пример:

let a = 'global';
function outer() {
    let b = 'outer';
function inner() {
      let c = 'inner'
      console.log(c);   // выдаст 'inner'
      console.log(b);   // выдаст 'outer'
      console.log(a);   // выдаст 'global'
    }
    console.log(a);     // выдаст 'global'
    console.log(b);     // выдаст 'outer'
    inner();
  }
outer();
console.log(a);         // выдаст 'global'

В общем, цепочка области видимости выше будет такой:

Global {
  outer {
    inner
  }
}

Практические примеры замыкания

function person() {
  let name = 'Peter';
  
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // выведет 'Peter'

function getCounter() {
  let counter = 0;
  return function() {
    return counter++;
  }
}
let count = getCounter();
console.log(count());  // 0
console.log(count());  // 1
console.log(count());  // 2

Как работают замыкания?

Чтобы реально это понять, нам надо разобраться в двумя самыми важными концепциями в JavaScript, а именно,

  1. Контекст выполнения

  2. Лексическое окружение.

Контекст выполнения

Это абстрактная среда, в которой JavaScript код оценивается и выполняется.

Когда выполняется “глобальный” код, он выполняется внутри глобального контекста выполнения, а код функции выполняется внутри контекста выполнения функции.

Тут может быть только один запущенный контекст выполнения (JavaScript это однопоточный язык), который управляется стеком запросов.

Стек выполнения это стек с принципом LIFO (Последний вошёл, первый вышел), в котором элементы могут быть добавлены или удалены только сверху стека.

Давайте посмотрим на пример кода, чтобы лучше понять контекст выполнения и стек:

help

Во время выполнения этого кода, движок JavaScript создаёт глобальный контекст вызова, для того, чтобы выполнить глобальный код и когда он доходит до вызова функции first(), он создаёт новый контекст выполнения для этой функции и ставит её на вершину стека вызовов.

Так что он будет выглядеть таким образом для кода выше:

help

У лексического окружения есть два компонента:

(1) запись в окружении

(2) отсылка к внешнему окружению.

  1. Запись в окружении(environment record) это место хранятся объявления переменной или функции.

  2. Отсылка к внешнему окружению (reference to the outer environment) означает то, что у него есть доступ к внешнему (родительскому) лексическому окружению.

Этот компонент самый важный для понимания того, как работают замыкания.

lexicalEnvironment = {
  environmentRecord: {
    <identifier> : <value>,
    <identifier> : <value>
  }
  outer: < Reference to the parent lexical environment>
}

Пример:

let a = 'Hello World!';
function first() {
  let b = 25;  
  console.log('Inside first function');
}
first();
console.log('Inside global execution context');

globalLexicalEnvironment = {
  environmentRecord: {
      a     : 'Hello World!',
      first : < reference to function object >
  }
  outer: null
}

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

Рекурсия

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

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

Вызов рекурсивной функции, называется рекурсивным вызовом.

В качестве примера, вычислим факториал с использованием рекурсии:

function f(n) {
  if (n === 1) return 1;
  return n * f(n - 1);
}
alert(f(4));

Визуально последовательное выполнение данной функции можно представить так:

help

Выполнение программы многократно спускается вниз, пока не упрётся в условие выхода из рекурсии.

Достигнув конца, она идёт обратно, возвращая результаты сделанных вызовов.

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

Любую рекурсию можно заменить циклом. Перепишем вычисление факториала с помощью цикла:

let result = 1;
for (let i = 2; i <= 4; i++) {
  result *= i;
}
console.log(result);   // 24

Еще примеры:

Как можно найти сумму

function sum(num) {
    if (num === 0) {
        return 0;
    } else {
        return num + sum(--num)
    }
}

const res = sum(3);

console.log(res); // 6

Еще пример

Напишем функцию pow(x, n), которая возводит x в натуральную степень n. Иначе говоря, умножает x на само себя n раз.

function pow(x, n) {
  if (n == 1) {
    return x;
  } else {
    return x * pow(x, n - 1);
  }
}

alert( pow(2, 3) ); // 8

Измерение производительности JavaScript-функций

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

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

Метод performance.now()

Интерфейс Performance даёт доступ к значению типа DOMHighResTimeStamp через метод performance.now().

Этот метод возвращает временную метку, указывающую на время в миллисекундах, прошедшее с момента начала существования документа.

Причём, точность этого показателя составляет порядка 5 микросекунд (доли миллисекунды).

Для того чтобы измерить производительность фрагмента кода, пользуясь методом performance.now(), нужно выполнить два измерения времени, сохранить результаты этих измерений в переменных, а затем вычесть из результатов второго измерения результаты первого:

const t0 = performance.now();
for (let i = 0; i < array.length; i++) 
{
  // какой-то код
}
const t1 = performance.now();
console.log(t1 - t0, 'milliseconds');

В Chrome после выполнения этого кода можно получить примерно такой результат:

0.6350000001020817 "milliseconds"

В Firefox — такой:

1 milliseconds

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

Дело в том, что в Firefox 60 точность результатов, возвращаемых API Performance, снижена.

Метод console.time()

Измерение времени с использованием этого API производится крайне просто.

Достаточно, перед кодом, производительность которого нужно оценить, вызвать метод console.time(), а после этого кода — метод console.timeEnd().

console.time('test');
for (let i = 0; i < array.length; i++) {
  // какой-то код
}
console.timeEnd('test');

После выполнения подобного кода система автоматически выведет в консоль сведения о прошедшем времени.

В Chrome это будет выглядеть примерно так:

test: 0.766845703125ms

В Firefox — так:

test: 2ms - timer ended

Формат JSON, метод toJSON

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

Естественно, такая строка должна включать в себя все важные свойства.

Мы могли бы реализовать преобразование следующим образом:

let user = {
  name: "John",
  age: 30,

  toString() {
    return `{name: "${this.name}", age: ${this.age}}`;
  }
};

alert(user); // {name: "John", age: 30}

…Но в процессе разработки добавляются новые свойства, старые свойства переименовываются и удаляются.

JSON.stringify

JSON (JavaScript Object Notation) – это общий формат для представления значений и объектов

JavaScript предоставляет методы:

  • JSON.stringify для преобразования объектов в JSON.

  • JSON.parse для преобразования JSON обратно в объект.

Например, здесь мы преобразуем через JSON.stringify данные студента:

let student = {
  name: 'John',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  wife: null
};

let json = JSON.stringify(student);

alert(typeof json); // мы получили строку!

alert(json);
/* выведет объект в формате JSON:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "wife": null
}
*/

HomeWork 7:

pure JS

1 ) Создайте элемент 'p', при клике на котором появляется картинка размером 100px

При наведении указателя мышки на картинку ее размер должен плавно увеличиваться до 200px

При клике на картинке она должна исчезать

  1. Дан массив с числами.

Найдите сумму последних N элементов до первого нуля с конца.

Пример: [1, 2, 3, 0, 4, 5, 6] - суммируем последние 3 элемента, так как дальше стоит элемент с числом 0.

  1. Дан массив с числами. Узнайте сколько элементов с начала массива надо сложить, чтобы в сумме получилось больше 10-ти.

  2. Есть инпут, в который что-то вводим. Рядом с инпутом есть кнопка. По нажатию на кнопку выводим в консоль то, что вписали в инпут.

  3. Привяжите всем ссылкам в документе событие - по наведению на ссылку в конец ее текста дописывается ее href в круглых скобках.

HTML:

<div>
    <a href="https://www.google.com/" title="">Текст первой ссылки</a>
</div>
<div>
    <a href="https://www.apple.com/" title="">Текст второй ссылки</a>
</div>
<div>
    <a href="https://www.youtube.com/" title="">Текст третьей ссылки</a>
</div>

  1. Добавьте JavaScript к кнопке button, чтобы при нажатии элемент
    исчезал.

Как должно быть:

https://prnt.sc/xu2l8p

  1. Сделать то, что на скрине

https://prnt.sc/xu1x8n

не используем type='number' для инпута

Логика:

  • Сделать валидацию для инпута. Нужно проверять то, что вводим в инпут. Вводить можно только тип данных number. Если юзер ввел букву, то удаляем все (все что ввели) в инпуте

  • В инпут юзер вводит кол-во пикселей, на которое хотим передвинуть круг (по Х координате)

  • При нажатии на кнопку START получаем данные из инпута (какое-то число) и передвигаем наш круг на то кол-во пикселей, которое ввел юзер в инпут

  • Передвижения круга должно быть с анимацией

  • Если юзер ввел число больше 600px || меньше нуля, то напрямую пишем в инпут ERROR (понимаем это после нажатия на кнопку)