Javascript В Typescript – Трудности Перевода

Многие наверняка знают, что в JS довольно ограниченная реализация ООП.

Некоторых людей устраивает уровень ООП в JS, другие не видят необходимости придерживаться правил ООП, третьи не могут писать код без ООП.

Здесь мы попытаемся разобраться в некоторых нюансах перехода с JS на TS без холивара.

О мотивации перехода мы поговорим в конце статьи и скорее всего для тех, кто понимает важность качества кода.

Но сначала скажем несколько слов.

Когда вы делаете небольшой тестовый код, с непонятным коммерческим статусом, то вряд ли вы этот код вылизаете.

А ООП — хороший способ отшлифовать код; это не влияет на функциональность вашего кода; наоборот, это часто задерживает быстрое написание тех фич, которые вы решили сделать.

Иногда страдает даже производительность.

Но наверное каждый знает тот уровень, когда ему уже сложно разобраться в своем коде, тогда начинаешь смотреть на него и время от времени задумываться о рефакторинге.

Если ваш язык интерпретируемый, без строгой типизации и недостаточно хорошо поддерживает ООП, то вы надолго оттянете этот момент — но я все же рекомендую об этом подумать.

Если у вас язык JS, хорошим вариантом будет перевести его на TS, вы ничего не потеряете, это точно.

Но есть некоторые сложности, из-за которых в процессе перевода вы можете усомниться в правильности такого решения.



Глобальные переменные — зло

Если вы уже решили придерживаться ООП, откажитесь от глобальных переменных ВООБЩЕ.

В JavaScript для этого иногда используют директиву use strict; В TypeScrip просто не объявляйте внешние переменные с помощью объявления var. Делайте объявления только внутри классов, по крайней мере, как MyVar: Any. Наверное, каждый может рассказать историю о том, почему глобальные переменные — это зло.

Я расскажу тебе свое.

Вы можете сказать, что я по ошибке объявил одну переменную, используя объявление var xmlhttp. Ну тогда мне казалось, что TypeScript ничего не знает о нем как о классе, а значит, он был кандидатом на внешнюю переменную.

Генератор TypeScript проигнорировал это объявление при переводе его в JavaScript, и поэтому оно стало глобальной переменной.

Ну а поскольку эта переменная содержала ссылку на XMLHttpRequest, обеспечивающую асинхронное получение данных с сервера, то, конечно, это потом приводит к ошибке, как только вы одновременно получаете через эту переменную разные типы данных.

Более того, некоторые браузеры нивелируют это; код даже сможет работать, но будет существенно тормозить и возникнут сбои из-за синтаксических ошибок JS. Вы не всегда проверяете свой код перед использованием переменной, чтобы убедиться, что она установлена?

Порядок скриптов при наследовании

TypeScript поддерживает истинное наследование; чем оно отличается от наследования JS, мы обсудим позже.

Это объявляется так:

  
  
  
  
  
   

/// <reference path="RequestData.ts" /> module CyberRise { export class Menu extends RequestData { } }

Объявлен класс Menu, наследуемый от RequestData в модуле CyberRise. В JS это генерируется ломающей зубы конструкцией, которую я здесь описывать не буду.

По сути, существует трёхуровневая вложенность функций (и даже больше), цель которой — разграничить области видимости.

Используя модули, нам не нужно беспокоиться об одинаковых именах классов в разных библиотеках.

Но на самом деле каждый разработчик любит использовать часто используемые имена, даже такие, как Window. Вы также можете заметить ссылочную конструкцию в начале файла.

Дело в том, что TypeScript проверяет соответствие типов и ему нужно знать все о типах на этапе компиляции (генерации JS).

Опять же, преимущества строго типизированных языков, думаю, все знают, отмечу самое непосредственное — проверка ошибок, связанных с некорректным использованием объектов на этапе компиляции.

Это значительно экономит время отладки в довольно больших проектах.

Итак, для любого знакомого с C и ссылочная конструкция, хотя формально и находится под комментарием, является аналогом include. И самое главное, код перестанет работать, если подключить скрипты не в том порядке.

Это не работает следующим образом: тип сценария="text/javascript" src="js/Menu.js"> тип скрипта="text/javascript" src="js/RequestData.js"> и вот как это работает: тип скрипта="text/javascript" src="js/RequestData.js"> тип сценария="text/javascript" src="js/Menu.js"> Ну вообще-то это похоже на C++, там тоже включения должны быть в определённом порядке.

Только поскольку в TypeScript не делается объявление скриптов, он не может это проверить на этапе компиляции и можно долго находиться в прострации, не понимая, почему ваш код не выполняется.

Хотя есть вариант, когда весь код скомпилирован в один js файл, тогда компилятор TS сам гарантирует правильный порядок кода.

Но для серьезного проекта это может быть не удобно, потому что.

Посмотреть JS-код пофайлово все-таки удобнее.

Придерживайтесь стиля «один файл — один класс».

обновление.

Если вы используете приложение HTML с проектом студии TS, то опять же в этом нет необходимости, но я, например, использую проект ASP.NET - он ничего не знает о TS, кроме того, что я говорю ему, как генерировать в пост-сборке.

Поэтому я думаю, если у вас есть хотя бы более-менее сложный проект (а иначе зачем переходить на TS?) или вы вообще не используете проект, то вы не будете использовать такой проект, как HTML Application with TS.

Обратные вызовы, делегаты и другие синонимы

С присвоением ссылки на функцию, т.е.

с созданием обратного вызова тоже есть некоторая путаница.

Но они скорее дисциплинируют, но адептам JS могут показаться противоестественными.

Те, кто привык к JS, я думаю, часто передают ссылки на функции, даже не задумываясь о безопасности этого.

Например, в .

NET намеренно запрещена прямая работа со ссылками, поскольку это часто приводит к ошибкам.

Но при использовании ООП в этом часто нет необходимости; вместо этого передаются ссылки на объекты, а затем принимающий объект использует открытую часть класса, используя необходимые ему члены класса.

Еще лучше, если класс реализует интерфейс и передается ссылка на интерфейсы.

К сожалению, в JS это не так, и поэтому там часто используют небезопасную передачу ссылок на функции.

Итак, код типа:

xmlhttp.onreadystatechange = this.OnDataProcessing;

У вас больше не будет работы.

Это будет точнее, но не в том контексте, т.е.

нужно использовать JS-костыль

xmlhttp.onreadystatechange = this.OnDataProcessing.bind(this);

Все дело в том, что теперь на уровне класса больше нельзя объявлять локальные переменные с помощью var. Теперь все, что вы объявляете на уровне класса =, — это свойства, используемые через это.

Поэтому если раньше на такой костыль закрывали глаза, то теперь он выглядит ещё более неестественно.

Поэтому было бы лучше использовать анонимные методы, чтобы устранить навязчивую тень необъектного JS.

this.xmlhttp.onreadystatechange = () => { alert(this.xmlhttp.responseText); }



Внешние вызовы

Мы часто уже используем библиотеки, разработанные на JS. И там часто предлагают использовать уже разработанные объекты-функции.

Если они вызываются через new, вот так

win = new Window({ className: "mac_os_x", title: locTitle, width: 1000, height: 600, destroyOnClose: true, recenterAuto: false });

Компилятор TypeScript скажет вам, что он ничего не знает о таком классе Window (он думает, что это класс :)).

Действительно, мы наверняка обнаружим, что класс Window уже объявлен.

Но это будет не тот класс и не из библиотеки, которую мы имеем в виду.

Но если мы использовали модули, то все будет хорошо.

И чтобы компилятор понял, что это за класс, нам нужно описать сигнатуру аналогично тому, как мы это делаем с помощью методов из dll. Написав:

declare var Window: new (a: any) => any;

компилятор будет отставать от нас, понимая, что мы все что угодно передаем в качестве параметра и что-то возвращаем.

К счастью, типы можно указать более точно.



Мотивация

У каждого может быть своя мотивация такого перехода с JS на TS. И говоря об этом, мы рискуем обмануть.

Поэтому я ограничусь тем списком, который мне важен, когда я начну структурировать код и для этого мне понадобятся инструменты (хоть многие и сочтут это синтаксическим сахаром, но за ним стоит не только синтаксис, но и глубокий реализация (или не реализация) понятий в языке): 1. За всеми попытками разделения данных на модули, классы, объекты, наличием наследования стоит простое желание разграничить зоны ответственности свойств и методов, а также разграничить область действия переменных на более глубоком уровне.

Для этого TS получается, как может, используя единственную для этого возможность в JS с помощью замыканий для инкапсуляции данных.

Так он вводит понятие класса — как функции внутри функции, где одна функция — это статические данные/методы/конструкторы на уровне класса, а во вложенной функции — данные объекта.

2. Получаем строгую типизацию 3. Получаем такой бонус как полную поддержку конструкторов в классах.

Без них надо изобретать т.н.

фабрики с методами init() 4. Наследование становится не реализацией агрегации, как по умолчанию в JS, а настоящим наследованием, с контролем над назначением нужных конструкторов в потомках.

Почему наследование лучше агрегации? Да, это не лучше, но это часто необходимо, когда вы разрабатываете и переопределяете поведение ряда базовых классов.

И тогда семантически эта же вещь является типом чего-то (is a), а не частью чего-то (частью) - без разделения этой семантики все превращается в одну кашу, где становятся отношения использования, агрегации, наследования.

один и тот же.

5. Получаем еще один бонус — интерфейсы, как описательную часть классов.

Иногда сложно объяснить их важность, и я не буду пытаться.

Те, кто умеет ими пользоваться, оценят это, другие нет. Скажу только одно: множественное наследование реализовано с помощью интерфейсов, и можно легко разделить сложный публичный интерфейс, входящий в состав целого класса, на ряд связанных интерфейсов, а затем передавать ссылки не всему классу, а ссылкам.

к выделенному интерфейсу.

6. еще мелочь — есть перечисления 7. Еще приятно, что есть т.н.

необязательные параметры, т. е.

те, которые не нужно передавать в функции, и, что более важно, те, которые не нужно реализовывать в интерфейсе.

Иногда это, наверное, даже лучше, чем в C# — где нужно уйти от реализаций формы return null (на что мой начальник как-то пошутил, что если бы он умел делать такие реализации в коммерческом смысле, то возвращался бы как реализация [здесь это что-то нечитабельное], тогда клиенты будут особенно рады :) ).

Пожалуй, это все, если вы оцените, через некоторое время я напишу о сложностях перевода базы данных — миграции с MySQL на MS SQL Server. обновление.

Народ, объясните некоторым ниже, кто говорит с большим апломбом (особенно высказывая свои заблуждения), чем интерфейс отличается от этого

Вы получите то же самое, просто пометив членов класса как публичные\частные.

и мне не интересно затевать подобный холивар/троллинг, если есть вопросы, пишите мне в личку и я отвечу.

В данном случае Declare ничего не объявляет, а указывает компилятору, что во время выполнения будет существовать переменная с этим типом, чтобы он не жаловался на ее использование.

И что? В статье прямо указано, что это может привести к использованию глобальной переменной.

Непонятно как - перечитайте статью.

Теги: #JavaScript #typescript #разработка сайтов #программирование #дизайн и рефакторинг

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.