Эта статья нуждается в редакционном обзоре. Как вы можете помочь.
Объектно-ориентированный до основания, JavaScript предоставляет мощные и гибкие OOP возможности. Эта статья начинается с введения в объектно-ориентированное программирование, затем рассматривает модель объекта JavaScript и, наконец, демонстрирует концепции объектно-ориентированного программирования в JavaScript.
Обзор JavaScript
Если вы неуверенно владеете такими концепциями JavaScript, как переменные, типы, функции и области видимости, вы можете прочитать об этих темах в Повторное вступление в JavaScript. Вы также можете обратиться к JavaScript Guide.
Объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая использует абстракции, чтобы создавать модели, основанные на объектах реального мира. ООП использует несколько техник из ранее признанных парадигм, включая модульность, полиморфизм и инкапсуляция. На сегодняшний день многие популярные языки программирования (такие как Java, JavaScript, C#, C++, Python, PHP, Ruby и Objective-C) поддерживают ООП.
ООП представляет программное обеспечение как совокупность взаимодействующих объектов, а не набор функций или просто список команд (как в традиционном представлении). В ООП, каждый объект может получать сообщения, обрабатывать данные, и отправлять сообщения другим объектам. Каждый объект может быть представлен как маленькая независимая машина с отдельной ролью или ответственностью.
ООП способствует большей гибкости и поддерживаемости в программировании, и широко распространена в крупномасштабном программном инжиниринге. Так как ООП настоятельно подчеркивает модульность, объектно-ориентированный код проще в разработке и проще для понимания впоследствии. Объектно-ориентированный код способствует более точному анализу, кодированию и пониманию сложных ситуаций и процедур, чем методы программирования с меньшей модульностью.1
Терминология
- Пространство имён
- Контейнер, который позволяет разработчикам связать весь функционал под уникальным, специфичным для приложения именем.
- Класс
- Определяет характеристики объекта. Класс является описанием шаблона свойств и методов объекта.
- Объект
- Экземпляр класса.
- Свойство
- Характеристика объекта, например, цвет.
- Метод
- Возможности объекта, такие как ходьба. Это подпрограммы или функции, связанные с классом.
- Конструктор
- Метод, вызываемый в момент создания экземпляра объекта. Он, как правило, имеет то же имя, что и класс, содержащий его.
- Наследование
- Класс может наследовать характеристики от другого класса.
- Инкапсуляция
- Способ комплектации данных и методов, которые используют данные.
- Абстракция
- Совокупность комплексных наследований, методов и свойств объекта должны адекватно отражать модель реальности.
- Полиморфизм
- Поли означает "много", а морфизм "формы". Различные классы могут объявить один и тот же метод или свойство.
Для более обширного описания объектно-ориентированного программирования, см Объектно-ориентированное_программирование в Wikipedia.
Прототипное программирование
Прототипное программирование — это модель ООП которая не использует классы, а вместо этого сначала выполняет поведение класса и затем использует его повторно (эквивалент наследования в языках на базе классов), декорируя (или расширяя) существующие объекты прототипы. (Также называемое бесклассовое, прототипно-ориентированное, или экземплярно-ориентированное программирование.)
Оригинальный (и наиболее каноничный) пример прототипно-ориентированного языка это Self разработанный Дэвидом Ангаром и Ренделлом Смитом. Однако бесклассовый стиль программирования стал набирать популярность позднее, и был принят для таких языков программирования, как JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Kevo, Squeak (при использовании фреймворка Viewer для манипуляции компонентами Morphic) и некоторых других.1
Объектно-ориентированное программирование в JavaScript
Пространство имён
Пространство имён — это контейнер, который позволяет разработчикам собрать функциональность под уникальным именем приложения. Пространство имён в JavaScript — это объект, содержащий методы, свойства и другие объекты.
Важно отметить, что на уровне языка в JavaScript нет разницы между пространством имён и любым другим объектом. Это отличает JS от множества других объектно-ориентированных языков и может стать причиной путаницы у начинающих JS программистов.
Принцип работы пространства имён в JS прост: создать один глобальный объект и все переменные, методы и функции объявлять как свойства этого объекта. Также использование пространств имён снижает вероятность возникновения конфликтов имён в приложении так как каждый объект приложения является свойством глобального объекта.
Давайте создадим глобальный объект MYAPP:
// Глобальное пространство имён var MYAPP = MYAPP || {};
Во фрагменте кода выше мы сначала проверяем определён ли объект MYAPP (в текущем файле или другом файле). Если да, то используем существующий глобальный объект MYAPP, иначе создаём пустой объект MYAPP, в котором мы инкапсулируем все методы, функции, переменные и объекты.
Также мы можем создать подпространство имён (учтите, что сначала нужно объявить глобальный объект):
// Подпространство имён MYAPP.event = {};
Далее следует пример синтаксиса создания пространства имён и добавления переменных, функций и методов:
// Создаём контейнер MYAPP.commonMethod для общих методов и свойств MYAPP.commonMethod = { regExForName: "", // определяет регулярное выражение для валидации имени regExForPhone: "", // определяет регулярное выражение для валидации телефона validateName: function(name){ // Сделать что-то с name, вы можете получить доступ к переменной regExForName // используя "this.regExForName" }, validatePhoneNo: function(phoneNo){ // Сделать что-то с номером телефона } } // Объект вместе с объявлением методов MYAPP.event = { addListener: function(el, type, fn) { // код }, removeListener: function(el, type, fn) { // код }, getEvent: function(e) { // код } // Можно добавить другие свойства и методы } // Синтаксис использования метода addListener: MYAPP.event.addListener("yourel", "type", callback);
Стандартные встроенные объекты
В JavaScript есть несколько объектов, встроенных в ядро, например Math
, Object
, Array
и String
. Пример ниже показывает как использовать объект Math, чтобы получить случайное число, используя его метод random().
console.log(Math.random());
console.log()
. Если точнее, то функция console.log()
не является частью JavaScript, но она поддерживается многими браузерами для облегчения отладки.Смотрите JavaScript Reference: Standard built-in objects, чтобы ознакомиться со списком всех встроенных объектов JavaScript.
Каждый объект в JavaScript является экземпляром объекта Object
, следовательно наследует все его свойства и методы.
Объекты, создаваемые пользователем
Класс
JavaScript — это прототипно-ориентированный язык, и в нём нет оператора class
, который имеет место в C++ или Java. Иногда это сбивает с толку программистов, привыкших к языкам с оператором class
. Вместо этого JavaScript использует функции как конструкторы классов. Объявить класс так же просто как объявить функцию. В примере ниже мы объявляем новый класс Person с пустым конструктором:
var Person = function () {};
Объект (экземпляр класса)
Для создания нового экзмепляра объекта obj
мы используем оператор new obj
, присваивая результат (который имеет тип obj
) в переменную.
В примере выше мы определили класс Person
. В примере ниже мы создаём два его экземпляра (person1
и person2
).
var person1 = new Person(); var person2 = new Person();
Object.create()
, новым, дополнительным методом инстанцирования, который создаёт неинициализированный экземпляр.Конструктор
Конструктор вызывается в момент создания экземпляра класса (в тот самый момент, когда создается объект). Конструктор является методом класса. В JavaScript функция служит конструктором объекта, поэтому нет необходимости явно определять метод конструктор. Любое действие определенное в классе будет выполненно в момент создания экземпляра класса.
Конструктор используется для задания свойств объекта или для вызова методов, которые подготовят объект к использованию. Добавление методов и их описаний производится с использованием другого синтаксиса, описанного далее в этой статье.
В примере ниже, конструктор класса Person
выводит в консоль сообщение в момент создания нового экземпляра Person
.
var Person = function () { console.log('instance created'); }; var person1 = new Person(); var person2 = new Person();
Свойство (аттрибут объекта)
Свойства — это переменные, содержащиеся в классе; каждый экземпляр объекта имеет эти свойства. Свойства устанавливаются в конструкторе (функции) класса, таким образом они создаются для каждого экземпляра.
Ключевое слово this
, которое ссылается на текущий объект, позволяет вам работать со свойствами класса. Доступ (чтение и запись) к свойствам снаружи класса осуществляется синтаксисом InstanceName.Property,
так же как в C++, Java и некоторых других языках. (Внутри класса для получения и изменения значений свойств используется синтаксис this.Property
)
В примере ниже, мы определяем свойство firstName
для класса Person
при создании экземпляра:
var Person = function (firstName) { this.firstName = firstName; console.log('Person instantiated'); }; var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Выводит свойство firstName в консоль console.log('person1 is ' + person1.firstName); // выведет "person1 is Alice" console.log('person2 is ' + person2.firstName); // выведет "person2 is Bob"
Методы
Методы — это функции (и определяются как функции), но с другой стороны следуют той же логике, что и свойства. Вызов метода похож на доступ к свойству, но вы добавляете () на конце имени метода, возможно, с аргументами. Чтобы объявить метод, присвойте функцию в именованное свойство свойства prototype
класса. Потом вы сможете вызвать метод объекта под тем именем, которое вы присвоили функции.
В примере ниже мы определяем и используем метод sayHello()
для класса Person
.
var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // вызываем метод sayHello() класса Person person1.sayHello(); // выведет "Hello, I'm Alice" person2.sayHello(); // выведет "Hello, I'm Bob"
В JavaScript методы это — обычные объекты функций, связанные с объектом как свойства: это означает, что вы можете вызывать методы "вне контекста". Рассмотрим следующий пример:
var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; // выведет "Hello, I'm Alice" person1.sayHello(); // выведет "Hello, I'm Bob" person2.sayHello(); // выведет "Hello, I'm undefined" (or fails // with a TypeError in strict mode) helloFunction(); // выведет true console.log(helloFunction === person1.sayHello); // выведет true console.log(helloFunction === Person.prototype.sayHello); // выведет "Hello, I'm Alice" helloFunction.call(person1);
Как показывает пример, все ссылки, которые мы имеем на функцию sayHello
— person1
, Person.prototype
, переменная helloFunction
и т.д. — ссылаются на одну и ту же функцию. Значение this
в момент вызова функции зависит от того, как мы её вызываем. Наиболее часто мы обращаемся к this
в выражениях, где мы получаем функцию из свойства объекта — person1.sayHello()
— this
устанавливается на объект, из которого мы получили функцию (person1
), вот почему person1.sayHello()
использует имя "Alice", а person2.sayHello()
использует имя "Bob". Но если вызов будет совершён иначе, то this
будет иным: вызов this
из переменной — helloFunction()
— установит this
на глобальный объект (window
в браузерах). Так как этот объект (вероятно) не имеет свойства firstName
, функция выведет "Hello, I'm undefined" (так произойдёт в нестрогом режиме; в strict mode всё будет иначе (ошибка), не будем сейчас вдаваться в подробности, чтобы избежать путаницы). Или мы можем указать this
явно с помощью Function#call
(или Function#apply
) как показано в конце примера.
Наследование
Наследование — это способ создать класс как специализированную версию одного или нескольких классов (JavaScript поддерживает только одиночное наследование). Специализированный класс, как правило, называют потомком, а другой класс родителем. В JavaScript наследование осуществляется присвоением экземпляра класса родителя классу потомку. В современных браузерах вы можете реализовать наследование с помощью Object.create.
prototype.constructor
класса потомка (смотрите Object.prototype) так что мы должны указать его вручную. Смотрите вопрос "Why is it necessary to set the prototype constructor?" на Stackoverflow.В примере ниже мы определяем класс Student
как потомка класса Person
. Потом мы переопределяем метод sayHello()
и добавляем метод addDoodBye()
.
// Определяем конструктор Person var Person = function(firstName) { this.firstName = firstName; }; // Добавляем пару методов в Person.prototype Person.prototype.walk = function(){ console.log("I am walking!"); }; Person.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName); }; // Определяем конструктор Student function Student(firstName, subject) { // Вызываем конструктор родителя, убедившись (используя Function#call) // что "this" в момент вызова установлен корректно Person.call(this, firstName); // Инициируем свойства класса Student this.subject = subject; }; // Создаём объект Student.prototype, который наследуется от Person.prototype. // Примечание: Рспространённая ошибка здесь, это использование "new Person()", чтобы создать // Student.prototype. Это неверно по нескольким причинам, не в последнюю очередь // потому, что нам нечего передать в Person в качестве аргумента "firstName" // Правильное место для вызова Person показано выше, где мы вызываем // его в конструкторе Student. Student.prototype = Object.create(Person.prototype); // Смотрите примечание выше // Устанавливаем свойство "constructor" для ссылки на класс Student Student.prototype.constructor = Student; // Заменяем метод "sayHello" Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // Добавляем метод "sayGoodBye" Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // Пример использования: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Проверяем, что instanceof работает корректно console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true
Относительно строки Student.prototype = Object.create(Person.prototype);
: В старых движках JavaScript, в которых нет Object.create
можно использовать полифилл (ещё известный как "shim") или функцию которая достигает тех же результатов, такую как:
function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Пример использования: Student.prototype = createObject(Person.prototype);
Инкапсуляция
В примере выше классу Student
нет необходимости знать о реализации метода walk()
класса Person
, но он может его использовать; Класс Student
не должен явно определять этот метод, пока мы не хотим его изменить. Это называется инкапсуляция, благодаря чему каждый класс собирает данные и методы в одном блоке.
Сокрытие информации распространённая особенность, часто реализуемая в других языках программирования как приватные и защищённые методы/свойства. Однако в JavaScript можно лишь имитировать нечто подобное, это не является необходимым требованием объектно-ориентированного программирования.2
Абстракция
Абстракция это механизм который позволяет смоделировать текущий фрагмент рабочей проблемы, с помощью наследования (специализации) или композиции. JavaScript достигает специализации наследованием, а композиции возможностью экземплярам класса быть значениями атрибутов других объектов.
В JavaScript класс Fucntion
наследуется от класса Object
(это демонстрирует специализацию), а свойство Function.prototype
это экземпляр класса Object
(это демонстрирует композицию).
var foo = function () {}; // выведет "foo is a Function: true" console.log('foo is a Function: ' + (foo instanceof Function)); // выведет "foo.prototype is an Object: true" console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));
Полиморфизм
Так как все методы и свойства определяются внутри свойства prototype
, различные классы могут определять методы с одинаковыми именами; методы находятся в области видимости класса в котором они определены, пока два класса не имеют связи родитель-потомок (например, один наследуется от другого в цепочке наследований).
Примечания
Это не все способы которыми можно реализовать объектно-ориентированное программирование в JavaScript, который очень гибок в этом отношении. Также способы рассмотренные здесь не отражают всех возможностей JavaScript и не подражают реализации теории объектов в других языках.
Существуют другие способы, которые реализуют ещё более продвинутое объектно-ориентированное программирование на JavaScript, но они выходят за рамки этой вводной статьи.
Ссылки
- Wikipedia. "Object-oriented programming"
- Wikipedia. "Encapsulation (object-oriented programming)"