Русский
Русский
English
Статистика
Реклама

Выгодный vds

Шпаргалка по JS-методам для работы с DOM

15.05.2021 10:13:08 | Автор: admin

Основные источники



Введение


JavaScript предоставляет множество методов для работы с Document Object Model или сокращенно DOM (объектной моделью документа): одни из них являются более полезными, чем другие; одни используются часто, другие почти никогда; одни являются относительно новыми, другие признаны устаревшими.


Я постараюсь дать вам исчерпывающее представление об этих методах, а также покажу парочку полезных приемов, которые сделают вашу жизнь веб-разработчика немного легче.


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


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


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


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


<ul id="list" class="list">  <li id="item1" class="item">1</li>  <li id="item2" class="item">2</li>  <li id="item3" class="item">3</li></ul>

У нас есть список (ul) с тремя элементами (li). Список и каждый элемент имеют идентификатор (id) и CSS-класс (class). id и class это атрибуты элемента. Существует множество других атрибутов: одни из них являются глобальными, т.е. могут добавляться к любому элементу, другие локальными, т.е. могут добавляться только к определенным элементам.


Мы часто будем выводить данные в консоль, поэтому создадим такую "утилиту":


const log = console.log

Миксин NonElementParentNode


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


В чем разница между узлами (nodes) и элементами (elements)? Если кратко, то "узлы" это более общее понятие, чем "элементы". Узел может быть представлен элементом, текстом, комментарием и т.д. Элемент это узел, представленный разметкой (HTML-тегами (открывающим и закрывающим) или, соответственно, одним тегом).


У рассматриваемого миксина есть метод, наследуемый от объекта Document, с которого удобно начать разговор.


Небольшая оговорка: разумеется, мы могли бы создать список и элементы программным способом.


Для создания элементов используется метод createElement(tag) объекта Document:


const listEl = document.createElement('ul')

Такой способ создания элементов называется императивным. Он является не очень удобным и слишком многословным: создаем родительский элемент, добавляет к нему атрибуты по одному, внедряем его в DOM, создаем первый дочерний элемент и т.д. Следует отметить, что существует и другой, более изысканный способ создания элементов шаблонные или строковые литералы (template literals), но о них мы поговорим позже.


Одним из основных способов получения элемента (точнее, ссылки на элемент) является метод getElementById(id) объекта Document:


// получаем ссылку на наш списокconst listEl = document.getElementById('list')log(listEl)// ul#list.list - такая запись означает "элемент `ul` с `id === list`" и таким же `class`

Почему идентификаторы должны быть уникальными в пределах приложения (страницы)? Потому что элемент с id становится значением одноименного свойства глобального объекта window:


log(listEl === window.list) // true

Как мы знаем, при обращении к свойствам и методам window, слово window можно опускать, например, вместо window.localStorage можно писать просто localStorage. Следовательно, для доступа к элементу с id достаточно обратиться к соответствующему свойству window:


log(list) // ul#list.list

Обратите внимание, что это не работает в React и других фреймворках, абстрагирующих работу с DOM, например, с помощью Virtual DOM. Более того, там иногда невозможно обратиться к нативным свойствам и методам window без window.


Миксин ParentNode


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


  • children потомки элемента

const { children } = list // list.childrenlog(children)/*HTMLCollection(3)  0: li#item1.item  1: li#item2.item  2: li#item3.item  length: 3*/

Такая структура называется коллекцией HTML и представляет собой массивоподобный объект (псевдомассив). Существует еще одна похожая структура список узлов (NodeList).


Массивоподобные объекты имеют свойство length с количеством потомков, метод forEach() (NodeList), позволяющий перебирать узлы (делать по ним итерацию). Такие объекты позволяют получать элементы по индексу, по названию (HTMLCollection) и т.д. Однако, у них отсутствуют методы настоящих массивов, такие как map(), filter(), reduce() и др., что делает работу с ними не очень удобной. Поэтому массивоподобные объекты рекомендуется преобразовывать в массивы с помощью метода Array.from() или spread-оператора:


const children = Array.from(list.children)// илиconst children = [...list.children]log(children) // [li#item1.item, li#item2.item, li#item3.item] - обычный массив

  • firstElementChild первый потомок элемент
  • lastElementChild последний потомок элемент

log(list.firstElementChild) // li#item1.itemlog(list.lastElementChild) // li#item2.item

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


const createEl = (id, text, tag = 'li', _class = 'item') => {  const el = document.createElement(tag)  el.id = id  el.className = _class  el.textContent = text  return el}

Наша утилита принимает 4 аргумента: идентификатор, текст, название тега и CSS-класс. 2 аргумента (тег и класс) имеют значения по умолчанию. Функция возвращает готовый к работе элемент. Впоследствии, мы реализует более универсальный вариант данной утилиты.


  • prepend(newNode) добавляет элемент в начало списка
  • append(newNode) добавляет элемент в конец списка

// создаем новый элементconst newItem = createEl('item0', 0)// и добавляем его в начало спискаlist.prepend(newItem)// создаем еще один элементconst newItem2 = createEl('item4', 4)// и добавляем его в конец спискаlist.append(newItem2)log(children)/*HTMLCollection(5)  0: li#item0.item  1: li#item1.item  2: li#item2.item  3: li#item3.item  4: li#item4.item*/

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


  • replaceChildren(nodes) заменяет потомков новыми элементами

const newItems = [newItem, newItem2]// заменяем потомков новыми элементамиlist.replaceChildren(...newItems) // list.replaceChildren(newItem, newItem2)log(children) // 2

Наиболее универсальными способами получения ссылок на элементы являются методы querySelector(selector) и querySelectorAll(selector). Причем, в отличие от getElementById(), они могут вызываться на любом родительском элементе, а не только на document. В качестве аргумента названным методам передается любой валидный CSS-селектор (id, class, tag и т.д.):


// получаем элемент `li` с `id === item0`const itemWithId0 = list.querySelector('#item0')log(itemWithId0) // li#item0.item// получаем все элементы `li` с `class === item`const allItems = list.querySelectorAll('.item')log(allItems) // массивоподобный объект/*NodeList(2)  0: li#item0.item  1: li#item4.item  length: 2*/

Создадим универсальную утилиту для получения элементов:


const getEl = (selector, parent = document, single = true) => single ? parent.querySelector(selector) : [...parent.querySelectorAll(selector)]

Наша утилита принимает 3 аргумента: CSS-селектор, родительский элемент и индикатор количества элементов (один или все). 2 аргумента (предок и индикатор) имеют значения по умолчанию. Функция возвращает либо один, либо все элементы (в виде обычного массива), совпадающие с селектором, в зависимости от значения индикатора:


const itemWithId0 = getEl('#item0', list)log(itemWithId0) // li#item0.itemconst allItems = getEl('.item', list, false)log(allItems) // [li#item0.item, li#item4.item]

Миксин NonDocumentTypeChildNode


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


  • previousElementSibling предыдущий элемент
  • nextElementSibling следующий элемент

log(itemWithId0.previousElementSibling) // nulllog(itemWithId0.nextElementSibling) // #item4

Миксин ChildNode


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


  • before(newNode) вставляет новый элемент перед текущим
  • after(newNode) вставляет новый элемент после текущего

// получаем `li` с `id === item4`const itemWithId4 = getEl('#item4', list)// создаем новый элементconst newItem3 = createEl('item3', 3)// и вставляем его перед `itemWithId4`itemWithId4.before(newItem3)// создаем еще один элементconst newItem4 = createEl('item2', 2)// и вставляем его после `itemWithId0`itemWithId0.after(newItem4)

  • replaceWith(newNode) заменяет текущий элемент новым

// создаем новый элементconst newItem5 = createEl('item1', 1)// и заменяем им `itemWithId0`itemWithId0.replaceWith(newItem5)

  • remove() удаляет текущий элемент

itemWithId4.remove()

Интерфейс Node


Данный интерфейс предназначен для обработки узлов.


  • nodeType тип узла

log(list.nodeType) // 1// другие варианты/* 1 -> ELEMENT_NODE (элемент) 3 -> TEXT_NODE (текст) 8 -> COMMENT_NODE (комментарий) 9 -> DOCUMENT_NODE (document) 10 -> DOCUMENT_TYPE_NODE (doctype) 11 -> DOCUMENT_FRAGMENT_NODE (фрагмент) и т.д.*/

  • nodeName название узла

log(list.nodeName) // UL// другие варианты/*  - квалифицированное название HTML-элемента прописными (заглавными) буквами  - квалифицированное название атрибута  - #text  - #comment  - #document  - название doctype  - #document-fragment*/

  • baseURI основной путь

log(list.baseURI) // .../dom/index.html

  • parentNode родительский узел
  • parentElement родительский элемент

const itemWithId1 = getEl('#item1', list)log(itemWithId1.parentNode) // #listlog(itemWithId1.parentElement) // #list

  • hasChildNodes() возвращает true, если элемент имеет хотя бы одного потомка
  • childNodes дочерние узлы

log(list.hasChildNodes()) // truelog(list.childNodes)/*NodeList(3)  0: li#item1.item  1: li#item2.item  2: li#item3.item*/

  • firstChild первый потомок узел
  • lastChild последний потомок узел

log(list.firstChild) // #item1log(list.lastChild) // #item3

  • nextSibling следующий узел
  • previousSibling предыдущий узел

log(itemWithId1.nextSibling) // #item2log(itemWithId1.previousSibling) // null

  • textContent геттер/сеттер для извлечения/записи текста

// получаем текстlog(itemWithId1.textContent) // 1// меняем текстitemWithId1.textContent = 'item1'log(itemWithId1.textContent) // item1// получаем текстовое содержимое всех потомковlog(list.textContent) // item123

Для извлечения/записи текста существует еще один (устаревший) геттер/сеттер innerText.


  • cloneNode(deep) копирует узел. Принимает логическое значение, определяющее характер копирования: поверхностное копируется только сам узел, глубокое копируется сам узел и все его потомки

// создаем новый список посредством копирования существующегоconst newList = list.cloneNode(false)// удаляем у него `id` во избежание коллизийnewList.removeAttribute('id')// меняем его текстовое содержимоеnewList.textContent = 'new list'// и вставляем его после существующего спискаlist.after(newList)// создаем еще один списокconst newList2 = newList.cloneNode(true)newList.after(newList2)

  • isEqualNode(node) сравнивает узлы
  • isSameNode(node) определяет идентичность узлов

log(newList.isEqualNode(newList2)) // truelog(newList.isSameNode(newList2)) // false

  • contains(node) возвращает true, если элемент содержит указанный узел

log(list.contains(itemWithId1)) // true

  • insertBefore(newNode, existingNode) добавляет новый узел (newNode) перед существующим (existingNode)

// создаем новый элементconst itemWithIdA = createEl('#item_a', 'a')// и вставляем его перед `itemWithId1`list.insertBefore(itemWithIdA, itemWithId1)

  • appendChild(node) добавляет узел в конец списка

// создаем новый элементconst itemWithIdC = createEl('#item_c', 'c')// и добавляем его в конец спискаlist.appendChild(itemWithIdC)

  • replaceChild(newNode, existingNode) заменяет существующий узел (existingNode) новым (newNode):

// создаем новый элементconst itemWithIdB = createEl('item_b', 'b')// и заменяем им `itemWithId1`list.replaceChild(itemWithIdB, itemWithId1)

  • removeChild(node) удаляет указанный дочерний узел

// получаем `li` с `id === item2`const itemWithId2 = getEl('#item2', list)// и удаляем егоlist.removeChild(itemWithId2)

Интерфейс Document


Данный интерфейс предназначен для обработки объекта Document.


  • URL и documentURI адрес документа

log(document.URL) // .../dom/index.htmllog(document.documentURI) // ^

  • documentElement:

log(document.documentElement) // html

  • getElementsByTagName(tag) возвращает все элементы с указанным тегом

const itemsByTagName = document.getElementsByTagName('li')log(itemsByTagName)/*HTMLCollection(4)  0: li##item_a.item  1: li#item_b.item  2: li#item3.item  3: li##item_c.item*/

  • getElementsByClassName(className) возвращает все элементы с указанным CSS-классом

const itemsByClassName = list.getElementsByClassName('item')log(itemsByClassName) // ^

  • createDocumentFragment() возвращает фрагмент документа:

// создаем фрагментconst fragment = document.createDocumentFragment()// создаем новый элементconst itemWithIdD = createEl('item_d', 'd')// добавляем элемент во фрагментfragment.append(itemWithIdD)// добавляем фрагмент в списокlist.append(fragment)

Фрагменты позволяют избежать создания лишних элементов. Они часто используются при работе с разметкой, скрытой от пользователя с помощью тега template (метод cloneNode() возвращает DocumentFragment).


  • createTextNode(data) создает текст


  • createComment(data) создает комментарий


  • importNode(existingNode, deep) создает новый узел на основе существующего



// создаем новый список на основе существующегоconst newList3 = document.importNode(list, true)// вставляем его перед существующим спискомlist.before(newList3)// и удаляем во избежание коллизийnewList3.remove()

  • createAttribute(attr) создает указанный атрибут

Интерфейсы NodeIterator и TreeWalker


Интерфейсы NodeIterator и TreeWalker предназначены для обхода (traverse) деревьев узлов. Я не сталкивался с примерами их практического использования, поэтому ограничусь парочкой примеров:


// createNodeIterator(root, referenceNode, pointerBeforeReferenceNode, whatToShow, filter)const iterator = document.createNodeIterator(list)log(iterator)log(iterator.nextNode()) // #listlog(iterator.nextNode()) // #item_alog(iterator.previousNode()) // #item_alog(iterator.previousNode()) // #listlog(iterator.previousNode()) // null// createTreeWalker(root, whatToShow, filter)// применяем фильтры - https://dom.spec.whatwg.org/#interface-nodefilterconst walker = document.createTreeWalker(list, '0x1', { acceptNode: () => 1 })log(walker)log(walker.parentNode()) // nulllog(walker.firstChild()) // #item_alog(walker.lastChild()) // nulllog(walker.previousSibling()) // nulllog(walker.nextSibling()) // #item_blog(walker.nextNode()) // #item3log(walker.previousNode()) // #item_b

Интерфейс Element


Данный интерфейс предназначен для обработки элементов.


  • localName и tagName название тега

log(list.localName) // ullog(list.tagName) // UL

  • id геттер/сеттер для идентификатора
  • className геттер/сеттер для CSS-класса

log(list.id) // listlist.id = 'LIST'log(LIST.className) // list

  • classList все CSS-классы элемента (объект DOMTokenList)

const button = createEl('button', 'Click me', 'my_button', 'btn btn-primary')log(button.classList)/*DOMTokenList(2)  0: "btn"  1: "btn-primary"  length: 2  value: "btn btn-primary"*/

Работа с classList


  • classList.add(newClass) добавляет новый класс к существующим
  • classList.remove(existingClass) удаляет указанный класс
  • classList.toggle(className, force?) удаляет существующий класс или добавляет новый. Если опциональный аргумент force имеет значение true, данный метод только добавляет новый класс при отсутствии, но не удаляет существующий класс (в этом случае toggle() === add()). Если force имеет значение false, данный метод только удаляет существующий класс при наличии, но не добавляет отсутствующий класс (в этом случае toggle() === remove())
  • classList.replace(existingClass, newClass) заменяет существующий класс (existingClass) на новый (newClass)
  • classList.contains(className) возвращает true, если указанный класс обнаружен в списке классов элемента (данный метод идентичен className.includes(className))

// добавляем к кнопке новый классbutton.classList.add('btn-lg')// удаляем существующий классbutton.classList.remove('btn-primary')// у кнопки есть класс `btn-lg`, поэтому он удаляетсяbutton.classList.toggle('btn-lg')// заменяем существующий класс на новыйbutton.classList.replace('btn', 'btn-success')log(button.className) // btn-successlog(button.classList.contains('btn')) // falselog(button.className.includes('btn-success')) // true

Работа с атрибутами


  • hasAttributes() возвращает true, если у элемента имеются какие-либо атрибуты
  • getAttributesNames() возвращает названия атрибутов элемента
  • getAttribute(attrName) возвращает значение указанного атрибута
  • setAttribute(name, value) добавляет указанные атрибут и его значение к элементу
  • removeAttribute(attrName) удаляет указанный атрибут
  • hasAttribute(attrName) возвращает true при наличии у элемента указанного атрибута
  • toggleAttribute(name, force) добавляет новый атрибут при отсутствии или удаляет существующий атрибут. Аргумент force аналогичен одноименному атрибуту classList.toggle()

log(button.hasAttributes()) // truelog(button.getAttributeNames()) // ['id', 'class']log(button.getAttribute('id')) // buttonbutton.setAttribute('type', 'button')button.removeAttribute('class')log(button.hasAttribute('class')) // false

В использовании перечисленных методов для работы с атрибутами нет особой необходимости, поскольку многие атрибуты являются геттерами/сеттерами, т.е. позволяют извлекать/записывать значения напрямую. Единственным исключением является метод removeAttribute(), поскольку существуют атрибуты без значений: например, если кнопка имеет атрибут disabled, установка значения данного атрибута в false не приведет к снятию блокировки для этого нужно полностью удалить атрибут disabled с помощью removeAttribute().


Отдельного упоминания заслуживает атрибут data-*, где символ * означает любую строку. Он предназначен для определения пользовательских атрибутов. Например, в нашей начальной разметке для уникальной идентификации элементов используется атрибут id. Однако, это приводит к загрязнению глобального пространства имен, что чревато коллизиями между нашими переменными и, например, переменными используемой нами библиотеки когда какой-либо объект библиотеки пытается записаться в свойство window, которое уже занято нашим id.


Вместо этого, мы могли бы использовать атрибут data-id и получать ссылки на элементы с помощью getEl('[data-id="id"]').


Название data-атрибута после символа - становится одноименным свойством объекта dataset. Например, значение атрибута data-id можно получить через свойство dataset.id.


  • closest(selectors) возвращает первый родительский элемент, совпавший с селекторами

LIST.append(button)log(button.closest('#LIST', 'document.body')) // #LIST

  • matches(selectors) возвращает true, если элемент совпадает хотя бы с одним селектором

log(button.matches('.btn', '[type="button"]'))// у кнопки нет класса `btn`, но есть атрибут `type` со значением `button`,// поэтому возвращается `true`

  • insertAdjacentElement(where, newElement) универсальный метод для вставки новых элементов перед/в начало/в конец/после текущего элемента. Аргумент where определяет место вставки. Возможные значения:


    • beforebegin перед открывающим тегом
    • afterbegin после открывающего тега
    • beforeend перед закрывающим тегом
    • afterend после закрывающего тега

  • insertAdjacentText(where, data) универсальный метод для вставки текста


  • Text конструктор для создания текста


  • Comment конструктор для создания комментария



const text = new Text('JavaScript')log(text) // "JavaScript"const part = text.splitText(4)log(part) // "Script"log(part.wholeText()) // Scriptconst comment = new Comment('TODO')log(comment) // <!--TODO-->

Объект Document


  • location объект с информацией о текущей локации документа

log(document.location)

Свойства объекта location:


  • hash хэш-часть URL (символ # и все, что следует за ним), например, #top
  • host название хоста и порт, например, localhost:3000
  • hostname название хоста, например, localhost
  • href полный путь
  • origin protocol + host
  • pathname путь без протокола
  • port порт, например, 3000
  • protocol протокол, например, https
  • search строка запроса (символ ? и все, что следует за ним), например, ?name=John&age=30

Методы location:


  • reload() перезагружает текущую локацию


  • replace() заменяет текущую локацию на новую


  • title заголовок документа



log(document.title) // DOM

  • head метаданные документа


  • body тело документа


  • images псевдомассив (HTMLCollection), содержащий все изображения, имеющиеся в документе



const image = document.createElement('img')image.className = 'my_image'image.src = 'https://miro.medium.com/max/875/1*ZIH_wjqDfZn6NRKsDi9mvA.png'image.alt = "V8's compiler pipeline"image.width = 480document.body.append(image)log(document.images[0]) // .my_image

  • links псевдомассив, содержащий все ссылки, имеющиеся в документе

const link = document.createElement('a')link.className = 'my_link'link.href = 'https://github.com/azat-io/you-dont-know-js-ru'link.target = '_blank'link.rel = 'noopener noreferrer'link.textContent = 'Вы не знаете JS'document.body.append(link)log(document.links[0]) // .my_link

  • forms псевдомассив, содержащий все формы, имеющиеся в документе

const form = document.createElement('form')form.className = 'my_form'document.body.append(form)log(document.forms[0]) // .my_form

Следующие методы и свойство считаются устаревшими:


  • open() открывает документ для записи. При этом документ полностью очищается
  • close() закрывает документ для записи
  • write() записывает данные (текст, разметку) в документ
  • writeln() записывает данные в документ с переносом на новую строку
  • designMode управление режимом редактирования документа. Возможные значения: on и off. Наберите document.designMode = 'on' в консоли DevTools и нажмите Enter. Вуаля, страница стала редактируемой: можно удалять/добавлять текст, перетаскивать изображения и т.д.
  • execCommand() выполняет переданные команды. Со списоком доступных команд можно ознакомиться здесь. Раньше этот метод активно использовался для записи/извлечения данных из буфера обмена (команды copy и paste). Сейчас для этого используются методы navigator.clipboard.writeText(), navigator.clipboard.readText() и др.

Миксин InnerHTML


Геттер/сеттер innerHTML позволяет извлекать/записывать разметку в элемент. Для подготовки разметки удобно пользоваться шаблонными литералами:


const itemsTemplate = `  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li>`LIST.innerHTML = itemsTemplatelog(LIST.innerHTML)/*<li data-id="item1" class="item">1</li><li data-id="item2" class="item">2</li><li data-id="item3" class="item">3</li>*/

Расширения интерфейса Element


  • outerHTML геттер/сеттер для извлечения/записи внешней разметки элемента: то, что возвращает innerHTML + разметка самого элемента

log(LIST.outerHTML)/*<ul id="LIST" class="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>*/

  • insertAdjacentHTML(where, string) универсальный метод для вставки разметки в виде строки. Аргумент where аналогичен одноименному аргументу метода insertAdjacentElement()

Метод insertAdjacentHTML() в сочетании с шаблонными литералами и их продвинутой версией тегированными шаблонными литералами (tagged template literals) предоставляет много интересных возможностей по манипулированию разметкой документа. По сути, данный метод представляет собой движок шаблонов (template engine) на стороне клиента, похожий на Pug, Handlebars и др. серверные движки. С его помощью (при участии History API) можно, например, реализовать полноценное одностраничное приложение (Single Page Application или сокращенно SPA). Разумеется, для этого придется написать чуть больше кода, чем при использовании какого-либо фронтенд-фреймворка.


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



Иногда требуется создать элемент на основе шаблонной строки. Как это можно сделать? Вот соответствующая утилита:


const createElFromStr = (str) => {  // создаем временный элемент  const el = document.createElement('div')  // записываем в него переданную строку - разметку  el.innerHTML = str  // извлекаем наш элемент  // если мы используем здесь метод `firstChild()`, может вернуться `#text`  // одна из проблем шаблонных строк заключается в большом количестве лишних пробелов  const child = el.fisrtElementChild  // удаляем временный элемент  el.remove()  // и возвращаем наш элемент  return child}// шаблон спискаconst listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`// создаем список на основе шаблонаconst listEl = createElFromStr(listTemplate)// и вставляем его в тело документаdocument.body.append(listEl)

Существует более экзотический способ создания элемента на основе шаблонной строки. Он предполагает использование конструктора DOMParser():


const createElFromStr = (str) => {  // создаем новый парсер  const parser = new DOMParser()  // парсер возвращает новый документ  const {    body: { children }  } = parser.parseFromString(str, 'text/html')  // нас интересует первый дочерний элемент тела нового документа  return children[0]}const listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`const listEl = createElFromStr(listTemplate)document.body.append(listEl)

Еще более экзотический, но при этом самый короткий способ предполагает использование расширения для объекта Range метода createContextualFragment():


const createElFromStr = (str) => {  // создаем новый диапазон  const range = new Range()  // создаем фрагмент  const fragment = range.createContextualFragment(str)  // и возвращаем его  return fragment}// или в одну строкуconst createFragment = (str) => new Range().createContextualFragment(str)const listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`document.body.append(createFragment(listTemplate))

В завершение, как и обещал, универсальная утилита для создания элементов:


// функция принимает название тега и объект с настройкамиconst createEl = (tag, opts) => {  const el = document.createElement(tag)  // перебираем ключи объекта и записывает соответствующие свойства в элемент  for (const key in opts) {    el[key] = opts[key]  }  // возвращаем готовый элемент  return el}const button = createEl('button', {  // настройками могут быть атрибуты  id: 'my_button',  className: 'btn btn-primary',  textContent: 'Click me',  title: 'My button',  autofocus: true,  // стили  style: 'color: red; cursor: pointer;',  // обработчики и т.д.  onmouseenter: function () {    this.style.color = 'green'  },  onmouseout: function () {    this.style.color = 'blue'  },  onclick: () => alert('Привет!')})document.body.append(button)

Заключение


Современный JS предоставляет богатый арсенал методов для работы с DOM. Данных методов вполне достаточно для решения всего спектра задач, возникающих при разработке веб-приложений. Хорошее знание этих методов, а также умение их правильно применять гарантируют не только высокое качество (чистоту) кода, но также избавляют от необходимости использовать DOM-библиотеки (такие как jQuery), что в совокупности обусловливает производительность приложения, его поддерживаемость и масштабируемость.


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




VDS от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

SDR приёмник SoftRock Ensemble RX II

10.06.2021 16:07:13 | Автор: admin


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

В этой публикации я хочу рассказать об удивительной конструкции, которая изменила моё представление об аппаратуре для любительской радиосвязи и положила начало моему увлечению SDR (Software Defined Radio).

Речь идёт о SoftRock Ensemble RX II, который я использую в качестве контрольного радиоприёмника уже седьмой год.

На официальном сайте SoftRock Ensemble RX II продаётся или за $65 в виде набора, или за $85 собранным. При такой цене больше хочется говорить о достоинствах, чем о недостатках.

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

Мной используется HF-версия SoftRock Ensemble RX II, которая работает в диапазоне частот 1,830 МГц. Этот диапазон разбит полосовыми фильтрами на четыре поддиапазона: 1,84 МГц; 48 МГц; 816 МГц и 1630 МГц.

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

После правильной сборки конструкция заработала сразу. Когда же я увидел эфир на панорамном индикаторе, я понял, это любовь навеки!

Аппаратная часть


Как понятно из названия, SoftRock Ensemble RX II был не первым в линейке. Видимая простота схемотехнического решения приёмника отрабатывалась американским радиолюбителем Tony Parks (KB9YIG) годами.

Принцип действия малосигнального тракта проще объяснить на схеме радиоприёмника SoftRock Lite II младшего брата SoftRock Ensemble RX II. Схема взята из инструкции по монтажу и наладке:


Из схемы видно, что это гетеродинный приёмник с квадратурным детектором (QSD).

Схема источника питания (1 Power Supply) стандартная схема включения микросхемы линейного стабилизатора 78L05.

Принимаемый сигнал радиочастоты (РЧ) через диапазонный полосовой фильтр (ДПФ) C3, L1, C4 подаётся на первичную обмотку трансформатора T1. На выводе 2 трансформатора присутствует сигнал РЧ, синфазный принимаемому, а на выводе 4 противофазный. На среднюю точку вторичной обмотки T1 (выводы 3, 5) подаётся напряжение смещения, равное половине напряжения питания. Напряжение формируется делителем на резисторах R3, R4 и фильтруется конденсаторами C7, C14, C16. Смещение нужно для правильной работы ключей и операционных усилителей (ОУ).

Ключи из состава мультиплексора-демультиплексора U3 переключаются сигналами со счётчика Джонсона, собранного на U2. В любой момент времени открыт только один ключ.

Сигнал I (Inphase) формируется на конденсаторе C5 подачей на него через R1 напряжения с вывода 2 трансформатора T1 через открытый ключ 1B4 (фаза сигнала гетеродина 0), а также подачей через резистор R2 и открытый ключ 1B1 напряжения с вывода 4 трансформатора T1 (фаза 180).

Сигнал Q (Quadrature) формируется на конденсаторе C6 подачей на него через R1 напряжения с вывода 2 трансформатора T1 через открытый ключ 2B3 (фаза сигнала гетеродина 90), а также подачей через резистор R2 и открытый ключ 2B2 напряжения с вывода 4 трансформатора T1 (фаза 270).

Резисторы R1 и R2 нужны для выравнивания разницы сопротивления открытых ключей мультиплексора-демультиплексора FST3253.

Сигналы I, Q усиливаются инвертирующими усилителями на двух половинках U4 и подаются на линейный вход звуковой карты компьютера, где и производится дальнейшая обработка квадратурного сигнала.

От простого к сложному


SoftRock Lite II предназначен для приёма сигналов на участке какого-либо одного радиолюбительского диапазона. Параметры участка определяются частотой кварцевого резонатора X1 (см. 2 Local Oscillator) и частотой дискретизации звуковой карты.

При резонансной частоте кварца 28224 кГц и частоте дискретизации 96 кГц SoftRock Lite II будет принимать сигналы в диапазоне от 7008 до 7104 кГц. Диапазонный полосовой фильтр (C3, L1, C4), соответственно, должен иметь точно такую же полосу пропускания.

На рисунке ниже показан экран программы HDSDR. Выход приёмника подключен к линейному входу звуковой карты компьютера. Частота дискретизации звуковой карты 96 кГц. Центральная частота приёма 7056 кГц. На панорамном индикаторе безмятежное спокойствие, царящее на любительском диапазоне 40 м воскресным утром 6 июня 2021 года:


В отличие от SoftRock Lite II приёмник SoftRock Ensemble RX II имеет плавную настройку в диапазоне частот от 1800 кГц до 30 МГц, поэтому схема устройства дополнена синтезатором частоты Si570, управляемым через USB контроллером ATTiny85, и переключаемым четырёхдиапазонным ДПФ.

Схема ДПФ приёмника SoftRock Ensemble RX II взята из инструкции по монтажу и наладке:


ДПФ состоит из четырёх фильтров с полосой пропускания: 1,84 МГц; 48 МГц; 816 МГц и 1630 МГц. Фильтры переключаются сигналами SEL 0 и SEL 1 силами двух мультиплексоров-демультиплексоров FST3253 U8 и U9. Аттенюаторы R17, R18, R19 и R20, R21, R22 в цепях ДПФ Band 0 и Band 1 ослабляют на 14 dB входной сигнал на низкочастотных КВ-диапазонах, которые традиционно отличаются высоким уровнем помех.

Трансформатор T2 служит для гальванической развязки ДПФ и антенны. Первичная обмотка трансформатора T3 является входом QSD.

Схема управления и синтезатора частоты показана ниже:


Схема питается от разъема USB. Подключенный к шине USB микроконтроллер U1 управляет синтезатором частоты U3 по интерфейсу I2C. Выходной сигнал синтезатора через трансформатор T1 подаётся на вход счётчика Джонсона. В зависимости от частоты приёма микроконтроллер через оптроны U4 и U5 формирует сигналы SEL 0 и SEL 1 для переключения полосовых фильтров в ДПФ.

Особенностью схемы является то, что она гальванически развязана с малосигнальным трактом приёмника. Полигоны земель изолированы друг от друга, сигналы передаются через оптроны и трансформатор:


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

Программная часть


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

Для управления приёмником и его конфигурирования применяется программа CFGSR, созданная радиолюбителем из Нидерландов F.W. Krom (PE0FKO):


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

Для правильной работы схемы управления нужно выбрать тип устройства. В нашем случае это SoftRock Ensemble RX II LF/HF(HF):


Для работы с SoftRock Ensemble RX II обычно используется программа HDSDR. Для связи HDSDR с CFGSR служит библиотека ExtIO_Si570.dll, которую после установки конфигуратора надо скопировать из папки, куда установлен конфигуратор, в папку, куда установлена HDSDR.

Выход приёмника подключен к линейному входу звуковой карты компьютера. Запускаем HDSDR. Выбираем источник сигнала SoftRock Si570. Устанавливаем рабочую частоту дискретизации звуковой карты. Пытаемся принять сигналы точного времени, которые передаются на частоте 9996 кГц:


Как видно по панорамному индикатору, сигналы точного времени мы принимаем на частоте 9994,18 кГц. Это нужно исправить!

Калибровка частоты


Опорный кварцевый генератор находится внутри корпуса синтезатора Si570. Калибровка опорной частоты производится прямо из конфигуратора вводом частоты настройки на радиостанцию и её фактической частоты вещания:


После проведения процедуры калибровки мы начинаем принимать сигналы точного времени, как и положено, на частоте 9996 кГц:


Теперь наш маленький аппаратно-программный комплекс можно смело использовать в качестве контрольного радиоприёмника с калиброванной шкалой.

Unfinished Sympathy


До знакомства с SoftRock Ensemble RX II такие вещи как синтезатор частоты, панорамный индикатор и управление радиостанцией с помощью компьютера были для меня атрибутами профессиональной связной техники, которая стоит отнюдь не $85.

Сборка приёмника оказалась простой и приятной. Инструкции по монтажу и наладке написаны простым и понятным техническим английским языком.

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

Мне очень нравится SDR. Надеюсь, и вам тоже.



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru