Что такое метка в JavaScript и как она работает?

Рейтинг: 6Ответов: 1Опубликовано: 13.04.2023

Не так давно наткнулся на такой кусок кода:

javascript:console.log('Пример какого-то текста')

Случайно изменив название javascript я заметил что код, как работал, так и работает, вот несколько примеров использования:

привет: console.log(42)

$$$ :console.info('Некоторая информация')

abc123:(4 + 4) / 2 // Не обязательно выводить в консоль

abc:def:ghi:69 // Можно писать множество раз

window:window

Как вы видите не обязательно писать javascript: с левой части чтобы код запустился. Также я заметил что значение, которое пишется слева, должно иметь валидное название как у переменных. То-есть если оно будет начинаться с числа или будет содержать какой-то символ, который не разрешён для использования в переменных, то будет ошибка (то-есть название должно быть валидным идентификатором). А справа от : должно быть какое-то воспроизводимое выражение.

Примеры с кодом, где получается ошибка:

123:456
// Получаем ошибку:
// Uncaught SyntaxError: unexpected token: ':'

@o@:console.log(42)
// Получаем ошибку:
// Uncaught SyntaxError: illegal character

'window':69
// Получаем ошибку:
// Uncaught SyntaxError: unexpected token: ':'

window:window:window // Почему получается ошибка?
// Получаем ошибку:
// Uncaught SyntaxError: duplicate label

Так какое же предназначение у такого синтаксиса? Какое у него есть применение? Какие есть неправильные и правильные способы применения? Какие могут произойти ошибки в при использовании меток, почему они происходят?

Ответы

▲ 7Принят

Как оказалось это так называемые метки (label'ы). Они используются вместе с операторами break и continue. Сами метки являются неким аналогом goto из других ЯП.

Синтаксис

label :
  statement
  • label (метка)

Как было отмечено в вопросе, слово использованное для метки должно быть валидным словом для переменной, то есть быть валидным идентификатором. Также слово не должно быть ключевым словом или зарезервированным словом. Стоит также отметить что идентификатор метки должен быть уникальным для своего блока кода, иначе будет ошибка Uncaught SyntaxError: duplicate label.

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

for:console.log('hello') // Uncaught SyntaxError: missing ( after for

return:console.log('world') // Uncaught SyntaxError: return not in function

void:console.log(42) // Uncaught SyntaxError: expected expression, got ':'

Пример неправильного кода с использованием зарезервированного слова.

enum:console.log('hello') // Uncaught SyntaxError: expected expression, got reserved word 'enum'

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

label1:
for (let i = 0; i < 5; i++) {
  label1: // Uncaught SyntaxError: duplicate label
  for (let j = 0; j < 10; j++) {
    console.log('inner', j)
  }
  console.log('outer', i)
}

При этом если они расположены отдельно, то их применение разрешено.

label1:
  for (let i = 0; i < 5; i++) {
    console.log(i)
  }

label1:
  for (let i = 0; i < 10; i++) {
    console.log(i)
  }

  • statement (выражение)

Это JavaScirpt выражение. break может быть использовано в любом блочном выражении, а вот continue только в циклах (Для примера: for, while, for...in и т.п.).

Пример неправильного кода с использованием continue вне цикла.

let i = 0

loop: {
  console.log(i)

  if(i < 10) {
    i++
    continue loop // SyntaxError: continue must be inside loop
  }
}

Внимание!

В строгом режиме использовать слово let в качестве идентификатора для метки запрещено.

'use strict';

let:
  console.log('hello')

Также стоит отметить что в строгом режиме нельзя использовать функции в качестве выражения. Об этом написано в документации ECMAScript 2015-го года (6-я версия ES).

Labelled Function Declarations

Prior to ECMAScript 2015, the specification of LabelledStatement did not allow for the association of a statement label with a FunctionDeclaration. However, a labelled FunctionDeclaration was an allowable extension for non-strict code and most browser-hosted ECMAScript implementations supported that extension. In ECMAScript 2015, the grammar productions for LabelledStatement permits use of FunctionDeclaration as a LabelledItem but 13.13.1 includes an Early Error rule that produces a Syntax Error if that occurs. For web browser compatibility, that rule is modified with the addition of the underlined text:

LabelledItem : FunctionDeclaration

  • It is a Syntax Error if any strict mode source code matches this rule.

Перевод:

Объявления отмеченных функций

В прошлой ECMAScript до 2015 года, в спецификации о LabelledStatement не было разрешено асоциировать выражение метки с FunctionDeclaration. Однако, отмеченный FunctionDeclaration был разрешённым расширением для не-строгого кода и в основном реализации ECMAScript замещённый в браузере поддерживают это расширение. В ECMAScript 2015, грамматические производства для LabelledStatement разрешают использование FunctionDeclaration как LabelledItem, но 13.13.1 включает раннюю ошибку (Early Error) для этого и вызывает синтаксическую ошибку (Syntax Error), если это имеет место быть. Для совместимости веб-браузером, это правило было модифицированно текущим подчёркнутым текстом:

LabelledItem : FunctionDeclaration

  • Это является синтаксической ошибкой (Syntax Error), если любой исходный код в строгом режим подходит под это правило.

'use strict';

label:
  function someFn() {
  }

Не простые функции, такие как функции генераторы и асинхронные функции не могут быть отмечены ни в строгом и ни в не-строгом коде. Об этом написано в MDN:

Non-plain functions, such as generator functions and async functions can neither be labeled in strict code, nor in non-strict code

label:
  function* someGenFn() { // SyntaxError: generator functions cannot be labelled
  }

Примеры

  • continue

Пропустить текущую итерацию ВЕРХНЕГО цикла если i и j равны одному.

loop1:
for (let i = 0; i < 3; i++) {
  loop2:
  for (let j = 0; j < 3; j++) {
    if(i === 1 && j === 1) {
      continue loop1
    }

    console.log('i =', i + '; j =', j)
  }
}

Заметьте! Пропало не только i = 1; j = 1, но также i = 1; j = 2 т.к. мы пропустили итерацию цикла for i. То-есть если бы мы использовали просто continue, то был бы пропуск только i = 1; j = 1 т.к. мы бы пропустили итерацию for j.

  • break

Прерываем цикл for i если i и j равны одному

loop1:
for (let i = 0; i < 3; i++) {
  loop2:
  for (let j = 0; j < 3; j++) {
    if(i === 1 && j === 1) {
      break loop1
    }

    console.log('i =', i + '; j =', j)
  }
}

Опять же, обратите внимание на то что мы прерываем цикл for i, а не for j. Это значит что если бы мы использовали просто break, прервался бы только цикл for j.

Как уже было упомянуто, break можно использовать не только в циклах, а ещё и в блоках, вот пример:

example: {
  console.log('Hello')

  break example

  console.log('This is secret message!')
}

console.log('World!')

Под конец также стоит упомянуть почему работают те выражения которые написаны в вопросе. Всё потому что это так называемые expession statement'ы и они разрешены для использования в качестве statement (см. синтаксис). Именно поэтому мы можем там написать даже математическое выражение и ничего страшного в этом не будет.

let x = 0
let obj = {
  x: 42,
}

// В expression statements входят:

label:console.log(2 + 2) // Вызовы функций
label:`hello world` // Шаблонные литералы
label:x = 2 // Операции присваивания
label:4 * 4 // Операции по уменьшению/увеличению
label:4 + 4 // Операции по уменьшению/увеличению (x2)
label:delete obj.x // Удаление
// label:import { CONSTANT } from './my_constant.js' // Импортирование (здесь не получится показать)

function* genFn() {
  label:yield x // yield и yield*
}

Источники

Найденный материал в процессе поиска на Stack Overflow (английском)