Введение в JavaScript символы
В ES2015 введен новый примитивный тип с именем Symbol
. Это уникальный и неизменный идентификатор. Как только вы создали его, его нельзя скопировать.
Каждый раз, когда вы создаете новый символ, он уникален. Символы в основном используются для уникальных идентификаторов в объекте. Это единственная цель символа.
Есть также специальные символы, которые мы можем использовать для реализации различных операций или переопределения поведения некоторых операций по умолчанию.
Определение символов
Существуют некоторые статические свойства и собственные методы, которые предоставляют глобальный реестр символов. Это похоже на встроенный объект, но у него нет конструктора, поэтому мы не можем написать, new Symbol
чтобы создать объект символа с ключевым словом new
.
Чтобы создать новые символы, мы можем написать:
const fooSymbol = Symbol('foo')
Обратите внимание, что каждый раз, когда мы вызываем функцию Symbol
, мы получаем новый символ, поэтому, если мы напишем:
Symbol('sym') === Symbol('sym')
Выражение выше вернет false
. Это потому, что каждый символ уникален.
Встроенные методы
Существуют встроенные символы, которые используются в качестве идентификаторов для различных методов и значений. Методы с некоторыми символами вызываются, когда используются некоторые операторы.
Symbol.hasInstance
Это метод, который проверяет, является ли объект экземпляром данного конструктора. Этот метод вызывается, когда вызывается оператор instanceof
.
Мы можем переопределить метод Symbol.hasInstance
следующим образом:
class Foo {
static [Symbol.hasInstance](instance) {
return typeof instance.foo != 'undefined';
}
}
console.log({ foo: 'abc' } instanceof Foo);
В приведенном выше коде мы определили, что объект является экземпляром класса Foo
, если есть значение для свойства foo
. Следовательно, { foo: ‘abc’ } instanceof Foo
должен вернуть true
, поскольку для него установлено свойство.
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
является логическим значением, которое указывает, должен ли объект быть сведен в массив методом concat
.
Мы можем использовать его как в следующем коде:
const arr1 = ['a', 'b', 'c'];
const arr2 = [true, false];
let arr3 = arr1.concat(arr2);
console.log(arr3);
arr2[Symbol.isConcatSpreadable] = false;
arr3 = arr1.concat(arr2);
console.log(arr3);
Первый console.log
должен вывести:
["a", "b", "c", true, false]
А второй должен вывести:
["a", "b", "c", Array(2)]
Это связано с тем, что перед вторым вызовом concat
мы устанавливаем arr2[Symbol.isConcatSpreadable]
значение в false
, которое предотвращает распространение содержимого arr2
в новый массив, возвращаемый методом concat
.
Symbol.iterator
Этот метод вызывается, когда мы хотим вернуть итератор для оператора распространения или цикла for...of
. Он вызывается при запуске цикла for...of
.
Например, учитывая, что у нас есть следующий код:
const obj = {
0: 1,
1: 2,
2: 3
};
console.log(obj[0]);
Если вы попытаетесь перебрать массив с помощью цикла for...of
или функции forEach
или попытаться использовать оператор распространения с ним, пример с объектом obj
приведет к ошибке, поскольку это не повторяемый объект.
Мы можем сделать его итеративным, добавив в него функцию генератора с символом Symbol.iterator
, как показано в следующем коде:
const obj = {
0: 1,
1: 2,
2: 3,
[Symbol.iterator]: function*() {
for (let prop in this) {
yield this[prop];
}
}
};
Затем, когда мы повторяем объект obj
с циклом for...of
, как показано ниже:
for (let num of obj) {
console.log(num);
}
Мы возвращаем записи нового объекта obj
, которые мы сделали итеративными.
Оператор распространения также будет работать. Если у нас есть следующий код:
console.log([...obj]);
Получим [1, 2, 3]
Symbol.match
Логическое свойство, являющееся частью экземпляра регулярного выражения, которое заменяет соответствующую подстроку строки. Он вызывается методом replace
.
Например, мы можем использовать его , чтобы позволить нам вызвать методы startsWith
и endsWith
с обычными строками:
const regexpFoo = /foo/;
regexpFoo[Symbol.match] = false;
console.log('/foo/'.startsWith(regexpFoo));
console.log('/baz/'.endsWith(regexpFoo));
Важной частью является то, что мы установили значение regexpFoo[Symbol.match]
как false
, которое указывает, что строка, с которой мы вызвали startsWith
и endsWith
не являются объектами регулярного выражения, поскольку проверка isRegExp
укажет, что строки '/foo/'
и '/baz/'
не являются объектами регулярного выражения.
В противном случае они будут считаться объектами регулярного выражения, даже если они являются строками, и мы получим следующую ошибку:
Uncaught TypeError: First argument to String.prototype.startsWith must not be a regular expression
Symbol.replace
Метод регулярного выражения, который заменяет совпавшие подстроки строки. Вызывается методом String.prototype.replace
.
Мы можем создать наш собственный метод replace
для нашего объекта следующим образом, используя Symbol.replace
в качестве идентификатора метода:
class Replacer {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return this.value.replace(string, this.value);
}
}
console.log('foo'.replace(new Replacer('bar')));
У класса Replacer
есть конструктор, который принимает значение, которое можно использовать для замены текущего экземпляра строки.
Когда мы запускаем последнюю строку, мы должны получить ‘bar’
, поскольку она имеет значение 'foo'
, и мы вызываем метод replace
, чтобы заменить себя тем, что мы передали в конструктор Replacer
.
Symbol.search
Метод регулярного выражения, который возвращает индекс в строке, соответствующей регулярному выражению. Вызывается по методу String.prototype.search
.
Например, мы можем реализовать наш собственный метод Symbol.search
, как в следующем коде:
class Searcher {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value) != -1;
}
}
console.log('foobar'.search(new Searcher('bar')));
Мы получаем true
из console.log
, так как 'bar'
находится в ‘foobar'
. С другой стороны, если мы выполним:
console.log('foobar'.search(new Searcher('baz')));
Тогда мы получаем значение, false
так как ‘baz’
нет в строке 'foobar'
.
Symbol.species
Свойство, в качестве значения которого используется функция, являющаяся функцией конструктора, которая используется для создания производных объектов.
Symbol.split
Метод, который является частью объекта регулярного выражения, который разбивает строку в соответствии с индексами, которые соответствуют регулярному выражению. Он вызывается методом строки split
.
Symbol.toPrimitive
Метод, который преобразует объект в соответствующее примитивное значение. Он вызывается, когда используется унарный оператор +
или конвертирует объект в примитивную строку.
Например, мы можем написать наш собственный метод Symbol.toPrimitive
для преобразования различных значений в примитивное значение:
let obj = {
[Symbol.toPrimitive](hint) {
if (hint == 'number') {
return 10;
}
if (hint == 'string') {
return 'hello';
}
if (hint == 'true') {
return true;
}
if (hint == 'false') {
return false;
}
return true;
}
};
console.log(+obj);
console.log(`${obj}`);
console.log(!!obj);
console.log(!obj);
Тогда мы получим:
10
hello
true
false
Symbol.toString
Метод, который возвращает строковое представление объекта. Он вызывается всякий раз, когда вызывается метод объекта toString
.
Symbol.unscopables
Объект, чьи собственные имена свойств являются именами свойств, которые исключены из привязок среды связанных объектов with
.
Вывод
Символы - это новый тип данных, который был представлен в ES6. Они используются для идентификации свойств объекта. Они неизменны, и каждый экземпляр считается разным, даже если они имеют одинаковое содержание.
Мы можем реализовать различные методы, идентифицируемые специальными символами, для реализации определенных операций, таких как instanceof
, преобразование объектов в примитивные значения и поиск подстрок в нашем собственном коде.