50/200+ вопросов по JavaScript

b6ciwgnykdiccqgqa3shiu3q4zk.png

Доброго времени суток, друзья! Предлагаю Вашему вниманию небольшой интерактив — своего рода викторину по JavaScript, на данный момент состоящую из 50 вопросов.

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

Предисловие


Данная часть основана на этом репозитории. Его автор, Lydia Hallie, позиционирует свой проект как список продвинутых вопросов и, действительно, среди них есть такие, которые, как мне кажется, даже опытному JavaScript-разработчику покажутся непростыми. Однако среди этих вопросов есть и такие, для ответа на которые достаточно владеть базовыми знаниями. В репозитории имеется русский перевод, но, мягко говоря, он оставляет желать лучшего, поэтому большую часть ответов (объяснений) пришлось переводить заново.

Следует отметить, что приводимые пояснения (ответы) не всегда в полной мере раскрывают суть проблемы. Это объясняется формой проекта — он представляет собой чеклист, а не учебник. Ответы, скорее, являются подсказкой для дальнейших поисков на MDN или Javascript.ru. Впрочем, многие из объяснений содержат исчерпывающие ответы.
Несмотря на то, что код неоднократно протестирован, никто не застрахован от ошибок, разумеется, кроме тех, кто ничего не делает. Поэтому при обнаружении ошибок, опечаток, неточностей, некорректности формулировок и т.п., а также при желании улучшить перевод, прошу писать в личку, буду признателен (активность на GitHub также приветствуется).

Собственно, это все, что я хотел сказать в качестве предисловия.

Правила


Правила простые: 50 вопросов, 3–4 варианта ответа, рейтинг: количество правильных и неправильных ответов, прогресс: номер и количество вопросов.

По результатам определяется процент правильных ответов и делается вывод об уровне владения JavaScript: больше 80% — отлично, больше 50% — неплохо, меньше 50%… ну, Вы понимаете.

К каждому вопросу прилагается пояснение. При неправильном ответе данное пояснение раскрывается.

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

Но довольно слов, пора переходить к делу.

Викторина


Код проекта находится здесь.

Механика


Несколько слов о том, как реализована викторина для тех, кому интересно.

Разметка выглядит так:


    
    
    200+ вопросов по JavaScript
    
    
    
    
    
    














Добавляем минимальные стили:

CSS:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: Ubuntu, sans-serif;
    font-size: 1em;
    text-align: center;
    letter-spacing: 1.05px;
    line-height: 1.5em;
    color: #111;
    user-select: none;
}

@media (max-width: 512px) {
    * {
        font-size: .95em;
    }
}

html {
    position: relative;
}

body {
    padding: 1em;
    min-height: 100vh;
    background: radial-gradient(circle, skyblue, steelblue);
    display: flex;
    flex-direction: column;
    justify-content: start;
    align-items: center;
}

h1 {
    margin: .5em;
    font-size: 1.05em;
}

output {
    margin: .5em;
    display: block;
}

.score {
    font-size: 1.25em;
}

form {
    text-align: left;
}

form p {
    text-align: left;
    white-space: pre;
}

form button {
    position: relative;
    left: 50%;
    transform: translateX(-50%);
}

button {
    margin: 2em 0;
    padding: .4em .8em;
    outline: none;
    border: none;
    background: linear-gradient(lightgreen, darkgreen);
    border-radius: 6px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .4);
    font-size: .95em;
    cursor: pointer;
    transition: .2s;
}

button:hover {
    color: #eee;
}

label {
    cursor: pointer;
}

input {
    margin: 0 10px 0 2em;
    cursor: pointer;
}

details {
    font-size: .95em;
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
    background: #eee;
    border-radius: 4px;
    cursor: pointer;
}

details h3 {
    margin: .5em;
}

details p {
    margin: .5em 1.5em;
    text-align: justify;
    text-indent: 1.5em;
}

.right {
    color: green;
}

.wrong {
    color: red;
}


Исходники (assets) представляют собой массив объектов, где каждый объект имеет свойства question (вопрос), answers (ответы), rightAnswer (правильный ответ) и explanation (объяснение):

[
{
    question: `
        function sayHi() {
            console.log(name);
            console.log(age);
            var name = "Lydia";
            let age = 21;
        }

        sayHi();
    `,
    answers: `
        A: Lydia и undefined
        B: Lydia и ReferenceError
        C: ReferenceError и 21
        D: undefined и ReferenceError
    `,
    rightAnswer: `D`,
    explanation: `
        Внутри функции мы сначала определяем переменную name с помощью ключевого слова var. Это означает, что name поднимется в начало функции. Name будет иметь значение undefined до тех пор, пока выполнение кода не дойдет до строки, где ей присваивается значение Lydia. Мы не определили значение name, когда пытаемся вывести ее в консоль, поэтому будет выведено undefined. Переменные, определенные с помощью let (и const), также поднимаются, но в отличие от var, не инициализируются. Доступ к ним до инициализации невозможен. Это называется "временной мертвой зоной". Когда мы пытаемся обратиться к переменным до их определения, JavaScript выбрасывает исключение ReferenceError.
    `
},
...
]


Основной скрипт:

JavaScript
// импортируем массив объектов - исходники
import assets from './assets.js'

// IIFE
;((D, B) => {
    // заголовок - вопрос
    const title = D.createElement('h1')
    B.append(title)

    // рейтинг: количество правильных и неправильных ответов
    const score = D.createElement('output')
    score.className = 'score'
    B.append(score)

    // прогресс: порядковый номер вопроса
    const progress = D.createElement('output')
    progress.className = 'progress'
    B.append(progress)

    // контейнер для вопроса, вариантов ответа и кнопки для отправки формы
    const div = D.createElement('div')
    B.append(div)

    // получаем значения правильных и неправильных ответов из локального хранилища
    // или присваиваем переменным 0
    let rightAnswers = +localStorage.getItem('rightAnswers') || 0
    let wrongAnswers = +localStorage.getItem('wrongAnswers') || 0

    // получаем значение счетчика из локального хранилища
    // или присваиваем ему 0
    let i = +localStorage.getItem('i') || 0

    // рендерим вопрос
    showQuestion()

    // обновляем рейтинг и прогресс
    updateScoreAndProgress()

    function showQuestion() {
        // если значение счетчика равняется количеству вопросов
        // значит, игра окончена,
        // показываем результат
        if (i === assets.length) {
            return showResult()
        }

        // заголовок-вопрос зависит от значения счетчика - номера вопроса
        switch (i) {
            case 4:
                title.textContent = `Что не является валидным?`
                break;
            case 9:
                title.textContent = `Что произойдет?`
                break;
            case 12:
                title.textContent = `Назовите три фазы распространения событий`
                break;
            case 13:
                title.textContent = `Все ли объекты имеют прототипы?`
                break;
            case 14:
                title.textContent = `Каким будет результат?`
                break;
            case 20:
                title.textContent = `Чему равно sum?`
                break;
            case 21:
                title.textContent = `Как долго будет доступен cool_secret?`
                break;
            case 23:
                title.textContent = `Каким будет результат?`
                break;
            case 25:
                title.textContent = `Глобальный контекст исполнения создает две вещи: глобальный объект и this`
                break;
            case 27:
                title.textContent = `Каким будет результат?`
                break;
            case 29:
                title.textContent = `Каким будет результат?`
                break;
            case 30:
                title.textContent = `Что будет в event.target после нажатия на кнопку?`
                break;
            case 33:
                title.textContent = `Каким будет результат?`
                break;
            case 34:
                title.textContent = `Какие из значений являются "ложными"?`
                break;
            case 38:
                title.textContent = `Все в JavaScript это`
                break;
            case 39:
                title.textContent = `Каким будет результат?`
                break;
            case 40:
                title.textContent = `Каким будет результат?`
                break;
            case 41:
                title.textContent = `Что возвращает setInterval?`
                break;
            case 42:
                title.textContent = `Каким будет результат?`
                break;
            case 42:
                title.textContent = `Каково значение num?`
                break;
            case 49:
                title.textContent = `Каким будет результат?`
                break;

            default:
                title.textContent = `Что будет выведено в консоль?`
                break;
        }

        // поскольку каждый элемент массива - это объект,
        // мы можем его деструктурировать, получив вопрос, правильный ответ и объяснение
        const {
            question,
            rightAnswer,
            explanation
        } = assets[i]

        // поскольку варианты ответа - это input type="radio",
        // строку необходимо преобразовать в массив (критерием является перенос строки - \n)
        // первый и последний элементы - пустые строки,
        // избавляемся от них с помощью slice(1, -1),
        // также удаляем пробелы
        const answers = assets[i].answers
            .split('\n')
            .slice(1, -1)
            .map(i => i.trim())

        // HTML-шаблон
        const template = `
        

Вопрос:
${question}

Варианты ответов:


${answers.reduce((html, item) => html += `
`, '')}
Показать правильный ответ

Правильный ответ: ${rightAnswer}

${explanation}

` // помещаем шаблон в контейнер div.innerHTML = template // находим форму const form = div.querySelector('form') // выбираем первый инпут form.querySelector('input').setAttribute('checked', '') // обрабатываем отправку формы form.addEventListener('submit', ev => { // предотвращаем перезагрузку страницы ev.preventDefault() // определяем выбранный вариант ответа const chosenAnswer = form.querySelector('input:checked').value.substr(0, 1) // проверяем ответ checkAnswer(chosenAnswer, rightAnswer) }) } function checkAnswer(chosenAnswer, rightAnswer) { // индикатор правильного ответа let isRight = true // если выбранный ответ совпадает с правильным, // увеличиваем количество правильных ответов, // записываем количество правильных ответов в локальное хранилище, // иначе увеличиваем количество неправильных ответов, // записываем количество неправильных ответов в локальное хранилище // и присваиваем индикатору false if (chosenAnswer === rightAnswer) { rightAnswers++ localStorage.setItem('rightAnswers', rightAnswers) } else { wrongAnswers++ localStorage.setItem('wrongAnswers', wrongAnswers) isRight = false } // находим кнопку const button = div.querySelector('button') // если ответ был правильным if (isRight) { // сообщаем об этом title.innerHTML = `

Верно!

` // выключаем кнопку button.disabled = true // через секунду вызываем функции // обновления рейтинга и прогресса и рендеринга следующего вопроса // отключаем таймер const timer = setTimeout(() => { updateScoreAndProgress() showQuestion() clearTimeout(timer) }, 1000) // если ответ был неправильным } else { // сообщаем об этом title.innerHTML = `

Неверно!

` // выключаем инпуты div.querySelectorAll('input').forEach(input => input.disabled = true) // раскрываем объяснение div.querySelector('details').setAttribute('open', '') // меняем текст кнопки button.textContent = 'Понятно' // по клику на кнопке вызываем функции // обновления рейтинга и прогресса и рендеринга следующего вопроса // удаляем обработчик button.addEventListener('click', () => { updateScoreAndProgress() showQuestion() }, { once: true }) } // увеличиваем значение счетчика i++ // записываем значение счетчика в локальное хранилище localStorage.setItem('i', i) } function updateScoreAndProgress() { // обновляем рейтинг score.innerHTML = `${rightAnswers} - ${wrongAnswers}` // обновляем прогресс progress.innerHTML = `${i + 1} / ${assets.length}` } function showResult() { // определяем процент правильных ответов const percent = (rightAnswers / assets.length * 100).toFixed() // объявляем переменную для результата let result // в зависимости от процента правильных ответов // присваиваем result соответствующее значение if (percent >= 80) { result = `Отличный результат! Вы прекрасно знаете JavaScript.` } else if (percent > 50) { result = `Неплохой результат, но есть к чему стремиться.` } else { result = `Вероятно, вы только начали изучать JavaScript.` } // рендерим результаты B.innerHTML = `

Ваш результат

Правильных ответов: ${rightAnswers}

Неправильных ответов: ${wrongAnswers}

Процент правильных ответов: ${percent}

${result}

` // при нажатии на кнопку // очищаем хранилище // и перезагружаем страницу, // удаляем обработчик B.querySelector('button').addEventListener('click', () => { localStorage.clear() location.reload() }, { once: true }) } })(document, document.body)


Благодарю за внимание, друзья.

Продолжение следует…

© Habrahabr.ru