- Оригинальный адрес:Metaprogramming in ES6: Symbols and why they're awesome
- Оригинальный автор:Keith Cirkel
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:yoyoyohamapi
- Корректор:Usey95 IridescentMia
Вы слышали о ES6, верно? Это новая версия JavaScript, которая хорошо работает во многих отношениях. Всякий раз, когда я обнаруживаю потрясающую новую функцию в ES6, я начинаю хвалить своих коллег (но не все рады брать чужие обеденные перерывы).
Множество замечательных новых функций ES6 появилось благодаря новым инструментам метапрограммирования, которые внедряют низкоуровневые хуки в механику кода. Сейчас очень мало статей о метапрограммировании ES6, поэтому я решил написать о них 3 поста в блоге (кстати, я слишком ленив, этот 90% полный пост в моем блоге — это три в моем ящике для черновиков). месяц, так как я сказал, что напишу статью,Здесь сделано больше):
Часть 1: Символы (эта статья),Часть II: Отражение,Часть третья: прокси
метапрограммирование
Во-первых, давайте кратко рассмотрим метапрограммирование и исследуем удивительный мир метапрограммирования. Метапрограммирование (в общих чертах) связано с базовой механикой языка, а не с высокоуровневыми абстракциями моделирования данных или бизнес-логики. Если программы можно описать как «создание программ», то метапрограммирование можно описать как «создание программ для создания программ». Возможно, вы неосознанно использовали метапрограммирование в своем повседневном программировании.
Метапрограммирование имеет несколько «поджанров», один из которыхГенерация кода, также называемыйeval
- JavaScript имеет возможности генерации кода с самого начала (JavaScript имеет это в ES1eval
, это даже раньшеtry
/catch
а такжеswitch
появляется). В настоящее время некоторые другие популярные языки программирования имеютгенерация кодахарактеристики.
Еще одним аспектом метапрограммирования является отражение, которое используется для обнаружения и настройки структуры и семантики вашего приложения. В JavaScript есть несколько инструментов для отражения. функция имеетFunction#name
,Function#length
,так же какFunction#bind
,Function#call
а такжеFunctin#apply
. Все методы, доступные в Object, также считаются отражением, например.Object.getOwnProperties
. В JavaScript также есть операторы отражения/самоанализа, такие какtypeof
,instancesof
так же какdelete
.
Рефлексия — действительно крутая часть метапрограммирования, потому что она позволяет вам изменить внутреннюю работу вашего приложения. Используя Ruby в качестве примера, вы можете объявить оператор как метод, тем самым переопределив то, как оператор работает для класса (это часто называют «перегрузкой оператора»):
class BoringClass
end
class CoolClass
def ==(other_object)
other_object.is_a? CoolClass
end
end
BoringClass.new == BoringClass.new #=> false
CoolClass.new == CoolClass.new #=> true!
По сравнению с другими языками, такими как Ruby или Python, функции метапрограммирования JavaScript сильно отстают, особенно учитывая отсутствие хороших инструментов, таких как перегрузка операторов, но ES6 начинает помогать JavaScript догонять язык метапрограммирования.
Метапрограммирование под ES6
ES6 предлагает три новых API:Symbol
,Reflect
,так же какProxy
. Когда вы впервые видите их, это немного сбивает с толку — все ли три API служат метапрограммированию? Если вы посмотрите на эти API по отдельности, нетрудно увидеть, что они имеют смысл:
- СимволыОтражение в реализации- Вы применяете символы к своим существующим классам и объектам, чтобы изменить их поведение.
- ОтразитьРефлексия через самоанализ- Часто используется для изучения очень низкоуровневой информации о коде.
- ПроксиОтражение через ходатайство- Обертывание объектов и перехват поведения объектов через ловушки.
Итак, как они работают? Как они стали полезными? В этой статье будут обсуждаться символы, а в следующих двух статьях будут обсуждаться отражение и прокси соответственно.
Символы - реализованное отражение
Символы — это новые примитивные типы. Это какNumber
,String
,а такжеBoolean
Такой же. Символы имеютSymbol
Функции используются для создания символов. В отличие от других типов-примитивов, символы Symbol не имеют литерального синтаксиса (например, String имеет''
) — единственный способ создать символ — использовать конструктор, но не конструктор.Symbol
функция:
Symbol(); // symbol
console.log(Symbol()); // 输出 "Symbol()" 至控制台
assert(typeof Symbol() === 'symbol')
new Symbol(); // TypeError: Symbol is not a constructor
Символы имеют встроенные возможности отладки
Символы могут указывать описание, что полезно при отладке, когда мы можем выводить больше полезной информации в консоль, наш опыт программирования будет более удобным:
console.log(Symbol('foo')); // 输出 "Symbol(foo)" 至控制台
assert(Symbol('foo').toString() === 'Symbol(foo)');
Символы могут использоваться в качестве ключей для объектов
Вот где символы действительно становятся интересными. Они тесно переплетаются с предметами. Символы можно использовать в качестве ключей объекта (аналогично строковым ключам), что означает, что вы можете назначить неограниченное количество уникальных символов объекту, и эти ключи гарантированно не будут конфликтовать с существующими строковыми ключами или с другим конфликтом ключей символов:
var myObj = {};
var fooSym = Symbol('foo');
var otherSym = Symbol('bar');
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(myObj.foo === 'bar');
assert(myObj[fooSym] === 'baz');
assert(myObj[otherSym] === 'bing');
Кроме того, ключ Symbols не может быть переданfor in
,for of
илиObject.getOwnPropertyNames
Получить - единственный способ получить ихObject.getOwnPropertySymbols
:
var fooSym = Symbol('foo');
var myObj = {};
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';
Object.keys(myObj); // -> [ 'foo' ]
Object.getOwnPropertyNames(myObj); // -> [ 'foo' ]
Object.getOwnPropertySymbols(myObj); // -> [ Symbol(foo) ]
assert(Object.getOwnPropertySymbols(myObj)[0] === fooSym);
Это означает, что символы могут предоставлять скрытый слой для объектов, помогая объектам достичь совершенно новой цели — свойства не повторяются, не могут быть получены с помощью существующих инструментов отражения и могут быть гарантированно не связаны с какими-либо существующими свойствами конфликта объектов.
Символы совершенно уникальны...
По умолчанию каждый вновь созданный символ имеет совершенно уникальное значение. Если вы создаете новый символ (var mysym = Symbol()
), внутри движка JavaScript создается совершенно новое значение. Если вы не сохраните ссылку на объект Symbol, вы не сможете его использовать. Это также означает, что два символа никогда не будут эквивалентны одному и тому же значению, даже если они имеют одинаковое описание:
assert.notEqual(Symbol(), Symbol());
assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.notEqual(Symbol('foo'), Symbol('bar'));
var foo1 = Symbol('foo');
var foo2 = Symbol('foo');
var object = {
[foo1]: 1,
[foo2]: 2,
};
assert(object[foo1] === 1);
assert(object[foo2] === 2);
...подождите, бывают исключения
Не волнуйтесь, есть небольшая оговорка — в JavaScript также есть другой способ создания символов, позволяющий легко получать и повторно использовать символы:Symbol.for()
.该方法在 “全局 Symbol 注册中心” 创建了一个 Symbol。额外注意的一点:这个注册中心也是跨域的,意味着 iframe 或者 service worker 中的 Symbol 会与当前 frame Symbol 相等:
assert.notEqual(Symbol('foo'), Symbol('foo'));
assert.equal(Symbol.for('foo'), Symbol.for('foo'));
// 不是唯一的:
var myObj = {};
var fooSym = Symbol.for('foo');
var otherSym = Symbol.for('foo');
myObj[fooSym] = 'baz';
myObj[otherSym] = 'bing';
assert(fooSym === otherSym);
assert(myObj[fooSym] === 'bing');
assert(myObj[otherSym] === 'bing');
// 跨域
iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
assert.notEqual(iframe.contentWindow.Symbol, Symbol);
assert(iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')); // true!
Глобальные символы усложняют ситуацию, но мы не хотим отказываться от хорошей стороны. Теперь некоторые из вас могут спросить: «Как мне узнать, какие символы уникальны, а какие нет?», на что я отвечу: «Не волнуйтесь, у нас еще естьSymbol.keyFor()
":
var localFooSymbol = Symbol('foo');
var globalFooSymbol = Symbol.for('foo');
assert(Symbol.keyFor(localFooSymbol) === undefined);
assert(Symbol.keyFor(globalFooSymbol) === 'foo');
assert(Symbol.for(Symbol.keyFor(globalFooSymbol)) === Symbol.for('foo'));
Что такое символы, а что нет?
Выше мы рассмотрели, что такое символы и как они работают, но, что более важно, нам нужно знать, для каких сценариев подходят символы, а для каких они не подходят.
-
Символы никогда не будут конфликтовать со строковым ключом объекта.. Эта функция позволяет Symbol хорошо работать при расширении существующего объекта (например, Symbol в качестве параметра функции) без явного воздействия на объект:
-
Символы не могут быть прочитаны существующими инструментами отражения. вам нужен новый метод
Object.getOwnPropertySymbols()
для доступа к символам на объектах, что делает символы пригодными для хранения информации, к которой вы не хотите, чтобы другие имели прямой доступ. использоватьObject.getOwnPropertySymbols()
это очень особый вариант использования, о котором обычный человек не знает. -
Символы не являются частными. Как обратная сторона обоюдоострого меча - все символы на объекте могут быть напрямую пропущены через
Object.getOwnPropertySymbols()
Get — это не способствует тому, чтобы мы использовали Symbols для хранения некоторых значений, которые действительно должны быть приватными. Не пытайтесь использовать символы для хранения значений в объектах, которые должны быть действительно приватными — символы всегда доступны. -
Перечислимые символы можно копировать в другие объекты., копия будет проходить примерно так
Object.assign
Новый метод готов. если вы попытаетесь позвонитьObject.assign(newObject, objectWithSymbols)
, и все итерируемые символы в качестве второго аргумента (objectWithSymbols
), эти символы будут скопированы в первый параметр (newObject
)начальство. Если вы не хотите, чтобы это произошло, используйтеObejct.defineProperty
чтобы сделать эти символы неитерируемыми. -
Символы не могут быть преобразованы в примитивные объекты. Если вы попытаетесь привести символ к объекту с примитивным значением (
+Symbol()
,-Symbol()
,Symbol() + 'foo'
), будет выдана ошибка. Это предотвращает случайное преобразование символов в строку, когда вы устанавливаете их как имена свойств объекта. (Аннотация: протестировано @Raoul1996, символ может быть преобразован в логическое значение (typeof !!Symbol('') === 'boolean'
), так что исходное авторское описание здесь несколько условно) -
Символы не всегда уникальны. упомянутый выше,
Symbol.for()
вернет вам неуникальный символ. Не думайте всегда, что Symbol уникален, если вы сами не можете гарантировать его уникальность. -
Символы — это не то же самое, что символы Ruby.. Оба имеют некоторые общие черты, такие как наличие реестра символов, но это все. Символы в JavaScript нельзя использовать как символы в Ruby.
Для чего действительно подходят символы?
На самом деле символы — это просто немного другой способ привязки свойств объекта — вы можете легко предоставить некоторые известные символы (такие как Symbols.iterator ) в качестве стандартных методов, какObject.prototype.hasOwnProperty
Этот метод присутствует во всех объектах, которые наследуются от Object (наследование от Object в основном означает, что все объекты имеютhasOwnProperty
Этот метод). На самом деле такие языки, как Python, предоставляют стандартные методы, подобные этому — в Python эквивалентSymbol.iterator
да__iter__
, ЭквивалентноSymbole.hasInstance
да__instancecheck__
, и я думаю__cmp__
также похож наSymbole.toPrimitive
. Подход Python может быть плохим, в то время как символы JavaScript предоставляют стандартные методы, не полагаясь на какой-либо причудливый синтаксис, и пользователи никогда не столкнутся с конфликтом с этими стандартными методами ни при каких обстоятельствах.
На мой взгляд, символы можно использовать в следующих двух сценариях:
1. Уникальное значение, используемое как заменяемая строка или целое число.
Предположим, у вас есть библиотека журналов, которая включает несколько уровней журналов, например.logger.levels.DEBUG
,logger.levels.INFO
,logger.levels.WARN
и т.п. В ES5 вы устанавливаете или определяете уровень строкой или целым числом:logger.levels.DEBUG === 'debug'
,logger.levels.DEBUG === 10
. Ни один из этих подходов не идеален, так как они не гарантируют уникальных уровней, но уникальность символов хорошо справляется со своей задачей! Сейчасlogger.levels
стал:
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');
2. Как место для размещения метаданных в объекте
Вы также можете использовать символы для хранения свойств метаинформации, которые менее важны для реального объекта. Думайте об этом как о еще одном уровне неитерируемости (в конце концов, неитерируемые ключи все равно будут появляться вObject.getOwnProperties
середина). Давайте создадим сплошной класс коллекции и добавим в него ссылку на размер, чтобы получить метаинформацию о размере коллекции, которая не выставляется внешнему миру с помощью символов (только помните,Символы не являются частными-- и используйте символы только в том случае, если вам все равно, что остальная часть приложения изменяет свойство символов):
var size = Symbol('size');
class Collection {
constructor() {
this[size] = 0;
}
add(item) {
this[this[size]] = item;
this[size]++;
}
static sizeOf(instance) {
return instance[size];
}
}
var x = new Collection();
assert(Collection.sizeOf(x) === 0);
x.add('foo');
assert(Collection.sizeOf(x) === 1);
assert.deepEqual(Object.keys(x), ['0']);
assert.deepEqual(Object.getOwnPropertyNames(x), ['0']);
assert.deepEqual(Object.getOwnPropertySymbols(x), [size]);
3. Предоставьте разработчикам возможность добавлять хуки к объектам в API.
Это может показаться немного странным, но вы можете проявить терпение и позволить мне объяснить. Предположим, у нас естьconsole.log
служебная функция стиля - эта функция принимаетЛюбыеобъект и вывести его на консоль. У него есть собственный механизм принятия решения о том, как отображать объекты в консоли, но вы, как разработчик, использующий API, получаете преимуществаinspect
Хук, реализованный Symbol, вы можете предоставить метод для переопределения механизма отображения:
// 从 API 的 Symbols 常量中获得这个充满魔力的 Inspect Symbol
var inspect = console.Symbols.INSPECT;
var myVeryOwnObject = {};
console.log(myVeryOwnObject); // 日志 `{}`
myVeryOwnObject[inspect] = function () { return 'DUUUDE'; };
console.log(myVeryOwnObject); // 日志输出 `DUUUDE`
Этот хук проверки примерно реализован следующим образом:
console.log = function (…items) {
var output = '';
for(const item of items) {
if (typeof item[console.Symbols.INSPECT] === 'function') {
output += item[console.Symbols.INSPECT](item);
} else {
output += console.inspect[typeof item](item);
}
output += ' ';
}
process.stdout.write(output + '\n');
}
Чтобы было ясно, это не означает, что вы должны писать код, изменяющий заданный объект. Это никогда не допускается (об этом см.WeakMaps, который предоставляет вам вспомогательные объекты для сбора собственной метаинформации, определенной для объекта).
Аннотация: Если у вас есть сомнения относительно WeakMap, вы можете обратиться кstackoverflow — Каково фактическое использование ES6 WeakMap?.
Node.js уже вconsole.log
Аналогичная реализация уже есть в.其使用了一个字符串('inspect'
) вместо Symbol, что означает, что вы можете установитьx.inspect = function(){}
- Это неразумно, потому что в какой-то момент это может конфликтовать с методами вашего класса. Вместо этого используйте символэто очень активный способ предотвратить это.
То, как символы используются таким образом, глубоко, оно стало частью языка, и с этим мы начинаем погружаться в некоторые хорошо известные символы.
Встроенные символы
Ключевой частью того, что делает символы полезными, является набор констант символов, называемых «встроенными символами». Эти константы на самом деле представляют собой набор статических методов класса Symbol, реализованных другими нативными объектами, такими как массивы, строки и т. д., а также внутри движка JavaScript. Именно здесь происходит настоящая часть «Отражение в реализации», потому что эти встроенные символы изменяют внутреннее поведение JavaScript. Далее я подробно расскажу, что делает каждый символ и почему эти символы так хороши.
Symbol.hasInstance: instanceof
Symbol.hasInstance
это реализацияinstanceof
Символ поведения. Когда движок, совместимый с ES6, видит в выраженииinstanceof
оператор, который звонитSymbol.hasInstance
. Например, выражениеlho instanceof rho
позвонюrho[Symbol.hasInstance](lho)
(rho
является правым операндом оператора, иlho
левый операнд). Затем метод может решить, наследуется ли объект от конкретного экземпляра, вы можете реализовать этот метод следующим образом:
class MyClass {
static [Symbol.hasInstance](lho) {
return Array.isArray(lho);
}
}
assert([] instanceof MyClass);
Symbol.iterator
Если вы более или менее слышали о символах, вы, вероятно, слышали оSymbol.iterator
. ES6 предлагает новый шаблон —for of
петля, петля вызываетSymbol.iterator
В качестве правого операнда для получения текущего значения для итерации. Другими словами, следующий код на обоих концах эквивалентен:
var myArray = [1,2,3];
// 使用 `for of` 的实现
for(var value of myArray) {
console.log(value);
}
// 没有 `for of` 的实现
var _myArray = myArray[Symbol.iterator]();
while(var _iteration = _myArray.next()) {
if (_iteration.done) {
break;
}
var value = _iteration.value;
console.log(value);
}
Symbol.ierator
позволит вам переопределитьof
оператор — а значит, если вы используете его для создания библиотеки, разработчики вас любят:
class Collection {
*[Symbol.iterator]() {
var i = 0;
while(this[i] !== undefined) {
yield this[i];
++i;
}
}
}
var myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(var value of myCollection) {
console.log(value); // 1, then 2
}
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
это особый символ, управляемыйArray#concat
поведение. Как вы видете,Array#concat
Может принимать несколько параметров, если вы передаете несколько массивов, эти массивы будут сведены и объединены позже. Рассмотрим следующий код:
x = [1, 2].concat([3, 4], [5, 6], 7, 8);
assert.deepEqual(x, [1, 2, 3, 4, 5, 6, 7, 8]);
Согласно ES6,Array#concat
буду использоватьSymbol.isConcatSepreadable
чтобы определить, являются ли его параметры расширяемыми. По этому поводу следует сказать, что ваш класс, наследующийся от Array, не особо подходит дляArray#concat
, и никакая другая причина:
class ArrayIsh extends Array {
get [Symbol.isConcatSpreadable]() {
return true;
}
}
class Collection extends Array {
get [Symbol.isConcatSpreadable]() {
return false;
}
}
arrayIshInstance = new ArrayIsh();
arrayIshInstance[0] = 3;
arrayIshInstance[1] = 4;
collectionInstance = new Collection();
collectionInstance[0] = 5;
collectionInstance[1] = 6;
spreadableTest = [1,2].concat(arrayInstance).concat(collectionInstance);
assert.deepEqual(spreadableTest, [1, 2, 3, 4, <Collection>]);
Symbol.unscopables
У этого символа интересная история. Фактически, при разработке ES6 TC (Технические комитеты: Технические комитеты) обнаружил, что в некоторых популярных библиотеках JavaScript есть такой старый код:
var keys = [];
with(Array.prototype) {
keys.push('foo');
}
Этот код отлично работал в ES5 или более ранних версиях JavaSacript, но теперь в ES6 естьArray#keys
- Это означает, что при выполненииwith(Array.prototype)
час,keys
Относится к прототипу Array наkeys
метод, то естьArray#keys
, а не снаружи определяешьkeys
. Есть три способа решить эту проблему:
- Получите все веб-сайты, использующие этот код, и обновите соответствующую кодовую базу. (Это в принципе невозможно)
- удалять
Array#keys
, и молитесь, чтобы подобные баги не появлялись. (Это тоже не решает проблему) - Напишите хак, чтобы обернуть весь такой код, предотвращая
keys
Появляться вwith
в рамках заявления.
Технический комитет выбрал третий путь, поэтомуSymbol.unscopables
Он возник, он определяет серию «недоступных (не ограниченных)» значений для объектов, когда эти значения используются вwith
заявление, они не устанавливаются в значения на объекте. Вы почти никогда не используете этот символ — в повседневном программировании на JavaScript вы не столкнетесь с такой ситуацией, но это все равно отражает использование символов и гарантирует целостность символов:
Object.keys(Array.prototype[Symbol.unscopables]); // -> ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']
// 不使用 unscopables:
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1!!
}
// 使用 unscopables:
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2!!
}
Symbol.match
Это еще один символ для функций.String#match
Функция сможет настроить поток математических правил, чтобы определить, соответствует ли заданное значение. Теперь вместо использования регулярных выражений вы можете реализовать собственную стратегию сопоставления:
class MyMatcher {
constructor(value) {
this.value = value;
}
[Symbol.match](string) {
var index = string.indexOf(this.value);
if (index === -1) {
return null;
}
return [this.value];
}
}
var fooMatcher = 'foobar'.match(new MyMatcher('foo'));
var barMatcher = 'foobar'.match(new MyMatcher('bar'));
assert.deepEqual(fooMatcher, ['foo']);
assert.deepEqual(barMatcher, ['bar']);
Symbol.replace
а такжеSymbol.match
аналогичный,Symbol.replace
Также разрешено передавать пользовательские классы для замены строк вместо использования регулярных выражений:
class MyReplacer {
constructor(value) {
this.value = value;
}
[Symbol.replace](string, replacer) {
var index = string.indexOf(this.value);
if (index === -1) {
return string;
}
if (typeof replacer === 'function') {
replacer = replacer.call(undefined, this.value, string);
}
return `${string.slice(0, index)}${replacer}${string.slice(index + this.value.length)}`;
}
}
var fooReplaced = 'foobar'.replace(new MyReplacer('foo'), 'baz');
var barMatcher = 'foobar'.replace(new MyReplacer('bar'), function () { return 'baz' });
assert.equal(fooReplaced, 'bazbar');
assert.equal(barReplaced, 'foobaz');
Symbol.search
а такжеSymbol.match
а такжеSymbol.replace
аналогичный,Symbol.search
улучшенString#search
- Позволяет передавать пользовательские классы для замены регулярных выражений:
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
var fooSearch = 'foobar'.search(new MySearch('foo'));
var barSearch = 'foobar'.search(new MySearch('bar'));
var bazSearch = 'foobar'.search(new MySearch('baz'));
assert.equal(fooSearch, 0);
assert.equal(barSearch, 3);
assert.equal(bazSearch, -1);
Symbol.split
Теперь идет последний символ, связанный со строкой -Symbol.split
соответствуетString#split
. Использование заключается в следующем:
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
var index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [string.substr(0, index), string.substr(index + this.value.length)];
}
}
var fooSplitter = 'foobar'.split(new MySplitter('foo'));
var barSplitter = 'foobar'.split(new MySplitter('bar'));
assert.deepEqual(fooSplitter, ['', 'bar']);
assert.deepEqual(barSplitter, ['foo', '']);
Symbol.species
Symbol.species
— очень умный Symbol, указывающий на конструктор класса, что позволяет классу создать собственную новую версию метода. кArray#map
Например, он может создать новый массив, и значение в новом массиве каждый раз получается из возвращаемого значения входящей функции обратного вызова — ES5.Array#map
Реализация может выглядеть так:
Array.prototype.map = function (callback) {
var returnValue = new Array(this.length);
this.forEach(function (item, index, array) {
returnValue[index] = callback(item, index, array);
});
return returnValue;
}
в ES6Array#map
и все другие неизменяемые методы массива, такие какArray#filter
д.), были обновлены для использованияSymbol.species
свойства для создания объектов, поэтому в ES6Array#map
Реализация может быть следующей:
Array.prototype.map = function (callback) {
var Species = this.constructor[Symbol.species];
var returnValue = new Species(this.length);
this.forEach(function (item, index, array) {
returnValue[index] = callback(item, index, array);
});
return returnValue;
}
Теперь, если вы напишетеclass Foo extends Array
-- всякий раз, когда вы звонитеFoo#map
, который возвращаетArray
type (а это не то, что нам нужно), вы должны были написать свою собственную реализацию Map для созданияFoo
вместо массива типаArray
массив классов, но теперь, сSympbol.species
,Foo#map
может напрямую вернутьFoo
Массив типа:
class Foo extends Array {
static get [Symbol.species]() {
return this;
}
}
class Bar extends Array {
static get [Symbol.species]() {
return Array;
}
}
assert(new Foo().map(function(){}) instanceof Foo);
assert(new Bar().map(function(){}) instanceof Bar);
assert(new Bar().map(function(){}) instanceof Array);
Вы можете спросить, зачем использоватьthis.constructor
заменитьthis.constructor[Symbol.species]
?Symbol.species
Это предусматривает необходимость создания типанастраиваемыйЗапись - возможно, вы не всегда хотите использовать подклассы и методы для создания подклассов, возьмите этот код в качестве примера:
class TimeoutPromise extends Promise {
static get [Symbol.species]() {
return Promise;
}
}
Это обещание тайм-аута может создать отложенную операцию — конечно, вы не хотите, чтобы обещание задержало последующие обещания во всей цепочке Prmoise, поэтомуSymbol.species
в состоянии сказатьTimeoutPromise
возвращает метод из цепочки прототиповPromise
(Примечание: если возвратTimeoutPromise
, затем поPromise#then
Каждое промис в цепочке промисов является TimeoutPromise). Это так удобно.
Symbol.toPrimitive
Этот Символ предоставляет нам перегрузку Абстрактного Оператора Равенства (Abstract Equality Operator, сокращенно==
). По сути, когда механизму JavaScript необходимо преобразовать ваш объект в примитивное значение,Symbol.toPrimitive
будет использоваться - например, если вы выполните+object
, то JavaScript вызоветobject[Symbol.toPrimitive]('number');
, если выполнить''+object
, то JavaScript вызоветobject[Symbol.toPrimive]('string')
, а если выполнитьif(object)
, JavaScript вызоветobject[Symbol.toPrimitive]('default')
. До этого у нас естьvalueOf
а такжеtoString
для обработки этих случаев, но оба они несколько грубы, и вы никогда не получите от них желаемого поведения.Symbol.toPrimitive
Реализация выглядит следующим образом:
class AnswerToLifeAndUniverseAndEverything {
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return 'Like, 42, man';
} else if (hint === 'number') {
return 42;
} else {
// 大多数类(除了 Date)都默认返回一个数值原始值
return 42;
}
}
}
var answer = new AnswerToLifeAndUniverseAndEverything();
+answer === 42;
Number(answer) === 42;
''+answer === 'Like, 42, man';
String(answer) === 'Like, 42, man';
Symbol.toStringTag
Это последний встроенный символ.Symbol.toStringTag
Действительно классный символ — если вы еще не пробовали реализовать свой собственный для заменыtypeof
суждение типа оператора, вы можете использоватьObject#toString()
- странно возвращается'[object Object]'
или'[object Array]'
Такая странная струна. До ES6 поведение метода было скрыто за деталями реализации, которые вы не могли видеть, но сегодня, в раю ES6, у нас есть символ, который определяет его поведение! любой проход кObject#toString()
Объект будет проверен, чтобы увидеть, есть ли[Symbol.toStringTag]
Свойство, это свойство - это строка, если таковая имеется, то строка будет использоваться какObject#toString()
Результат, пример такой:
class Collection {
get [Symbol.toStringTag]() {
return 'Collection';
}
}
var x = new Collection();
Object.prototype.toString.call(x) === '[object Collection]'
Еще одна вещь об этом - если вы используетеChaiдля тестирования, теперь он использует Symbol под капотом для проверки типов, так что вы можете писать в своих тестахexpect(x).to.be.a('Collection')
(x
есть аналоги вышеперечисленногоSymbol.toStringTag
свойство, этот код необходимо запустить в браузере, который поддерживает символ).
Отсутствие символа: Symbol.isabstracTequal
Возможно, вы уже знаете значение и использование символов в ES6, но мне очень нравится идея отражения в символах, поэтому я хочу сказать еще несколько слов. Для меня отсутствует символ, который меня бы порадовал:Symbol.isAbstractEqual
. Этот символ включает абстрактный оператор равенства (==
), чтобы вернуться к славе. Подобно таким языкам, как Ruby, Python и т. д., мы можем использовать его по-своему, для своих классов. когда вы видите такие вещи, какlho == rho
Когда такой код, JavaScript может преобразовать вrho[Symbol.isAbstractEqual](lho)
, позволяя классам перегружать операторы==
имея в виду. Это может быть достигнуто обратно совместимым способом — путем прототипирования всех существующих примитивных значений (например,Number.prototype
) для определения значений по умолчанию, этот символ сделает многие спецификации более понятными и даст разработчикам возможность выбрать==
причина использования.
В заключение
Как вы относитесь к Символам? Все еще в замешательстве? Хотите излить душу кому-нибудь? я@keithhamus на Titterverse- Вы можете дать мне пощечину, и когда-нибудь я потрачу все свое обеденное время, рассказывая вам о моих любимых новых функциях ES6.
Теперь, когда вы прочитали все о символах, пришло время прочитатьЧасть 2 - Отражение.
Наконец, я также хотел бы поблагодарить этих замечательных разработчиков@focusaurus,@mttshw, @colby_russell,@mdmazzola,так же как@WebReflectionЗа корректуру и улучшение статьи.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,React,внешний интерфейс,задняя часть,товар,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.