Объектно-ориентированное программирование на JavaScript

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

Объектно-ориентированное программирование на JavaScript
9 мин
Автор PINTA IT

Объектно-ориентированное программирование на JavaScript

Объектно-ориентированное программирование -это методология организации кода путем представления объектов данных.

Инкапсуляция позволяет частям кода управлять своим состоянием и взаимодействовать с другим кодом через интерфейс.

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

Есть два способа отличить публичную собственность от частной:

  • Укажите в документации публичные и частные свойства.
  • Используйте подчеркивание _ перед частным свойством.

Прототипы - это резервные объекты, от которых объект может наследовать. Большинство встроенных объектов наследуются от Object.prototypeобъекта- предка .

Классы - это план объекта. Объекты, производные от классов, являются экземплярами класса.

Шаблон класса был представлен в ES2015 и представляет собой функцию-конструктор со свойством прототипа. Это позволяет упростить описание классов.

Полиморфизм позволяет использовать объекты более чем одним способом.

Геттеры и сеттеры позволяют читать и писать в объявление объекта или класса.

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

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

Инкапсуляция

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

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

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

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

Метод

Методы  это имена свойств в объекте, которые содержат значения функций. Например, простой способ:

const pet = {};
pet.bark = (bark) => {
    console.log(`My dog says '${bark}'`);
}

pet.bark("Woof") // → My dog says 'woof'

Методы вызываются у объекта для выполнения определенных задач. Когда функция вызывается как метод, привязка thisуказывает на объект, для которого был вызван метод.

Например:

function speak(line) {
    console.log(`The ${this.position} person says '${line}'`);
}

const firstHomie = {position: "first", speak};
const secondHomie = {position: "second", speak};

firstHomie.speak("Yo man, wats happenin?");
// → The first person says 'Yo man, wats happenin?'

secondHomie.speak("All good dawg!");
// → The second person says 'All good dawg!'

thisМожно назвать , используя метод , называемый функцию call. callМетод принимает значение в thisкачестве первого аргумента, другие аргументы рассматриваются как нормальные параметры.

speak.call(secondHomie, "Good to know."); // → The second person says 'Good to know.'
ПРИМЕЧАНИЕ:
В JavaScript "this" всегда связывает объект или указывает на него и может использоваться для ссылки на объект.

Обычная функция, определенная с помощью functionключевого слова, не может ссылаться на область thisоболочки. такая функция может использовать только свою собственную thisпривязку. Стрелочные функции работают иначе. Стрелочная функция может получить доступ к thisпривязке области вокруг нее.

например, ссылка thisизнутри локальной функции:

/* Using normal function */
function normalize () { // → wrapping scope
    console.log(this.coords.map(function(n){
        n / this.length
    }));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → Undefinded values [ undefined, undefined, undefined ]

/** Using arrow function **/
function normalize () {
    console.log(this.coords.map(n => n / this.length));
}
normalize.call({coords: [0, 2, 3], length: 5});
// → [0, 0.4, 0.6]

Прототипы

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

let emptyObject = {};
console.log(emptyObject.toString());
// → [object Object]

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

Вы можете проверить прототип объекта с помощью конструктора объекта «сек getProtoypeOfметода:

//...

console.log(Object.getPrototypeOf(emptyobject));
// → {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ,…}

getProtoypeOfМетод возвращает наследственные свойства прототипа. Object.prototypeпредоставляет несколько методов по умолчанию, например toString(), которые отображаются во всех объектах.

Некоторые объекты JavaScript наследуются не от Object.prototypeсвоего прототипа, а от другого объекта, который предоставляет набор значений по умолчанию. Дата наследовать от Date.prototype, массивы от Array.prototypeи функции от Function.prototype.

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

let protoMouse = {
    speak(line) {
      console.log(`The ${this.type}Mouse says ${line}`);
  }
}

let dangerMouse = Object.create(protoMouse);
dangerMouse.type = "DANGER";
dangermouse.speak("Pooww!")
// → The DANGER mouse says Poow!

Из приведенного выше кода dangerMouseобъект имеет свойство по умолчанию, speakравное его резервному объекту, protoMouseи свойство typeэтого объекта применяется только к нему самому. protoMouseОбъект может быть использован в качестве контейнера для всех мышей. У вас могут быть «мышь-ниндзя», «мышь-ботаник», «техническая мышь» и так далее. Каждый объект мыши может реализовать свою собственность , но все один и тот же прототип, protoMouse.

Классы JavaScript

До того, как шаблон класса был введен в JavaScript, язык использовал прототип для реализации классов ООП.

Класс - это концепция ООП для определения свойств (методов и атрибутов) объекта в структурированном виде. Это план объекта. Производные классы объектов являются экземпляром этого класса.

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

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

//...

function makeMouse(type) {
    let mouse = Object.create(protoMouse);
    mouse.type = type;
    return mouse;
}

// Instance of 'makeMouse' class
let ninjaMouse = makeMouse("Ninja");

ninjaMouse.speak("Haiiyahh!");
// → The NinjaMouse says Haiiyahh!

Приведенный выше код является одним из способов сделать это, но есть более простой способ: использовать newключевое слово.

Когда перед функцией стоит newключевое слово, она создает и автоматически возвращает пустой объект, связанный с thisключевым словом. Объект имеет свойство прототипа, производное от Object.prototype.

На этот раз создадим другой класс:

function Person(first, last, age, eye) {
    this.firstName = first;
    this.lastName = last;
    this.age = age;
    this.eyeColor = eye;
}

// All instances will share this property
Person.prototype.speak = functin(line) {
    console.log(`${this.firstName} says '${line}'`);
}

// An instance of the 'Person' class
let teacher = new Person("Romeo", "Peter", 22, "black");
let student =  new Person("Jane", "Doe", 25, "brown");

console.log(teacher.name); // → Romeo
console.log(student.name); // → Jane

teacher.speak("hello world!"); // → Romeo says 'hello world!'
student.speak("hello!"); // → Jane says 'hello!
ПРИМЕЧАНИЕ:
Считается хорошей практикой называть функции-конструкторы с первой буквы в верхнем регистре.

Поскольку конструкторы являются функциями, фактический прототип функции Function.prototype.

Шаблон класса

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

class Person {
    constructor(first, last, age, eye) {
        this.firstName = first;
      this.lastName = last;
      this.age = age;
      this.eyeColor = eye;
    }

    speak(line) {
        console.log(`${this.firstName} says '${line}'`);
    }
}

// Instances of 'Person' class
let teacher = new Person("Romeo", "Peter", 22, "black");

teacher.speak("hello world!"); // → Romeo says 'hello world!'

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

ПРИМЕЧАНИЕ:
Считается хорошей практикой называть шаблон класса первой буквой в верхнем регистре.

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

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

Полиморфизм

Полиморфизм означает «множество форм».

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

Например, мы описали, какую форму может формировать человек , объявив Personкласс. Следовательно, человек может быть отцом , матерью , дочерью или сыном .

// ...

// Instances of 'Person' in more than one form
let father = new Person("John", "Doe", 30, "black");
let Mother = new Person("Jane", "Doe", 25, "brown");
let daughter new Person("Gill", "Doe", 3, "black");
let son = new Person("Jack", "Doe", 3, "brown");

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

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

Возможна перезапись toString()метода в прототипе. Давайте сделаем это, создав строку, которая будет возвращена String()методом.

// ...

Person.prototype.toString = function() {
    return `Teacher's name is ${this.firstName}.`;
}

console.log(String(teacher)); // → Teacher's name is Romeo.

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

Геттеры, сеттеры и статики

Геттеры и сеттеры позволяют читать и записывать в выражение объекта или объявление класса. Это свойства, у которых есть скрытые вызовы методов.

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

Например, класс для получения разных размеров:

// Object expression
let varyingSize = {
    get size() {
        return Math.floor(Math.random() * 100);
    }
}
console.log(varySize.size) // → 12

//-----------------------------------

// Class declaration
class VarifyingSize {
    get size() {
        return Math.floor(Math.random() * 100);
    }
}
let size = new verifyingSize();
console.log(size.size); // → 61

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

Например, температурный класс по Фаренгейту:

class Temperature {
    constructor(celsius) {
        this.celsius = celsius;
    }

    get fahrenheit() {
        return this.celsius * 1.8 + 32;
    }

    set fahrenheit(value) {
       this.celsius = (value - 32) / 1.8;
    }
}

let temp = new Temperature(40);

// Get
console.log(temp.farenheit); // → 104

// Set
temp.fahrenheit = 86;
console.log(temp.celcius) // → 30

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

class Temperature {
  //...

    // Store on the class constructore
    static fromFahrenheit(value) {
        return new Tempareture((value 32) / 1.8);
    }
}

// Create temperature using degrees Fahrenheit
Tempareture.fromFahrenheit(100);
// → Temperature {celsius: 37.77777777777778}

Наследование

Наследование в ООП это когда класс расширяет другой класс.

Когда вы наследуете класс, вы создаете новые функциональные возможности и функции поверх существующего.

// Parent or super class
class Animal {
    constrcutor(name) {
        this.name = name;
        this.speed = 0;
    }

    run(speed) {
        this.speed = speed;
        console.log(`${this.name} runs with speed ${this.speed}.`);
    }

    stop() {
        this.speed = 0;
      console.log(`${this.name} stands still.`);
    }
}

//...

Приведенный выше код является универсальным классом для животных. Обычное животное, такое как лошадь, может бежать и останавливаться. Чтобы создать объект лошади, объект должен расширить класс Animal . Для этого extendsиспользуется ключевое слово. Ключевое слово extendsсообщает классу, что он не должен производиться напрямую от значения по умолчанию, Object.prototypeа от класса.

class Horse extends Animal {
    hide () {
        alert(`${this.name} hides!`);
    }
}

let horse = new Horse("Black Stallion");
horse.run(120) // → Black Stallion runs with speed 120.
horse.hide("neigh") // → Black stands hides!.
horse.stop() // → Black stands still.

Прототип JavaScript позволяет выводить свойства из одного класса в другой. Верхний класс, известный как родительский класс, имеет общие свойства с нижним, известным как дочерний класс. Дочерний класс может определять свои свойства, как метод.

ПРИМЕЧАНИЕ:
Любое выражение разрешено после extends
Синтаксис класса позволяет указать не просто класс, а любое выражение после него extends.
Например, вызов функции, которая генерирует родительский класс:
function f(phrase) {
return class {
sayHi() { alert(phrase); }
};
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
Здесь class Userнаследуется от результата f("Hello").
Это может быть полезно для сложных шаблонов программирования, когда мы используем функции для генерации классов в зависимости от многих условий и можем наследовать от них.
Источник: JavascriptInfo

По умолчанию все методы, не указанные в дочернем классе, наследуются от родительского класса. Например, stop()метод в предыдущем примере является производным от родительского класса. Если в дочернем классе указан тот же метод, он перезапишет родительский метод. Свойства в дочернем классе должны расширяться или создаваться только поверх родительского класса. Чтобы избежать перезаписи методов, дочерний класс должен вызывать свойства родительского класса (методы и конструктор) с superключевым словом.

Например, лошадь автоматически скроется при остановке.

class Horse extends Animal {
    hide () {
        alert(`${this.name} hides!`);
    }

    stop() { // Child class method. Does not overwrite parent
        super.stop(); // Call parent 'stop()' method
        this.hide(); // hide
    }
}

В приведенном выше классе лошади есть метод остановки, который вызывает в процессе родительский метод остановки.

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

class Horse extends Animal {
    // generated for extending classes without own constructors
    constructor(...args) {
        super(...args)
    }
}

Чтобы добавить настраиваемый конструктор в дочерние классы, конструктор должен вызвать его super()перед использованием this. Это связано с тем, что производный конструктор не может получить доступ к объекту, thisесли родительский конструктор не вызывается первым.

class Horse extends Animal {
    constructor(name, sound) {
        super(name);
        this.sound = sound;
    }

    //...
}

sound() {
    console.log(`The ${this.name} ${this.sound}'s.`)
}

let horse  = new Horse("Black Stallion", "neigh")

console.log(horse.name) // → Black Stallion
horse.sound() // → The Black Stallion neigh's.

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

Похожие публикации