На сегодняшний день существуют большое количество JS-фреймворков, библиотек и тому прочее. Казалось бы, выбираешь крупный и надёжный фреймворк, и пишешь свой интерфейс. Но, во-первых, у разных фреймворков различный подход к написанию кода. Каждый предлагает свой синтаксис и свои фичи, по решению различных частных задач, наподобие создания элементов по шаблону, внедрение хуков, ссылок, данных. Во-вторых, у каждого есть свои зависимости. И размер всех зависимостей порой доходит аж до гигабайтов. В итоге, появляется желание написать свой, очередной, лучший фреймворк. В данной статье перечислены начальные шаги, и проблемы, с которыми Я столкнулся, когда решил написать для себя очередной, универсальный инструмент, для создания интерфейса веб-приложения. В статье не используются современные решения и стандарты. Хотя весь код был написан под браузер Chrome последней версии, автор ориентируется на API, стандартизированный спецификацией ECMAScript 5. Кроме того, в данной части статьи код использует API не совместимый с браузером IE (Internet Explorer). В следующих частях будет описано, как устранить несовместимость с IE. Во-первых, для начала решим, из чего будет состоять фреймворк. Можно тупо писать  всё в один файл. В итоге, можем закончить с файлом, состоящий из 100 тысяч строк кода. Мы можем разделить всё на отдельные файлы, каждый файл представить в виде отдельного модуля, и определить один главный файл, который всё подтянет. Окей, давайте же сделаем это. Напишем index.html с нашим главным скриптом: "widgets-all.js". В тэге <body> зададим <div> с идентификатором root. Данный <div> будет контейнером приложения. А через тэг <style> зададим данному элементу абсолютное позиционирование, и координаты верхнего, левого угла. А теперь сделаем так, чтобы наш "widgets-all.js" загружал модули последовательно и синхронно. И вот тут возникает ПЕРВАЯ ПРОБЛЕМА. Если мы динамически создаём тэги <script> и внедряем их в документ, то некоторые браузеры загружают их асинхронно, несмотря на то, что мы явно устанавливаем атрибут "async" в значение false. А это значит, что скрипт может загрузиться позже, чем этого хотелось. В результате из-за зависимостей у нас будет классическая ошибка "varialbeName is not a function". Самый простой способ динамически загрузить скрипты синхронно и последовательно, это использовать метод документа - document.write(htmlStr).  Напишем данный код Отлично. Теперь после нашего "widgets-all.js" будут добавлены два тэга <script>. Если же после самого "widgets-all.js" были другие тэги <script>, то они не затрутся, поскольку документ ещё не загружен. Когда документ загружен, то вызывать document.write нельзя, иначе можно стереть весь контент страницы. Следует отметить, что данное решение - костыль, так как согласно спецификации, некоторые браузеры будут игнорировать вставку и исполнение кода переданных тэгов <script>. Но оно работает в большинстве современных браузеров до сих пор. Итак, построим скелет одиночного модуля. Модуль должен возвращать открытые функции и свойства, с которыми может взаимодействовать другие модули, и не должен давать доступ к закрытому функционалу. Закрытость можно обеспечить функцией. Ниже дан следующий синтаксис, как это сделать И тут, разумеется, ВТОРАЯ ПРОБЛЕМА. НАИМЕНОВАНИЯ МОГУТ БЫТЬ НЕ УНИКАЛЬНЫМИ. Самый простой случай, это когда мы пытаемся подключить различные версии своей же библиотеки, в которых имена модулей не менялись. Добавим проверку учёта имени, а также определим функцию разрешения конфликтов. Мы переименовали локальную переменную, поскольку теперь нам нужно проверять глобальную. Если глобальная переменная уже определена, то сохраняем её старое значение в локальную переменную "_____module1". А с помощью открытого метода resolveConflict возвращаем её значение в глобальный контекст. А чтобы не потерять её предыдущее значение, возвращаем его этой же функцией. Но помимо разрешения имён, у нас есть ещё одна проблема. Нам необходимо, чтобы скрипт, который использует нашу библиотеку, запускался тогда и только тогда, когда уже документ загружен. Сейчас же доступность тэгов (элементов) документа напрямую зависит от позиции скрипта в данном документе.  Состояние загрузки документа определяется через свойство document.readyState.  "loading" - документ загружается. Не все тэги были разобраны. "interactive" - синтаксический анализ документа завершён. К этому моменту, все тэги были распознаны и вставлены в DOM. Однако, содержимое элементов <iframe> и <img>, <script> по src ещё не загружено. "complete" - документ полностью загружен. Нам нужно, чтобы скрипт приложения, который будет использовать нашу библиотеку, запускался в состоянии "interactive" или "complete". Разумеется, можно пойти простым путём и поместить скрипт в качестве последнего дочернего элемента тэга <body>, а содержимое с виджетами класть в элемент root.  Но если вы хотите поместить свой скрипт в тэге <head> перед <body>, то чтобы он корректно работал с документом, его необходимо запускать в состоянии "interactive" или "complete".  Ниже дан код, как это сделать. Теперь достаточно вызвать открытую функцию модуля Widgets.onReady и передать ей функцию, которая будет вызвана, когда документ будет обработан. Добавим простой тест в файл index.html. Для этого определим два тэга <script>, один перед фреймворком, другой после него. Первый определит глобальные имена, совпадающие с именами модулей. Второй будет содержать код приложения, который будет использовать фреймворк. В результате, у нас должен получиться следующий вывод: Разобравшись с загрузкой и разрешением имён модулей, теперь можно подумать о самих элементах модуля. Что есть компонент? Как его создавать? Как с ним работать? Что он содержит? Определим компонент как экземпляр класса, а класс - определение компонента, его схему (чертёж). Классы можно реализовать с помощью функций конструкторов, если не брать в расчёт современные стандарты (ES6). Будем реализовывать класс вручную. Следовательно, экземпляры класса будут создаваться вызовом функции конструктора через оператор "new". Хорошо, но что будет относится к самому классу, а что к конкретному экземпляру? Самый простой пример, имя класса - это свойство класса, а не экземпляра. Например, имя класса Человек - "Человек" общее для всех экземпляров людей, у которых может быть собственное имя (Джон, Смит, Билли). И где и как хранить свойства класса? Свойства класса - объект. Этот объект должен быть доступен, для извлечения метаданных класса. Хранить его мы будем не как отдельное свойство функции конструктора, а как свойство самого объекта-модуля. Определим, какие свойства будет содержать данный объект: className - имя класса callConstructor - функция-конструктор класса callParent - функция-конструктор родительского класса beforeCreate - функция-валидатор, вызывается перед созданием экземпляра класса, проверяет аргументы, переданные в конструктор, читая constructorParameters. noDefaultConstructor - флаг, указывающий запрещено ли использовать конструктор по умолчанию. Т.е. конструктор без параметров. constructorParameters - объект, описывающий параметры конструктора.  Свойства объекта - имена параметров. Каждый параметр представлен отдельным объектом со следующими свойствами: required - обязателен ли параметр. type - тип параметра в виде строки. oridnal - позиция в массиве аргументов функции-конструктора. getter - функция преобразователь. Преобразует параметр произвольного типа в соответствующий тип, указанный в type, если это возможно. count - число экземпляров данного класса. getCount - метод, возвращающий значение count. Описав свойства класса, мы можем написать функцию, которая создаст объект, представляющий класс, от которого можно создавать экземпляры. Но перед этим, мы должны решить три вопроса: будут ли переопределяться одинаковые имена в дочерних классах? Надо ли вызывать конструктор родительского класса самому напрямую, или же пусть этим займётся фреймворк (автоматически вызывая конструктор родителя при вызове конструктора дочернего класса)? Если переопределять метод, должны ли мы включать логику старого метода родителя, или мы его полностью стираем? Переопределение свойств из родителя разрешим только для примитивных значений, а также функций. При переопределении функций сохраним логику родителя таким образом, чтобы при вызове переопределённого метода дочернего класса, сначала вызывался метод родителя. Конструктор родительского класса будет вызываться автоматически. Окей, пойдем реализовывать. Начнём с имён классов и переопределения методов. С помощью функции type мы пытаемся получить имя функции-конструктора объекта, или имя класса из объекта класса. Каждый экземпляр класса имеет ссылку на объект самого класса в виде свойства self. Также была определена функция isSubClassOf, которая определяет является ли объект экземпляром класса, указанным по имени. Объект является экземпляром класса A, если он либо экземпляр самого класса A, либо экземпляр одного из его дочерних классов. Если это примитив, то функция проверяет,  совпадает ли его тип с указанным типом. Эти функции используются в методе overrides, который переопределяет методы и свойства дочернего класса, если указанные методы и свойства если Они уже определены в родительском классе. Они совпадают по типу и не являются объектом. Они являются экземпляром дочернего класса родительского экземпляра. Т.е. они совместимы по классовому типу. Теперь, можно определить функцию определения классов - defineClass Эта функция создаёт объект класса, с помощью которого можно будет создавать экземпляры самого класса (через свойство callConstructor или beforeCreate). Определим же функцию создания экземпляра класса, а также вызова его родительского конструктора - callParent. Функцию beforeCreate, определённую выше, можно использовать как общий валидатор аргументов функции конструктора класса для функции defineClass. А с помощью функции callParent, можно инициировать вызов конструктора прямого родителя. Теперь определим  классы и их функции конструкторы. Напишем две функции конструктора, для двух классов. Определим родительский класс Container и его дочерний класс Panel следующим образом. Данные классы определены в panels глобального модуля Widgets. Функция create ищет классы в объекте Widgets, извлекает из объекта класса функции beforeCreate и callConstructor. Если есть beforeCreate, то вызывается она, иначе вызывается конструктор (callConstructor). Теперь создадим экземпляры классов. В итоге на странице в веб-браузере получим следующий вывод. (Браузер Chrome). Теперь есть возможность создавать классы. Можно добавить открытую функцию определения классов, наподобие той, что написана ниже. Все определяемые классы, а точнее их объекты (мета-данные) храним в глобальном объекте Widgets. С помощью пар функций create/define можно создавать классы из объектов в Widgets, либо определять новые классы в объекте Widgets. Каждый отдельный класс представлен объектом. А через defineClass - напрямую определить класс, передав ему функции конструкторы родительского и дочернего классов, свойства экземпляров и самого класса, а также функцию-валидатор параметров конструктора вместе с их описанием. Всё это имеет следующие ограничения: Необходимо придерживаться чёткого порядка параметров в функции конструкторе. В каждом следующем дочернем классе по иерархии у функции конструктора сначала перечисляются все параметры функции родителя, начиная с самого общего предка. Например следующий порядок A(p1, p2) -> B(p1, p2, p3) -> C(p1..p3, own4, own5) обозначает три класса, где А родитель В, и С - потомок В. В должен перечислить параметры А, перед тем как объявить собственные, С - тоже самое, начиная с параметров А, и заканчивая параметрами В. Строка со стилем и обыкновенная строка по типу неразличимы, однако можно указать, что параметр имеет другой тип, отличный от строки. И тем не менее, необходимо писать обработку таких строк, для корректной инициализации аnрибута style. Пока приходится писать getters/setters вручную, поскольку логика обработки значения для каждого свойства может быть разной.  Нет кросс-браузерности.  Совместимость с IE никакая. В следующей части будет описано создание простого виджета - панели, о том как решить проблему с getters/setters, а также об управлении размещении компонентов внутри контейнера (панели) - layouts. А также обсудим проблему с IE и со строками стилей.