Нативный js или react?
Немного поизучал react в надежде начать применять на практике для удобства написания кода и легкости масштабирования проекта с возможностью многократного использования готовых решений в новых проектах.
Что первое мне бросилось в глаза, так это ощутимая древовидная структура файлов, которая с увеличением проекта всегда будет расти. Затем в файлах много импорта и экспорта, которые надо держать в голове. Наследование и оборачивание контейнеров в контейнер, наследование классов, проброс свойств и наконец рендеринг. Ну и, конечно, синтаксис, к которому надо привыкнуть и перестроиться.
Если мне надо, к примеру, обработать нажатие на кнопку Like или submit для отправки сообщения на сервер, то я традиционно возьму и напишу функции: обработки нажатия элемента, фэтчинга и рендеринга изменённых данных или ответа в модальном окне. Все это в одном — двух файлах и кода немного. В react, сдаётся мне, что придётся проделать больше работы и затронуть больше чем два файла. .
От сюда и вопрос к знатокам реакт : это действительно с ростом проекта заметно облегчает жизнь и ускоряет процесс разработки фронта по сравнению с нативным js или пока ещё живым jQuery? или это просто модный альтернативный framework со своими предпочтениями и нишей?
- Вопрос задан более двух лет назад
- 3151 просмотр
1 комментарий
Простой 1 комментарий
Нативный javascript: 6 функций, о которых вы не знаете
От автора: JavaScript — это язык, который быстро развивается; по этой причине иногда трудно уследить за всеми все его функциями и возможностями. В этой короткой статье мы рассмотрим некоторые нативные функции, о которых вы, возможно, не знали.
Получение параметров строки запроса
URLSearchParams это интерфейс, который позволяет обрабатывать параметры строки запроса, он существует уже несколько лет, но вас может удивить, что он не так популярен среди разработчиков, давайте посмотрим, как его использовать:
Нативные объекты — JS: Программирование, управляемое данными
Но важно понимать, что у строки нет свойств, прозрачно для нас происходит оборачивание в объект и работает это примерно так: new String(‘hello’) .
Изменение объекта
Обычно, изменение объекта происходит так:
const obj = key: 'value' >; obj.key = 'another value';
Но что делать, если свойство заранее неизвестно? Тогда можно воспользоваться таким способом:
const name = 'key'; const obj = key: 'value' >; obj[name] = 'another value';
Вычислимые свойства встречаются достаточно часто на этапе создания объекта, по этой причине появился специальный синтаксис, позволяющий задавать подобные свойства в литерале. Дополнительный бонус в том, что при таком подходе в коде пропадают лишние изменения (мутации) и код становится более функциональным:
const name = 'key2'; const obj = key: 'value', [name]: 'another value' >; //
Единственное отличие от стандартного синтаксиса в том, что ключ — это переменная (а не имя), взятая в квадратные скобки.
Еще одна интересная возможность объектов в JS — сокращенный синтаксис создания объектов при использовании переменных или констант. Обычное создание выглядит так:
const name = 'Mike'; const user = name: name >;
В примере выше, имя свойства совпадает с именем переменной, в которой хранится нужное значение. JS позволяет написать этот код лаконичнее. Можно просто опустить часть name: . И получится:
const name = 'Mike'; const user = name >;
То же самое работает и для нескольких переменных:
const name = 'Mike'; const surname = 'Smith'; const user = name, surname >;
Можно даже мешать разные стили в рамках одного объекта:
const name = 'Mike'; const user = name, surname: 'Smith' >;
Теперь, когда мы уже хорошо понимаем в чём соль объектно-ориентированного программирования, мы можем познакомиться с настоящими нативными объектами, которые реализованы прямо в JS.
Объекты в JS
Разберём простой пример:
// primitive type const str = 'hello, world';
Записываем в константу строчку и после этого используем известный уже для нас синтаксис доступа к свойствам, только не через модуль, а через объект. Это очень похожий механизм, когда мы вызывали методы или передавали сообщение. Например, с помощью length , мы можем посмотреть длину строчки:
// property str.length; // 12
Метод toUpperCase возвращает нам новую строчку (он не мутирует текущую), которая переводит строчку в верхний регистр:
// method str.toUpperCase(); // HELLO, WORLD
Если мы не будем делать вызов после toUpperCase , а просто сделаем обращение к свойству и распечатаем, то что лежит внутри, мы увидим, что там лежит function :
// actually, just property str.toUpperCase; // [Function: toUpperCase]
В большинстве языков принято разделять понятия свойства и методы и говорить об этом явно. В JavaScript нет методов, есть только свойства, просто в некоторых свойствах могут лежать функции, что довольно просто, потому что с функциями мы уже довольно хорошо знакомы: их можно куда угодно записывать, можно передавать как аргументы, получать как значения.
С объектом примерно также. Есть некоторый объект внутри переменной или константы, точка – вызов какого-то свойства, которое может быть функцией (тогда ставятся скобки).
Давайте договоримся, что в будущем мы будем говорить слово «методы», просто потому что так принято и все так делают во всем мире, на всех языках. При этом мы понимаем, что в JS – это просто функция, которая записана и лежит внутри определённого свойства с тем же именем.
JavaScript не является простым языком, когда мы говорим о понимании его внутренностей и глубоком понимании того, как он работает. На самом деле строчка – это примитивный тип, а не объект. Но если мы обратимся к строке напрямую, то все отработает также, как если бы она была записана в константу или переменную:
'hello'.length // 12
Почему нет ошибки и строка начинает вести себя, как объект?
Если простыми словами, то происходит какой-то некий процесс, который делает для вас прозрачным трансформацию в объект. Этот процесс называется boxing. То есть в тот момент, когда вы складываете в константу какой-то примитивный тип и после этого через точку к нему обращаетесь, то в этот момент JS за вас оборачивает этот примитивный тип в соответствующий объект, который связан с текущим примитивным типом и он начинает работать таким образом. После этого происходит его unboxing (разворачивание) и внутри всё равно оказывается примитивный тип. Этот механизм следует определённым целям и попозже мы разберёмся с ним чуть глубже.
Тип данных: объект
В JavaScript есть отдельный тип данных, который называется объект и по сути любой объект, с которым мы работаем, является этим типом данных (точнее его подтипом, но об этом мы будем говорить позже, потому что сейчас мы сосредоточены больше на базовом понимании и связи с ООП, чем на особенностях работы JS).
Как же работает этот тип данных:
const card = name: 'percent card', key: 'value', >;
Он представляет собой вот такую структуру, в которой мы задаём ограничители (фигурные скобки в начале и в конце), после этого описываем ключ и значение.
Значением может быть всё что угодно. На вход ожидается любое выражение, после этого ставится запятая и дальше опять пара ключ-значение и тд. После этого в card у нас оказывается объект.
Здесь начинается некоторая путаница, которую вводит JS и люди, которые знакомы только с этим языком часто смешивают это в одно и постоянно употребляют в разговоре. В свою очередь люди из других языков это не совсем понимают, потому что в других языках такая запись обозначает несколько иную вещь. К этому надо просто привыкнуть и всегда внутри у себя дифференцировать о чём мы говорим: о типе данных объект или объекте, как мы понимаем это с точки зрения ООП, хотя в каком-то смысле разница в JS стирается.
Как происходит обращение к объекту
Поскольку этот тип данных (объект) является и настоящим объектом в том числе, поэтому обращение к его ключам идёт, как обращение к обычным свойствам в объекте:
const card = name: 'percent card', key: 'value', >; card.key; // value
Мы пишем card.key и получаем значение, всё довольно просто.
Если мы обратимся к неизвестному свойству, то мы получим undefined .
card.wrongKey; // undefined
Имеется в виду, что свойство не определено, хотя оно может быть определено и равно undefined , но обычно так не делают.
Мы уже говорили, что определять самостоятельно undefined – неправильно, потому что вы просто не отличите определено оно или нет и что важно – вы не получаете ошибку. То есть, если вы где-то случайно (в JS легко это сделать, поскольку это динамический язык) допустите синтаксическую ошибку, то ваш код может продолжить работать, но продолжит работать с ошибками и при этом не упадёт. Это довольно опасная вещь, поэтому за этим надо следить.
У объектов в JS есть другой синтаксис. Вы всегда можете обратиться через квадратные скобки определив ключ, как строчку:
// map syntax card['key']; // value card['wrongKey']; // undefined
Это позволяет, например, обращаться к свойствам, которые названы чуть сложнее, чем просто имена состоящие из алфавита английского языка. Например, если вы используете целое предложение, пробелы или какие-то специальные символы, то используйте такой способ обращения. Это не частый кейс, но такое бывает когда у вас объект динамический, то есть вы откуда-то собираете информацию – это будет достаточно удобно. Этот синтаксис ведёт себя точно также, если мы ошибаемся с ключом, то получаем undefined .
Что такое константа в JS
Есть одна интересная деталь, которая уже может вводить в заблуждение. До этого момента мы привыкли, что константа обозначает реально константу и она никогда не меняется, но посмотрите внимательно на этот пример:
const card = name: 'percent card', key: 'value', >; card.wrongKey; // undefined card.wrongKey = 'something'; card.wrongKey; // something // TypeError: Assignment to constant variable. card = <>;
Мы пишем card.wrongKey , который естественно пишет undefined , потому что он не определён, но после этого мы делаем присваивание (ничего не происходит, всё срабатывает молча) и после этого мы делаем снова вызов и получаем это значение.
И вот тут возникает 2 интересных момента:
- Объекты являются mutable, то есть изменяемыми, что может и не привычно после наших курсов, в которых мы всё делали неизменяемым, но JS – это классический императивный язык, в котором всё изменяемое и при этом с хорошими возможностями функционального программирования.
- Ключ поменял сам объект. И если до этого мы говорили про константу, как о чём-то, что нельзя менять, то получается что константа – это что-то, что можно менять.
Возникает вопрос, а почему это константа?
Константа она только по той простой причине, что вы по имени card не можете записать какой-то объект целиком, то есть вы не можете заменить card полностью.
Если вы перезапишите card чем-то новым (динамический язык позволяет это) – вы получите ошибку:
// TypeError: Assignment to constant variable. card = <>;
Кстати, так можно определять пустой объект const obj = <>;
Но если вы меняете внутренности этого объекта, который вы положили в card , то всё будет работать.
У вас может оставаться какое-то странное ощущение, что константа не совсем константа, но какая-то определенная логика в этом тоже есть и к этому нужно просто привыкнуть. Этот момент будет детальнее разобран в следующем курсе JS: Коллекции.
Объект с функциями
Давайте посмотрим, как использовать наши объекты вместе с функциями.
Функции – это объекты первого рода.
В данном случае под словом «объект» имеется в виду, как некоторая сущность в обычном понимании этого слова. Важно не путать это с ООП.
В объект (тип данных) мы можем записать любую функцию и пользоваться ей.
const card = name: 'percent card', damage: health => Math.round(health * (80 / 100)), >; card.name; // percent card card.damage(10); // 8
Создаём карту и после этого вызываем name и damage .
name – статичное свойство, которое возвращает имя карты damage – вызов метода (на самом деле это свойство, внутри которого записана функция) куда передаётся значение здоровья.
Как видите, такой синтаксис выглядит приятнее, потому что это уже встроенный механизм, но по своему смыслу, семантике и результату действия – это абсолютно то же самое, что мы делали самостоятельно.
Создание объекта
Наши конструкторы и наши объекты превращаются в стандартный механизм, с которым мы работали.
// percentCard.js export const make = (name, percent) => return name: name, damage: health => Math.round(health * (percent / 100)), > >;
У нас есть файл, который представляет из себя модуль. Он экспортирует некий конструктор, внутри которого мы теперь чуть-чуть проще реализуем всю нашу логику.
Мы просто возвращаем объект, как тип данных, внутрь которого мы записываем переданные параметры: в name – имя, а в функцию damage мы передаём проценты исходя из которых будет считаться урон.
С точки зрения интерфейсов ничего не меняется кроме варианта использования. То есть мы теперь не просто вызываем функцию и передаём туда строчку, а берём константу, пишем точку и обращаемся к какому-то свойству.
Динамическая диспетчеризация
Используя стандартный механизм мы получаем диспетчеризацию.
const card = percentCard.make('percent card', 60) // const card = simpleCard.make('simple card', 3) card.name; // percent card card.damage(10); // 6
Диспетчеризация происходит по имени свойства, потому что здесь выбор строится на основе объекта. То есть объект уже сам связан с какой-то конкретной функцией.
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Проверяем, есть ли у нативной JavaScript‑функции манкипатч
Кратко: как можно понять, была ли переопределена нативная JavaScript‑функция? Никак — или не совсем надежно. Способы есть, но полностью доверять им нельзя.
Нативные функции в JavaScript
В JavaScript «нативная функция» — это функция, исходный код которой был скомпилирован в машинный код. Нативные функции можно найти в стандартных встроенных объектах JavaScript (таких, как eval() , parseInt() и т. д.) и веб-API браузеров (таких, как fetch() , localStorage.getItem() и т. д.).
Из-за динамической природы JavaScript разработчики могут переопределять нативные функции, предоставляемые браузером. Этот метод известен как манкипатчинг.
Манкипатчинг
Манкипатчинг, в основном, используется для изменения поведения встроенных API и нативных функций браузера. Часто, это единственный способ добавить специфичную функциональность, полифиллы или «зацепиться» за API, который иначе изменить невозможно.
Например, инструменты мониторинга, такие как Bugsnag, переопределяют интерфейсы Fetch и XMLHttpRequest, чтобы получить представление о сетевых подключениях, запускаемых кодом JavaScript.
Манкипатчинг — это мощный, но опасный метод, потому что код, который вы переопределяете, не находится под вашим контролем: будущие обновления движка JavaScript могут нарушить предположения, сделанные в вашем патче, и вызвать серьезные ошибки.
Кроме того, делая манкипатчинг кода, которым не владеете, вы можете переопределить код, уже содержащий манкипатч другого разработчика, что приведет к потенциальному конфликту.
По этим (и многим другим) причинам может понадобиться проверить, является ли данная функция нативной или она имеет манкипатч… Но получится ли?
Использование toString() для проверки того, что у функции есть манкипатч
Самый распространенный способ проверить, является ли функция «чистой» (то есть, без манкипатча) — это проверить вывод ее toString() .
По умолчанию, нативная функция toString() возвращает что-то вроде строки «function fetch() < [native code] >» :
Эта строка может немного отличаться в зависимости от того, какой движок JavaScript используется. Тем не менее в большинстве браузеров вы можете с уверенностью предположить, что эта строка будет включать подстроку «[native code]» .
Если нативная функция будет иметь манкипатч, ее toString() перестанет возвращать строку «[native code]» в пользу возврата тела функции в виде строки.
Таким образом, простой способ проверить, является ли функция по-прежнему нативной — это проверить, содержит ли ее вывод toString() строку «[native code]» .
Элементарная проверка может выглядеть так:
function isNativeFunction(f) < return f.toString().includes("[native code]"); >isNativeFunction(window.fetch); // → true // Манкипатч fetch API (function () < const < fetch: originalFetch >= window; window.fetch = function fetch(. args) < console.log("Вызов fetch перехвачен:", . args); return originalFetch(. args); >; >)(); window.fetch.toString(); // → "function fetch(. args)
Этот подход отлично работает в большинстве случаев. Однако, его легко обойти, заставив считать, что функция по-прежнему нативна, когда это не так. Будь то злой умысел (например, вредоносное изменение кода) или потому, что кто-то хочет скрыть факт переопределения. Есть несколько способов, которыми вы можете сделать функцию «нативной».
Например, вы можете добавить код (или даже комментарий!) в тело функции, содержащий строку "[native code]" :
(function () < const < fetch: originalFetch >= window; window.fetch = function fetch(. args) < // function fetch() < [native code] >console.log("Вызов fetch перехвачен:", . args); return originalFetch(. args); >; >)(); window.fetch.toString(); // → "function fetch(. args)
…Или вы можете переопределить метод toString() , чтобы он возвращал строку, содержащую "[native code]" :
(function () < const < fetch: originalFetch >= window; window.fetch = function fetch(. args) < console.log("Вызов fetch перехвачен:", . args); return originalFetch(. args); >; >)(); window.fetch.toString = function toString() < return `function fetch() < [native code] >`; >; window.fetch.toString(); // → "function fetch() < [native code] >" isNativeFunction(window.fetch); // → true
…Или вы можете создать функцию с манкипатчем, используя bind, которая генерирует нативную функцию:
(function () < const < fetch: originalFetch >= window; window.fetch = function fetch(. args) < console.log("Вызов fetch перехвачен:", . args); return originalFetch(. args); >.bind(window.fetch); // >)(); window.fetch.toString(); // → "function fetch() < [native code] >" isNativeFunction(window.fetch); // → true
…Или вы сделаете манкипатч функции, перехватив вызовы apply() с помощью ES6 Proxy, что сделает функцию снаружи похожей на нативную:
window.fetch = new Proxy(window.fetch, < apply: function (target, thisArg, argumentsList) < console.log("Вызов fetch перехвачен:", . argumentsList); Reflect.apply(. arguments); >, >); window.fetch.toString(); // → "function fetch() < [native code] >" isNativeFunction(window.fetch); // → true
Остановимся на этом.
Моя точка зрения такова: разработчики могут легко сделать так, чтобы манкипатч не был заметен, если вы проверяете функцию toString() .
Я думаю, зачастую вам не следует заботиться о крайних случаях, описанных выше. Но если захотите сделать это, можете попробовать покрыть их дополнительными проверками.
- вы можете использовать одноразовые iframe, чтобы получить «чистое» значение toString() и использовать его в строгом сравнении;
- вы можете вызвать несколько .toString().toString() , чтобы убедиться, что функция toString() не переопределена;
- вы можете надеть шляпу метапрограммиста и сделать манкипатч конструктора Proxy , чтобы определить, является ли проксируемой нативная функция (потому что, следуя спецификациям, невозможно определить, является ли что-то Proxy );
- и т.п.
Все зависит от того, насколько глубоко вы хотите проникнуть в кроличью нору toString() .
Но стоит ли? Можно ли охватить все крайние случаи?
Получение чистой функции из iframe
Если вам нужно вызвать «чистую» функцию (вместо того, чтобы проверять, есть ли манкипатч у нативной функции) — можно получить ее из iframe с таким же источником (same-origin):
// Создаем новый iframe с тем же источником. // Вы, вероятно, захотите добавить некоторые стили для его скрытия и, в конечном итоге, удалить его из DOM позже. const iframe = document.createElement("iframe"); document.body.appendChild(iframe); // Новый iframe создаст свой собственный "чистый" объект window, так что вы сможете захватить интересующую вас функцию оттуда. const cleanFetch = iframe.contentWindow.fetch;
Хотя я думаю, что этот подход все же лучше, чем проверка функции с помощью toString() , но он имеет некоторые существенные ограничения:
- Из-за строгой политики безопасности контента (CSP) или из-за того, что ваш код не работает в браузере, iframe может быть недоступен.
- Хоть это и маловероятно, другая сторона может сделать манкипатч API iframe. Таким образом, вы все еще не можете на 100% доверять объекту window созданного iframe.
- Нативные функции, которые манипулируют DOM (например, document.createElement ), не будут работать с этим подходом, потому что они будут нацелены на DOM созданного iframe, а не на родительский.
Данное решение было предложено в треде lobstre.rs.
Использование сравнения ссылок для проверки того, что у функции есть манкипатч
Если безопасность является первостепенной задачей, я думаю, следует использовать другой подход: сохранить ссылку на «чистую» нативную функцию и позже сравнить с ней функцию с потенциальным манкипатчем:
Используя строгое сравнение ссылок, мы избегаем всех лазеек toString() . Это работает даже при использовании Proxy , потому что оно не может перехватить сравнение .
Главный недостаток этого подхода заключается в том, что он может быть непрактичным. Он требует сохранения исходной ссылки на функцию перед запуском любого другого кода в вашем приложении (чтобы быть уверенным, что она все еще не тронута), что иногда вы не сможете сделать (например, если вы создаете библиотеку).
Могут быть способы сломать этот подход, но на момент написания мне не было известно о них. Дайте знать, если я что-то упустил!
Итак, как понять, была ли переопределена нативная функция JavaScript?
Я могу посчитать на пальцах одной руки, сколько раз мне нужно было проверить, есть ли у функции манкипатч.
Так или иначе, мое мнение (или, лучше, «предположение») касательно этого вопроса заключается в том, что, в зависимости от варианта использования, может и не быть надежного способа определить манкипатч.
- Если вы контролируете всю веб-страницу, вы можете написать свой код заранее, сохранив ссылки на функции, которые хотите проверить, когда они еще «чисты», и сравнить их позже.
- В противном случае, если вы можете использовать iframe, вы можете создать скрытый одноразовый iframe и взять оттуда «чистую» функцию — понимая, что вы все еще не можете быть на 100% уверены, что iframe API не имеет манкипатча.
- Иначе, учитывая динамическую природу JavaScript, вы можете либо использовать простую проверку toString().includes("[native code]") (понимая, что злоумышленники могут легко остаться незамеченными), либо добавить массу проверок безопасности, чтобы покрыть большинство (но не всех) пограничных случаев.
Дальнейшее чтение и связанные ресурсы
- StackOverflow: Is there a way to check if a native Javascript function was monkey patched?
- StackOverflow: Detect if function is native to browser
- StackOverflow: How to determine that a JavaScript function is native (without testing ‘[native code]‘)
- David Walsh: Detect if a Function is Native Code with JavaScript