Какая разница между var, let и const в JavaScript?

Рассмотрим три ключевых отличия между объявлениями переменных в JavaScript:

  1. Область видимости (Scope)

var: Имеет функциональную область видимости. Переменная доступна внутри функции, где объявлена, или глобально, если объявлена вне функции.
let: Имеет блочную область видимости. Переменная доступна только внутри блока {}, где объявлена.
const: Также имеет блочную область видимости, как и let.

  1. Поднятие (Hoisting)

var: Поднимается. Может использоваться до объявления, но значение будет undefined.
let: Тоже поднимается, но нельзя использовать до объявления (временная мертвая зона).
const: Аналогично let, существует временная мертвая зона.

  1. Переназначение и изменение

var: Можно переназначать и изменять.
let: Можно изменять, но нельзя переобъявлять в той же области видимости.
const: Нельзя переназначать, но если содержит объект, свойства объекта можно изменять.

Пример:

// var
var x = 1;
var x = 2; // Допустимо
if (true) {
  var x = 3; // Изменит переменную x даже за пределами блока
}
console.log(x); // 3

// let
let y = 1;
// let y = 2; // Ошибка: нельзя повторно объявить
if (true) {
  let y = 2; // Новая переменная, только для этого блока
}
console.log(y); // 1

// const
const z = 1;
// z = 2; // Ошибка: нельзя переназначить константу
const obj = { value: 1 };
obj.value = 2; // Допустимо: изменяем свойство, а не саму константу
console.log(obj.value); // 2

Как удалить дубликаты чисел из массива?

В JavaScript

Использование Set (самый простой способ):

const array = [1, 2, 3, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
// Результат: [1, 2, 3, 4, 5]

Использование filter() и indexOf():

const array = [1, 2, 3, 3, 4, 4, 5];
const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
// Результат: [1, 2, 3, 4, 5]

Использование reduce():

const array = [1, 2, 3, 3, 4, 4, 5];
const uniqueArray = array.reduce((unique, item) => 
  unique.includes(item) ? unique : [...unique, item], 
[]);
// Результат: [1, 2, 3, 4, 5]

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

В PHP

Использование array_unique() (самый простой способ):

$array = [1, 2, 3, 3, 4, 4, 5];
$uniqueArray = array_unique($array);
// Результат: [1, 2, 3, 4, 5]

Использование array_keys() и array_flip():

$array = [1, 2, 3, 3, 4, 4, 5];
$uniqueArray = array_keys(array_flip($array));
// Результат: [1, 2, 3, 4, 5]

Использование цикла foreach с проверкой:

$array = [1, 2, 3, 3, 4, 4, 5];
$uniqueArray = [];
foreach($array as $value) {
    if (!in_array($value, $uniqueArray)) {
        $uniqueArray[] = $value;
    }
}
// Результат: [1, 2, 3, 4, 5]

Рекомендации по выбору метода:

  • array_unique() — самый простой и читаемый способ, подходит для большинства случаев
  • array_flip() + array_keys() — может быть более эффективным для больших массивов
  • Цикл foreach — полезен, когда вам нужна дополнительная логика при удалении дубликатов

Если вы хотите сохранить исходные ключи массива, используйте:

$uniqueArray = array_unique($array, SORT_REGULAR);

После удаления дубликатов может быть полезно «перезагрузить» индексы массива с помощью array_values():

$array = [1, 2, 3, 3, 4, 4, 5];
$uniqueArray = array_values(array_unique($array));
// Результат: [1, 2, 3, 4, 5] с последовательными индексами

Как добавить класс к корневому элементу html с помощью Javascript?

Добавление класса ‘js’ к корневому HTML-элементу обычно используется для того чтобы убедиться в поддержки JavaScript (чтобы можно было менять стили и поведение элементов в зависимости от поддержки JS)

Чистый JavaScript (ES6+):

document.documentElement.classList.add('js');

Vanilla JavaScript (более старый синтаксис):

document.documentElement.className += ' js';

С проверкой поддержки classList:

if (document.documentElement.classList) {
    document.documentElement.classList.add('js');
}

Универсальный вариант, который:

  • Проверяет поддержку classList
  • Если поддерживает — использует classList.add()
  • Если не поддерживает — использует старый метод конкатенации className
  • Добавляет пробел перед ‘js’, только если className уже не пустой
if ('classList' in document.documentElement) {
    document.documentElement.classList.add('js');
} else {
    document.documentElement.className += (document.documentElement.className ? ' ' : '') + 'js';
}

Эти варианты полностью заменяют jQuery-версию и добавляют класс ‘js’ к корневому HTML-элементу, что обычно используется для индикации поддержки JavaScript в браузере.

Альтернативный вариант, который дополнительно удаляет класс ‘no-js’, если он был установлен заранее.

document.documentElement.className = 
    document.documentElement.className.replace(/\bno-js\b/, '') + ' js';

Основные преимущества:

  • Не требуется подключение jQuery
  • Нативный JavaScript
  • Меньше кода
  • Лучшая производительность

Как выбрать соседний элемент после первого дочернего в JavaScript?

Чтобы выбрать соседний элемент после первого дочернего элемента в JavaScript, вы можете использовать свойство nextElementSibling. Вот пример кода:

// Получаем родительский элемент по классу
const parentElement = document.querySelector('.parent-class');

// Получаем первый дочерний элемент
const firstChild = parentElement.firstElementChild;

// Получаем соседний элемент после первого дочернего
const nextSibling = firstChild.nextElementSibling;

// Выводим результат в консоль
console.log(nextSibling);

Объяснение:

  • parentElement.firstElementChild возвращает первый дочерний элемент родителя.
  • firstChild.nextElementSibling возвращает следующий элемент-сосед после первого дочернего.

Таким образом, вы можете легко получить соседний элемент после первого дочернего. Замените '.parent-class' на ваш класс родителя.

Как сделать выбор элементов из DOM на JavaScript?

В этой статье проведём обзор способов выбора элементов на чистом JavaScript.

Базовые методы querySelector:

// Выбор первого совпадения
const element1 = document.querySelector('.class');      // по классу
const element2 = document.querySelector('#id');         // по ID
const element3 = document.querySelector('div');         // по тегу
const element4 = document.querySelector('[data-attr]'); // по атрибуту

// Выбор ВСЕХ совпадений
const elements1 = document.querySelectorAll('.class');

Классические методы DOM:

// По ID
const elementById = document.getElementById('myId');

// По имени класса
const elementsByClass = document.getElementsByClassName('myClass');

// По тегу
const elementsByTag = document.getElementsByTagName('div');

Вложенные селекторы:

// Nested селекторы
const nestedElement = document.querySelector('.parent .child');

// Прямой потомок
const directChild = document.querySelector('.parent > .child');

// Nth элемент
const nthElement = document.querySelector('.list li:nth-child(2)');

Методы с деструктуризацией:

// Первый и последний элемент
const [firstElement] = document.querySelectorAll('.items');
const [lastElement] = [...document.querySelectorAll('.items')].reverse();

Поиск ближайших элементов:

// Родительский элемент
const parentElement = currentElement.parentElement;

// Дочерние элементы
const childElements = currentElement.children;

// Соседние элементы
const nextSibling = currentElement.nextElementSibling;
const prevSibling = currentElement.previousElementSibling;

Безопасный выбор с проверкой:

Пример реализации через функцию:

function safeSelect(selector) {
    const element = document.querySelector(selector);
    
    if (!element) {
        console.warn(`Элемент с селектором ${selector} не найден`);
        return null;
    }
    
    return element;
}

? Рекомендации:

  • querySelector медленнее, чем getElementById
  • Используйте const для неизменяемых селекторов
  • Кэшируйте часто используемые элементы
  • Проверяйте существование элемента перед работой
  • Для производительности используйте делегирование событий
  • Учтите, что querySelectorAll() возвращает NodeList
  • Всегда проверяйте поддержку браузеров

? Производительность (от быстрого к медленному):

  1. getElementById() — Самый быстрый
  2. getElementsByClassName()
  3. getElementsByTagName()
  4. querySelector()
  5. querySelectorAll()

Как получить текущий год на JavaScript?

const currentYear = new Date().getFullYear().toString();
  • new Date() — создаём объект который хранит в себе текущий момент времени
  • getFullYear() — выдёргиваем из объекта год указанной даты. Так как мы создавали экземпляр Date() без аргументов, то год будет соответствовать текущему моменту, а следовательно быть актуальным
  • toString() — возвращаем тип данных строку.

Ну вот и всё! Теперь в константе currentYear будет находится текущий год

пример

Как сделать маску (шаблон) в input при вводе номера телефона для WooCommerce?

Дано: Интернет-магазин на WordPress+WooCommerce

Задача: На странице оформления заказа в поле для ввода номера телефона задать предустановленную схему номера в формате пригодному для русскоязычного сегмента

+7 xxx-xxx-xx-xx (или вариант через 8-ку)

Решение: Выполнять поставленную задачу мы будем на JavaScript, в первом случае — на базе библиотеки jQuery, которая поставляется с WordPress и доступна «из коробки» и во-втором — решение на нативном JS (то есть без зависимостей от сторонних библиотек, в частности jQuery)

Мне встретилось в интернете уже готовое решение, поэтому придерживаясь принципа: «Зачем писать то, что уже написано», я взял его за пример. Единственное, нужно проинспектировать нашу форму отправления заказа и выяснить идентификатор нашего «инпута» для указания номера телефона.

Давайте взглянем на форму при оформлении заказа в WooCommerce.

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

// Hook in
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );

// Our hooked in function - $fields is passed via the filter!
function custom_override_checkout_fields( $fields ) {
    $fields['order']['order_comments']['placeholder'] = 'My new placeholder';
    $fields['order']['order_comments']['label']       = 'My new label';
    return $fields;
}

	

В примерах из документации можно найти все названия полей. В нашем случае идентификатор нашего поля billing_phone (смотреть рисунок выше, с исследованием элемента) и он относится к группе Billing (эта информация получена тут). Поэтому наш код будет выглядеть следующим образом:

// Hook in
add_filter( 'woocommerce_checkout_fields', 'poet_override_checkout_fields' );
   
function poet_override_checkout_fields( $fields ) {
    $fields['billing']['billing_phone']['placeholder'] = '+7 (123) 456-78-90';
    $fields['billing']['billing_phone']['maxlength'] = 10; // ожидаем ввода 10 основных символов номера телефона
    return $fields;
}

Выполнение данного кода привнесло в поле нашей формы подсказку-заполнитель

placeholder

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

В WooCommerce для этого есть удобная функция wc_enqueue_js(), которая добавляет пользовательский код в вывод в нижней части сайта перед закрывающим тегом </body>. Вывод справедлив если страница каким то образом относится к WooCommerce. То есть на одиночной странице записи этого вывода не будет.

Вывод скрипта

Найти функцию можно от корня вашего сайта в wp-contents/plugins/woocommerce/includes/wc-core-functions.php

plugins/woocommerce/includes/wc-core-functions.php

Она позволяет добавлять в глобальную переменную (global $wc_queued_js) произвольный код. А её вызов можно увидеть чуть ниже по коду:

Обратите внимание, что обёртка для jQuery уже присутствует и можно просто пользоваться её методами. И вот изящное решение, от Rodolfo Melogli можно увидеть ниже.

/**
 * @snippet       Phone Mask @ WooCommerce Checkout
 * @how-to        Get CustomizeWoo.com FREE
 * @author        Rodolfo Melogli
 * @compatible    WooCommerce 5
 * @community     https://businessbloomer.com/club/
 */
 
add_filter( 'woocommerce_checkout_fields', 'bbloomer_checkout_phone_us_format' );
   
function bbloomer_checkout_phone_us_format( $fields ) {
    $fields['billing']['billing_phone']['placeholder'] = '123-456-7890';
    $fields['billing']['billing_phone']['maxlength'] = 12; // 123-456-7890 is 12 chars long
    return $fields;
}
 
add_action( 'woocommerce_after_checkout_form', 'bbloomer_phone_mask_us' );
 
function bbloomer_phone_mask_us() {
   wc_enqueue_js( "
      $('#billing_phone')
      .keydown(function(e) {
         var key = e.which || e.charCode || e.keyCode || 0;
         var phone = $(this);       
         if (key !== 8 && key !== 9) {
           if (phone.val().length === 3) {
            phone.val(phone.val() + '-'); // add dash after char #3
           }
           if (phone.val().length === 7) {
            phone.val(phone.val() + '-'); // add dash after char #7
           }
         }
         return (key == 8 ||
           key == 9 ||
           key == 46 ||
           (key >= 48 && key <= 57) ||
           (key >= 96 && key <= 105));
        });
         
   " );
}

Оно рабочее. Но зависит от библиотеки jQuery и «заточено» под американский стандарт, так сказать. Так же данное решение не сработает на страницах, которые не относятся к WooCommerce (справедливо для данного кода выше). Ещё мне не нравится пример заполнителя, пользовательский опыт говорит об обратном, вряд ли все будут придерживаться строгого заполнения.

Можно конечно доработать и всё исправить (в случае с примером выше основанным на jQuery), но мне больше понравилась библиотека Cleave.js и её аддон, который заточен под русские стандарты при вводе номера телефона, учитывая с «восьмёрки» или с «плюс, семёрки» начинает набирать пользователь номер.

Поэтому в данном примере мы подключим саму библиотеку и её аддон для страны с кодом RU (в целях уменьшения размера подключаемого кода) и проверим его работу в действии.

К тому же, подключать мы будет стандартной функцией WP для того чтобы решение было универсальным и могло использоваться на разных страницах к разным формам, в том числе популярного плагина Contact Form 7.

Важное примечание! Библиотека с минувшей осени (обращение автора от 25.11.23г) уже не поддерживается по ряду причин, которые описаны в обращении. Она переписана автором на TypeScript и ES6. Пример в статье просто иллюстрирует решение задачи. В целом рекомендуется эта библиотека.

Первым делом задействуем старую добрую функцию WP wp_enqueue_script(), которая будет подключать скрипт библиотеки Cleave.js и её аддон ориентированный на RU. Проверка ! is_checkout() гарантирует подключение только на странице оформления заказа и исключает подключение на прочих страницах

/**
 * Подключаем библиотеку Cleave.js
 * для форматирования содержимого <input/> при вводе текста
 */
function poet_load_scripts() {

	if ( ! is_checkout() ) {
		return;
	}

	wp_enqueue_script(
		'cleave',
		'https://cdn.jsdelivr.net/npm/cleave.js@1.6.0/dist/cleave.min.js',
		array(),
		'1.6.0',
		array(
			'strategy'  => 'defer',
			'in_footer' => true
		)
	);

	wp_enqueue_script(
		'cleave-phone',
		'https://cdn.jsdelivr.net/npm/cleave.js@1.6.0/dist/addons/cleave-phone.ru.js',
		array( 'cleave' ),
		'1.6.0',
		array(
			'strategy'  => 'defer',
			'in_footer' => true
		)
	);

	wp_add_inline_script( 'cleave-phone', "
		const cleave = new Cleave('#billing_phone', {
		    phone: true,
		    phoneRegionCode: 'ru',
		    delimiter: '-'
		});
	" );
	
}

add_action( 'wp_enqueue_scripts', 'poet_load_scripts' );

wp_add_inline_script( ‘cleave-phone’… инициализирует (вызывает функционал) на нужном нам поле с нужными параметрами, а частности разделителем в виде чёрточки.

Результатом выполнения кода будет предопределённая маска, которая не позволит пользователю вводит через чур много символов и будет учитывать начало ввода «+7» или «8»

ввод с +7
ввод с 8

Как подключить маску на других страницах, к другим формам?

Всё очень просто. Решение универсальное в пределах WP.

  1. У нужной формы необходимо понять, как «зацепиться» за нужное поле, или «по айди» или «по классу»
  2. Узнать «айди» страницы, для того чтобы добавить её в условие проверки, дабы не вызывать где не попадя 🙂

Например, у нас есть форма для обратной связи на странице контактов. Первым делом исследуем нужное поле:

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

/**
 * Подключаем библиотеку Cleave.js
 * для форматирования содержимого <input/> при вводе текста
 */
function poet_load_scripts() {

	if ( ! is_checkout() && ! is_page( 53 ) ) {
		return;
	}

	wp_enqueue_script(
		'cleave',
		'https://cdn.jsdelivr.net/npm/cleave.js@1.6.0/dist/cleave.min.js',
		array(),
		'1.6.0',
		array(
			'strategy'  => 'defer',
			'in_footer' => true
		)
	);

	wp_enqueue_script(
		'cleave-phone',
		'https://cdn.jsdelivr.net/npm/cleave.js@1.6.0/dist/addons/cleave-phone.ru.js',
		array( 'cleave' ),
		'1.6.0',
		array(
			'strategy'  => 'defer',
			'in_footer' => true
		)
	);

	wp_add_inline_script( 'cleave-phone', "
		const cleave = new Cleave('#billing_phone, .wpcf7-tel', {
		    phone: true,
		    phoneRegionCode: 'ru',
		    delimiter: '-'
		});
	" );

}

add_action( 'wp_enqueue_scripts', 'poet_load_scripts' );

Из примера видно, что мы просто, через запятую, добавили новый селектор по CSS классу. Теперь наша маска применяется к полу телефона созданной плагином Contact Form 7

маска на CF7

Как сделать копирование артикула товара по клику в WooCommerce?

Задача: для удобства сотрудников сделать копирование артикула по клику.

Реализовывать мы будем это на стороне клиента (браузера) при помощи JavaScript используя нужные нам методы при работе с DOM.

Давайте рассуждать. У нас есть артикул, значение которого выводится в строчном элементе <span>, благодаря атрибуту class мы можем «зацепиться за него» — это, условно говоря, его имя.

sku

Так как нам нужно только его значение, отметим для себя элемент span с классом «sku»

Нам нужно его выдернуть из DOM (Объектная Модель Документа) и в этом нам поможет метод querySelector()

Согласитесь, что нашего элемента может на странице и не существовать, поэтому нужно проверить его существование. Если совпадений не будет найдено, то наш метод вернёт значение null.

Давайте реализуем эту логику в виде кода.

const sku = document.querySelector(".sku");

if(sku !== null){
    console.log(sku);
}

Если вы всё сделали правильно, то непременно увидите в консоли этот выбранный элемент:

Image 00000089 - Как сделать копирование артикула товара по клику в WooCommerce?

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

addEventListener() который будет обрабатывать событие клика по этому элементу

const sku = document.querySelector(".sku");

if (sku !== null) {
    sku.addEventListener("click", (e) => {

    });
}

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

Функцию назовём copyWooSKU()

Что должно находиться в функции? Очевидно, что нам нужно получить (зачитать) значение артикула, а затем его каким то чудесным образом скопировать в буфер обмена пользователя. Реализовывать мы это будем через современный API Async Clipboard (на замену синхронного метода document.execCommand())

Clipboard API предоставляет возможность реагировать на команды буфера обмена (вырезать, копировать и вставить), а также выполнять асинхронные чтение/запись в системный буфер обмена.

https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API

Итак, нам понадобится:

  1. Объект Navigator
  2. Свойство этого объекта .clipboard — которое используется для чтения и записи содержимого буфера обмена
  3. И метод writeText() — для записи текста в буфер обмена
async function copyWooSKU(txt) {
    try {
        await navigator.clipboard.writeText(txt);
    } catch (error) {
        console.error("Ошибочка вышла: ", error);
    }
}

const sku = document.querySelector(".sku");

if (sku !== null) {
    sku.addEventListener("click", (e) => {
        copyWooSKU(e.target.innerText);
    });
}

Ну вот и всё! Это должно работать 🙂

Обращу внимание, что стрелочные функции не имеют собственного контекста this. То есть в стрелочных функциях

e.currentTarget != this

Комментарий к коду:

  • async перед функцией, потому что в теле функции есть await и вернётся промис
  • try / catch для обработки исключения
  • e.target.innerText — это извлечение значения свойства (текста в нашем случае) элемента по которому кликаем

Как и многие новые API, API буфера обмена поддерживается только для страниц, обслуживаемых по протоколу HTTPS. Чтобы предотвратить злоупотребления, доступ к буферу обмена разрешен только тогда, когда страница является активной вкладкой. Страницы на активных вкладках могут записывать в буфер обмена без запроса разрешения, но чтение из буфера обмена всегда требует разрешения.

Учтите, что метод writeText() ожидает безопасного контекста HTTPS и если ваше соединение осуществляется по схеме протокола HTTP то может вылететь как раз таки ошибочка 🙂

Рекомендуется для чтения:

Разблокирование доступа к буферу обмена

Как добавить уведомление?

Очевидно, что в момент клика непонятно, произошло копирование или нет. Если улучшать наш код для пользователя, то по доброму необходимо сделать уведомление, чтобы клиенту было понятно, что копирование осуществилось.

Давайте рассуждать. Нам нужно методами JavaScript создать элемент в котором будет содержаться текст нашего уведомления. После вызова функции необходимо отобразить это уведомление и спустя пару секунд скрыть. Весь наш код теперь выглядит следующим образом:



async function copyWooSKU(txt) {
    try {
        await navigator.clipboard.writeText(txt);
    } catch (error) {
        console.error("Ошибочка вышла: ", error);
    }
}

const sku = document.querySelector(".sku");

if (sku !== null) {
    sku.addEventListener("click", (e) => {

        e.preventDefault();
        copyWooSKU(e.target.innerText);

        const myNotice = document.createElement("span");
        myNotice.style.color = "red";
        myNotice.innerText = "Скопировано";

        // селектор родителя
        if (sku.parentElement) {
          const parentElement = sku.parentElement;
          parentElement.appendChild(myNotice);

          window.setTimeout(function () {
            parentElement.removeChild(myNotice);
          }, 2000);
        } // TODO else 

    });
}

В этом коде использован следующий JavaScript:

  • Метод preventDefault () — добавил на всякий случай для отмены обработки клика (до нашего обработчика)
  • document.createElement() — метод для создания элемента
  • style.color — присваиваем CSS-свойство для изменения цвета шрифта
  • Свойство .innerText — позволяет задавать текст нашему созданному элементу
  • Метод .appendChild() добавляет узел в конец списка дочерних элементов указанного родительского узла. Будьте внимательны! Селектор родительского элемента у вас может отличаться.
  • Функция .setTimeout() — для задержки выполнения
  • Метод .removeChild() удаляет дочерний элемент из DOM.
  • parentElement это родительский элемент текущего узла.

Возможно некоторые свойства, такие как : target, innerText станут понятнее, если мы на них взглянем. Для этого в этом отрезке кода, где мы отслеживаем действие по клику, можно вывести в консоль это событие клика мышкой:


if (sku !== null) {
    sku.addEventListener("click", (e) => 

        console.log(e);

    });
}

Мы увидим объект, и именно в нём и находятся наши свойства:

свойство таргет

А вот свойство со значением артикула, к которому мы обращаемся

Image 00000143 - Как сделать копирование артикула товара по клику в WooCommerce?

Поиграть с рабочим кодом можно тут