Проверка функций и проверка типов с помощью instanceof

Уже более десяти лет гуру javascript избегают оператора instanceof по уважительной причине. Это было чревато проблемами, поскольку до недавнего времени оно работало только в самом простом объектно-ориентированном коде, где ваши объекты создаются непосредственно из конструктора. Кроме того, это не позволяло сравнивать объекты из разных окон или контекстов. Эти две основные проблемы сделали instanceof очень ненадежным для большинства кода js/es. Вместо этого мы все выступали за ручную проверку функций, которые вам нужны в объектах, с которыми вы работаете, независимо от их области действия. Недавно я был удивлен, узнав, что в этом больше нет необходимости, так как большая часть процесса может быть автоматизирована. Я был еще больше удивлен, что не слышал об этом, так как я много читал о коде, поэтому я решил поговорить об этом…

(Повторное) открытие

Последние пару недель я действительно углублялся в функциональное программирование с помощью javascript. Я не буду утомлять вас подробностями, но это серьезный сдвиг в мышлении, даже если функции являются гражданами первого класса. Один из принципов, критически важных для FP, а также важный для этой истории, заключается в том, что код FP в целом должен избегать ошибок, когда это возможно. Реальность, однако, такова, что если вы не очень хорошо разбираетесь в методах FP, вы все равно столкнетесь с проблемами с исключениями, которые зависят от конкретных требований типа или соответствия конкретным интерфейсам. В FP есть много полезных практик и трюков, и я быстро научился избегать многих из этих проблем непосредственно в своем коде. Проблемы снова возникали при работе с кодом и данными вне компетенции конкретного приложения. Мне нужны были дополнительные инструменты.

Мои поиски привели меня к множеству различных аспектов реализации ES6+, некоторые из которых я не исследовал подробно. И тут я увидел Символ. В модуле Symbol есть много интересных символов, которые при определении в классе переопределяют семантику по умолчанию механизма javascript, поскольку он относится к его экземплярам. Symbol.hasInstance в частности, было приятно увидеть:

С помощью этого символа можно настроить поведение оператора instanceof.

О, возможности! Время экспериментировать. По умолчанию instanceof сравнивает класс с constructor и prototype объекта. Приведенный пример относится к проверке на основе class, но function — это просто еще один объект, верно? А классы — это просто сахар для function в качестве конструктора и прототипа, верно?

Поиск неклассического использования

function myCustomConstructor() {
  // Initialize my object
}
// Static Method
myCustomConstructor[Symbol.hasInstance] = obj => true;
// <-- Immediately errors because Symbol.hasInstance is not writable

Судя по всему, class больше не просто сахар. Он лишь немного изменяет семантику движка, автоматически разблокируя определенные клавиши. Что я действительно ищу, так это способ проверки объектов, которые уже были созданы или получены из другого источника. Что ж, существует более двух способов создания объекта. Черт возьми, это JavaScript. Там, наверное, больше 350. Я просто попробую самый простой:

const myCustomClass = {
  create: cfg => { /*Initialize my object*/ }, 
  [Symbol.hasInstance]: (o) => true
};
// Didn't error this time. Let's check...
console.log({} instanceof myCustomClass);
// <-- true! We have hit paydirt!!!

Вы спросите, почему приведенный выше фрагмент так интересен? Что ж, позвольте мне прояснить это для вас. Я изучал функторы, моноиды и монады. Это концепции функционального программирования, которые представляют собой простые правила для вычислительных типов, но часто создаются и применяются (в частности, в javascript) с использованием очень небезопасных (с точки зрения FP) объектов. - Ориентированные методики. Я не буду вдаваться в аргументы, почему это не идеально, но это дает нам некоторые дополнительные инструменты без необходимости кодировать и/или импортировать еще одну точку отсчета полезности. Теперь все, что нам нужно, это несколько вариантов использования…

UC1: проверка концептуального соответствия

Функтор очень прост. Это любой объект или тип, имеющий метод map, который принимает любой function и возвращает новый объект исходного типа. Это очень, очень простое правило. Поскольку map — такое простое имя, которое легко может столкнуться со многими возможными интерпретациями, главное — убедиться, что оно соответствует основным правилам. Итак, давайте проверим это, не создавая привязку:

const Functor = {
  [Symbol.hasInstance]: (check) => {
  // Doing this the verbose way for demonstration
    if ('function' === typeof check['map'])
      if (check.constructor === check['map'](x => x).constructor)
        return true;
    return false;
  },
// Some other utility static methods
};
//Now, lets test:
console.log([] instanceof Functor);
//<-- true

Не создавая класс или привязку, мы просто используем то, что уже есть. Конечно, если бы мы действительно хотели проверить законы функторов, мы могли бы это сделать. Поскольку это простой объект, мы могли бы вместо этого написать функциональный код, если бы захотели. Это решение не беспокоится о кросс-контекстах и ​​не требует класса (на самом деле, препятствует этому). Наконец, основанный на стандартах, независимый от парадигмы и совместимый способ проверки функций любой реализации или требования. Ладно, это было для фанатов ФП. Оо, я тоже прикрою тебя:

UC2: подтверждение реализации

const MyMixin = {
  doSomething: () => {}, // No-op: Just for show
  doSomethingElse: () => {}, // No-op: see above
  [Symbol.hasInstance]: function (check){
    return Object.keys(this).reduce(
      (is, key) => is && check[key] === this[key], true
    )
  }
}

Теперь наш миксин самопроверяется. Мы можем добавить столько методов, сколько захотим, и нам не нужно взламывать или манипулировать цепочкой наследования. Наша модель наследования может быть только такой, какой нам нужно, (я смотрю на вас, extends hacks), и мы не даже приходится отслеживать использование нашего миксина с любой стороны. Дополнительный бонус: если какой-то другой код перезаписал наши украшения Mixin, мы сразу узнаем, что он больше не находится под нашим контролем. Конечно, вышеизложенное — просто мозговой штурм, но оно должно возбудить вкус любого компетентного программиста. Последнее, что пришло мне в голову, которым я хотел бы поделиться:

UC3: Независимая реализация интерфейса

const ICustomInterface = {
  propName1: v => v + 0 === v, // Fast Number check
  propName2: v => v + '' === v, // Fast String check
  propName3: v => ['enum0', 'enum1', 'enum2'].indexOf(v) > -1,
  methodName: f => 'function' === typeof f && f.length === 3,
  [Symbol.hasInstance]: function(chk) {
    return Object.keys(this).reduce(
      (is, key) => is && this[key](chk[key]), true
    )
  }
}

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

Будем надеяться, что многие высококачественные способы использования instanceof стали очевидными. instanceof, возможно, получил гораздо больше пользы, чем наши предыдущие попытки заполнить или обойти пробелы. В настоящее время потребность в isType() API или DSL с проверкой типов очень мала. Если они нам понадобятся для совместимости, они могут быть гораздо менее подробными. Меньшее количество логических ссылок означает более простой код и когнитивную нагрузку (не то чтобы isTypes() требовалось много, но каждая мелочь помогает). Честно говоря, идеи текут так быстро, что я, наверное, скоро перестану об этом говорить.

Последние мысли

Это должно дать много намеков на обобщение и большие абстракции. Добавьте в смесь немного Proxy и Reflect, и у нас получится несколько очень интересных рецептов для приготовления. Проверка структуроподобных типов теперь чрезвычайно проста. «Что?! Мы можем проверить наш JSON и проверить наши перечисления?!» Я знаю, что с тех пор, как я обнаружил это, я иду к instanceof как для одиночных , так и для многоэтапная проверка функций и требований. Я хотел бы услышать от других о любых новых идеях, которые приходят на ум. Для тех, кто уже был в курсе, расскажите, пожалуйста, о некоторых наиболее интересных способах повторного использования instanceof в вашем коде.

Примечание. В течение некоторого времени это поддерживается во всех основных браузерах, за исключением, конечно, IE. Хорошо, что больше не поддерживаю. И большой плюс, все крупные мобильные браузеры также уделили особое внимание instanceof.