Creation of new accounts on MDN is unavailable while we devise a solution to an ongoing denial-of-service attack. We're sorry about the inconvenience! If you see something that needs to be fixed, please file a bug: https://bugzilla.mozilla.org/form.doc

Вступление в Объектно-ориентированный JavaScript

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

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Объектно-ориентированный до основания, 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 нет разницы между пространством имён и любым другим объектом. Это может вызвать путаницу у новичков в JavaScript.

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

Давайте создадим глобальный объект MYAPP:

// Глобальное пространство имён
var MYAPP = MYAPP || {};

В этом коде мы сначала проверяем, определён ли объект MYAPP. Если да, то переменной присваивается существующий глобальный объект MYAPP, иначе создаём пустой объект, в котором мы инкапсулируем все методы, функции, переменные и объекты.

Можно создать подпространство имён:

// Подпространство имён
MYAPP.event = {};

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

// Создаём контейнер MYAPP.commonMethod для стандартных методов и свойств
MYAPP.commonMethod = {
  regExForName: "", // определяет regex для проверки имени
  regExForPhone: "", // определяет regex для проверки телефона
  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 является экземпляром объекта Object, следовательно наследует от него свойства и методы.

Пользовательские объекты

Класс

JavaScript это язык, основанный на прототипах, в котором нет понятия классов, как в C++ или Java. Пусть это не сбивает с толку новичков, привыкших к другим языкам. В JavaScript вместо классов можно использовать функции. На самом деле это довольно просто. В примере ниже мы создадим новый класс Person используя пустой конструктор:

var Person = function () {};

Объект (экземпляр класса)

Для создания нового экзмепляра объекта obj мы исползуем выражение new 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();

Свойство (аттрибут объекта)

Свойства это переменные, которые находятся внутри класса; каждый екземпляр объекта имеет эти свойства. Свойсвта устанавливаются в конструкторе (функции) класса, таким образом они создаются для каждого екземпляра класса.

The keyword this, which refers to the current object, lets you work with properties from within the class. Accessing (reading or writing) a property outside of the class is done with the syntax: InstanceName.Property, just like in C++, Java, and several other languages. (Inside the class the syntax this.Property is used to get or set the property's value.)

В примере ниже, мы объявляем свойство firstName для класса Person at instantiation:

var Person = function (firstName) {
  this.firstName = firstName;
  console.log('Person instantiated');
};

var person1 = new Person('Alice');
var person2 = new Person('Bob');

// Show the firstName properties of the objects
console.log('person1 is ' + person1.firstName); // logs "person1 is Alice"
console.log('person2 is ' + person2.firstName); // logs "person2 is Bob"

The methods

Methods are functions (and defined like functions), but otherwise follow the same logic as properties. Calling a method is similar to accessing a property, but you add () at the end of the method name, possibly with arguments. To define a method, assign a function to a named property of the class's prototype property. Later, you can call the method on the object by the same name as you assigned the function to.

In the example below, we define and use the method sayHello() for the Person class.

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");

// call the Person sayHello method.
person1.sayHello(); // logs "Hello, I'm Alice"
person2.sayHello(); // logs "Hello, I'm Bob"

In JavaScript methods are regular function objects bound to an object as a property, which means you can invoke methods "out of the context". Consider the following example code:

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;

// logs "Hello, I'm Alice"
person1.sayHello();

// logs "Hello, I'm Bob"
person2.sayHello();

// logs "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
helloFunction();                                    

// logs true
console.log(helloFunction === person1.sayHello);

// logs true
console.log(helloFunction === Person.prototype.sayHello);

// logs "Hello, I'm Alice"
helloFunction.call(person1);

As that example shows, all of the references we have to the sayHello function— the one on person1, on Person.prototype, in the helloFunction variable, etc.— refer to the same function. The value of this during a call to the function depends on how we call it. Most commonly, when we call this in an expression where we got the function from an object property— person1.sayHello()this is set to the object we got the function from (person1), which is why person1.sayHello() uses the name "Alice" and person2.sayHello() uses the name "Bob". But if we call it other ways, this is set differently: calling this from a variable— helloFunction()— sets this to the global object (window, on browsers). Since that object (probably) doesn't have a firstName property, we end up with "Hello, I'm undefined". (That's in loose mode code; it would be different [an error] in strict mode, but to avoid confusion we won't go into detail here.) Or we can set this explicitly using Function#call (or Function#apply), as shown at the end of the example.

Note: See more about this on Function#call and Function#apply

Inheritance

Inheritance is a way to create a class as a specialized version of one or more classes (JavaScript only supports single inheritance). The specialized class is commonly called the child, and the other class is commonly called the parent. In JavaScript you do this by assigning an instance of the parent class to the child class, and then specializing it. In modern browsers you can also use Object.create to implement inheritance.

Note: JavaScript does not detect the child class prototype.constructor (see Object.prototype), so we must state that manually. See the question "Why is it necessary to set the prototype constructor?" on Stackoverflow.

In the example below, we define the class Student as a child class of Person. Then we redefine the sayHello() method and add the sayGoodBye() method.

// Define the Person constructor
var Person = function(firstName) {
  this.firstName = firstName;
};

// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
  console.log("I am walking!");
};

Person.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName);
};

// Define the Student constructor
function Student(firstName, subject) {
  // Call the parent constructor, making sure (using Function#call)
  // that "this" is set correctly during the call
  Person.call(this, firstName);

  // Initialize our Student-specific properties
  this.subject = subject;
};

// Create a Student.prototype object that inherits from Person.prototype.
// Note: A common error here is to use "new Person()" to create the
// Student.prototype. That's incorrect for several reasons, not least 
// that we don't have anything to give Person for the "firstName" 
// argument. The correct place to call Person is above, where we call 
// it from Student.
Student.prototype = Object.create(Person.prototype); // See note below

// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;

// Replace the "sayHello" method
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying "
              + this.subject + ".");
};

// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// Example usage:
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!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

Regarding the Student.prototype = Object.create(Person.prototype); line: On older JavaScript engines without Object.create, one can either use a "polyfill" (aka "shim", see the linked article), or one can use a function that achieves the same result, such as:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

// Usage:
Student.prototype = createObject(Person.prototype);
Note: See Object.create for more on what it does, and a shim for older engines.

Encapsulation

In the previous example, Student does not need to know how the Person class's walk() method is implemented, but still can use that method; the Student class doesn't need to explicitly define that method unless we want to change it. This is called encapsulation, by which every class packages data and methods into a single unit.

Information hiding is a common feature in other languages often as private and protected methods/properties. Even though you could simulate something like this on JavaScript, this is not a requirement to do Object Oriented programming.2

Abstraction

Abstraction is a mechanism that allows you to model the current part of the working problem, either by inheritance (specialization) or composition. JavaScript achieves specialization by inheritance, and composition by letting class instances be the values of other objects' attributes.

The JavaScript Function class inherits from the Object class (this demonstrates specialization of the model) and the Function.prototype property is an instance of Object (this demonstrates composition).

var foo = function () {};

// logs "foo is a Function: true"
console.log('foo is a Function: ' + (foo instanceof Function));

// logs "foo.prototype is an Object: true"
console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));

Polymorphism

Just as all methods and properties are defined inside the prototype property, different classes can define methods with the same name; methods are scoped to the class in which they're defined, unless the two classes hold a parent-child relation (i.e. one inherits from the other in a chain of inheritance).

Notes

These are not the only ways you can implement object-oriented programming in JavaScript, which is very flexible in this regard. Likewise, the techniques shown here do not use any language hacks, nor do they mimic other languages' object theory implementations.

There are other techniques that make even more advanced object-oriented programming in JavaScript, but those are beyond the scope of this introductory article.

References

  1. Wikipedia. "Object-oriented programming"
  2. Wikipedia. "Encapsulation (object-oriented programming)"

Метки документа и участники

 Внесли вклад в эту страницу: RayzRazko, Leo240, impetuhant, Saviloff, VolodymyrKr, hydrognomik, iegik
 Обновлялась последний раз: RayzRazko,