Понимание прототипного наследования, полиморфизма и инкапсуляции в JavaScript

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

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

Итак, приведенное выше утверждение означает, что объекты обрабатывают связанные данные, группируя их по состоянию и/или поведению, поскольку эти данные структурированы как группа внутри объекта, и ими становится легче манипулировать. Большинство вещей в программировании субъективны, поскольку определения меняются и появляются новые идеи, поэтому, чтобы полностью решить, где вы хотите стоять, вы должны провести собственное исследование. Я хочу сказать, что вы увидите статьи, которые не принимают JavaScript как объектно-ориентированный, но я отношусь к группе, которая просто рассматривает его как прототип объектно-ориентированного языка.

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

function Human () {
//   Encapsulated value to be available only within the object
  var head = 1;
  this.eyeColor = 'black'; // Public value
}
Human.prototype.getHead = function() {
  return this.head;
}
const boy = new Human();
console.log(boy.eyeColor); //'black'
console.log(boy.getHead()); //head is undefined

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

Что такое прототипирование в JavaScript

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

Когда создается новая функция или класс, JavaScript создает новый объект свойства, известный как прототип, который будет присоединен к конструктору. Новый объект prototype также указывает на собственный объект JavaScript, используя внутреннюю структуру. личная ссылка. Частная ссылка называется прототипом в двойных скобках или [[Prototype]], а для упрощения доступа браузер предоставляет общедоступную ссылку, известную как __proto__ . Причина, по которой я это объясняю, заключается в том, что прототип с момента объявления конструктора связан с основным объектом JavaScript, и оттуда начинается связь между объектами.

class Year  {
 year = new Date().getFullYear;
  constructor(season){
    this.season = season
    
  }
}
console.log(Year.prototype)// constructor Year and [[Prototye]]
const day = new Year('summer');
console.log(day.__proto__ === Year.prototype); //true

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

Если вы внимательно посмотрите, то увидите, что поле year было объявлено вне конструктора, оно известно как общедоступное поле и используется, когда вы знаете значение, которое оно примет. Подробнее об этом, когда мы обсуждаем классы, я не хотел вас смущать.

Существуют разные способы создания объектов в JavaScript;

  1. Литеральный метод объекта
  2. Метод функционального конструктора
  3. Метод класса

ОБЪЕКТНЫЙ ЛИТЕРАЛЬНЫЙ МЕТОД

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

Литерал объекта может быть объявлен как

const house = new Object();
house.color = 'green';
console.log(house.color);// 'green'
OR
const house = {color: 'green', address: 'anywhere'}
console.log(house.color);// 'green'

Различные способы доступа к объектам

  1. Точечный метод доступа: этот метод мне нравится использовать, если нет причин не делать этого. например. «дом.цвет», «дом.адрес».
  2. Доступ к свойствам квадратных скобок: этот метод использует квадратную скобку с ключом в виде строки, например. дом[‘цвет’], дом[‘адрес’]
  3. Деструктуризация объекта: этот метод использует фигурные скобки и ключи объекта для доступа к значению объекта сразу после создания переменной. например. «const {цвет, адрес} = дом;» теперь переменные имеют свойства, доступные ключам.

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

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

  1. Метод Object.create: создает новый объект или создает новый объект и назначает прототип объекта, переданного в качестве аргумента.
  2. Object.defineProperty или Object.defineProperties: как следует из названий, первый предназначен для определения одного свойства, а другой — для одного или нескольких. Вы можете подумать, что не лучше ли просто использовать запись через точку для создания свойства. Этот метод очень удобен, когда вы хотите установить правила для своей собственности.
const human = {
legs: 2,
hands: 2
}
Object.defineProperty(human, 'age', {
value: 40,
writable: false,
configurable: false
})

Что делает приведенный выше код, так это определяет свойство возраст, присваивает ему значение 40, устанавливает для записи значение ложь, это означает, что свойство возраста неизменяемо, поэтому, если вы сделали Human.age = 30 после человека. age останется равным 40. Настраиваемый означает, что после того, как свойство было определено, параметр свойства больше не может быть настроен, может быть установлена ​​только конфигурация с возможностью записи. проверьте mdn, чтобы узнать больше.

3. Object.freeze: предотвращает изменение существующих атрибутов и значений свойств, а также предотвращает добавление новых свойств. После создания и установки значений для объекта, и вы не хотите, чтобы он каким-либо образом изменялся, вы замораживаете его.

const human = {
legs: 2,
hands: 2
}
Object.freeze(human) // Sets it to readonly

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

const human = {
legs: 2,
hands: 2
}
const year= {
month: 'October',
day: 'Tuesday'
}
const humanPlusYear = Object.assign({}, year, human);//{month: October, day: Tuesday, legs: 2, hands: 2}

Вы можете проверить Object.setPrototypeOf(), Object.getPrototypeOf(), Object.seal(), похожий на замораживание, но ограниченный, Object.isSealed(), проверяющий, запечатан ли объект, Object.isFrozen(), проверяющий, заморожен ли объект , Object.preventExtensions() предотвращает добавление дополнительных свойств, Object.keys() получает все ключи объекта, Object.is() проверяет, равны ли два объекта.

Метод функционального конструктора

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

function Human() {
 this.head = 1;
 this.legs = 2;
 this.getAge = (yob) => {
 return new Date().getFullYear() - yob
 }
}

Из-за функции getAge вы добавляете поведение к объекту, и вполне вероятно, что вам потребуются экземпляры этого конструктора, которых вы не можете достичь с помощью литералов объекта. Функциональные конструкторы также ограничены, и современная среда IDE предлагает вам переключиться на класс. До появления классов в ES6 единственным способом достижения инкапсуляции в JavaScript были функциональные конструкторы. Все, что вам нужно было сделать, это объявить поле как переменную, и из-за ограничения области видимости оно рассматривается как частное поле.

function Human() {
 //this is private to this constructor
 let specie = 'human';
 this.head = 1;
 this.legs = 2;
 this.getAge = (yob) => {
 return new Date().getFullYear() - yob
 }
}

Переменная вида недоступна вне Конструктора, поэтому достигается инкапсуляция. Это подводит нас к следующим классам.

Метод класса

Метод класса раньше широко назывался синтаксическим сахаром для функциональных конструкторов, но он эволюционировал и стал предпочтительным синтаксисом, когда требуются конструкторы. Он поставляется с более простым и одобренным синтаксисом, который теперь широко доступен. Один из синтаксиса, который выглядит небольшим, но не может быть переоценен, заключается в том, что ключевое слово «это» используется свободно, вы можете добавить функцию свойства без явного использования ключевого слова «это». Частное и общественное поле также были введены и теперь хорошо известны.

class Year  {
 year = new Date().getFullYear();
 #somethingPrivate= 'I am Private'
 constructor(season){
    this.season = season
    
  }
 getYear() {
  return this.year;
 }
 getSomethingPrivate() {
  return this.#somethingPrivate
 }
}
const thisYear = new Year('Summer');
console.log(thisYear.year)//2022
console.log(thisYear.season)// Summer
console.log(thisYear.getSomethingPrivate())// I am Private
console.log(thisYear.#somethingPrivate)//Uncaught SyntaxError: Private field '#month' must be declared in an enclosing class

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

Расширение класса

Класс Extends весьма полезен, поскольку он помогает копировать значения из родительского класса в дочерний, и теперь этот дочерний класс также может быть создан. Все, что имеет конструктор, может быть расширено, даже основные классы JavaScript, такие как объекты Date, Math, Error и т. д. Таким образом, все доступные свойства копируются в дочерний класс.

class myDate extends Date {
  getFormattedDate() {
    const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    return `${this.getDate()}-${months[this.getMonth()]}-${this.getFullYear()}`;
  }
}

Это популярный пример из MDN, расширяющий класс Date и использующий метод непосредственно в классе myDate. Доступ к свойствам можно получить с помощью ключевого слова this.

Я надеюсь, что вы лучше разберетесь в JavaScript Object после прочтения этого, спасибо, что дочитали до конца.

Удачного кодирования.