- PVSM.RU - https://www.pvsm.ru -
Метапрограммирование — вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы, либо программ, которые меняют себя во время выполнения. (Википедия)
Говоря более простым языком, метапрограммированием в рамках JavaScript можно считать механизмы, позволяющие анализировать и менять программу в режиме реального времени в зависимости от каких-либо действий. И, скорее всего, вы так или иначе используете их при написании скриптов каждый день.
JavaScript по своей природе является очень мощным динамическим языком и позволяет приятно писать гибкий код:
/**
* Динамическое создание save-метода для каждого свойства
*/
const comment = { authorId: 1, comment: 'Комментарий' };
for (let name in comment) {
const pascalCasedName = name.slice(0, 1).toUpperCase() + name.slice(1);
comment[`save${pascalCasedName}`] = function() {
// Сохраняем поле
}
}
comment.saveAuthorId(); // Сохраняем authorId
comment.saveComment(); // Сохраняем comment
Аналогичный код для динамического создания методов в других языках очень часто может потребовать специальный синтаксис или API для этого. Например, PHP тоже является динамическим языком, но в нём это потребует больше усилий:
<?php
class Comment {
public $authorId;
public $comment;
public function __construct($authorId, $comment) {
$this->authorId = $authorId;
$this->comment = $comment;
}
// Перехватываем все вызовы методов в классе
public function __call($methodName, $arguments) {
foreach (get_object_vars($this) as $fieldName => $fieldValue) {
$saveMethodName = "save" . strtoupper($fieldName[0]) . substr($fieldName, 1);
if ($methodName == $saveMethodName) {
// Сохраняем поле
}
}
}
}
$comment = new Comment(1, 'Комментарий');
$comment->saveAuthorId(); // Сохраняем authorId
$comment->saveComment(); // Сохраняем comment
В дополнение к гибкому синтаксису, у нас есть ещё и куча полезных функций для написания динамического кода: Object.create, Object.defineProperty, Function.apply и многие другие.
Рассмотрим же их поподробнее.
Стандартным средством для динамического выполнения кода является функция eval [8], позволяющая выполнить код из переданной строки:
eval('alert("Hello, world")');
К сожалению, eval имеет много нюансов:
Для решения этих проблем есть прекрасная альтернатива — new Function [9].
const hello = new Function('name', 'alert("Hello, " + name)');
hello('Андрей') // alert("Hello, Андрей");
В отличие от eval, мы всегда можем явно передавать параметры через аргументы функции и динамически указывать ей контекст this (через Function.apply [10] или Function.call [11]). К тому же создаваемая функция всегда вызывается в глобальной области видимости.
В старые времена, eval часто использовался для динамического изменения кода, т.к. JavaScript имел очень мало механизмов для рефлексии и без eval обойтись было нельзя. Но в современном стандарте языка появилось намного больше высокоуровневого функционала и eval теперь применяется намного реже.
JavaScript предоставляет нам множество прекрасных средств для динамической работы с функциями, позволяя как получать в рантайме различную информацию о функции, так и менять её:
Function.length [12] — позволяет узнать количество аргументов у функции:
const func = function(name, surname) {
console.log(`Hello, ${surname} ${name}`)
};
console.log(func.length) // 2
Function.caller [13] — позволяет получить вызывающую функцию:
const a = function() {
console.log(a.caller == b);
}
const b = function() {
a();
}
b(); // true
Function.apply [10] и Function.call [11] — позволяют динамически менять контекст this у функции:
const person = {
name: 'Иван',
introduce: function() {
return `Я ${this.name}`;
}
}
person.introduce(); // Я Иван
person.introduce.call({ name: 'Егор' }); // Я Егор
Отличаются они друг друга только тем, что в Function.apply аргументы функции подаются в виде массива, а в Function.call — через запятую. Эту особенность раньше часто использовали, чтобы передавать в функцию список аргументов в виде массива. Распространённый пример — это функция Math.max [14] (по умолчанию она не умеет работать с массивами):
Math.max.apply(null, [1, 2, 4, 3]); // 4
С появлением нового spread-оператора [15] можно просто писать так:
Math.max(...[1, 2, 4, 3]); // 4
Function.bind [16] — позволяет создать копию функцию из существующей, но с другим контекстом:
const person = {
name: 'Иван',
introduce: function() {
return `Я ${this.name}`;
}
}
person.introduce(); // Я Иван
const introduceEgor = person.introduce.bind({ name: 'Егор' });
introduceEgor(); // Я Егор
Function.toString [17] — возвращает строковое представление функции. Это очень мощная возможность, позволяющая исследовать как содержимое функции, так и её аргументы:
const getFullName = (name, surname, middlename) => {
console.log(`${surname} ${name} ${middlename}`);
}
getFullName.toString()
/*
* "(name, surname, middlename) => {
* console.log(`${surname} ${name} ${middlename}`);
* }"
*/
Получив строковое представление функции, мы можем его распарсить и проанализировать. Это можно использовать, чтобы, например, вытащить названия аргументов функции и, в зависимости от названия, автоматически подставлять нужный параметр. В целом, парсить можно двумя способами:
Простые примеры с парсингом функций регулярками:
/**
* Получить список параметром функции.
* @param fn Функция
*/
const getFunctionParams = fn => {
const COMMENTS = /(//.*$)|(/*[sS]*?*/)|(s*=[^,)]*(('(?:\'|[^'rn])*')|("(?:\"|[^"rn])*"))|(s*=[^,)]*))/gm;
const DEFAULT_PARAMS = /=[^,]+/gm;
const FAT_ARROW = /=>.*$/gm;
const ARGUMENT_NAMES = /([^s,]+)/g;
const formattedFn = fn
.toString()
.replace(COMMENTS, "")
.replace(FAT_ARROW, "")
.replace(DEFAULT_PARAMS, "");
const params = formattedFn
.slice(formattedFn.indexOf("(") + 1, formattedFn.indexOf(")"))
.match(ARGUMENT_NAMES);
return params || [];
};
const getFullName = (name, surname, middlename) => {
console.log(surname + ' ' + name + ' ' + middlename);
};
console.log(getFunctionParams(getFullName)); // ["name", "surname", "middlename"]
/**
* Получить строковое представление тела функции.
* @param fn Функция
*/
const getFunctionBody = fn => {
const restoreIndent = body => {
const lines = body.split("n");
const bodyLine = lines.find(line => line.trim() !== "");
let indent = typeof bodyLine !== "undefined" ? (/[ t]*/.exec(bodyLine) || [])[0] : "";
indent = indent || "";
return lines.map(line => line.replace(indent, "")).join("n");
};
const fnStr = fn.toString();
const rawBody = fnStr.substring(
fnStr.indexOf("{") + 1,
fnStr.lastIndexOf("}")
);
const indentedBody = restoreIndent(rawBody);
const trimmedBody = indentedBody.replace(/^s+|s+$/g, "");
return trimmedBody;
};
// Получим список параметров и тело функции getFullName
const getFullName = (name, surname, middlename) => {
console.log(surname + ' ' + name + ' ' + middlename);
};
console.log(getFunctionBody(getFullName));
В JavaScript имеется глобальный объект Object, содержащий множество методов для динамической работы с объектами.
Большинство таких методов оттуда уже давно существуют в языке и повсеместно используются.
Object.assign [22] — для удобного копирования свойств одного или нескольких объектов в объект, указанный первым параметром:
Object.assign({}, { a: 1 }, { b: 2 }, { c: 3 }) // {a: 1, b: 2, c: 3}
Object.keys [23] и Object.values [24] — возвращает либо список ключей, либо список значений объекта:
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // ["a", "b", "c"]
console.log(Object.values(obj)); // [1, 2, 3]
Object.entries [25] — превращает объект в массив вида [[ключ, значение], [ключ, значение]]:
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]
Object.prototype.hasOwnProperty [26] — проверяет, содержится ли свойство в объекте (не в его прототипной цепочке):
const obj = { a: 1 };
obj.__proto__ = { b: 2 };
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('b')) // false
Object.getOwnPropertyNames [27] — возвращает список собственных свойств, включая как перечисляемые, так и неперечисляемые:
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'c', { value: 3, enumerable: false }); // Создаём неперечисляемое свойство
for (let key in obj) {
console.log(key);
}
// "a", "b"
console.log(Object.getOwnPropertyNames(obj)); // [ "a", "b", "c" ]
Object.getOwnPropertySymbols [28] — возвращает список собственных (содержащихся именно в объекте, а не в его прототипной цепочке) символов:
const obj = {};
const a = Symbol('a');
obj[a] = 1;
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol(a) ]
Object.prototype.propertyIsEnumerable [29] — проверяет, является ли свойство перечисляемым (к примеру, доступно ли в циклах for-in, for-of):
const arr = [ 'Первый элемент' ];
console.log(arr.propertyIsEnumerable(0)); // true — элемент 'Первый элемент' является перечисляемым
console.log(arr.propertyIsEnumerable('length')); // false — свойство length не является перечисляемым
Дескрипторы позволяют тонко настраивать параметры свойств. С помощью них мы можем удобно делать собственные перехватчики во время чтения/записи какого-либо свойства (геттеры и сеттеры — get/set), делать свойства неизменяемыми или неперечисляемыми и ряд других вещей.
Object.defineProperty [30] и Object.defineProperties [31] — создаёт один или несколько дескрипторов свойств. Создадим свой собственный дескриптор с геттером и сеттером:
const obj = { name: 'Михаил', surname: 'Горшенёв' };
Object.defineProperty(obj, 'fullname', {
// Вызывается при чтении свойства fullname
get: function() {
return `${this.name} ${this.surname}`;
},
// Вызывается при изменении свойства fullname (но не умеет перехватывать удаление delete obj.fullname)
set: function(value) {
const [name, surname] = value.split(' ');
this.name = name;
this.surname = surname;
},
});
console.log(obj.fullname); // Михаил Горшенёв
obj.fullname = 'Егор Летов';
console.log(obj.name); // Егор
console.log(obj.surname); // Летов
В примере выше, свойство fullname не имело своего собственного значения, а динамически работало со свойствами name и surname. Необязательно определять одновременно геттер и сеттер — мы можем оставить только геттер и получить свойство, доступное только для чтения. Или можем в сеттере вместе с установкой значения добавить дополнительное действие, например, логгирование.
Кроме свойств get/set, дескрипторы имеют ещё несколько свойств для настройки:
const obj = {};
// Если не нужны свои обработчики get/set, то можно просто указать значение через value. Нельзя одновременно использовать get/set и value. По умолчанию — undefined.
Object.defineProperty(obj, 'name', { value: 'Егор' });
// Указываем, что созданное свойство видно при итерации свойств объекта (for-in, for-of, Object.keys). По умолчанию — false.
Object.defineProperty(obj, 'a', { enumerable: true });
// Можно ли в дальнейшем поменять созданное свойство через defineProperty или удалить его через delete. По умолчанию — false.
Object.defineProperty(obj, 'b', { configurable: false });
// Можно ли будет менять значение свойства. По умолчанию — false.
Object.defineProperty(obj, 'c', { writable: true });
Object.getOwnPropertyDescriptor [32] и Object.getOwnPropertyDescriptors [33] — позволяют получить нужный дескриптор объекта или их полный список:
const obj = { a: 1, b: 2 };
console.log(Object.getOwnPropertyDescriptor(obj, "a")); // { configurable: true, enumerable: true, value: 1, writable: true }
/**
* {
* a: { configurable: true, enumerable: true, value: 1, writable: true },
* b: { configurable: true, enumerable: true, value: 2, writable: true }
* }
*/
console.log(Object.getOwnPropertyDescriptors(obj));
Object.freeze [34] — "замораживает" свойства объекта. Следствием такой "заморозки" является полная неизменяемость свойств объекта — их нельзя изменять и удалять, добавлять новые, менять дескрипторы:
const obj = Object.freeze({ a: 1 });
// В строгом режиме следующие строчки кидают исключения, а в обычном просто ничего не происходит.
obj.a = 2;
obj.b = 3;
console.log(obj); // { a: 1 }
console.log(Object.isFrozen(obj)) // true
Object.seal [35] — "запечатывает" свойства объекта. "Запечатывание" похоже на Object.freeze, но имеет ряд отличий. Мы также, как и в Object.freeze запрещаем добавлять новые свойства, удалять существующие, менять их дескрипторы, но в то же время можем менять значения свойств:
const obj = Object.seal({ a: 1 });
obj.a = 2; // Свойство a теперь равно 2
// В строгом режиме кинет исключение, а в обычном просто ничего не происходит.
obj.b = 3;
console.log(obj); // { a: 2 }
console.log(Object.isSealed(obj)) // true
Object.preventExtensions [36] — запрещает добавление новых свойств/дескрипторов:
const obj = Object.preventExtensions({ a: 1 });
obj.a = 2;
// В строгом режиме следующие строчки кидают исключения, а в обычном просто ничего не происходит.
obj.b = 3;
console.log(obj); // { a: 2 }
console.log(Object.isExtensible(obj)) // false
Object.create [37] — для создания объекта с указанным в параметре прототипом. Эту возможность можно использовать как для прототипного наследования [38], так и для создания "чистых" объектов, без свойств из Object.prototype [39]:
const pureObj = Object.create(null);
Object.getPrototypeOf [40] и Object.setPrototypeOf [41] — для получения/изменения прототипа объекта:
const duck = {};
const bird = {};
Object.setPrototypeOf(duck, bird);
console.log(Object.getPrototypeOf(duck) === bird); // true
console.log(duck.__proto__ === bird); // true
Object.prototype.isPrototypeOf [42] — проверяет, содержится ли текущий объект в прототипной цепочке другого:
const duck = {};
const bird = {};
duck.__proto__ = bird;
console.log(bird.isPrototypeOf(duck)); // true
С появлением ES6, в JavaScript добавили глобальный объект Reflect [43], предназначенный для хранения различных методов, связанных с рефлексией и интроспекцией.
Большая часть его методов — это результат переноса существующих методов из таких глобальных объектов, как Object [44] и Function [9] в отдельное пространство имён с небольшим рефакторингом для более комфортного использования.
Перенос функций в объект Reflect не только облегчил поиск нужных методов для рефлексии и дал большую семантичность, но также позволил избежать неприятных ситуаций, когда наш объект не содержит в своём прототипе Object.prototype [39], но мы хотим использовать методы оттуда:
let obj = Object.create(null);
obj.qwerty = 'qwerty';
console.log(obj.__proto__) // null
console.log(obj.hasOwnProperty('qwerty')) // Uncaught TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty === undefined); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'qwerty')); // true
Рефакторинг сделал поведение методов более явным и однообразным. К примеру, если раньше при вызове Object.defineProperty [30] на некорректном значении (как число или строка) кидалось исключение, но в то же время вызов Object.getOwnPropertyDescriptor [32] на несуществующем дескрипторе объекта молча возвращал undefined, то аналогичные методы из Reflect при некорректных данных всегда кидают исключения.
Также добавилось несколько новых методов:
Reflect.construct [45] — более удобная альтернатива Object.create [37], позволяющая не просто создать объект с указанным прототипом, но и сразу проинициализировать его:
function Person(name, surname) {
this.name = this.formatParam(name);
this.surname = this.formatParam(surname);
}
Person.prototype.formatParam = function(param) {
return param.slice(0, 1).toUpperCase() + param.slice(1).toLowerCase();
}
const oldPerson = Object.create(Person.prototype); // {}
Person.call(oldPerson, 'Иван', 'Иванов'); // {name: "Иван", surname: "Иванов"}
const newPerson = Reflect.construct(Person, ['Андрей', 'Смирнов']); // {name: "Андрей", surname: "Смирнов"}
Reflect.ownKeys [46] — возвращает массив свойств, принадлежащих именно указанному объекту (а не объектам в цепочке прототипов):
let person = { name: 'Иван', surname: 'Иванов' };
person.__proto__ = { age: 30 };
console.log(Reflect.ownKeys(person)); // ["name", "surname"]
Reflect.deleteProperty [47] — альтернатива оператору delete, выполненная в виде метода:
let person = { name: 'Иван', surname: 'Иванов' };
delete person.name; // person = {surname: "Иванов"}
Reflect.deleteProperty(person, 'surname'); // person = {}
Reflect.has [48] — альтернатива оператору in, выполненная в виде метода:
let person = { name: 'Иван', surname: 'Иванов' };
console.log('name' in person); // true
console.log(Reflect.has(person, 'name')); // true
Reflect.get [49] и Reflect.set [50] — для чтения/изменения свойств объекта:
let person = { name: 'Иван', surname: 'Иванов' };
console.log(Reflect.get(person, 'name')); // Иван
Reflect.set(person, 'surname', 'Петров') // person = {name: "Иван", surname: "Петров"}
Более подробно с изменениями можно ознакомиться здесь [43].
Кроме перечисленных выше методов объекта Reflect, существует экспериментальный proposal для удобного привязывания различных метаданных к объектам.
Метаданными может быть любая полезная информация не относящаяся к объекту напрямую, например:
const typeData = Reflect.getMetadata("design:type", object, propertyName);
В данный момент для его работы в браузерах используется этот полифилл [53]
Символы являются новым неизменяемым типом данным, в основном использующийся для создания уникальных названий идентификаторов свойств объектов. У нас имеется возможность создавать символы двумя способами:
Локальные символы — текст в параметрах функции Symbol не влияет на уникальность и нужен лишь для отладки:
const sym1 = Symbol('name');
const sym2 = Symbol('name');
console.log(sym1 == sym2); // false
Глобальные символы — символы хранятся в глобальном реестре, поэтому символы с одинаковым ключом равны:
const sym3 = Symbol.for('name');
const sym4 = Symbol.for('name');
const sym5 = Symbol.for('other name');
console.log(sym3 == sym4); // true, символы имеют один и тот же ключ 'name'
console.log(sym3 == sym5); // false, символы имеют разные ключи
Возможность создавать такие идентификаторы позволяет не бояться того, что мы можем затереть какое-то свойство в неизвестном нам объекте. Это качество позволяет создателям стандарта легко добавлять новые стандартные свойства в объекты, при этом не сломав совместимость с различными существующими библиотеками (которые уже могли определить такое же свойство) и пользовательским кодом. Поэтому существует ряд стандартных символов и часть из них даёт новые возможности для рефлексии:
Symbol.iterator [54] — позволяет создавать собственные правила итерации объектов с помощью for-of или ...spread operator:
let arr = [1, 2, 3];
// Выводим элементы массива в обратном порядке
arr[Symbol.iterator] = function() {
const self = this;
let pos = this.length - 1;
return {
next() {
if (pos >= 0) {
return {
done: false,
value: self[pos--]
};
} else {
return {
done: true
};
}
}
}
};
console.log([...arr]); // [3, 2, 1]
Symbol.hasInstance [55] — метод, определяющий, распознает ли конструктор некоторый объект как свой экземпляр. Используется оператором instanceof:
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
Symbol.isConcatSpreadable [56] — указывает, должен ли массив сплющиваться при конкатенации в Array.concat:
let firstArr = [1, 2, 3];
let secondArr = [4, 5, 6];
firstArr.concat(secondArr); // [1, 2, 3, 4, 5, 6]
secondArr[Symbol.isConcatSpreadable] = false;
console.log(firstArr.concat(secondArr)); // [1, 2, 3, [4, 5, 6]]
Symbol.species [57] — позволяет получить/изменить ссылку на конструктор, использующийся для создания новых объектов:
class MyArray extends Array {
static get [Symbol.species]() { return this; }
}
// Обычная реализация Array.map вернула бы экземпляр класса Array, но мы переопределили Symbol.species на this и теперь возвращается экземпляр класса MyArray
const doubledArr = new MyArray(1, 2, 3).map(x => x * 2);
console.log(arr instanceof MyArray); // true
console.log(arr instanceof Array); // true
Symbol.toPrimitive [58] — позволяет указать каким образом нужно конвертировать наш объект в примитивное значение. Если ранее для приведения к примитиву нам нужно было использовали toString вместе с valueOf, то теперь всё можно сделать в одном удобном методе:
const figure = {
id: 1,
name: 'Прямоугольник',
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return this.name;
} else if (hint === 'number') {
return this.id;
} else { // default
return this.name;
}
}
}
console.log(`${figure}`); // hint = string
console.log(+figure); // hint = number
console.log(figure + ''); // hint = default
Symbol.match [59] — позволяет создавать свои собственные классы-обработчики для метода для функции String.prototype.match [60]:
class StartAndEndsWithMatcher {
constructor(value) {
this.value = value;
}
[Symbol.match](str) {
const startsWith = str.startsWith(this.value);
const endsWith = str.startsWith(this.value);
if (startsWith && endsWith) {
return [this.value];
}
return null;
}
}
const testMatchResult = '|тест|'.match(new StartAndEndsWithMatcher('|'));
console.log(testMatchResult); // ["|"]
const catMatchResult = 'кот|'.match(new StartAndEndsWithMatcher('|'));
console.log(catMatchResult) // null
Также существуют похожие символы — Symbol.replace [61], Symbol.search [62] и Symbol.split [63] для аналогичных методов из String.prototype [64].
Важно заметить, что символы (как и reflect-metadata из прошлой секции) можно использовать для присоединения своих метаданных к любому объекту. Ведь из-за уникальности создаваемых символов, мы можем не бояться, что случайно перезапишем имеющееся свойство в объекте. Для примера присоединим метаданные для валидации к объекту:
const validationRules = Symbol('validationRules');
const person = { name: 'Иван', surname: 'Иванов' };
person[validationRules] = { name: ['max-length-256', 'required'], surname: ['max-length-256'] }
Proxy [65] является принципиально новым функционалом, появившимся вместе с Reflect API и Symbols в ES6, предназначающийся для перехвата в любом объекте чтения/записи/удаления любых свойств, вызова функций, переопределения правил итерирования и других полезных вещей. Важно заметить, что прокси нормально не полифилятся.
С помощью проксей мы можем сильно расширить удобство использования кучи библиотек, например библиотек для data-binding вроде MobX из React, Vue и других. Рассмотрим пример до использования прокси и после.
С прокси:
const formData = {
login: 'User',
password: 'pass'
};
const proxyFormData = new Proxy(formData, {
set(target, name, value) {
target[name] = value;
this.forceUpdate(); // Перерисовываем наш React-компонент
}
});
// При изменении любого свойства также вызывается forceUpdate() для перерисовки в React
proxyFormData.login = 'User2';
// Такого свойства ещё не существует, но прокси всё-равно перехватит присваивание и корректно обработает
proxyFormData.age = 20;
Без прокси мы можем попробовать сделать нечно похожее, используя геттеры/сеттеры:
const formData = {
login: 'User',
password: 'pass'
};
const proxyFormData = {};
for (let param in formData) {
Reflect.defineProperty(proxyFormData, `__private__${param}`, {
value: formData[param],
enumerable: false,
configurable: true
});
Reflect.defineProperty(proxyFormData, param, {
get: function() {
return this[`__private__${param}`];
},
set: function(value) {
this[`__private__${param}`] = value;
this.forceUpdate(); // Перерисовываем наш React-компонент
},
enumerable: true,
configurable: true
});
}
// При изменении любого свойства также вызывается forceUpdate() для перерисовки в React
proxyFormData.login = 'User2';
// Такого свойства не существует и мы не сможем его обработать пока явно не зададим ещё одну пара геттеров-сеттеров через Reflect.defineProperty
proxyFormData.age = 20;
При использовании геттеров и сеттеров мы получаем кучу неудобного бойлерплейт-кода, а самый главный минус — при использовании Proxy мы создаём проксируемый объект один раз и он перехватывает все свойства (независимо от того, существуют они в объекте или ещё нет), а с использованием геттеров/сеттеров нам приходится для каждого нового свойства вручную создавать пару из геттера и сеттера, к тому же сеттером мы не можем отслеживать работу оператора delete obj[name].
JavaScript является мощным и гибким языком и очень радует, что он больше не находится в застое как во времена ECMAScript 4, а постоянно улучшается и добавляет в себя всё больше новых и удобных возможностей. Благодаря этому мы можем писать всё более хорошие программы как с точки зрения пользовательского опыта, так и разработческого.
Для более детального погружения в тему рекомендую прочитать находящийся в свободном доступе раздел про метапрограммирование [66] замечательной книги You Don't Know JS [67].
Автор: kyoumur
Источник [68]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/286194
Ссылки в тексте:
[1] Генерация кода: #1-generaciya-koda
[2] Работа с функциями: #2-rabota-s-funkciyami
[3] Работа с объектами: #3-rabota-s-obektami
[4] Reflect API: #4-reflect-api
[5] Символы (Symbols): #5-simvoly-symbols
[6] Прокси (Proxy): #6-proksi-proxy
[7] Заключение: #7-zaklyuchenie
[8] eval: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
[9] new Function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
[10] Function.apply: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
[11] Function.call: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
[12] Function.length: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length
[13] Function.caller: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller
[14] Math.max: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
[15] spread-оператора: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
[16] Function.bind: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
[17] Function.toString: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/toString
[18] esprima: http://esprima.org/
[19] acorn: https://github.com/acornjs/acorn
[20] Пример разбора в AST через esprima.: https://is.gd/u0jVJk
[21] хороший доклад про парсеры: https://www.youtube.com/watch?v=au9_j2NjNaI
[22] Object.assign: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
[23] Object.keys: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
[24] Object.values: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
[25] Object.entries: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
[26] Object.prototype.hasOwnProperty: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
[27] Object.getOwnPropertyNames: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
[28] Object.getOwnPropertySymbols: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols
[29] Object.prototype.propertyIsEnumerable: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable
[30] Object.defineProperty: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
[31] Object.defineProperties: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties
[32] Object.getOwnPropertyDescriptor: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
[33] Object.getOwnPropertyDescriptors: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
[34] Object.freeze: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
[35] Object.seal: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal
[36] Object.preventExtensions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
[37] Object.create: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
[38] прототипного наследования: https://learn.javascript.ru/class-inheritance
[39] Object.prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype
[40] Object.getPrototypeOf: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
[41] Object.setPrototypeOf: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
[42] Object.prototype.isPrototypeOf: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
[43] Reflect: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
[44] Object: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
[45] Reflect.construct: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct
[46] Reflect.ownKeys: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys
[47] Reflect.deleteProperty: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty
[48] Reflect.has: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has
[49] Reflect.get: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
[50] Reflect.set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
[51] emitDecoratorMetadata: http://www.typescriptlang.org/docs/handbook/decorators.html
[52] InversifyJS: https://github.com/inversify/InversifyJS
[53] полифилл: https://github.com/rbuckton/reflect-metadata
[54] Symbol.iterator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator
[55] Symbol.hasInstance: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance
[56] Symbol.isConcatSpreadable: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable
[57] Symbol.species: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species
[58] Symbol.toPrimitive: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive
[59] Symbol.match: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match
[60] String.prototype.match: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match
[61] Symbol.replace: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace
[62] Symbol.search: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search
[63] Symbol.split: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/split
[64] String.prototype: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/prototype
[65] Proxy: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
[66] раздел про метапрограммирование: https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch7.md
[67] You Don't Know JS: https://github.com/getify/You-Dont-Know-JS
[68] Источник: https://habr.com/post/417097/?utm_campaign=417097
Нажмите здесь для печати.