Типы
Также есть специальные числовые значения Infinity (бесконечность) и NaN.
Значение NaN обозначает ошибку и является результатом числовой операции, если она некорректна.
Значение null не является «ссылкой на нулевой адрес/объект» или чем-то подобным. Это просто специальное значение. Оно присваивается, если мы хотим указать, что значение переменной неизвестно.
Значение undefined означает «переменная не присвоена».
x = 1; // число
x = "Тест"; // строка, кавычки могут быть одинарные или двойные
x = true; // булево значение true/false
x = null; // спец. значение (само себе тип)
x = undefined; // спец. значение (само себе тип)
var user = { name: "Вася" };// объектытип данных Symbol служит для создания уникальных идентификаторов.
Результатом typeof является строка, содержащая тип:
typeof undefined // "undefined"
typeof 0 // "number"
typeof true // "boolean"
typeof "foo" // "string"
typeof {} // "object"
typeof null // "object" (1)
typeof function(){} // "function" (2)«prompt(вопрос[, по_умолчанию])»
Задать вопрос и возвратить введённую строку, либо null, если посетитель нажал «Отмена».
Задать вопрос и предложить кнопки «Ок», «Отмена». Возвращает, соответственно, true/false.
Вывести сообщение на экран.
Сравнение === проверяет точное равенство, включая одинаковый тип. Это самый очевидный и надёжный способ сравнения.
Остальные сравнения == < <= > >= осуществляют числовое приведение типа
Сравнение строк – лексикографическое, символы сравниваются по своим unicode-кодам. Метод str1.localeCompare(str2) возвращает -1, если str1 < str2, 1, если str1 > str2 и 0, если они равны
alert( '1' + 2 ); // "12"
alert( 2 - '1' ); // 1
alert( '2' > 1 ); // true, сравнивается как 2 > 1
alert( '01' == 1 ); // true, сравнивается как 1 == 1
alert( null > 0 ); // false, т.к. null преобразовано к 0
alert( null >= 0 ); // true, т.к. null преобразовано к 0
alert( null == 0 ); // false, в стандарте явно указано, что null равен лишь undefinedПостфиксная форма i++ отличается от префиксной ++i тем, что возвращает старое значение, бывшее до увеличения.
Также: значения null и undefined при == равны друг другу и не равны ничему ещё. А при операторах больше/меньше происходит приведение null к 0, а undefined к NaN.
Преобразование типов
3. логическое
Преобразование к true/false происходит в логическом контексте, таком как if(value), и при применении логических операторов.
Для явного преобразования используется двойное логическое отрицание !!value или вызовBoolean(value).
строка "0" становится true
Преобразование типов для объектов
1. Логическое - всегда True
2. Строковое. Стандартно "[object Object]"
используется его метод toString. Он должен возвращать примитивное значение, причём не обязательно именно строку.
3. Численное. Для численного преобразования используется метод valueOf, который также может возвратить любое примитивное значение. У большинства объектов valueOf не работает (возвращает сам объект и потому игнорируется), при этом для численного преобразования используется toString.
var user = {
firstName: 'Василий',
toString: function() {
return 'Пользователь ' + this.firstName;
}
};
var room = {
number: 777,
valueOf: function() { return this.number; },
toString: function() { return this.number; }
};
alert( +room ); // 777, вызвался valueOf
delete room.valueOf; // valueOf удалён
alert( +room ); // 777, вызвался toStringalert( new Date() ); // toString: Дата в виде читаемой строки
alert( +new Date() ); // valueOf: кол-во миллисекунд, прошедших с 01.01.1970
// бинарный плюс для даты toString, для остальных объектов valueOf
alert( new Date + "" ); // "строка даты"|| (ИЛИ), && (И) и ! (НЕ).
|| запинается на «правде»,
&& запинается на «лжи».
Результатом логического оператора служит последнее значение в коротком цикле вычислений.
Можно сказать и по-другому: значения хоть и интерпретируются как логические, но то, которое в итоге определяет результат, возвращается без преобразования.
alert( 0 && 1 ); // 0
alert( 1 && 2 && 3 ); // 3
alert( null || 1 || 2 ); // 1// 1
while (условие) {
...
}
// 2
do {
...
} while (условие);
// 3
for (var i = 0; i < 10; i++) {
...
}Поддерживаются директивы break/continue для выхода из цикла/перехода на следующую итерацию.
Для выхода одновременно из нескольких уровней цикла можно задать метку.
outer:
for(;;) {
...
for(;;) {
...
break outer;
}
}При сравнениях в конструкции switch используется оператор ===.
var age = prompt('Ваш возраст', 18);
switch (age) {
case 18:
alert( 'Никогда не сработает' ); // результат prompt - строка, а не число
case "18": // вот так - сработает!
alert( 'Вам 18 лет!' );
break;
default:
alert( 'Любое значение, не совпавшее с case' );
}// Function Expression
var f = function() { ... }
// Function Declaration
function f() { ..isNaN(n) преобразует аргумент к числу и возвращает true, если получилось NaN
Функция isFinite(n) преобразует аргумент к числу и возвращает true, если это не NaN/Infinity/-Infinity:
Функция parseInt и ее аналог parseFloat преобразуют строку символ за символом, пока это возможно. При возникновении ошибки возвращается число, которое получилось. Функция parseInt читает из строки целое число, а parseFloat – дробное.
alert( isFinite(1) ); // true
alert( isFinite(Infinity) ); // false
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}Числа
Math.floor - округляет вниз
Math.ceil - округляет вверх
Math.round - округляет до ближайшего целого
alert( Math.floor(3.1) ); // 3
alert( Math.ceil(3.1) ); // 4
alert( Math.round(3.1) ); // 3var n = 12.36;
alert( n.toFixed(1) ); // "12.4"
var price = 6.35;
alert( price.toFixed(1) ); // 6.3
alert( Math.round(price * 10) / 10 ); // 6.4charAt(позиция) - получить символ = str[poz]
toLowerCase() и toUpperCase() меняют регистр строки
indexOf(подстрока[, начальная_позиция]) - поиск подстроки. Возвращает позицию, или -1
substring(start, end) возвращает подстроку с позиции start до, но не включая end.
substr(start [, length]) length- количество-символов
slice(start [, end]) как и substring, с отрицательными аргументами отчитывается с конца
var str = "jQuery";
alert( str.charAt(0) ); // "j"
alert( str[0] ); // "j"
var pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos );
}
alert( "testme".slice(1, -1) ); // "estm", от 1 позиции до первой с конца.Ассоциативный массив – структура данных, в которой можно хранить любые данные в формате ключ-значение.
В переменной, которой присвоен объект, хранится не сам объект, а «адрес его места в памяти», иными словами – «ссылка» на него. При копировании переменной с объектом – копируется эта ссылка, а объект по-прежнему остается в единственном экземпляре.Так как объект всего один, то изменения через любую переменную видны в других переменных:
if ("name" in person) {
alert( "Свойство name существует!" );
}
for (key in obj) {
/* ... делать что-то с obj[key] ... */
}
var clone = {}; // новый пустой объект
// скопируем в него все свойства user
for (var key in user) {
clone[key] = user[key];
}Массив – разновидность объекта, которая предназначена для хранения пронумерованных значений и предлагает дополнительные методы для удобного манипулирования такой коллекцией.
Длина length – не количество элементов массива, а последний индекс + 1.
new Array(число) создаёт массив заданной длины, без элементов.
Операции с концом массива:
Операции с началом массива:
// предпочтительное
var arr = [элемент1, элемент2...];
// new Array
var arr = new Array(элемент1, элемент2...);arr.forEach(function(item, i, arr) {
alert( i + ": " + item + " (массив:" + arr + ")" );
});
var positiveArr = arr.filter(function(number) {
return number > 0;
});
var nameLengths = names.map(function(name) {
return name.length;
});
// для каждого элемента массива запустить функцию,
// промежуточный результат передавать первым аргументом далее
var result = arr.reduce(function(sum, current) {
return sum + current;
}, 0);Создание:
Получение компонентов:
Установка компонентов:
set....
даты можно вычитать, результат вычитания объектов Date – их временная разница, в миллисекундах.
Форматирование даты:
Date.now() возвращает дату сразу в виде миллисекунд.
В JavaScript все глобальные переменные и функции являются свойствами специального объекта, который называется «глобальный объект» (global object). В браузере window
В результате инициализации, к началу выполнения кода:
Замыкание – это функция вместе со всеми внешними переменными, которые ей доступны.
Говоря «замыкание функции», подразумевают не саму эту функцию, а именно внешние переменные.
При создании функции с использованием new Function, её свойство [[Scope]] ссылается не на текущийLexicalEnvironment, а на window.=> такие функции не могут использовать замыкание. Но это хорошо, так как бережёт от ошибок проектирования, да и при сжатии JavaScript проблем не будет.
function makeCounter() {
var currentCount = 1;
// возвращаемся к функции
function counter() {
return currentCount++;
}
// ...и добавляем ей методы!
counter.set = function(value) {
currentCount = value;
};
counter.reset = function() {
currentCount = 1;
};
return counter;
}
var counter = makeCounter();
alert( counter() ); // 1
alert( counter() ); // 2
counter.set(5);
alert( counter() ); // 5Модуль – это оборачивание пакета функционала в единую внешнюю функцию, которая тут же выполняется.
Все функции модуля будут иметь доступ к другим переменным и внутренним функциям этого же модуля через замыкание.
Но снаружи программист, использующий модуль, может обращаться напрямую только к тем переменным и функциям, которые экспортированы. Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
«модуль» – это всего лишь функция-обёртка для скрытия переменных.
(function() {
alert( "объявляем локальные переменные, функции, работаем" );
// ...
}());на месте» разрешено вызывать только Function Expression.
Для доступа к текущему объекту из метода используется ключевое слово this. Значением this является объект перед «точкой», в контексте которого вызван метод,
Значение this называется контекстом вызова и будет определено в момент вызова функции. ( без контекста
[object Window]или undefined )
Ссылочный тип (Reference Type) – точка возвращает не функцию а ссылочный тип:
Поэтому любая операция над результатом операции получения свойства, кроме вызова, приводит к потере контекста.
var user = {
name: "Вася",
hi: function() { alert(this.name); },
bye: function() { alert("Пока"); }
};
user.hi(); // Вася (простой вызов работает)
// а теперь вызовем user.hi
или user.bye в зависимости от имени
(user.name == "Вася" ?
user.hi : user.bye)(); // undefinedКонструкторы - функции, которые предназначены для создания объектов . Названия пишутся с большой буквы
Любая функция может быть вызвана с new, при этом она получает новый пустой объект в качестве this, в который она добавляет свойства.
Вызов return с объектом вернёт объект, а с чем угодно, кроме объекта – возвратит, как обычно, this.
function Animal(name) {
// this = {};
// в this пишем свойства, методы
this.name = name;
this.canWalk = true;
// return this;
}function User(firstName, lastName) {
// вспомогательная переменная
var phrase = "Привет";
// вспомогательная вложенная функция
function getFullName() {
return firstName + " " + lastName;
}
this.sayHi = function() {
alert( phrase + ", " + getFullName() ); // использование
};
}func.call(context, arg1, arg2, ...)Вызов func.call(context, a, b...) – то же, что обычный вызов func(a, b...), но с явно указанным this(=context).
«одалживание» метода - вызвать метод одного объекта, в том числе встроенного, в контексте другого.
// вызов arr.slice() скопирует все элементы из this в новый массив
var args = [].slice.call(arguments);Вызов функции при помощи func.apply работает аналогично func.call, но принимает массив аргументов вместо списка.
func.call(context, arg1, arg2);
// идентичен вызову
func.apply(context, [arg1, arg2]);// получить максимум из элементов arr
alert( Math.max.apply(null, arr) ); // 5function bind(context) {
let func = this;
return function() { // (*)
return func.apply(context, arguments);
};
}в результате вызова bind(func, context) мы получаем «функцию-обёртку», которая прозрачно передаёт вызов в func, с теми же аргументами, но фиксированным контекстом context.
var wrapper = func.bind(context[, arg1, arg2...])Если указаны аргументы arg1, arg2... – они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове.
Карринг (currying) или каррирование – термин функционального программирования, который означает создание новой функции путём фиксирования аргументов существующей.
function mul(a, b) {
return a * b;
};
// double умножает только на два
var double = mul.bind(null, 2);
// контекст фиксируем null, он не
используетсяObject.defineProperty(obj, prop, descriptor)-метод для управления свойствами
descriptor – объект, который описывает поведение свойства. В нём мб следующие поля:
defineProperty позволяет нам начать с обычных свойств, а в будущем, при необходимости, можно в любой момент заменить их на функции, реализующие более сложную логику.
Методы объектов
Object.freeze(obj) Запрещает добавление, удаление и изменение свойств, все текущие свойства делает configurable: false, writable: false.
// 2. указание значения через дескриптор (удаляемое, изменяемое, перечисляемое) свойство.
Object.defineProperty(user, "name", { value: "Вася", configurable: true, writable: true, enumerable: true });
Object.defineProperty(user, "name", {
value: "Вася",
writable: false, // запретить присвоение "user.name="
configurable: false // запретить удаление "delete user.name"
});
// помечаем toString как не подлежащий перебору в for..in
Object.defineProperty(user, "toString", {enumerable: false});
Object.defineProperty(user, "fullName", {
get: function() {
return this.firstName + ' ' + this.surname;
},
set: function(value) {
var split = value.split(' ');
this.firstName = split[0];
this.surname = split[1];
}
});
user.fullName = "Петя Иванов";
alert( user.firstName ); // Петя
alert( user.surname ); // Ивановvar user = {
firstName: "Вася",
surname: "Петров",
get fullName() {
return this.firstName + ' ' + this.surname;
},
set fullName(value) {
var split = value.split(' ');
this.firstName = split[0];
this.surname = split[1];
}
};
alert( user.fullName ); // Вася Петров (из геттера)
user.fullName = "Петя Иванов";
alert( user.firstName ); // Петя (поставил сеттер)
alert( user.surname ); // Иванов (поставил сеттер)Методы и свойства, которые не привязаны к конкретному экземпляру объекта, называют «статическими». Их записывают прямо в саму функцию-конструктор.
function Article() {
Article.count++;
}
Article.count = 0; // статическое свойство-переменная
Article.DEFAULT_FORMAT = "html"; // статическое свойство-константаОни хранят данные, специфичные не для одного объекта, а для всех статей целиком.
Journal.compare = function(journalA, journalB) {
return journalA.date - journalB.date;
};
function findMin(journals) {
var min = 0;
for (var i = 0; i < journals.length; i++) {
// используем статический метод
if (Journal.compare(journals[min], journals[i]) > 0) min = i;
}
return journals[min];
}«Фабричный статический метод» – статический метод, который служит для создания новых объектов
function User() {
this.sayHi = function() {
alert(this.name)
};
}
User.createAnonymous = function() {
var user = new User;
user.name = 'Аноним';
return user;
}
User.createFromData = function(userData) {
var user = new User;
user.name = userData.name;
user.age = userData.age;
return user;
}
// Использование
var guest = User.createAnonymous();
guest.sayHi(); // Аноним
var knownUser = User.createFromData({
name: 'Вася',
age: 25
});
knownUser.sayHi(); // Васяfunction User(userData) {
if (userData) { // если указаны данные -- одна ветка if
this.name = userData.name;
this.age = userData.age;
} else { // если не указаны -- другая
this.name = 'Аноним';
}
this.sayHi = function() {
alert(this.name)
};
// ...
}
// Использование
var guest = new User();
guest.sayHi(); // Аноним
var knownUser = new User({
name: 'Вася',
age: 25
});
knownUser.sayHi(); // ВасяПреимущества:
Поэтому полиморфные конструкторы лучше использовать там, где нужен именно полиморфизм, т.е. когда непонятно, какого типа аргумент передадут, и хочется в одном конструкторе охватить все варианты.
А в остальных случаях отличная альтернатива – фабричные методы.
Декоратор – приём программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
Декоратор получает функцию и возвращает обертку, которая делает что-то своё «вокруг» вызова основной функции.
Определение типа данных
function getClass(obj) {
return {}.toString.call(obj).slice(8, -1);
}if (something.splice) {
alert( 'Это утка! То есть, массив!' );
}
if (x.getTime) {
alert( 'Дата!' );
alert( x.getTime() ); // работаем с датой
}JSON - формат , который используется для представления объектов в виде строки. Используется для передачи по сети
При сериализации объекта вызывается его метод toJSON.
Если такого метода нет – перечисляются его свойства, кроме функций.
var numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
var str = '{"title":"Конференция",
"date":"2014-11-30T12:00:00.000Z"}';
var event = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});var room = {
number: 23,
occupy: function() {
alert( this.number );
}
};
alert( JSON.stringify(room) );
/*
{
"number":23"
}
*/
var room = {
number: 23,
toJSON: function() {
return this.number;
}
};
alert( JSON.stringify(room) ); // 23var user = {
name: "Вася",
age: 25,
window: window
};
var str = JSON.stringify(user, function(key, value) {
if (key == 'window') return undefined;
return value;
});Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.
Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval/clearTimeout.
В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо setInterval используют рекурсивный setTimeout.
Минимальная задержка по стандарту составляет 4 мс.
Рекурсивный setTimeout гарантирует паузу между вызовами, setInterval – нет.
function func(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася
var timerId = setTimeout(function() { alert(1) }, 1000);
alert(timerId); // число - идентификатор таймера
clearTimeout(timerId);var timerId = setInterval(function() {
alert( "тик" );
}, 2000);
// через 5 сек остановить повторы
setTimeout(function() {
clearInterval(timerId);
alert( 'стоп' );
}, 5000);var timerId = setTimeout(function tick() {
alert( "тик" );
timerId = setTimeout(tick, 2000);
}, 200function mul(a, b) {
return a * b;
}
function square(a) {
return mul(a, a);
}
function printSquare() {
console.log(square(42));
}
printSquare();function asyncJob() {
setTimeout(fucntion job() {
console.log("I'm done");
}, 5000);
}
asyncJob();При объектно-ориентированной разработке мы описываем происходящее на уровне объектов, которые создаются, меняют свои свойства, взаимодействуют друг с другом и (в случае браузера) со страницей, в общем, живут.
Классом в объектно-ориентированной разработке называют шаблон/программный код, предназначенный для создания объектов и методов.
Методы и свойства объекта разделяются на две группы:
Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.
Свойства, записанные в this, можно считать публичными.
Подчёркивание в начале свойства – общепринятый знак, что свойство является внутренним, предназначенным лишь для доступа из самого объекта и его наследников. Такие свойства называют защищёнными.
Для лучшего контроля над свойством его делают приватным, а запись значения осуществляется через специальный метод, который называют «сеттер» (setter method).
название для сеттера – setСвойство
var waterAmount = 0;
// "умная" установка свойства
this.setWaterAmount = function(amount) {
if (amount < 0) {
throw new Error("Значение должно быть положительным");
}
if (amount > capacity) {
throw new Error("Нельзя залить воды больше, чем " + capacity);
}
waterAmount = amount;
};
this.getWaterAmount = function() {
return waterAmount;
};Для того, чтобы дать возможность внешнему коду узнать его значение, создадим специальную функцию – «геттер» (getter method).
Для большего удобства иногда делают единый метод, который называется так же, как свойство и отвечает и за запись и за чтение.
При вызове без параметров такой метод возвращает свойство, а при передаче параметра – назначает его.
инкапсуляция - отделение и защита внутреннего интерфейса
Плюсы:
this.run = function() {
setTimeout(onReady, getBoilTime.call(this));
};
var getBoilTime = function() {
return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power;
}.bind(this);
var self = this;
function getBoilTime() {
return self.waterAmount * WATER_HEAT_CAPACITY * 80 / power;
}Наследование
Наследование – создание новых «классов» на основе существующих.
Механизм наследования позволяет определить базовый класс Машина, в нём описать то, что свойственно всем машинам, а затем на его основе построить другие, более конкретные: Кофеварка и т.п.
Можно выделить такой общий функционал в класс Компонент и наследовать их от него, чтобы не дублировать код.
function Machine(params) {
// локальные переменные и функции доступны только внутри Machine
var privateProperty;
// публичные доступны снаружи
this.publicProperty = ...;
// защищённые доступны внутри Machine и для потомков
// мы договариваемся не трогать их снаружи
this._protectedProperty = ...
}
var machine = new Machine(...)
machine.public();2. Для наследования конструктор потомка вызывает родителя в своём контексте через apply. После чего может добавить свои переменные и методы:
function CoffeeMachine(params) {
// универсальный вызов с передачей любых аргументов
Machine.apply(this, arguments);// передавая ей в качестве контекста this текущий объект
this.coffeePublicProperty = ...
}3. В CoffeeMachine свойства, полученные от родителя, можно перезаписать своими. Но обычно требуется не заменить, а расширить метод родителя. Для этого он предварительно копируется в переменную:
function CoffeeMachine(params) {
Machine.apply(this, arguments);
var parentProtected = this._protectedProperty;
this._protectedProperty = function(args) {
parentProtected.apply(this, args); // (*)
// ...
};
}Строку (*) можно упростить до parentProtected(args), если метод родителя не использует this, а, например, привязан к var self = this:
function Machine(params) {
var self = this;
this._protected = function() {
self.property = "value";
};
}Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи __proto__, тот ссылается на третий, и так далее.
прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.
__proto__=[[Prototype]]
Вызов obj.hasOwnProperty(prop) возвращает true, если свойство prop принадлежит самому объекту obj, иначе false.
Object.getPrototypeOf(obj) Возвращает obj.__proto__ (кроме IE8-)
Object.setPrototypeOf(obj, proto) Устанавливает obj.__proto__ = proto (кроме IE10-).
Создание объекта с прототипом: Object.create(proto, descriptors)
Создаёт пустой объект с __proto__, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать дескрипторы свойств.
Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство prototype.
При создании объекта через new, в его прототип __proto__ записывается ссылка из prototype функции-конструктора.
var animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;function Rabbit() {}
Rabbit.prototype = {
constructor: Rabbit
};function Rabbit(name) {
this.name = name;
alert( name );
}
var rabbit = new Rabbit("Кроль");
var rabbit2 = new rabbit.constructor("Крольчиха");Object.myCreate=function (proto) {
function F() {};
F.prototype = proto;
var object = new F;
return object;
}"все объекты наследуют от Object", а если более точно, то от Object.prototype., у которого __proto__ равно null.
«Псевдоклассом» или, более коротко, «классом», называют функцию-конструктор вместе с её prototype.
Если обратиться к свойству примитива, то будет создан объект соответствующего типа, например new String для строки, new Number для чисел, new Boolean – для логических выражений.
Далее будет произведена операция со свойством или вызов метода по обычным правилам, с поиском в прототипе, а затем этот объект будет уничтожен.
Добавление методов в Object.prototype, если оно не сопровождается Object.defineProperty с установкой enumerable (IE9+), «сломает» циклы for..in, поэтому стараются в этот прототип методы не добавлять.
Object.defineProperty(Object.prototype, 'each', {
enumerable: false
});Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный – только свойства. Поэтому прототипный стиль – быстрее и экономнее по памяти.
Rabbit.prototype.__proto__ = Animal.prototype;Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;Однако, прямой доступ к __proto__ не поддерживается в IE10-, поэтому мы используем функцию Object.create.
// --------- Класс-Родитель ------------
// Конструктор родителя пишет свойства конкретного объекта
function Animal(name) {
this.name = name;
this.speed = 0;
}
// Методы хранятся в прототипе
Animal.prototype.run = function() {
alert(this.name + " бежит!")
}
// --------- Класс-потомок -----------
// Конструктор потомка
function Rabbit(name) {
Animal.apply(this, arguments);
}
// Унаследовать
Rabbit.prototype = Object.create(Animal.prototype);
// Желательно и constructor сохранить
Rabbit.prototype.constructor = Rabbit;
// Методы потомка
Rabbit.prototype.run = function() {
// Вызов метода родителя внутри своего
Animal.prototype.run.apply(this, arguments);
alert( this.name + " подпрыгивает!" );
};
// Готово, можно создавать объекты
var rabbit = new Rabbit('Кроль');
rabbit.run();Примесь (англ. mixin) – класс или объект, реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для самостоятельного использования.
// примесь
var sayHiMixin = {
sayHi: function() {
alert("Привет " + this.name);
},
sayBye: function() {
alert("Пока " + this.name);
}
};
// использование:
function User(name) {
this.name = name;
}
// передать методы примеси
for(var key in sayHiMixin) User.prototype[key] = sayHiMixin[key];
// User "умеет" sayHi
new User("Вася").sayHi(); // Привет ВасяСамый простой вариант примеси – это объект с полезными методами, которые мы просто копируем в нужный прототип.
Объект мог специальным вызовом генерировать «уведомление о событии», а на эти уведомления другие объекты могли «подписываться», чтобы их получать.
var eventMixin = {
/**
* Подписка на событие
* Использование:
* menu.on('select', function(item) { ... }
*/
on: function(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Прекращение подписки
* menu.off('select', handler)
*/
off: function(eventName, handler) {
var handlers = this._eventHandlers && this._eventHandlers[eventName];
if (!handlers) return;
for(var i=0; i<handlers.length; i++) {
if (handlers[i] == handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Генерация события с передачей данных
* this.trigger('select', item);
*/
trigger: function(eventName /*, ... */) {
if (!this._eventHandlers || !this._eventHandlers[eventName]) {
return; // обработчиков для события нет
}
// вызвать обработчики
var handlers = this._eventHandlers[eventName];
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(this, [].slice.call(arguments, 1));
}
}
};// Класс Menu с примесью eventMixin
function Menu() {
// ...
}
for(var key in eventMixin) {
Menu.prototype[key] = eventMixin[key];
}
// Генерирует событие select при выборе значения
Menu.prototype.choose = function(value) {
this.trigger("select", value);
}
// Создадим меню
var menu = new Menu();
// При наступлении события select вызвать эту функцию
menu.on("select", function(value) {
alert("Выбрано значение " + value);
});
// Запускаем выбор (событие select вызовет обработчики)
menu.choose("123");Переменные let:
Переменная const – это константа, в остальном – как let.
let apples = 5; // (*)
if (true) {
let apples = 10;
alert(apples); // 10 (внутри блока)
}
alert(apples); // 5 (снаружи блока значение не изменилось)Деструктуризация (destructuring assignment) позволяет разбивать объект или массив на переменные при присвоении
let [firstName, lastName] = ["Илья", "Кантор"];
alert(firstName); // Илья
alert(lastName); // Кантор
оператора "..."(«spread») получит «всё остальное»
let [firstName, lastName, ...rest] = "Ю Ц Имп Рима".split(" ");
alert(rest); // Император,Рима (массив из 2х элементов)// значения по умолчанию
let [firstName="Гость", lastName="Анонимный"] = [];
// lastName получит значение, соответствующее текущей дате:
let [firstName, lastName=defaultLastName()] = ["Вася"];// первый и второй элементы не нужны
let [, , title] = "Юлий Цезарь Император Рима".split(" ");
let options = {
title: "Меню",
width: 100,
height: 200
};
let {title, width, height} = options;
let {width: w, height: h, title} = options;let a, b;
({a, b} = {a:5, b:6}); // внутри выражения это уже не блокСинтаксис:
Здесь двоеточие : задаёт отображение свойства prop в переменную varName, а равенство =default задаёт выражение, которое будет использовано, если значение отсутствует (не указано или undefined).
Для массивов имеет значение порядок, поэтому нельзя использовать :, но значение по умолчанию – можно:
Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки.
Вложенные объекты и массивы тоже работают, при деструктуризации нужно лишь сохранить ту же структуру, что и исходный объект/массив.
let {prop : varName = default, ...} = objectlet [var1 = default, var2, ...rest] = arrayМетод Number.isInteger() определяет, является ли переданное значение целым числом.
function showMenu(title = "Заголовок", width = 100, height = 200) {
}
// title=Заголовок width=null height=200
showMenu(undefined, null);по умолчанию - при передаче undefined
function showName(firstName, lastName, ...rest) {
alert(firstName + ' ' + lastName + ' - ' + rest);
}
// выведет: Юлий Цезарь - Император,Рима
showName("Юлий", "Цезарь", "Император", "Рима");let numbers = [2, 3, 15];
// Оператор ... в вызове передаст
массив как список аргументов
// Этот вызов аналогичен
Math.max(2, 3, 15)
let max = Math.max(...numbers);
Math.max(...numbers);
Math.max.apply(Math, numbers);let options = {
title: "Меню"
};
function showMenu({title="Заголовок", width:w=100, height:h=200}) {
alert(title + ' ' + w + ' ' + h);
}
// для вызова без аргументов
function showMenu({title="Заголовок", width:w=100, height:h=200} = {}) {
alert(title + ' ' + w + ' ' + h);
}
if (true) {
sayHi(); // работает
function sayHi() {
alert("Привет!");
}
}
sayHi(); // ошибка, функции не существуетlet inc = x => x+1;
//let inc = function(x) { return x + 1; };
let sum = (a,b) => a + b;
// let inc = function(a, b) { return a + b; };
let getTime = () => {
let date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
return hourse + ':' + minutes;
};
let sorted = arr.sort( (a,b) => a - b );function defer(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms)
}
}
function defer(f, ms) {
return function() {
let args = arguments;
let ctx = this;
setTimeout(function() {
return f.apply(ctx, args);
}, ms);
}
}let str = `обратные кавычки`;
Добавлены ряд полезных методов общего назначения:
В них разрешён перевод строки.
Можно вставлять выражения при помощи ${…}.
+ функции-щаблонизаторы
let apples = 2;
let oranges = 3;
alert(`${apples} + ${oranges}
= ${apples + oranges}`);
// 2 + 3 = 5Улучшения в описании свойств:
В методах работает обращение к свойствам прототипа через super.parentProperty.
Для работы с прототипом:
Дополнительно:
let name = "Вася";
let isAdmin = true;
let user = {
name,
isAdmin
};let a = "Мой ";
let b = "Зелёный ";
let c = "Крокодил";
let user = {
[(a + b + c).toLowerCase()]: "Вася"
};
alert( user["мой зелёный крокодил"] ); // Вася// clone = пустой объект + все свойства user
let clone = Object.assign({}, user);let name = "Вася";
let user = {
name,
// вместо "sayHi: function() {" пишем "sayHi() {"
sayHi() {
alert(this.name);
}
};
let user = {
name,
surname,
get fullName() {
return `${name} ${surname}`;
}
};
alert( user.fullName ); // Вася ПетровПри обращении через super используется [[HomeObject]] текущего метода, и от него берётся __proto__. Поэтому super работает только внутри методов.
let rabbit = {
__proto__: animal,
walk() {
alert(super.walk); // walk() { … }
super.walk(); // I'm walking
setTimeout(() => super.walk()); // I'm walking
}
};используется super внешней функции.
class Название [extends Родитель] {
constructor
методы
}Функция constructor запускается при создании new User, остальные методы записываются в User.prototype.
Статические свойства класса User – это свойства непосредственно User, то есть доступные из него «через точку». Для их объявления используется ключевое слово static.
A.isPrototypeOf(B);
Конструктор constructor родителя наследуется автоматически. То есть, если в потомке не указан свой constructor, то используется родительский
Если же у потомка свой constructor, то, чтобы в нём вызвать конструктор родителя – используется синтаксис super() с аргументами для родителя.
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("Вася");
user.sayHi(); // Васяfunction User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
};class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
static createGuest() {
return new User("Гость", "Сайта");
}
};
let user = User.createGuest();class Animal {
constructor(name) {
this.name = name;
}
walk() {
alert("I walk: " + this.name);
}
}
class Rabbit extends Animal {
constructor() {
// вызвать конструктор Animal с аргументом "Кроль"
super("Кроль"); // то же, что и Animal.call(this, "Кроль")
}
walk() {
super.walk();
alert("...and jump!");
}
}class Rabbit extends Animal {
constructor() {
alert(this); // ошибка, this не определён!
// обязаны вызвать super() до обращения к this
super();
// а вот здесь уже можно использовать this
}
}
class A {}
class B extends A {}
const isIt = A.isPrototypeOf(B);Итерируемые объекты – это те, содержимое которых можно перебрать в цикле. Итератор – объект, предназначенный для перебора другого объекта.
Для перебора таких объектов добавлен новый синтаксис цикла: for..of.
let arr = [1, 2, 3]; // массив — пример итерируемого объекта
for (let value of arr) {
alert(value); // 1, затем 2, затем 3
}Конструкция for..of в начале своего выполнения автоматически вызывает Symbol.iterator(), При вызове метода Symbol.iterator перебираемый объект должен возвращать другой объект («итератор»), который умеет осуществлять перебор. У итератора должен быть метод next(), возвращающий объект {done: Boolean, value: any}, где value – очередное значение, а done: true в конце. Внешний код при переборе через for..of видит только значения
'use strict';
let range = {
from: 1,
to: 5
}
// сделаем объект range итерируемым
range[Symbol.iterator] = function() {
let current = this.from;
let last = this.to;
// метод должен вернуть объект с методом next()
return {
next() {
if (current <= last) {
return {
done: false,
value: current++
};
} else {
return {
done: true
};
}
}
}
};
for (let num of range) {
alert(num); // 1, затем 2, 3, 4, 5
}Основная область применения Map – ситуации, когда строковых ключей не хватает (нужно хранить соответствия для ключей-объектов), либо когда строковый ключ может быть совершенно произвольным.
основная задача – быть «вторичным» хранилищем данных для объектов, актуальный список которых (и сами они) хранятся в каком-то другом месте.
let map = new Map();
map
.set('1', 'str1') // ключ-строка
.set(1, 'num1') // число
.set(true, 'bool1'); // булевое значение
// map сохраняет тип ключа
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3let recipeMap = new Map([
['огурцов', '500 гр'],
['помидоров', '350 гр'],
['сметаны', '50 гр']
]);
// объект user является ключом
visitsCountMap.set(user, 123);
Методы для удаления записей:
Для проверки существования ключа:
Для итерации по map используется один из трёх методов:
// цикл по ключам
for(let fruit of recipeMap.keys()) {
alert(fruit); // огурцов, помидоров, сметаны
}
// цикл по значениям [ключ,значение]
for(let amount of recipeMap.values()) {
alert(amount); // 500 гр, 350 гр, 50 гр
}
// цикл по записям
for(let entry of recipeMap) { // то же что и recipeMap.entries()
alert(entry); // огурцов,500 гр , и т.д., массивы по 2 значения
}recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // огурцов: 500 гр, и т.д.
});let set = new Set();
let vasya = {name: "Вася"};
let petya = {name: "Петя"};
let dasha = {name: "Даша"};
// посещения, некоторые пользователи заходят много раз
set.add(vasya);
set.add(petya);
set.add(dasha);
set.add(vasya);
set.add(petya);
// set сохраняет только уникальные значения
alert( set.size ); // 3
set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
let set = new Set(["апельсины", "яблоки", "бананы"]);
// то же, что: for(let value of set)
set.forEach((value, valueAgain, set) => {
alert(value); // апельсины, затем яблоки, затем бананы
});Генераторы – новый вид функций в современном JavaScript. Они отличаются от обычных тем, что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени.
Генераторы создаются при помощи функций-генераторов function*(…) {…}..
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// generator function создаёт generator
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one));
// {value: 1, done: false}При запуске generateSequence() код не выполняется. Функция возвращает специальный объект, который называют «генератором».
При создании генератора код находится в начале своего выполнения.
Основным методом генератора является next(). При вызове он возобновляет выполнение кода до ближайшего ключевого слова yield. По достижении yield выполнение приостанавливается, а значение – возвращается во внешний код:
//get the values from a generator in two ways
var convertedToAnArray = Array.from(generatorFunction());
var iterator = generatorFunction();
var [...iteratedOver] = [iterator.next().value, iterator.next().value];
const {value} = generator.next();Композиция – встраивание одного генератора в поток другого.
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generateAlphaNum() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generateAlphaNum()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..zЗдесь использована специальная форма yield*. Она применима только к другому генератору и делегирует ему выполнение.
То есть, при yield* интерпретатор переходит внутрь генератора-аргумента, к примеру, generateSequence(48, 57), выполняет его, и все yield, которые он делает, выходят из внешнего генератора.
function* gen() {
// Передать вопрос во внешний код и подождать ответа
let result = yield "2 + 2?";
alert(result);
}
let generator = gen();
let question = generator.next().value;
// "2 + 2?"
setTimeout(() => generator.next(4), 2000);
function* generatorFunction() {
let a=yield 1;
yield a;
}
var iterator = generatorFunction();
var iteratedOver = [iterator.next().value, iterator.next(2).value];
1.2Вызов let result = yield value делает следующее:
Для того, чтобы передать в yield ошибку, используется вызов generator.throw(err). При этом на строке с yield возникает исключение.
function* gen() {
try {
// в этой строке возникнет ошибка
let result = yield "Сколько будет 2 + 2?"; // (**)
alert("выше будет исключение ^^^");
} catch(e) {
alert(e); // выведет ошибку
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("ответ не найден
в моей базе данных")); // (*)function* gen() {
// В этой строке возникнет ошибка
let result = yield "Сколько будет 2 + 2?";
}
let generator = gen();
let question = generator.next().value;
try {
generator.throw(new Error("ответ не найден
в моей базе данных"));
} catch(e) {
alert(e); // выведет ошибку
}«Вброшенная» в строке (*) ошибка возникает в строке с yield (**). Далее она обрабатывается как обычно. В примере выше она перехватывается try..catch и выводится.
Если ошибку не перехватить, то она «выпадет» из генератора. По стеку ближайший вызов, который инициировал выполнение – это строка с .throw.
Модули
Когда приложение сложное и кода много – мы пытаемся разбить его на файлы. В каждом файле описываем какую-то часть, а в дальнейшем – собираем эти части воедино.
Модули в стандарте ECMAScript предоставляют удобные средства для этого.
Все перечисленные выше системы требуют различных библиотек или систем сборки для использования.
В этом файле ключевым словом export помечаются переменные и функции, которые могут быть использованы снаружи.
Другие модули могут подключать их через вызов import.
Экспорт:
Импорт:
// экспорт прямо перед объявлением
export let one = 1;
let two = 2;
export {one as once, two as twice};
export class User {
constructor(name) {
this.name = name;
}
};
export function sayHi() {
alert("Hello!");
};
// отдельно от объявлений было бы так:
// export {User, sayHi}import {one, two} from "./nums";
// импорт one под именем item1, а two – под именем item2
import {one as item1, two as item2} from "./nums";
alert( `${item1} and ${item2}` ); // 1 and 2
import * as numbers from "./nums";
// теперь экспортированные переменные - свойства numbers
alert( `${numbers.one} and ${numbers.two}` ); // 1 and 2Для такой ситуации, когда один модуль экспортирует одно значение, предусмотрено особое ключевое сочетание export default. Такое значение можно импортировать без фигурных скобок.
export default class User {
constructor(name) {
this.name = name;
}
};
import User from './user';
new User("Вася");
// если бы user.js содержал
// export class User { ... }
// …то при импорте User понадобились бы фигурные скобки:
import {User} from './user';
new User("Вася");Promise
Promise – удобный способ организации асинхронного кода
Promise – это специальный объект, который содержит своё состояние. Вначале pending («ожидание»), затем – одно из: fulfilled («выполнено успешно») или rejected («выполнено с ошибкой»).
На promise можно навешивать коллбэки двух типов:
Способ использования, в общих чертах, такой:
var promise = new Promise(function(resolve, reject) {
// Эта функция будет вызвана автоматически
// В ней можно делать любые асинхронные операции,
// А когда они завершатся — нужно вызвать одно из:
// resolve(результат) при успешном выполнении
// reject(ошибка) при ошибке
})Универсальный метод для навешивания обработчиков:
promise.then(onFulfilled, onRejected)// onFulfilled сработает при успешном выполнении
promise.then(onFulfilled)
// onRejected сработает при ошибке
promise.then(null, onRejected)
.catch(onRejected) – это то же самое.Когда промис переходит в состояние «выполнен» – с результатом (resolve) или ошибкой (reject) – это навсегда.
'use strict';
// Создаётся объект promise
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
// переведёт промис в состояние fulfilled с результатом "result"
resolve("result");
}, 1000);
});
// promise.then навешивает обработчики на успешный результат или ошибку
promise
.then(
result => {
// первая функция-обработчик - запустится при вызове resolve
alert("Fulfilled: " + result); // result - аргумент resolve
},
error => {
// вторая функция - запустится при вызове reject
alert("Rejected: " + error); // error - аргумент reject
}
);Промисификация – это когда берут асинхронный функционал и делают для него обёртку, возвращающую промис.
Функция httpGet(url) будет возвращать промис, который при успешной загрузке данных с url будет переходить в fulfilled с этими данными, а при ошибке – в rejected с информацией об ошибке:
function httpGet(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
if (this.status == 200) {
resolve(this.response);
} else {
var error = new Error(this.statusText);
error.code = this.status;
reject(error);
}
};
xhr.onerror = function() {
reject(new Error("Network Error"));
};
xhr.send();
});
}httpGet("/article/promise/user.json")
.then(
response => alert(`Fulfilled: ${response}`),
error => alert(`Rejected: ${error}`)
);httpGet(...)
.then(...)
.then(...)
.then(...)При чейнинге, то есть последовательных вызовах .then…then…then, в каждый следующий then переходит результат от предыдущего.
Если очередной then вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.
// сделать запрос
httpGet('/article/promise/user.json')
// 1. Получить данные о пользователе в JSON и передать дальше
.then(response => {
console.log(response);
let user = JSON.parse(response);
return user;
})
// 2. Получить информацию с github
.then(user => {
console.log(user);
return httpGet(`https://api.github.com/users/${user.name}`);
})
// 3. Вывести аватар на 3 секунды (можно с анимацией)
.then(githubUser => {
console.log(githubUser);
githubUser = JSON.parse(githubUser);
let img = new Image();
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.appendChild(img);
setTimeout(() => img.remove(), 3000); // (*)
});Если мы хотим, чтобы после setTimeout (*) асинхронная цепочка могла быть продолжена, то последний then тоже должен вернуть промис. Это общее правило: если внутри then стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
// вместо setTimeout(() => img.remove(), 3000); (*)
return new Promise((resolve, reject) => {
setTimeout(() => {
img.remove();
// после таймаута — вызов resolve,
// можно без результата, чтобы управление перешло в следующий then
// (или можно передать данные пользователя дальше по цепочке)
resolve();
}, 3000);
});При возникновении ошибки – она отправляется в ближайший обработчик onRejected.
Такой обработчик нужно поставить через второй аргумент .then(..., onRejected) или, что то же самое, через .catch(onRejected).
.then.
.then
.catch(error => {
alert(error); // Error: Not Found
});Есть два варианта развития событий:
Параллельное выполнение
Вызов Promise.all(iterable) получает массив (или другой итерируемый объект) промисов и возвращает промис, который ждёт, пока все переданные промисы завершатся, и переходит в состояние «выполнено» с массивом их результатов.если какой-то из промисов завершился с ошибкой, то результатом Promise.all будет эта ошибка. При этом остальные промисы игнорируются.
Promise.all([
httpGet('/article/promise/user.json'),
httpGet('/article/promise/guest.json')
]).then(results => {
alert(results);
});Вызов Promise.race, как и Promise.all, получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис. Но, в отличие от Promise.all, результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
Promise.race([
httpGet('/article/promise/user.json'),
httpGet('/article/promise/guest.json')
]).then(firstResult => {
firstResult = JSON.parse(firstResult);
alert( firstResult.name ); // iliakan или guest, смотря что загрузится раньше
});Вызов Promise.resolve(value) создаёт успешно выполнившийся промис с результатом value.
Он аналогичен конструкции:
Promise.resolve используют, когда хотят построить асинхронную цепочку, и начальный результат уже есть.
new Promise((resolve) => resolve(value))Promise.resolve(window.location) // начать с этого значения
.then(httpGet) // вызвать для него httpGet
.then(alert) // и вывести результатAsync functions
Developers can define an async function which may or may not contain await for promise-based asychronous operations. Under the hood, an async function is a function that returns Promise
async fetchData(query) => {
try {
const response = await axios.get(`/q?query=${query}`)
const data = response.data
return data
} catch (error) {
console.log(error)
}
}
fetchData(query).then(data => {
this.props.processfetchedData(data)
})После вызова, функция async возвращает Promise. Когда результат был получен, Promise завершается, возвращая полученное значение. Когда функция async выбрасывает исключение, Promise ответит отказом с выброшенным (throws) значением.
Функция async может содержать выражение await, которое приостанавливает выполнение функции async и ожидает ответа от переданного Promise, затем возобновляя выполнение функции async и возвращая полученное значение.
а значением, возвращённым при разрешении этого промиса, будет то, что возвратит инструкция return
Object.values returns an array of object's own enumerable property values. We can iterate over it using good Array.prototype.forEach, but with ES6 arrow function and implicit return:
let obj = {a: 1, b: 2, c: 3}
Object.values(obj).forEach(value=>console.log(value)) // 1, 2, 3
let obj = {a: 1, b: 2, c: 3}
for (let value of Object.values(obj)) {
console.log(value)
}
// 1, 2, 3Object.entries, on the other hand, will return an array of object's own enumerable property key-value pairs (as an array). Each item of the resulting array will be an array too.
let obj = {a: 1, b: 2, c: 3}
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} is ${value}`)
})
// a is 1, b is 2, c is 3
let obj = {a: 1, b: 2, c: 3}
for (let [key, value] of Object.entries(obj)) {
console.log(`${key} is ${value}`)
}padStart(targetLength, pad) returns a string of a given length (targetLength) by inserting pads at the beginning.
console.log('react'.padStart(10).length) // " react" is 10
console.log('backbone'.padStart(10).length) // " backbone" is 10