Академическое и интуитивное понимание (практика делает компетентным)

Добро пожаловать в мой первый пост в блоге!

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

Одна вещь, которая бросается мне в глаза снова и снова, — это огромный разрыв между знанием (т.е. академическим пониманием) чего-то и реальным получением (интуитивным пониманием) чего-то таким образом, чтобы эти знания можно было применить правильно и практически. В этом, собственно, и заключается основное различие между тем, как преподают в Flatiron School, и тем, как преподают на многих курсах в колледже. Обычный курс в колледже может научить вас всему, что нужно знать о программировании; Flatiron School делает из вас программиста. Разница тонкая, но глубокая.

Я приведу небольшой пример, чтобы показать, что я имею в виду. Сейчас мне трудно представить, но когда я завершил свою подготовку к Flatiron, я понятия не имел, как итерация/зацикливание могут быть полезны. Это не значит, что я не понимал основ того, как это работает, но я интуитивно не понимал, как это можно применить в реальности. Поэтому, когда я создавал веб-сайт для своего проекта, мне и в голову не пришло, что это может быть полезно:

flipCards(lineOne, 0, 0);
flipCards(lineOne, 1, 1);
flipCards(lineOne, 2, 2);
flipCards(lineOne, 3, 3);
flipCards(lineOne, 4, 4);
flipCards(lineOne, 5, 5);
flipCards(lineTwo, 0, 6);
flipCards(lineTwo, 1, 7);
flipCards(lineTwo, 2, 8);
flipCards(lineTwo, 3, 9);
flipCards(lineTwo, 4, 10);
flipCards(lineTwo, 5, 11);
flipCards(lineThree, 0, 12);
flipCards(lineThree, 1, 13);
flipCards(lineThree, 2, 14);
flipCards(lineThree, 3, 15);
flipCards(lineThree, 4, 16);
flipCards(lineThree, 5, 17);
flipCards(lineFour, 0, 18);
flipCards(lineFour, 1, 19);
flipCards(lineFour, 2, 20);
flipCards(lineFour, 3, 21);
flipCards(lineFour, 4, 22);
flipCards(lineFour, 5, 23);
Войти в полноэкранный режим Выйти из полноэкранного режима

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

for (let i = 0; i < allCards.length; i++) {
    flipCards(i)
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Так как же совершить прыжок? Как перейти от академических знаний к интуитивному пониманию? Я нашел ответ в практике, а точнее, в совершении ошибок. «Учиться на своих ошибках» — это клише, которое, возможно, старше самого компьютерного программирования, но редко когда его можно применить так буквально, как в случае с кодированием. Разбирая свой код и выясняя, что он делает на самом деле, а что, по вашему мнению, он должен делать, вы можете получить больше, чем когда-либо, изучая только «книжки». Позвольте мне провести вас через некоторый код, основанный на коде, который я написал, и показать, как на каждом этапе каждая ошибка, которую я совершал, приводила меня к более глубокому и полному пониманию кода, с которым я работал.

Скажем, у нас есть объект, ключами которого являются производители автомобилей, значениями — пустые строки, а массив содержит несколько моделей автомобилей, выпускаемых этой компанией:

const cars = {
    honda: '',
    toyota: ['corolla', 'camry', 'avalon', 'sienna'],
    gmc: '',
    chrysler: '',
    ford: ['explorer', 'fusion'],
    hyundai: ['elantra', 'sonata', 'genesis', 'santa fe'],
    infiniti: ''
};
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь предположим, что я хочу отобразить эти автомобили в виде <ul> на моей веб-странице, с вложенным <ul> для списка моделей автомобилей, которые включены для тех производителей, у которых они есть. Если бы мы просто составляли список производителей (ключи), это было бы довольно просто. У нас есть <ul> в нашем файле html с id #main-list. Давайте создадим функцию, которая создаст тег <li> и добавит его в этот <ul>:

function makeLi(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем использовать простой for...in для создания списка:

for (const car in cars) {
    makeLi(car)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь давайте посмотрим, что мы получим:


Это было легко!

Однако нам хотелось бы добавить вложенный <ul>, поэтому давайте посмотрим, как это можно сделать.

Сначала добавим еще один параметр к makeLi:

function makeLi(car, id) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById(id).appendChild(li)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Все то же самое, за исключением того, что id параметра <ul> передается в качестве аргумента, а не прописывается в функции.

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

function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = 'child-ul'
    li.appendChild(ul)
    cars.car.map(model => makeLi(model, 'child-ul'))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь обновим нашу for...in простым троичным оператором:

for (const car in cars) {
    cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

И посмотрим, что мы получим:


Ну… Похоже, мы только что столкнулись с нашей первой возможностью обучения! Сообщение об ошибке говорит нам, что JavaScript, видимо, не распознает наш метод .map() таким, какой он есть, скорее, он пытается придать слову «map» какое-то значение (которого не существует). Видимо, cars.car, который должен возвращать значение того ключа, который наш цикл for..in передает в качестве аргумента функции, по какой-то причине не распознается как array. Давайте воспользуемся console.log(), чтобы устранить некоторые неполадки.

for (const car in cars) {
    console.log(cars.car)
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Вход в полноэкранный режим Выход из полноэкранного режима

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


Это странно… Похоже, что cars.car возвращает undefined. Неужели мы что-то не поняли в нашем цикле for..in? Сейчас мы быстро убедимся, что ничего не упустили:

for (const car in cars) {
    console.log(car)
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Проверим консоль:


Похоже, что car действительно возвращает ключи к нашему объекту cars, так почему же cars.car не определен? Мы только что выучили наш первый урок! Теперь я понял последствия того, что я уже знал, но, возможно, так и не усвоил так, как мне было нужно. Все ключи на самом деле являются строками. Наш for..in сохраняет ключи cars в переменной car как строки, и поэтому наша точечная нотация не работает! На самом деле, если мы посмотрим на наш DOM, кажется, что троичное выражение тоже не работает должным образом:


Похоже, что наш <li> для honda имеет вложенный <ul>, несмотря на то, что его значение в нашем объекте cars является пустой string. Наш троичный оператор проверял, что cars.car === '' и возвращал false, потому что cars.car был на самом деле undefined! Поэтому наш код вызвал функцию makeLiWithNestedUl на первом ключе нашего объекта cars, honda, а затем столкнулся с ошибкой, когда мы попытались использовать метод .map() на cars.car, который был undefined. Давайте сделаем быстрый console.log(), на этот раз со скобками, чтобы подтвердить наши подозрения:

for (const car in cars) {
    console.log(cars[car])
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

И вот результат:


Отлично! Он регистрирует правильные значения для всех наших клавиш! (В некоторых случаях это ничто, т.е. пустая строка).

Давайте избавимся от console.log(), откомментируем наш тернарный оператор, заменим точечную нотацию на скобочную (как в тернаре, так и в функции makeLiWithNestedUl) и посмотрим, что получится!


Ой-ёй-ёй. Наша скобочная нотация, похоже, исправила наше тернарное утверждение и заставила наш метод .map() работать. Однако по какой-то причине все вложенные элементы <li> добавляются в первый вложенный элемент <ul>, под именем toyota. Давайте посмотрим на наш DOM, чтобы увидеть, не получают ли ford и hyundai свои <ul> по какой-то причине.


Хммм. Они определенно получают вложенные <ul>, но я думаю, что наша ошибка уже достаточно очевидна. Мы случайно назначили один и тот же id всем нашим вложенным ul. Здесь можно извлечь два урока. Во-первых, хотя документ html не должен иметь более одного элемента с одинаковым id, JavaScript, очевидно, не имеет проблем с добавлением одного и того же id более чем одному узлу DOM, если мы ему это скажем. Во-вторых, если несколько узлов или элементов DOM имеют одинаковый id, document.getElementByID() вернет первый элемент с этим id. Давайте еще раз посмотрим на наши функции:

function makeLi(car, id) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = 'child-ul'
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, 'child-ul'))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Наша функция makeLiWithNestedUl вызывает makeLi и передает 'child-ul' в качестве аргумента для параметра id. Из-за этого все наши вложенные <li> добавляются к первому <ul> с id, равным 'child-ul', то есть к тому, который предназначен для toyota.

Как мы можем назначить разные id нашим вложенным <ul> и при этом эффективно использовать итерацию? Здесь мы можем использовать простой инструмент, который гораздо мощнее, чем я предполагал, когда впервые узнал о нем. Простая интерполяция строк позволит нам присвоить уникальные id каждому вложенному <ul>:

ul.id = `${car}-list`
Вход в полноэкранный режим Выход из полноэкранного режима
cars[car].map(model => makeLi(model, `${car}-list`))
Войти в полноэкранный режим Выход из полноэкранного режима

И вот наша обновленная функция:

function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте обновим наш браузер и посмотрим на результат:


У нас получилось!

Давайте пойдем дальше. Нужно написать еще больше кода, сделать еще больше ошибок и извлечь еще больше уроков!

Я хотел бы иметь возможность изменять цвет названия производителя автомобиля на синий при нажатии на него, а название модели автомобиля — на красный при нажатии на него. Я также хотел бы, чтобы при повторном щелчке оба цвета менялись на черный. Все должно быть просто, верно? Мы можем создать относительно простой слушатель событий. Сначала давайте немного изменим наши исходные функции:

function makeLi(car, id, className) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = className
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = 'parent-li'
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`, 'child-li'))
}
for (const car in cars) {
    cars[car] === '' ? makeLi(car, 'main-list', 'parent-li') : makeLiWithNestedUl(car)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь я добавил параметр в функцию makeLi, позволяющий назначить className создаваемому элементу <li>. Это позволяет мне назначить отдельные classNames для родительского и дочернего <li> элементов. Теперь давайте создадим несколько массивов:

const parentLiArray = Array.from(document.getElementsByClassName('parent-li'))
const childArray = Array.from(document.getElementsByClassName('child-li'))
Вход в полноэкранный режим Выход из полноэкранного режима

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

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
    })
})
Вход в полноэкранный режим Выход из полноэкранного режима

Отлично! Теперь давайте проверим результат:


Упс. Мы забыли, что стили, примененные к родителю, будут применяться и к дочерним элементам, если не указано иное. Когда мы нажимаем на hyundai, он также делает его дочерние элементы синими. Давайте зададим стиль по умолчанию для всех наших элементов <ul>, чтобы предотвратить это:

Array.from(document.getElementsByTagName('ul')).map(ul => ul.style.color = 'black')
Войдите в полноэкранный режим Выход из полноэкранного режима


Вот так! О-о-о. Похоже, мы столкнулись с другими проблемами.


Почему наши вложенные <li> также становятся синими? Что еще хуже, они не меняют цвет на черный при повторном щелчке. Давайте снова запустим console.log().

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
        console.log(e.target.style.color)
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        console.log(e.target.style.color)
    })
})
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем проверить консоль на наличие подсказок о том, что происходит:


Вот это уже интересно. При нажатии на вложенный <li> регистрируются два значения. Первое — красное, как и должно быть. Второе — синее. Видимо, наш код меняет цвет на красный, как и должно быть, но затем сразу же меняет его на синий. При повторном щелчке регистрируется тот же результат. (Можете поверить мне на слово). Давайте попробуем другой console.log(), чтобы проверить, ведут ли наши слушатели событий себя так, как должны:

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
        console.log(e.target)
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        console.log(e.target)
    })
})
Войти в полноэкранный режим Выход из полноэкранного режима

Давайте снова посмотрим на консоль:


Потрясающе. Щелкнутый элемент регистрируется дважды! Изначально эта ошибка научила меня тому, чего я на самом деле не знал. Добавление слушателя событий в родительский элемент автоматически добавляет такой же слушатель событий в его дочерние элементы. Когда я щелкнул дочерний элемент <li>, он сначала запустил свой собственный слушатель событий, который проверил, установлен ли его стиль на красный (это было не так), а затем изменил его на красный. Эта часть работала отлично. Проблема заключалась в том, что после этого срабатывал слушатель событий родительского элемента, проверял, был ли стиль синим (не был), и менял его на синий. Причина, по которой стиль не менялся обратно при повторном нажатии, заключалась в том, что он повторял один и тот же процесс: проверял, был ли стиль красным, менял его на красный, затем проверял, был ли он синим, и менял его на синий. Теперь, когда мы поняли это, возможно, нам нужен только один слушатель событий для обоих! Как мы можем это сделать? Давайте посмотрим… Нам нужен какой-то способ, чтобы JavaScript различал родительский и дочерний элементы и действовал соответствующим образом. Я собираюсь избавиться от слушателя событий для дочерних элементов <li>, потому что он нам больше не нужен. Теперь давайте немного изменим наш другой слушатель событий:

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        if (li.className === 'parent-li') {
            li.style.color === 'blue' ? li.style.color = 'black' : li.style.color ='blue'
        } else {
            li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        }
    })
})
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь наш слушатель событий сначала проверяет className своей цели и действует соответствующим образом, меняя цвет между синим и черным, если className является 'parent-li', и меняя цвет между красным и черным, если нет. Давайте попробуем это снова в нашем браузере:


Отлично! Теперь давайте снова щелкнем по нескольким из них, чтобы убедиться, что они снова стали черными:


Отлично! Мы сделали это! Вот готовый, работающий код:

const cars = {
    honda: '',
    toyota: ['corolla', 'camry', 'avalon', 'sienna'],
    gmc: '',
    chrysler: '',
    ford: ['explorer', 'fusion'],
    hyundai: ['elantra', 'sonata', 'genesis', 'santa fe'],
    infiniti: ''
};

function makeLi(car, id, className) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = className
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = 'parent-li'
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`, 'child-li'))
}
for (const car in cars) {
    cars[car] === '' ? makeLi(car, 'main-list', 'parent-li') : makeLiWithNestedUl(car)
}

Array.from(document.getElementsByTagName('ul')).map(ul => ul.style.color = 'black')

const parentLiArray = Array.from(document.getElementsByClassName('parent-li'))

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        if (li.className === 'parent-li') {
            li.style.color === 'blue' ? li.style.color = 'black' : li.style.color ='blue'
        } else {
            li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        }
    })
})

Вход в полноэкранный режим Выход из полноэкранного режима

Теперь я продемонстрировал лишь один небольшой пример того, как практика работы с кодом, ошибки и выяснение того, что пошло не так, — это лучший и, возможно, единственный способ действительно понять и усвоить, как на самом деле работает ваш код. Ошибки, которые я допустил в этом примере, основаны на реальных ошибках, которые я совершил, и шагах, которые я предпринял для их исправления. (В то время это было очень неприятно, скажу я вам!) Я только начинаю свой путь программиста и с нетерпением жду многих будущих ошибок! Спасибо, что остаетесь со мной до конца моего первого в жизни блога. Любая и любая обратная связь приветствуется!

Оцените статью
Procodings.ru
Добавить комментарий