Создайте страницу, которая запрашивает имя пользователя и выводит его.
const name = prompt('Введите ваше имя:')
alert(`Привет, ${name}!`)
Чему будут равны переменные a, b, c и d в примере ниже?
let a = 1
let b = 1
const c = ++a // ?
const d = b++ // ?
a = 2
b = 2
c = 2
d = 1
Какой результат будет у выражений ниже?
'' + 1 + 0
'' - 1 + 0
true + false
6 / '3'
'2' * '3'
4 + 5 + 'px'
'$' + 4 + 5
'4' - 2
'4px' - 2
7 / 0
' -9 ' + 5
' -9 ' - 5
null + 1
undefined + 1
' \\t \\n' - 2
'' + 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 ' + 5 // ' -9 5' (3)
' -9 ' - 5 // -14 (4)
null + 1 // 1 (5)
undefined + 1 // NaN (6)
' \\t \\n' - 2 // -2 (7)
- Сложение со строкой "" + 1 преобразует 1 к строке: "" + 1 = "1", и в следующем случае "1" + 0 работает то же самое правило.
- Вычитание - (как и большинство математических операторов) работает только с числами, пустая строка "" приводится к 0.
- Сложение со строкой превращает число 5 в строку и добавляет к строке.
- Вычитание всегда преобразует к числу, значит строка " -9 " становится числом -9 (пробелы по краям обрезаются).
- null становится 0 после численного преобразования.
- undefined становится NaN после численного преобразования.
- Пробельные символы, такие как \t и \n, по краям строки игнорируются при преобразовании в число, так что строка " \t \n", аналогично пустой строке, становится 0 после численного преобразования.
Каким будет результат этих выражений?
5 > 4
'ананас' > 'яблоко'
'2' > '12'
undefined == null
undefined === null
null == '\\n0\\n'
null === +'\\n0\\n'
5 > 4 // true
'ананас' > 'яблоко' // false
'2' > '12' // true
undefined == null // true
undefined === null // false
null == '\\n0\\n' // false
null === +'\\n0\\n' // false
- Очевидно, true.
- Используется посимвольное сравнение, поэтому false. "а" меньше, чем "я".
- Снова посимвольное сравнение. Первый символ первой строки "2" больше, чем первый символ второй "1".
- Специальный случай. Значения null и undefined равны только друг другу при нестрогом сравнении.
- Строгое сравнение разных типов, поэтому false.
- Аналогично (4), null равен только undefined.
- Строгое сравнение разных типов.
Перепишите конструкцию "if" с использованием условного оператора "?".
let result
if (a + b < 4) {
result = 'Мало'
} else {
result = 'Много'
}
const result = a + b < 4 ? 'Мало' : 'Много'
Перепишите "if-else" с использованием нескольких операторов "?".
let message
if (login == 'Сотрудник') {
message = 'Привет'
} else if (login == 'Директор') {
message = 'Здравствуйте'
} else if (login == '') {
message = 'Нет логина'
} else {
message = ''
}
const message = login === 'Сотрудник'
? 'Привет'
: login === 'Директор'
? 'Здравствуйте'
: login === ''
? 'Нет логина'
: ''
Какие из перечисленных ниже "console.log()" выполнятся? Какие конкретно значения будут результатами выражений в условиях "if"?
if (-1 || 0) console.log('первый')
if (-1 && 0) console.log('второй')
if (null || (-1 && 1)) console.log('третий')
Первое и третье выполнятся.
// Выполнится
// Результат -1 || 0 = -1, в логическом контексте true
if (-1 || 0) console.log('первый')
// Не выполнится
// -1 && 0 = 0, в логическом контексте false
if (-1 && 0) console.log('второй')
// Выполнится
// оператор && имеет больший приоритет, чем ||
// так что -1 && 1 выполнится раньше
// вычисления: null || -1 && 1 -> null || 1 -> 1
if (null || (-1 && 1)) console.log('третий')
При помощи цикла "for" выведите четные числа от 2 до 10.
for (let i = 2; i <= 10; i++) {
if (i % 2 === 0) console.log(i)
}
Напишите цикл, который предлагает (prompt) ввести число, большее 100. Если пользователь ввел другое число – попросить ввести еще раз, и так далее. Цикл должен спрашивать число до тех пор, пока либо пользователь не введет число, большее 100, либо не нажмет кнопку "Отмена" (ESC). Предполагается, что пользователь вводит только числа. Предусматривать обработку нечисловых строк не обязательно.
let num
do {
num = prompt('Введите число, большее 100', 0)
} while (num <= 100 && num)
Цикл "do-while" повторяется, пока верны две проверки:
- Проверка "num <= 100" – то есть, введенное число все еще меньше 100.
- Проверка "&& num" вычисляется в false, когда num имеет значение null или пустая строка (""). В этом случае цикл "while" тоже нужно прекратить.
Кстати, сравнение "num <= 100" при вводе null даст true, так что вторая проверка необходима.
Следующая функция возвращает true, если параметр "age" больше 18. В ином случае она задает вопрос (confirm) и возвращает его результат.
function checkAge(age) {
if (age > 18) {
return true
} else {
return confirm('Родители разрешили?')
}
}
Перепишите функцию, чтобы она делала то же самое, но без "if", в одну строку. Сделайте два варианта функции "checkAge":
- Используя оператор "?"
- Используя оператор "||"
// оператор ?
const checkAge = (age) => (age > 18 ? true : confirm('Родители разрешили?'))
// оператор ||
const checkAge = (age) => age > 18 || confirm('Родители разрешили?')
Напишите функцию "min(a, b)"", которая возвращает меньшее из чисел "a" и "b". Пример вызовов:
min(2, 5) // 2
min(3, -1) // -1
min(1, 1) // 1
function min(a, b) {
if (a < b) return a
return b
}
// или
const min = (a, b) => (a < b ? a : b)
Напишите функцию "pow(x,n)"", которая возвращает "x" в степени "n". Иначе говоря, умножает "x" на себя "n" раз и возвращает результат.
pow(3, 2) // 3 * 3 = 9
pow(3, 3) // 3 * 3 * 3 = 27
function pow(x, n) {
if (n <= 0) return false
if (n === 1) return x
let result = x
for (let i = 1; i < n; i++) {
result *= x
}
return result
}
// или с помощью рекурсии
const pow = (x, n) => {
if (n <= 0) return false
if (n === 1) return x
return x * pow(x, n - 1)
}
// или в одну строку
const pow = (x, n) => (n <= 0 ? false : n === 1 ? x : x * pow(x, n - 1))
Напишите функцию "isEmpty(obj)", которая возвращает true, если у объекта нет свойств, иначе false. Должно работать так:
const schedule = {}
console.log(isEmpty(schedule)) // true
schedule['8:30'] = 'Подъем!'
console.log(isEmpty(schedule)) // false
function isEmpty(obj) {
for (const key in obj) {
return false
}
return true
}
// или
const isEmpty = (obj) => (Object.keys(obj).length ? false : true)
У нас есть объект, в котором хранятся зарплаты нашей команды:
const salaries = {
John: 100,
Jane: 200,
Bob: 300,
Alice: 400,
}
Напишите код для суммирования всех зарплат и сохраните результат в переменной "sum". Должно получиться 1000. Если объект "salaries" пуст, то результат должен быть равен 0.
let sum = 0
for (let key in salaries) {
sum += salaries[key]
}
// или
function sum(obj) {
let sum = 0
for (let salary of Object.values(salaries)) {
sum += salary
}
return sum
}
// или
const sum = (obj) => (Object.keys(obj).length ? Object.values(obj).reduce((x, y) => x + y) : 0)
Создайте функцию "multiplyNumeric(obj)", которая умножает все числовые свойства объекта "obj" на 2. Например:
// до вызова функции
const menu = {
width: 200,
height: 300,
title: 'My menu',
}
multiplyNumeric(menu)
// после вызова функции
menu = {
width: 400,
height: 600,
title: 'My menu',
}
Обратите внимание, что "multiplyNumeric()" не нужно ничего возвращать. Следует напрямую изменять объект.
function multiplyNumeric(obj) {
for (const key in obj) {
if (typeof obj[key] === 'number') {
obj[key] *= 2
}
}
}
Создайте объект "calculator" (калькулятор) с тремя методами:
- read() (читать) запрашивает два значения и сохраняет их как свойства объекта
- sum() (суммировать) возвращает сумму сохраненных значений
- mul() (умножить) перемножает сохраненные значения и возвращает результат
const calculator = {
sum() {
return this.a + this.b
},
mul() {
return this.a * this.b
},
read() {
this.a = +prompt('a?', 0)
this.b = +prompt('b?', 0)
},
}
calculator.read()
console.log(calculator.sum())
console.log(calculator.mul())
Это "ladder" (лестница) – объект, который позволяет подниматься вверх и спускаться:
const ladder = {
step: 0,
up() {
this.step++
},
down() {
this.step--
},
showStep: function () {
// показывает текущую ступеньку
console.log(this.step)
},
}
Теперь, если нам нужно сделать несколько последовательных вызовов, мы можем выполнить это так:
ladder.up()
ladder.up()
ladder.down()
ladder.showStep() // 1
Измените код методов "up", "down" и "showStep" таким образом, чтобы их вызов можно было сделать по цепочке, например так:
ladder.up().up().down().showStep() // 1
Такой подход широко используется в библиотеках JavaScript.
Решением является возврат самого объекта в каждом методе.
const ladder = {
step: 0,
up() {
this.step++
return this
},
down() {
this.step--
return this
},
showStep() {
console.log(this.step)
return this
},
}
ladder.up().up().down().up().down().showStep() // 1
Создайте функцию-конструктор "Calculator", который создает объекты с тремя методами:
- read() запрашивает два значения при помощи "prompt()" и сохраняет их значение в свойствах объекта
- sum() возвращает сумму введенных свойств
- mul() возвращает произведение введенных свойств
function Calculator() {
this.read = function () {
this.a = +prompt('a?', 0)
this.b = +prompt('b?', 0)
}
this.sum = function () {
return this.a + this.b
}
this.mul = function () {
return this.a * this.b
}
}
const calculator = new Calculator()
calculator.read()
console.log(calculator.sum())
console.log(calculator.mul())
Напишите функцию-конструктор "Accumulator(initialValue)". Объект, который она создает, должен уметь следующее:
- Хранить «текущее значение» в свойстве "value". Начальное значение устанавливается в аргументе конструктора "initialValue"
- Метод "read" использует "prompt()" для получения числа и прибавляет его к свойству "value"
function Accumulator(initialValue) {
this.value = initialValue
this.read = function () {
this.value += +prompt('Сколько нужно добавить?', 0)
}
}
const accumulator = new Accumulator(1)
accumulator.read()
accumulator.read()
console.log(accumulator.value)
Напишите функцию "randomInteger(min, max)", которая генерирует случайное целое (integer) число от min до max (включительно). Любое число из интервала "min-max" должно появляться с одинаковой вероятностью.
function randomInteger(min, max) {
// случайное число от min до (max + 1)
let rand = min + Math.random() * (max + 1 - min)
return Math.floor(rand)
}
// или
const randomInteger = (min, max) => ~~(min + Math.random() * (max + 1 - min))
Напишите функцию "ucFirst(str)", возвращающую строку "str" с заглавным первым символом. Например:
ucFirst('ванька') === 'Ванька'
function ucFirst(str) {
return str[0].toUpperCase() + str.slice(1)
}
// или
const ucFirst = (str, trimmed = str.trim()) => `${trimmed[0].toUpperCase()}${trimmed.slice(1)}`
console.log(ucFirst(' ванька')) // Ванька
Создайте функцию "truncate(str, maxlength)", которая проверяет длину строки "str" и, если она превосходит "maxlength", заменяет конец "str" на "…", так, чтобы ее длина стала равна "maxlength". Результатом функции должна быть та же строка, если усечение не требуется, либо, соответственно, усеченная строка.
function truncate(str, maxlength) {
return str.length > maxlength ? str.slice(0, maxlength - 1) + '…' : str
}
// или
const truncate = (str, max) => (str.length > max ? `${str.slice(0, max - 1)}...` : str)
const str = 'Очень длинная строка'
console.log(truncate(str, 6)) // Очень...
Давайте произведем 5 операций с массивом.
- Создайте массив "styles" с элементами "Джаз" и "Блюз".
- Добавьте "Рок-н-ролл" в конец.
- Замените значение в середине на "Классика". Ваш код для поиска значения в середине должен работать для массивов с любой длиной.
- Удалите первый элемент массива и покажите его.
- Вставьте "Рэп" и "Регги" в начало массива.
Массив по ходу выполнения операций:
Джаз, Блюз
Джаз, Блюз, Рок - н - ролл
Джаз, Классика, Рок - н - ролл
Классика, Рок - н - ролл
Рэп, Регги, Классика, Рок - н - ролл
const styles = ['Джаз', 'Блюз']
styles.push('Рок-н-ролл')
styles[~~((styles.length - 1) / 2)] = 'Классика'
console.log(styles.shift())
styles.unshift('Рэп', 'Регги')
На входе массив чисел, например: arr = [1, -2, 3, 4, -9, 6]. Задача: найти непрерывный подмассив в "arr", сумма элементов в котором максимальна. Функция "getMaxSubSum(arr)" должна возвращать эту сумму. Например:
getMaxSubSum([-1, 2, 3, -9]) // 5
getMaxSubSum([2, -1, 2, 3, -9]) // 5
getMaxSubSum([-1, 2, 3, -9, 11]) // 11
getMaxSubSum([-2, -1, 1, 2]) // 3
getMaxSubSum([100, -9, 2, -3, 5]) // 100
getMaxSubSum([1, 2, 3]) // 6
Если все элементы отрицательные – ничего не берем (подмассив пустой) и сумма равна 0:
getMaxSubSum([-1, -2, -3]) // 0
function getMaxSubSum(arr) {
let maxSum = 0
let partialSum = 0
for (const item of arr) {
partialSum += item
maxSum = Math.max(maxSum, partialSum)
if (partialSum < 0) partialSum = 0
}
return maxSum
}
// или
const getMaxSubSum = (arr, max = 0) => {
arr.reduce((x, y) => {
y < 0 ? (x = 0) : (x += y)
if (x > max) max = x
return x
}, 0)
return max
}
Напишите функцию "camelize(str)", которая преобразует строки вида "my-short-string" в "myShortString". То есть дефисы удаляются, а все слова после них получают заглавную букву. Примеры:
camelize('background-color') === 'backgroundColor'
camelize('list-style-image') === 'listStyleImage'
function camelize(str) {
return str
.split('-')
.map((word, index) => (index == 0 ? word : word[0].toUpperCase() + word.slice(1)))
.join('')
}
// или
const camelize = (str, trimmed = str.trim()) =>
trimmed
.split('-')
.map((word, i) => (i === 0 ? word : `${word[0].toUpperCase()}${word.slice(1)}`))
.join('')
console.log(camelize(' my-long-word')) // myLongWord
Отсортировать элементы массива по убыванию. Пример:
const arr = [5, 2, 1, -10, 8]
// ...
console.log(arr) // 8, 5, 2, 1, -10
const arr = [5, 2, 1, -10, 8]
arr.sort((a, b) => b - a)
console.log(arr) // 8, 5, 2, 1, -10
Создайте функцию конструктор "Calculator", которая создает "расширяемые" объекты калькулятора. Задание состоит из двух частей.
- Во-первых, реализуйте метод "calculate(str)", который принимает строку типа "1 + 2" в формате «ЧИСЛО оператор ЧИСЛО» (разделено пробелами) и возвращает результат. Метод должен понимать плюс (+) и минус (-). Пример использования:
const calc = new Calculator()
console.log(calc.calculate('3 + 7')) // 10
- Затем добавьте метод "addMethod(name, func)", который добавляет в калькулятор новые операции. Он принимает оператор "name" и функцию с двумя аргументами "func(a,b)", которая описывает его. Например, давайте добавим умножение *, деление / и возведение в степень **:
const powerCalc = new Calculator()
powerCalc.addMethod('*', (a, b) => a * b)
powerCalc.addMethod('/', (a, b) => a / b)
powerCalc.addMethod('**', (a, b) => a ** b)
const result = powerCalc.calculate('2 ** 3')
console.log(result) // 8
- Для этой задачи не нужны скобки или сложные выражения.
- Числа и оператор разделены ровно одним пробелом.
- Не лишним будет добавить обработку ошибок.
Обратите внимание, как хранятся методы. Они просто добавляются к внутреннему объекту. Все тесты и числовые преобразования выполняются в методе "calculate". В будущем он может быть расширен для поддержки более сложных выражений.
function Calculator() {
this.methods = {
'-': (a, b) => a - b,
'+': (a, b) => a + b,
}
this.calculate = function (str) {
const split = str.split(' ')
const a = +split[0]
const op = split[1]
const b = +split[2]
if (!this.methods[op] || isNaN(a) || isNaN(b)) return NaN
return this.methods[op](a, b)
}
this.addMethod = function (name, func) {
this.methods[name] = func
}
}
У вас есть массив объектов "users", и у каждого из объектов есть firstName, lastName и id. Напишите код, который создаст еще один массив объектов с параметрами "id" и "fullName", где fullName – состоит из firstName и lastName. Например:
const vasya = { firstName: 'Вася', lastName: 'Пупкин', id: 1 }
const petya = { firstName: 'Петя', lastName: 'Иванов', id: 2 }
const masha = { firstName: 'Маша', lastName: 'Петрова', id: 3 }
const users = [vasya, petya, masha]
// const usersMapped = ...
/*
usersMapped = [
{ fullName: "Вася Пупкин", id: 1 },
{ fullName: "Петя Иванов", id: 2 },
{ fullName: "Маша Петрова", id: 3 }
]
*/
console.log(usersMapped[0].id) // 1
console.log(usersMapped[0].fullName) // Вася Пупкин
const usersMapped = users.map((user) => ({
fullName: `${user.firstName} ${user.lastName}`,
id: user.id,
}))
// или
const usersMapped = users.map(({ firstName, lastName, id }) => ({
fullName: `${firstName} ${lastName}`,
id,
}))
Напишите функцию "shuffle(array)", которая перемешивает (переупорядочивает случайным образом) элементы массива. Многократные прогоны через "shuffle" могут привести к разным последовательностям элементов. Например:
const arr = [1, 2, 3]
shuffle(arr)
// arr = [3, 2, 1]
shuffle(arr)
// arr = [2, 1, 3]
shuffle(arr)
// arr = [3, 1, 2]
Все последовательности элементов должны иметь одинаковую вероятность. Например, [1, 2, 3] может быть переупорядочено как [1, 2, 3] или [1, 3, 2], или [3, 1, 2] и т.д., с равной вероятностью каждого случая.
// Тасование Фишера-Йетса
const shuffle = (arr) => {
for (let i = arr.length - 1; i > 0; i--) {
const j = ~~(Math.random() * (i + 1)) // случайный индекс от 0 до i
// меняем элементы местами
// аналогично let t = arr[i]; arr[i] = arr[j]; arr[j] = t
;[arr[i], arr[j]] = [arr[j], arr[i]]
}
return arr
}
Напишите функцию "getAverageAge(users)", которая принимает массив объектов со свойством "age" и возвращает средний возраст. Формула вычисления среднего арифметического значения: (age1 + age2 + ... + ageN) / N. Например:
const users = [
{ name: 'Вася', age: 25 },
{ name: 'Петя', age: 30 },
{ name: 'Маша', age: 29 },
]
console.log(getAverageAge(users)) // (25 + 30 + 29) / 3 = 28
const getAverageAge = (users) => users.reduce((acc, cur) => acc + cur.age) / users.length
Пусть "arr" – массив строк. Напишите функцию "unique(arr)", которая возвращает массив, содержащий только уникальные элементы "arr". Например:
const arr = ['раз', 'раз', 'раз', 'два', 'три', 'проверка', 'проверка']
console.log(unique(arr)) // раз, два, три, проверка
// без использования Set
const unique = (arr, result = []) => {
arr.forEach((str) => {
!result.includes(str) && result.push(str)
})
return result
}
// с использованием Set
const unique = (arr) => [...new Set(arr)]
Анаграммы – это слова, у которых те же буквы в том же количестве, но они располагаются в другом порядке. Например:
nap - pan
ear - are - era
cheaters - hectares - teachers
Напишите функцию "aclean(arr)", которая возвращает массив слов, очищенный от анаграмм. Например:
const arr = ['nap', 'teachers', 'cheaters', 'PAN', 'ear', 'era', 'hectares']
alert(aclean(arr)) // "nap,teachers,ear" или "PAN,cheaters,era"
Из каждой группы анаграмм должно остаться только одно слово, неважно какое.
function aclean(arr) {
const obj = {}
for (let i = 0; i < arr.length; i++) {
const sorted = arr[i].toLowerCase().split('').sort().join('')
obj[sorted] = arr[i]
}
return Object.values(obj)
}
const arr = ['nap', 'teachers', 'cheaters', 'PAN', 'ear', 'era', 'hectares']
console.log(aclean(arr))
// или
const aclean = (arr, obj = {}) => {
for (const word of arr) {
const sorted = word.toLowerCase().split('').sort().join('')
obj[sorted] = word
}
return Object.values(obj)
}
Есть массив сообщений:
const messages = [
{ text: 'Hello', from: 'John' },
{ text: 'How are you?', from: 'Jane' },
{ text: 'See you soon', from: 'John' },
]
- У вас есть к ним доступ, но управление этим массивом происходит где-то еще. Добавляются новые сообщения и удаляются старые, и вы не знаете, в какой момент это может произойти. Имея такую вводную информацию, решите, какую структуру данных вы могли бы использовать для ответа на вопрос "было ли сообщение прочитано?". Структура должна быть подходящей, чтобы можно было однозначно сказать, было ли прочитано это сообщение для каждого объекта сообщения.
- Когда сообщение удаляется из массива "messages", оно должно также исчезать из структуры данных. Нам не следует модифицировать сами объекты сообщений, добавлять туда свойства. Если сообщения принадлежат какому-то другому коду, то это может привести к плохим последствиям.
Можно хранить прочитанные сообщения в WeakSet:
const readMessages = new WeakSet()
readMessages.add(messages[0])
readMessages.add(messages[1])
// readMessages содержит 2 элемента
readMessages.add(messages[0])
// readMessages до сих пор содержит 2 элемента
// было ли сообщение message[0] прочитано?
console.log(`Read message 0: ${readMessages.has(messages[0])}`) // true
messages.shift()
// теперь readMessages содержит 1 элемент (хотя технически память может быть очищена позже)
WeakSet позволяет хранить набор сообщений и легко проверять наличие сообщения в нем. Он очищается автоматически. Минус в том, что мы не можем перебрать его содержимое, не можем получить "все прочитанные сообщения" напрямую. Но мы можем сделать это, перебирая все сообщения и фильтруя те, которые находятся в WeakSet. Альтернативным решением может быть добавление свойства вида "message.isRead = true" к сообщению после его прочтения. Так как сообщения принадлежат чужому коду, то это не очень хорошо, но если использовать свойство-символ, то вероятность конфликтов будет небольшой.
Есть массив сообщений:
const messages = [
{ text: 'Hello', from: 'John' },
{ text: 'How are you?', from: 'Jane' },
{ text: 'See you soon', from: 'John' },
]
Теперь вопрос стоит так: какую структуру данных вы бы предложили использовать для хранения информации о том, когда сообщение было прочитано? В предыдущем задании нам нужно было сохранить только факт прочтения "да или нет". Теперь же нам нужно сохранить дату, и она должна исчезнуть из памяти при удалении "сборщиком мусора" сообщения.
Для хранения даты мы можем использовать WeakMap:
const readMap = new WeakMap()
readMap.set(messages[0], new Date().toLocaleDateString())
Напишите функцию "count(obj)", которая возвращает количество свойств объекта:
const user = {
name: 'John',
age: 30,
}
console.log(count(user)) // 2
const count = (obj) => Object.keys(obj).length
У нас есть объект "salaries" с зарплатами:
const salaries = {
John: 100,
Jane: 200,
Bob: 300,
Alice: 400,
}
Создайте функцию "topSalary(salaries)", которая возвращает имя самого высокооплачиваемого сотрудника.
- Если объект "salaries" пустой, то нужно вернуть null.
- Если несколько высокооплачиваемых сотрудников, можно вернуть любого из них.
function topSalary(salaries) {
let max = 0
let maxName = null
for (const [name, salary] of Object.entries(salaries)) {
if (max < salary) {
max = salary
maxName = name
}
}
return maxName
}
// или
const topSalary = (obj, keys = Object.keys(obj), values = Object.values(obj)) => (keys.length ? keys[values.indexOf(Math.max(...values))] : null)
В простых случаях циклических ссылок мы можем исключить свойство, из-за которого они возникают, из сериализации по его имени. Но иногда мы не можем использовать имя, так как могут быть и другие, нужные свойства с этим именем во вложенных объектах. Поэтому можно проверять свойство по значению. Напишите функцию "replacer" для JSON-преобразования, которая удалит свойства, ссылающиеся на "meetup":
const room = {
number: 23,
}
const meetup = {
title: 'Совещание',
occupiedBy: [{ name: 'Иванов' }, { name: 'Петров' }],
place: room,
}
// цикличные ссылки
room.occupiedBy = meetup
meetup.self = meetup
console.log(
JSON.stringify(meetup, function replacer(key, value) {
// ...
})
)
/* в результате должно быть:
{
"title":"Совещание",
"occupiedBy":[{"name":"Иванов"},{"name":"Петров"}],
"place":{"number":23}
}
*/
console.log(
JSON.stringify(meetup, function replacer(key, value) {
return key !== '' && value === meetup ? undefined : value
})
)
Факториал натурального числа – это число, умноженное на "себя минус один", затем на "себя минус два", и так далее до 1. Факториал n обозначается как "n!" Определение факториала можно записать как:
n! = n * (n - 1) * (n - 2) * ...*1
Примеры значений для разных n:
1! = 1
2! = 2 * 1 = 2
3! = 3 * 2 * 1 = 6
4! = 4 * 3 * 2 * 1 = 24
5! = 5 * 4 * 3 * 2 * 1 = 120
Задача – написать функцию "factorial(n)", которая возвращает "n!", используя рекурсию.
console.log(factorial(5)) // 120
const factorial = (n) => (n ? n * factorial(n - 1) : 1)
// мемоизация
const memo = (fn, cache = Object.create(null)) => (val) => cache[val] || (cache[val] = fn(val))
const fact = (n) => (n < 0 ? new Error() : n === 0 || n === 1 ? 1 : n * fact(n - 1))
const memoFact = memo(fact)
console.time('t1')
console.log(memoFact(100))
console.timeEnd('t1') // 0.15
console.time('t2')
console.log(memoFact(101))
console.timeEnd('t2') // 0.05
console.time('t3')
console.log(memoFact(102))
console.timeEnd('t3') // 0.05
Последовательность чисел Фибоначчи определяется формулой "Fn = Fn - 1 + Fn - 2". То есть, следующее число получается как сумма двух предыдущих. Первые два числа равны 1, затем 2(1+1), 3(1+2), 5(2+3) и так далее: 1, 1, 2, 3, 5, 8, 13, 21.... Числа Фибоначчи тесно связаны с золотым сечением и множеством природных явлений вокруг нас. Напишите функцию "fib(n)" которая возвращает n-е число Фибоначчи. Пример работы:
console.log(fib(3)) // 2
console.log(fib(7)) // 13
console.log(fib(77)) // 5527939700884757
// медленно
const fib = (n) => (n <= 1 ? n : fib(n - 1) + fib(n - 2))
// быстро
const fib = (n, a = 1, b = 1) => {
for (let i = 3; i <= n; i++) {
let c = a + b
a = b
b = c
}
return b
}
Допустим, у нас есть такой односвязный список:
const list = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null,
},
},
},
}
Напишите функцию "printList(list)", которая выводит элементы списка по одному, и другую функцию "printReverseList(list)", которая делает тоже самое, но в обратном порядке.
// с использованием цикла
const printList = (list, tmp = list) => {
while (tmp) {
console.log(tmp.value)
tmp = tmp.next
}
}
const printReverseList = (list, tmp = list, arr = []) => {
while (tmp) {
arr.push(tmp.value)
tmp = tmp.next
}
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i])
}
}
// с использованием рекурсии
const printList = (list) => {
console.log(list.value)
if (list.next) {
printList(list.next)
}
}
const printReverseList = (list) => {
if (list.next) {
printReverseList(list.next)
}
console.log(list.value)
}
Напишите функцию "sum", которая работает таким образом: sum(a)(b) = a + b. Да, именно таким образом, используя двойные круглые скобки (не опечатка). Например:
sum(1)(2) = 3
sum(5)(-1) = 4
// это называется каррированием (currying)
function sum(a) {
return function (b) {
return a + b
}
}
// или
const sum = (a) => (b) => a + b
У нас есть встроенный метод "arr.filter(f)" для массивов. Он фильтрует все элементы с помощью функции "f". Если она возвращает true, то элемент добавится в возвращаемый массив. Сделайте набор "готовых к употреблению" фильтров:
- inBetween(a, b) – между a и b (включительно).
- inArray([...]) – находится в данном массиве.
Они должны использоваться таким образом:
- arr.filter(inBetween(3, 6)) – выбирает только значения между 3 и 6 (включительно).
- arr.filter(inArray([1, 2, 3])) – выбирает только элементы, совпадающие с одним из элементов массива
Например:
const arr = [1, 2, 3, 4, 5, 6, 7]
console.log(arr.filter(inBetween(3, 6))) // 3, 4, 5, 6
console.log(arr.filter(inArray([1, 2, 10]))) // 1, 2
function inBetween(a, b) {
return function (x) {
return x >= a && x <= b
}
}
function inArray(arr) {
return function (x) {
return arr.includes(x)
}
}
// или
const inBetween = (a, b) => (x) => x >= a && x <= b
const inArray = (arr) => (x) => arr.includes(x)
У нас есть массив объектов, который нужно отсортировать:
const users = [
{ name: 'John', age: 20, surname: 'Johnson' },
{ name: 'Pete', age: 18, surname: 'Peterson' },
{ name: 'Ann', age: 19, surname: 'Hathaway' },
]
Обычный способ был бы таким:
// по имени (Ann, John, Pete)
users.sort((a, b) => (a.name > b.name ? 1 : -1))
// по возрасту (Pete, Ann, John)
users.sort((a, b) => (a.age > b.age ? 1 : -1))
Можем ли мы сделать его короче, скажем, вот таким?
users.sort(byField('name'))
users.sort(byField('age'))
То есть, чтобы вместо функции, мы просто писали "byField(fieldName)". Напишите функцию "byField", которая может быть использована для этого.
const byField = (field) => (a, b) => (a[field] > b[field] ? 1 : -1)
Напишите функцию "printNumbers(from, to)", которая выводит число каждую секунду, начиная от "from" и заканчивая "to". Сделайте два варианта решения.
- Используя setInterval.
- Используя рекурсивный setTimeout.
// setInterval
const printNumbers = (from, to) => {
const timerId = setInterval(() => {
console.log(from)
from === to ? clearInterval(timerId) : from++
}, 1000)
}
// setTimeout
function printNumbers(from, to) {
setTimeout(function go() {
console.log(from)
if (from < to) setTimeout(go, 1000)
from++
}, 1000)
}
Создайте декоратор "spy(func)", который должен возвращать обертку, которая сохраняет все вызовы функции в своем свойстве "calls". Каждый вызов должен сохраняться как массив аргументов. Например:
function work(a, b) {
console.log(a + b) // произвольная функция или метод
}
work = spy(work)
work(1, 2) // 3
work(4, 5) // 9
for (const args of work.calls) {
console.log(`call:${args.join()}`) // "call:1,2", "call:4,5"
}
Здесь мы можем использовать call.push(args) для хранения всех аргументов в списке и f.apply(this, args) для переадресации вызова.
function spy(fn) {
function wrap(...args) {
wrap.calls.push(args)
return fn.apply(this, arguments)
}
wrap.calls = []
return wrap
}
// без this
const spy = (fn) => {
const wrap = (...rest) => {
wrap.calls.push(rest)
return fn(...rest)
}
wrap.calls = []
return wrap
}
Создайте декоратор "delay(f, ms)", который задерживает каждый вызов "f" на "ms" миллисекунд. Например:
function f(x) {
console.log(x)
}
// создаем обертки
let f1000 = delay(f, 1000)
let f1500 = delay(f, 1500)
f1000('test') // показывает "test" после 1000 мс
f1500('test') // показывает "test" после 1500 мс
Другими словами, "delay(f, ms)" возвращает вариант "f" с "задержкой на ms мс". В приведенном выше коде f – функция с одним аргументом, но ваше решение должно передавать все аргументы и контекст this.
Здесь мы можем использовать call.push(args) для хранения всех аргументов в списке и f.apply(this, args) для переадресации вызова.
function delay(f, ms) {
return function () {
setTimeout(() => f.apply(this, arguments), ms)
}
}
// без this
const delay = (fn, ms) => (...rest) => setTimeout(() => fn(...rest), ms)
Что выведет функция?
function f() {
console.log(this) // ?
}
const user = {
g: f.bind(null),
}
user.g()
Функция выведет null.
function f() {
console.log(this) // null
}
const user = {
g: f.bind(null),
}
user.g()
Контекст связанной функции жестко фиксирован. Изменить однажды привязанный контекст уже нельзя. Так что хоть мы и вызываем "user.g()", внутри исходная функция будет вызвана с this = null. Однако, функции "g" совершенно без разницы, какой this она получила. Ее единственное предназначение – это передать вызов в "f" вместе с аргументами и ранее указанным контекстом null, что она и делает. Таким образом, когда мы запускаем "user.g()", исходная функция вызывается с this = null.
Можем ли мы изменить this дополнительным связыванием? Что выведет этот код?
function f() {
console.log(this.name)
}
f = f.bind({ name: 'Вася' }).bind({ name: 'Петя' })
f() // ?
Функция выведет "Вася".
function f() {
console.log(this.name)
}
f = f.bind({ name: 'Вася' }).bind({ name: 'Петя' })
f() // Вася
Экзотический объект bound function, возвращаемый при первом вызове "f.bind()", запоминает контекст (и аргументы, если они были переданы) только во время создания. Следующий вызов "bind" будет устанавливать контекст уже для этого объекта. Это ни на что не повлияет. Можно сделать новую привязку, но нельзя изменить существующую.
Вызов "askPassword()" в приведённом ниже коде должен проверить пароль и затем вызвать "user.loginOk/loginFail" в зависимости от ответа. Однако, его вызов приводит к ошибке. Почему? Исправьте последнюю строку кода, чтобы все работало (других строк изменять не надо).
function askPassword(ok, fail) {
const password = prompt('Password?', '')
if (password == 'rockstar') ok()
else fail()
}
const user = {
name: 'John',
loginOk() {
console.log(`${this.name} logged in`)
},
loginFail() {
console.log(`${this.name} failed to log in`)
},
}
askPassword(user.loginOk, user.loginFail)
Ошибка происходит потому, что "askPassword" получает функции "loginOk/loginFail" без контекста. Когда они вызываются, то, естественно, this = undefined. Используем "bind", чтобы передать в "askPassword" функции "loginOk/loginFail" с привязанным контекстом:
function askPassword(ok, fail) {
const password = prompt('Password?', '')
if (password == 'rockstar') ok()
else fail()
}
const user = {
name: 'John',
loginOk() {
console.log(`${this.name} logged in`)
},
loginFail() {
console.log(`${this.name} failed to log in`)
},
}
askPassword(user.loginOkюиштв(user), user.loginFail.bind(user))
Теперь все работает корректно. Альтернативное решение – сделать функции-обертки над "user.loginOk/loginFail":
askPassword(
() => user.loginOk(),
() => user.loginFail()
)
Обычно, это также работает и хорошо выглядит. Но может не сработать в более сложных ситуациях, а именно – когда значение переменной "user" меняется между вызовом "askPassword" и выполнением "() => user.loginOk()".
Это задание является немного усложненным вариантом предыдущего. Объект "user" был изменен. Теперь вместо двух функций "loginOk/loginFail" у него есть только одна – user.login(true/false). Что нужно передать в вызов функции "askPassword" в коде ниже, чтобы она могла вызывать функцию "user.login(true)" как "ok" и функцию "user.login(false)" как "fail"?
function askPassword(ok, fail) {
const password = prompt('Password?', '')
if (password == 'rockstar') ok()
else fail()
}
const user = {
name: 'John',
login(result) {
console.log(this.name + (result ? ' logged in' : ' failed to log in'))
},
}
askPassword() // ?
Ваши изменения должны затрагивать только последнюю строку кода.
Можно использовать стрелочную функцию-обертку:
askPassword(
() => user.login(true),
() => user.login(false)
)
Или же можно создать частично примененную функцию на основе "user.login", которая использует объект "user" в качестве контекста и получает соответствующий первый аргумент:
askPassword(user.login.bind(user, true), user.login.bind(user, false))
Объект rabbit наследует от объекта animal. Какой объект получит свойство full при вызове rabbit.eat(): animal или rabbit?
const animal = {
eat() {
this.full = true
}
}
const rabbit = {
__proto__: animal
}
rabbit.eat()
Поскольку this – это объект, который стоит перед точкой, rabbit.eat() изменяет объект rabbit. Поиск свойства и исполнение кода – два разных процесса. Сначала осуществляется поиск метода rabbit.eat в прототипе, а затем этот метод выполняется с this=rabbit.
У нас есть два хомяка: шустрый (speedy) и ленивый (lazy); оба наследуют от общего объекта hamster. Когда мы кормим одного хомяка, второй тоже наедается. Почему? Как это исправить?
const hamster = {
stomach: [],
eat(food) {
this.stomach.push(food)
}
}
const speedy = {
__proto__: hamster
}
const lazy = {
__proto__: hamster
}
speedy.eat("apple")
console.log( speedy.stomach ) // apple
console.log( lazy.stomach ) // apple
Давайте внимательно посмотрим, что происходит при вызове speedy.eat("apple").
- Сначала в прототипе (=hamster) находится метод speedy.eat, а затем он выполняется с this=speedy (объект перед точкой).
- Затем в this.stomach.push() нужно найти свойство stomach и вызвать для него push. Движок ищет stomach в this (=speedy), но ничего не находит.
- Он идёт по цепочке прототипов и находит stomach в hamster.
- И вызывает для него push, добавляя еду в живот прототипа.
Получается, что у хомяков один живот на двоих! И при lazy.stomach.push(...) и при speedy.stomach.push(), свойство stomach берётся из прототипа (так как его нет в самом объекте), затем в него добавляются данные. Обратите внимание, что этого не происходит при простом присваивании this.stomach=:
const hamster = {
stomach: [],
eat(food) {
this.stomach = [food]
}
}
const speedy = {
__proto__: hamster
}
const lazy = {
__proto__: hamster
}
speedy.eat("apple")
console.log( speedy.stomach ) // apple
console.log( lazy.stomach ) // пусто
Теперь всё работает правильно, потому что this.stomach= не ищет свойство stomach. Значение записывается непосредственно в объект this. Также мы можем полностью избежать проблемы, если у каждого хомяка будет собственный живот:
const hamster = {
stomach: [],
eat(food) {
this.stomach.push(food)
}
}
const speedy = {
__proto__: hamster,
stomach: []
}
const lazy = {
__proto__: hamster,
stomach: []
}
speedy.eat("apple")
console.log( speedy.stomach ) // apple
console.log( lazy.stomach ) // пусто
Все свойства, описывающие состояние объекта (как свойство stomach в примере выше), рекомендуется записывать в сам этот объект. Это позволяет избежать подобных проблем.
Представьте, что у нас имеется некий объект obj, созданный функцией-конструктором – мы не знаем какой именно, но хотелось бы создать ещё один объект такого же типа. Можем ли мы сделать так?
const obj2 = new obj.constructor()
Приведите пример функции-конструктора для объекта obj, с которой такой вызов корректно сработает. И пример функции-конструктора, с которой такой код поведёт себя неправильно.
Мы можем использовать такой способ, если мы уверены в том, что свойство "constructor" существующего объекта имеет корректное значение. Например, если мы не меняли "prototype", используемый по умолчанию, то код ниже, без сомнений, сработает:
function User(name) {
this.name = name
}
const user = new User('John')
const user2 = new user.constructor('Jane')
console.log( user2.name ) // Jane
Всё получилось, потому что User.prototype.constructor == User. Но если кто-то перезапишет User.prototype и забудет заново назначить свойство "constructor", чтобы оно указывало на User, то ничего не выйдет. Например:
function User(name) {
this.name = name
}
User.prototype = {} // (*)
const user = new User('John')
const user2 = new user.constructor('Jane')
console.log( user2.name ) // undefined
Почему user2.name приняло значение undefined? Рассмотрим, как отработал вызов new user.constructor('Jane'):
- Сначала ищется свойство constructor в объекте user. Не нашлось.
- Потом задействуется поиск по цепочке прототипов. Прототип объекта user – это User.prototype, и там тоже нет искомого свойства.
- Значение User.prototype – это пустой объект {}, чей прототип – Object.prototype. Object.prototype.constructor === Object. Таким образом, свойство constructor всё-таки найдено.
Наконец, срабатывает const user2 = new Object('Jane'), но конструктор Object игнорирует аргументы, он всегда создаёт пустой объект: const user2 = {} – это как раз то, чему равен user2 в итоге.
Добавьте всем функциям в прототип метод defer(ms), который возвращает обёртку, откладывающую вызов функции на ms миллисекунд. Например, должно работать так:
function f(a, b) {
console.log( a + b )
}
f.defer(1000)(1, 2) // 3 через 1 секунду
Пожалуйста, заметьте, что аргументы должны корректно передаваться оригинальной функции.
Мы можем использовать такой способ, если мы уверены в том, что свойство "constructor" существующего объекта имеет корректное значение. Например, если мы не меняли "prototype", используемый по умолчанию, то код ниже, без сомнений, сработает:
Function.prototype.defer = function(ms) {
let f = this
return function(...args) {
setTimeout(() => f.apply(this, args), ms)
}
}
Имеется объект dictionary, созданный с помощью Object.create(null) для хранения любых пар ключ/значение. Добавьте ему метод dictionary.toString(), который должен возвращать список ключей, разделённых запятой. Ваш toString не должен выводиться при итерации объекта с помощью цикла for..in. Вот так это должно работать:
const dictionary = Object.create(null)
// ...
dictionary.apple = "apple"
dictionary.banana = "banana"
for(const key in dictionary) {
console.log(key) // apple banana
}
alert(dictionary) // apple,banana
В методе можно получить все перечисляемые ключи с помощью Object.keys и вывести их список. Чтобы сделать toString неперечисляемым, давайте определим его, используя дескриптор свойства. Синтаксис Object.create позволяет нам добавить в объект дескрипторы свойств как второй аргумент.
const dictionary = Object.create(null, {
toString: {
value() {
return Object.keys(this).join()
}
}
})
// или
const dictionary = Object.create(null)
Object.defineProperty(dictionary, 'toString', {
value: () => Object.keys(dictionary).join()
})
Класс Clock написан в функциональном стиле:
function Clock({ template }) {
let timer
function render() {
const date = new Date()
const hours = date.getHours()
if (hours < 10) hours = '0' + hours
const mins = date.getMinutes()
if (mins < 10) mins = '0' + mins
const secs = date.getSeconds()
if (secs < 10) secs = '0' + secs
const output = template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs)
console.log(output)
}
this.stop = function () {
clearInterval(timer)
}
this.start = function () {
render()
timer = setInterval(render, 1000)
}
}
const clock = new Clock({ template: 'h:m:s' })
clock.start()
Перепишите его, используя современный синтаксис классов.
class Clock {
constructor({ template }) {
this.template = template
}
render() {
const date = new Date()
const hours = date.getHours()
if (hours < 10) hours = '0' + hours
const mins = date.getMinutes()
if (mins < 10) mins = '0' + mins
const secs = date.getSeconds()
if (secs < 10) secs = '0' + secs
const output = this.template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs)
console.log(output)
}
stop() {
clearInterval(this.timer)
}
start() {
this.render()
this.timer = setInterval(() => this.render(), 1000)
}
}
const clock = new Clock({ template: 'h:m:s' })
clock.start()
У нас есть класс Clock. Сейчас он выводит время каждую секунду:
class Clock {
constructor({ template }) {
this.template = template
}
render() {
const date = new Date()
const hours = date.getHours()
if (hours < 10) hours = '0' + hours
const mins = date.getMinutes()
if (mins < 10) mins = '0' + mins
const secs = date.getSeconds()
if (secs < 10) secs = '0' + secs
const output = this.template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs)
console.log(output)
}
stop() {
clearInterval(this.timer)
}
start() {
this.render()
this.timer = setInterval(() => this.render(), 1000)
}
}
Создайте новый класс ExtendedClock, который будет наследоваться от Clock и добавьте параметр precision – количество миллисекунд между «тиками». Установите значение в 1000 (1 секунда) по умолчанию. Не изменяйте класс Clock. Расширьте его.
class ExtendedClock extends Clock {
constructor(options) {
super(options)
const { precision = 1000 } = options
this.precision = precision
}
start() {
this.render()
this.timer = setInterval(() => this.render(), this.precision)
}
}
Почему instanceof в примере ниже возвращает true? Мы же видим, что a не создан с помощью B().
function A() {}
function B() {}
A.prototype = B.prototype = {}
const a = new A()
console.log( a instanceof B ) // true
clock.start()
Да, действительно, выглядит странно. Но instanceof не учитывает саму функцию при проверке, а только prototype, который проверяется на совпадения в прототипной цепочке. И в данном примере a.proto == B.prototype, так что instanceof возвращает true. Таким образом, по логике instanceof, именно prototype в действительности определяет тип, а не функция-конструктор.
Создайте класс FormatError, который наследует от встроенного класса SyntaxError. Класс должен поддерживать свойства message, name и stack. Пример использования:
const err = new FormatError("ошибка форматирования")
console.log( err.message ) // ошибка форматирования
console.log( err.name ) // FormatError
console.log( err.stack ) // stack
console.log( err instanceof FormatError ) // true
console.log( err instanceof SyntaxError ) // true
class FormatError extends SyntaxError {
constructor(message) {
super(message)
this.name = "FormatError"
}
}
Что выведет код ниже?
const promise = new Promise(function(resolve, reject) {
resolve(1)
setTimeout(() => resolve(2), 1000)
})
promise.then(console.log)
Вывод будет: 1. Второй вызов resolve будет проигнорирован, поскольку учитывается только первый вызов reject/resolve. Все последующие вызовы – игнорируются.
Встроенная функция setTimeout использует колбэк-функции. Создайте альтернативу, использующую промисы. Функция delay(ms) должна возвращать промис, который перейдёт в состояние «выполнен» через ms миллисекунд, так чтобы мы могли добавить к нему .then:
function delay(ms) {
// ...
}
delay(3000).then(() => console.log('выполнилось через 3 секунды'))
const delay = (ms) =>
new Promise(resolve => setTimeout(resolve, ms))
Заметьте, что resolve вызывается без аргументов. Мы не возвращаем из delay ничего, просто гарантируем задержку.
Что вы думаете? Выполнится ли .catch? Поясните свой ответ.
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!")
}, 1000)
}).catch(console.log)
Нет, не выполнится. Здесь присутствует "скрытый try..catch" вокруг кода функции. Поэтому обрабатываются все синхронные ошибки. В данном примере ошибка генерируется не по ходу выполнения кода, а позже. Поэтому промис не может обработать её.
Перепишите пример, используя "async/await":
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json()
} else {
throw new Error(response.status)
}
})
}
async function loadJson(url) {
const response = await fetch(url)
const {status} = response
if (status === 200) {
const json = await response.json()
return json
}
throw new Error(status)
}
Перепишите пример, используя "async/await":
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`)
this.name = 'HttpError'
this.response = response
}
}
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json()
} else {
throw new HttpError(response)
}
})
}
function demoGithubUser() {
let name = prompt("Введите логин?", "harryheman")
return loadJson(`https://api.github.com/users/${name}`)
.then(user => {
alert(`Полное имя: ${user.name}.`)
return user
})
.catch(err => {
if (err instanceof HttpError && err.response.status == 404) {
alert("Такого пользователя не существует, пожалуйста, повторите ввод.")
return demoGithubUser()
} else {
throw err
}
})
}
demoGithubUser()
class HttpError extends Error {
constructor(response) {
super(`${response.status} for ${response.url}`)
this.name = 'HttpError'
this.response = response
}
}
async function loadJson(url) {
let response = await fetch(url)
if (response.status == 200) {
return response.json()
} else {
throw new HttpError(response)
}
}
async function demoGithubUser() {
let user
while(true) {
const name = prompt("Введите логин?", "harryheman")
try {
user = await loadJson(`https://api.github.com/users/${name}`)
break
} catch(err) {
if (err instanceof HttpError && err.response.status === 404) {
alert("Такого пользователя не существует, пожалуйста, повторите ввод.")
} else {
throw err
}
}
}
alert(`Полное имя: ${user.name}.`)
return user
}
demoGithubUser()
Обычно при чтении несуществующего свойства из объекта возвращается undefined. Создайте прокси, который генерирует ошибку при попытке прочитать несуществующее свойство. Это может помочь обнаружить программные ошибки пораньше. Напишите функцию wrap(target), которая берёт объект target и возвращает прокси, добавляющий в него этот аспект функциональности. Вот как это должно работать:
const user = {
name: "John"
}
function wrap(target) {
return new Proxy(target, {
// ...
})
}
user = wrap(user)
console.log(user.name) // John
console.log(user.age) // Ошибка: такого свойства не существует
function wrap(target) {
return new Proxy(target, {
get(target, prop, receiver) {
if (prop in target) {
return Reflect.get(target, prop, receiver)
} else {
throw new ReferenceError(`Свойства "${prop}" не существует`)
}
}
})
}
Создайте функцию makeObservable(target), которая делает объект «наблюдаемым», возвращая прокси. Вот как это должно работать:
function makeObservable(target) {
// ...
}
const user = {}
user = makeObservable(user)
user.observe((key, value) => {
console.log(`SET ${key}=${value}`)
})
user.name = "John" // SET name=John
Другими словами, возвращаемый makeObservable объект аналогичен исходному, но также имеет метод observe(handler), который позволяет запускать handler при любом изменении свойств. При изменении любого свойства вызывается handler(key, value) с именем и значением свойства.
Решение состоит из двух частей:
- При вызове .observe(handler) нам нужно где-то сохранить обработчик, чтобы вызвать его позже. Можно хранить обработчики прямо в объекте, создав в нём для этого свой символьный ключ.
- Нам нужен прокси с ловушкой set, чтобы вызывать обработчики при изменении свойств.
const handlers = Symbol('handlers')
function makeObservable(target) {
target[handlers] = []
target.observe = function(handler) {
this[handlers].push(handler)
}
return new Proxy(target, {
set(target, property, value) {
const success = Reflect.set(...arguments)
if (success) {
target[handlers].forEach(handler => handler(property, value))
}
return success
}
})
}
Создайте калькулятор, который запрашивает ввод какого-нибудь арифметического выражения и возвращает результат его вычисления.
const expr = prompt("Введите арифметическое выражение:", '2*3/2')
const calc = expr => {
if (!expr) return
;[...expr].forEach(char => {
if (!char.match(/[\d\+\-*/]+/)) {
throw new Error('wrong symbol')
}
})
return eval(expr)
}
console.log(calc(expr))
Используя "Intl.Collator", отсортируйте массив:
const animals = ["тигр", "ёж", "енот", "ехидна", "АИСТ", "ЯК"]
// ...
console.log(animals)
// ["АИСТ", "ёж", "енот", "ехидна", "тигр", "ЯК"]
В этом примере порядок сортировки не должен зависеть от регистра. Что касается буквы "ё", то мы следуем обычным правилам сортировки буквы ё, по которым «е» и «ё» считаются одной и той же буквой, за исключением случая, когда два слова отличаются только в позиции буквы «е» / «ё» – тогда слово с «е» ставится первым.
const collator = new Intl.Collator()
animals.sort((a, b) => collator.compare(a, b))
console.log(animals.sort())
// // ["АИСТ", "ЯК", "енот", "ехидна", "тигр", "ёж"]