Краткий обзор JavaScript

Что нужно знать о JavaScript’е?

Краткий обзор главных возможностей JavaScript, в том числе обсуждение различных видов объектов, основных структур данных, функция как объект первого класcа, а также шаблоны проектирования для реализации классов.

Введение

JavaScript это динамический функциональный объектно-ориентированный язык программирования, который может использоваться для:

  1. Обогащения веб-страниц через:
    • генерацию специфического для браузера HTML контента или CSS стилизацию,
    • динамическую вставку HTML контента,
    • произведение специальных аудио-визуальных эффектов(анимации).
  2. Обогащение веб-интерфейса пользователя через:
    • реализацию продвинутых компонентов пользовательского интерфейса,
    • проверку пользовательского ввода на стороне клиента,
    • автоматическое предварительное заполнение полей формы.
  3. Реализации фронт-енд части веб приложений с локальными или отдалеными хранилищами данных, как это описано в книге Building Front-End Web Apps with Plain JavaScript.
  4. Реализации фронд-енд компонентов для распределенного веб-приложения с удаленным хранилищем данных, которое управляется бек-енд компонентом, который является частью серверной программы, традиционно написаной на серверном языке программирования, таком как php, Java или C#, но в настоящее время может быть написаный на JavaScript благодаря NodeJS.
  5. Реализации полностью распределенного приложения, в котором и клиент и сервер написан на JavaScript’е.

Текущая версия JavaScript’а, которая в настоящее время поддерживается веб-браузерами называется «ECMAScript 5.1», или просто «ES5», но следующие две версии названые «ES6″ и»ES7» (или «ES 2015″ и»ES 2016», поскольку новые версии планируются на ежегодной основе), с большим количеством дополнительных возможностей и улучшенным синтаксисом, находятся в двух шагах ( и уже частично поддерживаются текущими браузерами и серверными JS средами).

Эта статья была взята из книги Building Front-End Web Apps with Plain JavaScript, которая доступная онлайн в открытом доступе. Это попытка собрать все важные моменты с classical JavaScript summary написаной Дуглас Крокфорд.

Типы данных

JavaScript содержит три примитивных типы данных:
string, number и boolean, проверить тип переменной v
мы можем с помощью typeof(v), например typeof(v)==="number"

Есть также пять основных ссылочных типов
данных: Object, Array, Function, Date и
RegExp. Массивы и функции — это просто специальные виды объектов, но,
концептуально, даты и регулярные выражения — это примитивные значения,
и так случилось, что они реализованы в виде объектов-обгорток.

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

Переменная может принимать следущее значения

  1. значения данных: значения типов string, number или boolean;
  2. ссылку на значение данных: ссылка на обыкновенный объект, массив, функцию, дату или регулярное выражения
  3. специальное значение null, которое обычно используется как значение по умолчанию при инициализации переменной ссылочного типа
  4. специальное значение undefined, которое является неявно начальным значениям всех переменных, которые были обьявлены, но не инициализированы.

string представляет собой последовательность Unicode символов. Строковые литералы, например «Hello world!», ‘A3F0’, или пустая строка «», заключаются в одинарные или двойные кавычки. Два строковых выражения можно объединять(конкатенация) с помощью оператора +, и проверять на равенство с помощью оператора строгого равенства:

Количество символов в строке можно получить с помощью свойства length:

Все числовые значения представляются в 64 битном формате с плавающей точкой и с возможным экспонентом (например 3.1e10).
Нет явного различия между значениями целых чисел и чисел с плавающей точкой. Если значение не может быть преобразовано в число, ему присваивается значение NaN («not a number» — «не число»), которое может проверятся с помощью встроенного предиката isNaN(
expr).

К сожалению, встроенная функция Number.isInteger, которая проверяет целое ли число, была введена только в ES6, так что для ее использование в браузерах, которые ее не поддерживают, нужен полифилл. Чтобы убедится, что числовое значение есть целым или что строка приводится целому числовому типу, можно использовать встроеную функцию parseInt. Точно также можно привести строку к вещественному типу с помощью функции parseFloat. Для приведения числового значения к строковому лучшим вариантом будет использование функции String(n).

Как и в Java, есть два предопределенных Boolean литералы, true и false, и логические операторы, представлены восклицательным знаком ! для NOT, двойным амперсантом && для AND, и двойной вертикальной линией || для OR. Когда не логическое значение используется в условии или как операнд в логическом выражении, оно неявно преобразуется в значение Boolean в соответствии следующим правилам. Пустая строка, числовое значение 0, а также значения undefined и null преобразуются в false, все другие значения преобразуются в true. Эти преобразования могут быть выполнены и в явном виде с помощью двойного оператора отрицания !!.

К дополнении к строкам, числам и логическим значением, также дата и время важный примитивный тип данных. Хотя они не реализованы как примитивные значения, представлены в виде обёртки в типе Date. Обратите внимание что объекты Date, по сути, не представляют собой даты, а скорее моменты времени представленые в миллисекундах, отчисленые от 1 января 1970 года го Гринвичу. Для преобразования внутреннего значения объекта Date к читабильному виду, мы можем использовать несколько способов. Два важных способа — это использования либо стандартный ISO формат даты и времени в виде «2015-01-27», или в формате локального времени, например «27.1.2015»(для простоты, мы упустили время в наших примерах). Когда x instanceof
Date
, x.toISOString() даст нам строку в формате ISO , а x.toLocaleDateString() даст локальное представление даты и времени в виде строки. Стоит отметить, что если строка представляет дату ds в ISO формате или локальном, с помощью new Date(ds) создается соответствующий объект даты.

Для сравнения на равность или неравность всегда используйте оператор строгого сравнения === и !== в место простых == и !=. В противном случае, например, при сравнивании числа 2 с такой же строкой «2», выражение (2 == "2") вернет истинное значение.

Объявление пустого массива как var a = [] — это тоже самое, но более краткая форма, чем вызов конструктора Array() без параметров, пример var a = new Array()

Объявление пустого объекта как var a = {} - это тоже самое, но более краткая форма, чем вызов конструктора Object() без параметров, пример var a = new Object(). Надо заметить, что пустой объект {} не совсем пустой, он содержит свойства и методы унаследованы от Object.prototype. Таким образом, для того что бы создать действительно пустой объект (без унаследованых свойств), он должен иметь null в качестве прототипа, var emptyObject =
Object.create(null)
.

Тестирования типов
Тип Пример значения Проверка принадлежности значения x к типу
string «Hello world!», ‘A3F0’ typeof(x)==="string"
boolean true, false typeof(x)==="boolean"
(плавающая точка) number -2.75, 0, 1, 1.0, 3.1e10 typeof(x)==="number"
integer -2, 0, 1, 250 Number.isInteger(x)*)
Object {}, {num:3, denom:4}, {isbn:»006251587X,» title:»Weaving the Web»}, {«one»:1, «two»:2,
«three»:3}
без учетаnull: x instanceof Object

с учетом null: typeof(x) === "object"

Array [], [«one»], [1,2,3], [1,»one», {}] Array.isArray(x)
Function function () { return "one"+1;} typeof(x)==="function"
Date new Date("2015-01-27") x instanceof Date
RegExp /(\w+)\s(\w+)/ x instanceof RegExp
Приведение типов
Тип Конвертация в строку Конвертация строки в значение типа
boolean String(x) Boolean(y)
(вещественные числа) number String(x) parseFloat(y)
integer String(x) parseInt(y)
Object x.toString() or JSON.stringify(x) JSON.parse(y)
Array x.toString() or JSON.stringify(x) y.split() or JSON.parse(y)
Function x.toString() new Function(y)
Date x.toISOString() new Date(y)
RegExp x.toString() new RegExp(y)

Область видимости переменных

В текущей версии JavaScript, ES5, есть только две области видимости переменных: глобальна область видимости (с window в качестве контекстного объекта) и область видимости внутри функции, но не как блочная область видимости. Следовательно, объявление переменной внутри блока сбивает с толку, это нужно избегать. Например, хотя это часто использованный шаблон, даже опытных javascript программистов, все попадается в ловушку при объявлении переменной в цикле как в следующем коде

В место того, как и JavaScript интерпретирует этот код (с помощью «подъемных»(hoisting) объявлений переменных), мы должны писать:

Все переменные должны быть объявлены в начале функции. Только следующая версия JavaScript, ES6, будет поддерживать блочную область видимости с помощью нового способа объявления переменной с ключевым словом let.

Строгий режим

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

Мы можем включить стогий режим с помощью следующего выражения, поместив его в первую строку JavaScript файла или внутрь <script> элемента:

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

Типы объектов

JS отличаются от классических OO/UML объектов. В частности, они могут не являтся экземпляром класса. Также они могут иметь свои собственые (на уровне экземпляра) методы, представлены в виде слотов методов, по этому они имеют не только слоты свойств, а также слоты методов. Следовательно, они могут иметь три вида слотов, в то время как классические объекты только слоты свойств.

JS объект является по существу набором пар имя-значение, также называемыми слотами, когда имена могут быть названиями свойств, функций или ключей набора. Объекты могут быть созданы используя JSON (JavaScript’s object literal notation), без создания екземпляра класса:

Всякий раз, когда имя в слоте является допустимым JavaScript идентификатором, слот может быть либо слотом свойства, слотом метода или слотом ключ-значения. В других случаях, если имя представлено другим типом строки(в частности, когда она содержит любое количество пробелов), тогда слот представлен слотом ключ-значение, который является элементом ассоциированного массива, как описано ниже.

Имя слота свойства может обозначать либо:

  1. свойство значения примитивного типа, в этом случае значение в это значение данных или, в более общем плане, выражением значений данных; или
  2. свойством значения ссылочного типа, в этом случае значение это ссылка на объект или, в более общем плане, выражением объекта.

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

Свойства объекта можно получить двумя способами:

  1. Используя точковую анотацию (привет от C++/Java):
  2. Используя скобочную аннотацию:

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

  1. Запись представлена набором свойств, например,
  2. Карта (также называемая ‘ассоциативным списком’, ‘словарем’, ‘хеш картой’ или ‘хеш таблицей’ в других языках программирования) поддерживает связки значений базирующих на ключах, например,

    в которых значение «1» ассоциируется с ключем «one», «2» с «two», и т.д.. Ключи не должны быть валидными JavaScript идентификаторами и приставляются в виде строки (т.е. могут содержать пробелы).
  3. Нетипизированные объекты не могут быть екземплярами классов. Они могут содержать слоты свойств и методов, например

    В теле слота метода объекта, специальная переменная this ссылается на этот самый объект.
  4. Пространства имен могут быть описаны в виде нетипизированого объекта, который ссылается на глобальную переменную, имя которой представляет префикс пространства имен. Например, следующая переменная обеспечивает главное простанство имен для приложения базирующее на
    Model-View-Controller (MVC) шаблоне проектирования, тогда мы имеем три суб-пространства соответствующих трем частям MVC приложения:

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

Массивы

Массивы в JavaScript представляют, по сути, логическую структуру данных список-массив, в котором для доступа к элементам списка можно обратится по индексу (на подобе элементов массива). Использование термина ‘массив’ вместо ‘JS массив’ создает терминологическую неоднозначность. Но для простоты, мы иногда будем просто говорить ‘массив’ вместо ‘JS массив’.

Переменная может быть инициализирована с помощью литерала массива:

Поскольку они представляются как список-массивы, JS массивы могут расти динамически: это позволяет нам использовать индексы, которые будут больше чем длина массива. Например, после инициализации переменной массива выше, массив что хранится в переменной a будет иметь длину 3, но мы все же можем присвоить пятый элемент массива

Контент массива a можно обработать с помощью стандартного цикла for и переменной счетчика, считая от первого элемента массива с индексом 0 и до последнего с индексом a.length-1:

Поскольку массивы являются объектами специального типа, нам иногда нужно узнать является ли переменная массивом. Мы можем проверить, если переменная a представляет собой массив с помощью Array.isArray( a).

Для добавления нового элемента в массив, мы присоединяем его к массиву используя операцию push:

Для удаления элемента с индексом i с массива a, мы используем предопределенный метод массива splice:

Для поиска значения v в массиве a, мы можем использовать предопределенный метод массива indexOf, который возвращает номер позиции, если элемент найден, или -1, в других случаях:

Для перебора элементов массива a, мы имеем два способа: либо использовать цикл for, или метод перебора элементов массива forEach. В любом случае, мы можем использовать цикл for:

Если производительность не важна, то есть размер массива a достаточно мал (скажем, он может содержать не больше нескольких сотень элементов), мы можем использовать метод forEach, как показано в следующем примере где параметр elem итеративно принимает значение каждого элемента массива a:

Для клонирования массива a, мы можем использовать функцию массива slice следующим способом:

Карты

Карта (также называемая ‘хеш карта’ или ‘ассоциированый массив’) представляет собой набор пар ключ-значение. Ключи в картах представлены строковыми литералами, которые могут содержать пробелы, например:

Карту можна обработать перебором всех ключей с помощью предопределенной функции Object.keys(m), котороя возвращает массив всех ключей карты m. Например,

Для добавления новой записи в карту, мы просто ассоциируем новое значение с его ключем как в следующим коде:

Для удаления записи с карты, мы можем использовать предопределенный оператор delete:

Для поиска по карте, можем проверять содержит ли карта запись для определенного ключа, для примера, что бы проверить содержит карта myTranslation запись с ключем «my bike» мы можем использовать следующую проверку:

Для перебора всех записей карты m, мы сначала конвертируем ее в список ключей с помощью предопределенной функции Object.keys, и после мы можем использовать или цикл for или метод forEach. Следующий пример показывает перебор карты с помощью цикла for:

Повторим, если карта m достаточно мала, мы можем использовать метод forEach, как в следующим примере:

Обратите внимание на то, что использование метода forEach дает более меньший объем кода.

Для клонирования карты m, мы можем использовать сочетание JSON.stringify и JSON.parse. Мы сначала сериализуем m в строковое представление с помощью JSON.stringify, затем десериализируем строковое представление в объект карты с помощью JSON.parse:

Обратите внимание, этот метод хорошо работает в том случае если карта содержит только простые значение данных или (возможно вложеные) массивы/карты, содержащие также простые значение данных. В других случаях, например, когда карта содержит объекты Date, мы должны написать наш собственный метод клонирования.

Четыре базовых структуры данных

В итоге, в JS поддерживаются четыре базовые структуры данных:

  1. array lists, в виде ["one","two","three"], которые есть специальными JS объектами называемые ‘arrays’, но так как они динамические, называются array lists как это определено в языке программирования Java.
  2. records, которые есть специальными JS объектами, в виде {firstName: "Tom", lastName: "Smith"}, как обсуждалось выше,
  3. maps, которые есть также специальными JS объектами, представлены как {"one":1,"two":2,"three":3}, как обсуждалось выше,
  4. entity tables, например, таблица 1 показаная ниже, которая представляет собой специальные карты где значения это запыси с стандартным ID (или приватным ключем) слотом, таким образом что ключи карты являются также стандартными идентификаторами сущностей.
Таблица сущностей представляющая коллекцию книг
Ключ Значение
006251587X { isbn:»006251587X,» title:»Weaving the Web», year:2000 }
0465026567 { isbn:»0465026567,» title:»Gödel, Escher, Bach», year:1999 }
0465030793 { isbn:»0465030793,» title:»I Am A Strange Loop», year:2008 }

Обратите внимание на наше различие карт, записей и таблиц сущностей. Это чисто концептуальное различие, а не только синтаксическое. Для движка JavaScript, код {firstName:"Tom", lastName:"Smith"} и {"one":1, "two":2, "three":3} - просто объекты. Но концептуально, {firstName:"Tom", lastName:"Smith"} — это запись, потому что firstName и lastName предназначены для обозначения свойств и полей, тогда как {"one":1, "two":2, "three":3} представляет карту, потому что "one" и "two" не предназначены для обозначения свойств/полей, а используются как простые произвольные строковые значения в качестве ключей карты.

Создание таких концептуальных различий помогает лучше понять возможности предлагаемые JavaScript.

Методы и функции

В JavaScript, методы называются «функциями», не зависимо от того возвращают они значения или нет. Как изображено на Фигуре 1 ниже, JS функции это специальные JS объекты, имеющие необязательные свойства имя и длину которая указывает на количество принимаемых параметров. Если переменная v ссылается на JS функцию, ее можно протестировать с помощью

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

В общем виде JS определение функции это присвоение виражения функции переменной:

где theNameOfMyF не обязательно. Когда его опускают, функция становится анонимной . В других случаях, функции будет вызыватся через переменные, которые ссылаются на функции. В приведеном выше случае, это означает что функция может быть вызвана с помощью myF(), но не theNameOfMyF().

Выражения анонимных функций называются лямбда выражениями в других языках программирования.

Для примера, передадим выражение анонимной функции как параметр при визове другой (выше стоящей) функции, мы можем взять функцию для сравнения элементов и передать ее в предопределеною функцию sort для сортирования элементов списка. Даная функция сравнения должна возвращать отрицательное число, если первый аргумент меньше за второй, число 0, если элементы равны, или положительное число, если второе число меньше за первое. В следующем примере, мы сортируем список в списках по двум числам в лексикографическом порядке:

JS определение функции может иметь следующий вид:

Это эквивалетно следующему именованом определению функции:

то есть, создается функция с именем theNameOfMyF а также переменная theNameOfMyF которая ссылается на эту функцию.

JS функции могут иметь внутрение функции. Механизм замыкания позволяет JS функциям использовать переменные (кроме this) с ее внешней области видимости, и функция созданая в замикании помнит ту среду, в которой была создана. В следующем примере, нет необходимости передавать переменною result с внешней области видимости у внутреннюю функцию через параметер, она и так будет доступной:

Когда метод/функция виполняется, мы можем получить доступ к их аргументам внутри их тел используя встроенный объект arguments, который на подобе массивов имеет проиндексированных элементов и свойство length, и мы можем обработать с помощью обычного цикла for, но так как он не является екземпляром Array, методы JS массива (такие как forEach) не применимы к нему. Объект arguments содержит елемент для каждого аргумента передаваемого методу. Это позволяет определять метод без параметров и вызывать его передавая в него любое количество аргументов, например:

Метод определенный в прототипе функции конструктора, может быть вызван на любом объекте созданом с помощью этой функции-конструктора. Например как Array.prototype.forEach, когда Array представляет конструктор, должен быть вызван на екземпляре класса как объект контекста на который ссылается переменная this (смотри также следующую секцию о классах). В следующем примере, массив numbers контекстный объект в вызове
forEach:

Всякий раз этот метод прототипа должен быть вызван на объекта контекста, но также можем этот объект быть передан в качестве аргумента с помощью функции call
которая получает объект, на котором вызывается метод, как его первый параметр, за которым следуют параметры для вызова метода. Например, мы можем
применить метод перебора forEach для масивоподобного объекта arguments следующим путем:

Вариант метода Function.prototype.call, который все аргументы метода захватывает в простой массив аргументов представлен методом Function.prototype.apply.

Всякий раз когда метод, который определен для прототипа, должен быть вызван без объекта контекста или когда метод, определенный в контексте объекта, должен быть вызван без его объекта контекста, мы можем связать его переменную this с передаваемым объектом с помощью функции bind
(Function.prototype.bind). Это позволяет создавать ярлики для вызова методов, как var querySel =
document.querySelector.bind( document)
, который позволяет использовать querySel вместо document.querySelector.

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

Этот шаблон был предложеный на WebPlatform.org в статье JavaScript best practices.

Определения и использования классов

Концепция классов — фундамент объектно-ориентированного программированния. Объект есть экземпляром (или квалифицирован с помощью) класса. Класс определяет свойства и методы (как чертеж) для объектов, созданых с его помощью. Концепция класса имеет важную роль для создания модели данных в виде модели классов в Model-View-Controller (MVC) архитектуре. Однако, классами с их механизмами наследования/разширения злоупотребляют в классических ОО языках программирования, таких как Java, где все переменные и процедуры должны быть определены в контексте класса и, следовательно, классы используются не только для реализации типов даных (или классов модели), но также в качестве контейнеров для многих других применений доступных в этих языках. Но не в случае JavaScript где мы имеем свободу в использовании классов для реализации только типов объектов, при этом сохраняя библиотеку методов в простанстве имен объекта.

Любой паттерн для определения классов в JavaScript должен удовлетворять пять требований. Прежде всего, (1) он должен позволять определять имя класса, набор (на уровне экземпляра) свойств, желательно с реализацией приватности, набор (на уровне экземпляра) методов, а также набор статических свойств и методов. Желательно, что бы свойства можно было объявлять с ограничениями, такими как диапазон принимаемых значений, тип и других мета-даных. Также должна быть реализовано две возможности для самоанализа: (2) предикат is-instance-of который позволит определить является объект прямым или не прямым экземпляром класса, и (3) свойство на уровне екземпляра для извлечения прямого типа объекта. В дополнении, желательно иметь третюю возможность интроспекции для извлечения прямого супертипа класса. В заключении, должены быть два механихма наследования: (4) наследование свойств и (5) наследование методов. В дополнение, желательно иметь поддержку множественного наследования и множественной классификации, для позволения объектам играть несколько ролей одновременно для инстанцировании ролей нескольких классов.

Нет явного понятия класса в JavaScript. Различные паттерны для пределения классов в JavaScript были предложены и используются в разных фреймворках. Но ини часто не удовлетворяют всех пяти требований описаных выше. Два найболее важных подхода к определению классов:

  1. В виде функции конструктора которая достигает наследования через цепочку прототипов и позволяет создавать новые экземпляры классов с помощью оператора new. Этот классический подход рекомендует Mozilla в ее JavaScript Guide.
  2. В виде объекта фабрики, которая использует преопределенный метод Object.create для создания нового екземпляра класса. В этом подходе механизм наследования базирующий на констркторах, заменяется другим механизмов. Eric Elliott утверждает что этот подход является жизнеспособной альтернативой классам базирующим на конструкторам в JavaScript (на самом деле, он даже осуждает использование классического наследования классов на основе конструкторов, «как выбрасывая ребенка с води ванной»).

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

Требуемые и желаемые возможности паттернов для реализации классов в JavaScript
Возможность класса Подход на основе конструкторов Подход на основе фабрики mODELcLASSjs
Определения свойств и методов да да да
Определения свойств с диапазоном доступных значений (и другие мета-даные) нет возможно да
Встроеный предикат is-instance-of да да да
Встроеное свойство прямой тип да да да
Встроеное свойство прямой супер тип для класса нет возможно да
Наследование свойств да да да
Наследование методов да да да
Множественое наследование нет возможно да
Множественая классификация нет возможно да
Доступный пул объектов нет да да

Классы базирующие на конструкторах

В ES5, мы могли определить класс на основе конструктора с субклассом ввиде функций конструкторов, следующий шаблон рекомендует использовать Mozilla в ее JavaScript Guide, как показано в следующих шагах.

Шаг 1.a)
Первым определяем функцию конструктор которая неявно реализовывает определения свойств, присваевая им значения параметров конструктора при создании нового объекта:

Обратите внимание на то что в конструкторе специальная переменная this ссылается на новый объект,
который создается при визове конструктора.

Шаг 1.b) Следующее, определения
метода на уровне экземпляра класса как слотов методов объекта, который ссылается на свойство prototype конструктора:

Шаг 1.c)
Методы на уровне класса («статические») могут быть определенны как слоты методов самой функции конструктора (поскольку, JS функции есть объектами, они могут иметь слоты), например

Шаг 1.d) Последнее, определения свойств на уровне класса («статических») как слотов свойств функции конструктора:

Шаг 2.a): Определения субкласса с дополнительными свойствами:

При вызове конструктора супертипа с помощью Person.call( this, ...) создается новый объект и ссылается на this, как экземпляр подтипа Student, мы понимаем что слоты свойств созданы в конструкторе супертипа (firstName и lastName) также созданы в экземпляре подтипа, вдоль всей цепочки супертипов в даной иерархии классов. Таким образом мы создали механизм наследования свойств который гарантирует, что наши свойства, которые определены для объекта подтипа, включают наши свойства, обределены с помощью конструктора базового типа.

На Шаге 2b), мы создаем механизм наследования методов с помощью свойства коструктора prototype. Мы назначаем новий объект, созданый с помощью объекта prototype суперкласса, свойству prototype конструктора подтипа и настроиваем свойство прототипа конструктора:

С помощью Object.create( Person.prototype) мы создаем новый объект с
Person.prototype как его прототипом и без наших слотов свойств. Назначая этот объект свойству prototype конструктора подтипа, мы достигаем того, что методи определены в, и унаследованы от, суперкласса также доступны для экземпляров подтипа. Этот механизм сцепления прототипов заботится об наследовании методов. Обратите внимание что установка значения Student.prototype в Object.create(
Person.prototype)
является более предпочительной чем new
Person()
, который делал тоже самое до появления ES5.

Шаг 2c): Определения методов подтипа, который переопределяет метод суперкласса:

Екземпляр класса, базирующий на конструкторе, создается путем применения оператора new
к функции конструктора и поддерживает подходящие аргументы для параметров конструктора:

Метод
toString вызывается для объекта pers1 типа Person
используя ‘точковую аннотацию’:

Когда объект o создается с помощью o = new C(), где
C ссылается на именованую функцию с именем «C», имя типа (или класса)
o можно получить интроспективным выражением
o.constructor.name, который вернет «C». Однако, использование свойства Function::name
поддерживается всеми браузерами, за исключением Internet Explorer до 11 версии.

В JavaScript, prototype это объект с слотами методов (иногда также с слотами свойств), который могут бить унаследованы другими объектами в помощью JavaScript механизма перебора слотов методов и свойств. Этот механизм наблюдает за цепочкой прототипов
определенной с помощью (в ES5 еще не официально) встроеному ссылочтому свойству __proto__ (с двойным подстрочним префиксом и суфиксом) для поиска свойств и методов. Как показано на Фигуре 1, каждая функция конструктор имеет ссылку на прототип, как значения ее собственого ссылочного свойства prototype. Когда новый объект создается с помощью оператора new, его свойство __proto__ установлено в prototype конструктора. Для примера, создания нового объекта с помощью f = new Foo() означает что Object.getPrototypeOf(f), тоже самое что f.__proto__,
равняется Foo.prototype. Вследствии чего, изменения в слотах
Foo.prototype отображаются на всех объектах которые были созданы с помощью new Foo(). В то время, как каждый объект имеет слот свойства __proto__
(исключение Object), только объекты созданы с помощью new имеют слоты свойств конструктора constructor.

Фигура 1: Встроеные JavaScript классы Object и Function

Обратите внимание на то что мы можем получить прототип объекта с помощью Object.getPrototypeOf(o),
который есть официальной альтернативой o.__proto__ в ES5

Класы на основе фабрики

При таком подходе мы определяем JS объект Person (фактически представляющий класс) с специальным методом create, который вызывается предопределенным методом Object.create при создании объекта типа Person:

Обратите внимание на то, что JS обьект Person на самом деле представляет класс базирующийся на фабрике. Экземпляр такого класса создается при вызове его метода create:

Метод getFullName вызваный для объекта pers1 типа Person используя ‘точковою аннотацию’, такой же самый как подходе базирующейся на конструкторах:

Обратите внимание, что каждое объявление свойства при создании объекта с помощью Object.create должно включать ‘дескрипторы’ writable: true и enumerable: true, как на 5 и 7 строках объекта Person описаного выше.

В общем подходе, также как в библиотеке mODELcLASSjs разработки на основе моделей, мы не должны определять метод create в каждом определении класса, а скорее должны иметь общую функцию конструктор для определения классов на основе фабрики. Такой конструктор класса-фабрики, также как в mODELcLASS, должен также предоставлять механизм наследования через слияние собственых свойств и методов со свойствами и методами суперкласса.

JavaScript как объекто-ориентированный язык программирования

JavaScript — объектно-ориэнтированный, но отличается от классических ОО языков программирования, таких как Java и C++. В JavaScript нет явного определения концепции класса. Скорее, классы должны быть определение в виде специальных объектов: либо через функцию конструктора либо через объект фабрики.

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

LocalStorage API

Для фронт-єнд приложений, нам нужно сохранять даные на устройстве пользователя.
Современные веб-браузеры поддерживают две технологии для этой цели: простую называемую Local Storage, и более мощную называемую IndexDB.

База даных Local Storage создается для конкретного браузера и происхождения(origin), которое определяется комбинацией протокола и имени домена. Например, http://example.com и http://www.example.com есть разными происхождениями, потому что они имеют разные имена доменов, в тоже время http://www.example.com и https://www.example.com будут разными происхождениями, потому что в них разные протоколы (HTTP против HTTPS).

База даных Local Storage управляется браузером и ассоциируется с приложением (через его происхождение) и получить доступ к ней можно с помощью предопределенного объекта JavaScript localStorage с помощью методов getItem, setItem, removeItem и clear. Тем не менее, в место того что бы вызывать getItem и setItem, есть более удобный способ работать с localStorage как с картой, присвоения значения определенному ключу будет иметь вид localStorage["id"] = 2901465, а получения даных — как чтение с карты, var id = localStorage["id"].

Следующий пример показывает как создать сущность таблицы и сохранить ее после сериализации в Local Storage:

Обратите внимание на то, что мы можем использовать предопределенный метод JSON.stringify для сериализации в JSON таблицу persons в строку, которая присваивается как значение по ключу personTable в localStorage. Мы можем получить таблицу с помощью предопределенного метода десериализации JSON.parse следующим образом:

Дополнительная литература о JavaScript

Хорошими книгами в открытом доступе о JavaScript’е являются

Автор Gerd Wagner


Creative Commons License
Copyright 2015, Gerd Wagner. Эта статья распространяется на условиях Creative Commons
Attribution-NonCommercial 4.0 International License
. Доступна в сети по адресу http://web-engineering.info/SummariesCheatsheetsPosters.