Что такое инкапсуляция в javascript

ООП в JavaScript

В данной статье мы поговорим об основных особенностях объектно-ориентированного программирования в JavaScript:

  • создание объектов,
  • функция-конструктор,
  • инкапсуляция через замыкания,
  • полиморфизм и ключевые слова call/apply ,
  • наследование и способы его реализации.

Объекты в JavaScript

Объект в JavaScript — это ассоциативный массив, который содержит в себе наборы пар ключ-значение («хэш», «объект», «ассоциативный массив» означают в JavaScript одно и то же).

Создание объекта в JavaScript:

var obj = new Object(); // вызов функции конструктора var obj = <>; // при помощи фигурных скобок. 
obj.name = ‘Victor’; // через .property obj[‘name’]=‘Victor’; // как элементу массива 
console.log(obj.name); // через .property console.log(obj[‘name’]); // как к элементу массива через квадратные скобки 

Constructor и ключевое слово new

«Конструктор — это любая функция, которая используется как конструктор». До появления ECMAScript 6 в JavaScript не было понятия конструктор. Им могла быть любая функция, которая вызывается с помощью ключевого слова new .

Пример использования конструктора:

 var Donkey = function()< //… >; // создаем объект «ослик» var iaDonkey = new Donkey(); 

При вызове new Donkey (), JavaScript делает четыре вещи:

  1. 1. Создаёт новый объект:
    iaDonkey = new Object(); // присваивается новый пустой объект.
  2. 2. Помещает свойства конструктора объекта Donkey:
    aDonkey.constructor == Donkey // true
    iaDonkey instanceof Donkey // true
  3. 3. Устанавливает объект для переноса в Donkey.prototype:
    iaDonkey.__proto__ = Donkey.prototype
  4. 4. Вызывает Donkey() в контексте нового объекта:
// То же самое, только на грубом псевдокоде: function New (F, args) < /*1*/ var n = ; /*2*/ F.apply(n, args); /*3*/ return n; > 
  1. Создание нового значения (n) и запись значения prototype в proto .
  2. Вызов нашего метода конструктор через apply .
  3. Возвращение нового объекта, класса New .

Инкапсуляция через замыкания

Замыкание — это основанный на области видимости механизм, который может создаваться через функцию. Каждая функция создаёт новую область видимости.

В этом цикле десятка выводится на экран десять раз: после последней итерации будет 10, и тогда начнётся выполнение setTimeout .

Анонимная самовызывающаяся функция позволяет начать выполнение функции сразу после ее объявления.

Мы применили принцип замыкания: объявляем функцию, передаем в неё фактическое значение, и она «замыкает» в себе значение переменной i. m попытается через замыкания получить значение из ближайшей верхней области видимости. А так как мы передали ее через самовызывающуюся функцию, то она каждый раз будет равна своему значению (значению переменной i ), и мы 10 раз получим от 0 до 9.

Этот пример про замыкания и инкапсуляцию взят из реального проекта:

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

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

Полиморфизм и ключевые слова call/apply

Применение конструкции apply :

var obj = < outerWidth: ‘pliers‘ >; function getWidth() < return this.outerWidth; >var a = getWidth(); var b = getWidth.apply(obj); console.log(a); // текущая ширина браузера, this будет windows console.log(b); // на экран выведется pliers. outerWidth — это свойство объекта windows, мы, по сути, вызовем windows.outerWidth 
Calling func.call(context, a, b. ) 
func(a, b. ), but this == context. 

Оба вызова идентичны, только apply позволяет передавать параметры через массив.

call(context, param1, param2 …) apply(context, [args]) 

Четыре варианта вызова и его результаты:

Вызов function: function(args) – this == window Вызов method: obj.funct(args) – this == obj Apply: func.apply(obj,args) – this == obj Constructor: new func(args) – this == new object 

Наследование и методы реализации

Модель базового фильтра — стандартный набор параметров, который есть в фильтре любого приложения. Эти параметры необходимы для пагинации, номера страницы и т.п.

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

Есть класс-наследник и конкретная страница “RouteHistorical”. Класс наследуется от базового фильтра, но дополнительно имеет свои поля и параметры.

В строке 73 мы передаём в базовый класс через контекст apply новосозданный объект RouteHistorical и те же аргументы. Метод инициализирует все поля, и мы получаем новый объект.

Строки 81-82 позволяют нам сделать RouteHistorical наследником базового фильтра. В строке 81 мы записываем ссылку на класс конструктора базы в свойство prototype . Метод prototype перезаписывается полностью, и конструктор теряется. Когда мы создаем новый объект, он не будет знать, к чему обратиться.

В строке 82 мы задаем свойству prototype.constructor ссылку на саму себя. Свойство класса constructor всегда ссылается на самого себя.

Прототипы

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

prototype нужен только для того, чтобы сказать, что нужно записать в __proto__ при инстанцировании нового объекта.

 // unsafe var filter = < EnablePagination: true >; function BaseFilter(size) < this.PageSize = size; this.__proto__ = filter; >// safe var filter= < EnablePagination: true >; function BaseFilter(size) < this.PageSize = size; >BaseFilter.prototype = filter; 

Две записи одинаковы, но обращаться напрямую к __proto__ считается небезопасным, и не все браузеры это позволяют.

Создание потомка из базового класса

 function extend(Child, Parent) < var F = function() < >F.prototype = Parent.prototype // Child.prototype = new F() // при создании Child в __proto__ запишется наш родитель prototype Child.prototype.constructor = Child // задаём конструктор, должен ссылаться на самого себя. Child.superclass = Parent.prototype // чтобы иметь доступ к методам Parent >; 
 function BaseFilterModel(..) < . >function RouteHistoricalFilterModel(..)

instanceof

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

instanceof (псевдокод метода):

 function isInstanceOf(obj, constructor) < if (obj.__proto__ === constructor.prototype) < return true; >else if (obj.__proto__ !== null) < return isInstanceOf(obj.__proto__, constructor) >else < return false >>; 

Итог

1. В JavaScript до ECMAScript 6 не было классов, были только функции конструктора, которые вызываются с помощью ключевого слова new .
2. Цепочка прототипирования — это основа наследования в JavaScript.
3. Когда мы обращаемся к свойству, то оно ищется в объекте. Если не находится, то в __proto__ , и так далее по всей цепочке. Таким образом в JavaScript реализуется наследование.
4. fn.__proto__ хранит ссылку на fn.prototype .
5. Оператор new создает пустой объект с единственным свойством __proto__ , который ссылается на F.prototype . Конструктор выполняет F , где контекст this — ранее созданный объект, устанавливает его свойства и возвращает этот объект.
6. Оператор instanceof не проверяет, чтобы объект был создан через конструктор ObjectsConstructor , а принимает решение на основе всей цепочки прототипирования.
7. В JavaScript this зависит от контекста, т.е. от того, как мы вызываем функцию.

Источник

Что такое инкапсуляция в javascript

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

function User(pName, pAge) < this.name = pName; this.age = pAge; this.displayInfo = function()< document.write("Имя: " + this.name + "; возраст: " + this.age); >; >; var tom = new User("Том", 26); tom.name=34; console.log(tom.name);

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

function User (name, age) < this.name = name; var _age = age; this.displayInfo = function()< document.write("Имя: " + this.name + "; возраст: " + _age + "
"); >; this.getAge = function() < return _age; >this.setAge = function(age) < if(typeof age === "number" && age >0 && age <110)< _age = age; >else < console.log("Недопустимое значение"); >> > var tom = new User("Том", 26); console.log(tom._age); // undefined - _age - локальная переменная console.log(tom.getAge()); // 26 tom.setAge(32); console.log(tom.getAge()); // 32 tom.setAge("54"); // Недопустимое значение

В конструкторе User объявляется локальная переменная _age вместо свойства age . Как правило, названия локальных переменных в конструкторах начинаются со знака подчеркивания.

Для того, чтобы работать с возрастом пользователя извне, определяются два метода. Метод getAge() предназначен для получения значения переменной _age. Этот метод еще называется геттер (getter). Второй метод — setAge , который еще называется сеттер (setter), предназначен для установки значения переменной _age.

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

Источник

Читайте также:  Python prettytable from dict
Оцените статью