1/5 (1) ООП в JavaScript ES5

Раннее использование ООП в JavScript ES5 базировалась на прототипной реализации ОО модели и описывается в данной статье для лучшего понимания ООП в JavaScript ES6.

«Старая версия» — условное понимание, потому что, во многом, реализация ООП в ES6 — это синтаксический сахар поверх реализации ES5 и имеется обратная совместимость кода.

Очень важно понимать, при чтении данной статьи, что все описание ООП в данном контексте относится к ES5.

1. Объекты в JavaScript

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

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

В JavaScript версии ES5 объект создается передачей параметров родителя и свойств в метод глобального объекта Object.create(params). Если параметры не заданы и задан прототип, как null, то создается пустой объект без родительского объекта и свойств. Объекты можно создать литеральным способ, который неявно будет наследоваться от Object.prototype или явно через метод Object.create

var person = Object.create(null)
console.log(person) // [Object: null prototype] {}

person = {}
console.log(person) // {}

2. Свойства объектов

Если кратко, то свойства — это ключи или свойства объекта, которые уникальны в пределах данного объекта.

2.1. Прототип и свойства

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

Грубо говоря, в парадигме ООП в JavaScript, принято говорить, что:

  1. родительский объект — это прототип, т.е. объект, с которого делается копия или наследуется
  2. свойства — это поля и методы нового объекта.

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

  • Object.defineProperty(prototype, property, descriptor)
  • Object.defineProperties({prototype, { property, descriptor}, … )

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

var person = {
    name: "Sara",
    profession: "Student",
    age: 19,
}

Object.defineProperties(person, { name:   { value:        'Sara'
                                           , writable:     true
                                           , configurable: true
                                           , enumerable:   true }

                                 , age:    { value:        20
                                           , writable:     true
                                           , configurable: true
                                           , enumerable:   true }

                                 , gender: { value:        'Female'
                                           , writable:     true
                                           , configurable: true
                                           , enumerable:   true }})

console.log(person) // { name: 'Sara', profession: 'Student', age: 20, gender: 'Female' }

2.2. Дескрипторы свойств

Дескриптор — это объект, который описывает поведение создаваемого или переопределяемого свойства. Свойства дескриптора или флаги управляют данными и доступом к этим данных. Флаги задаются значением true или false, а свойство данных принимает определенное значение

Рассмотрим некоторые флаги:

  • writable — значение свойства может быть изменено, используется только для дескрипторов данных.
  • configurable — тип свойства может быть изменён или свойство может быть удалено.
  • enumerable — свойство используется в общем перечислении.
  • value — значение свойства. Если новому свойству не задано значение, то оно будет undefined
  • get — принимает функцию без аргументов, которая возвращает значение свойства после обработки.
  • set — принимает функцию с аргументом, который становится значением свойства после обработки.

2.3. Другие способы создания свойств

Способ создания и переопределения свойств объекта при помощи Object.defineProperty/Object.defineProperties не удобен для повседневного использования программистом, поэтому, чтобы облегчить динамическое создание свойств в JavaScript есть возможность использовать скобочный и точечный способы определения.

2.3.1. Скобочный способ определения свойств объекта

Первый способ — это скобочный способ создания или переопределения значения свойств объекта, который заключается в использовании квадратных скобок

var person = {
    name: "Sara",
    profession: "Student",
    age: 19,
}

person["gender"] = "Female"
person["age"] = 20

console.log(person) // { name: 'Sara', profession: 'Student', age: 20, gender: 'Female' }

2.3.2. Точечный способ определения свойств объекта

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

var person = {
    name: "Sara",
    profession: "Student",
    age: 19,
}

person.gender = "Female"
person.age = 20

console.log(person) // { name: 'Sara', profession: 'Student', age: 20, gender: 'Female' }

2.4. Удаление свойств объекта

Удаление свойств объекта производится ключевым словом delete, после чего значение удаленного свойства вновь становится undefined

var person = {
    name: "Sara",
    profession: "Student",
    age: 19,
}

person.gender = "Female"
person["age"] = 20

delete person.gender
delete person["age"]

console.log(person) // { name: 'Sara', profession: 'Student' }

Оператор delete вернёт true, если свойство удалено, и false — в противном случае.

2.5. Геттеры и сеттеры

В разделе 1.3 мы не разъяснили суть 2-х дополнительных флагов get и set. Если кратко, то суть их заключается в создании прокси блока, в котором принимаемый или устанавливаемый свойству значение проходит дополнительный фильтр обработки.

var person = {
    firstName: "Sara",
    lastName: "Rain",
    profession: "Student",
    age: 19,
}
function setName (name){
    var n = name.trim().split(/\s+/)
    this.firstName = n[0]
    this.lastName = n[1]
}
function getName (){
    return this.firstName + ' ' + this.lastName
}
Object.defineProperty(person,   'name',   { 
                                            get: getName, 
                                            set: setName,
                                            //writable: true,
                                            configurable: true,
                                            enumerable:   true })
console.log(person) // {firstName:'Sara',lastName:'Rain',profession:'Student',age:19,name:[Getter/Setter]}
person.name = "Erika Johnson"
console.log(person) // {firstName:'Erika',lastName:'Johnson',profession:'Student',age:19,name:[Getter/Setter]}
console.log(person.name) // Erika Johnson

нужно заметить, что в случае указания значений для свойств дескриптора set и get нужно убрать флаг writable, иначе выйдет ошибка неоднозначности.

2.6. Перечисление свойств

Учитывая то, что свойства в JavaScript являются динамическими коллекциями пары ключ-значение, то в языке предусмотрен механизм, который следит за тем, чтобы контролировать процесс перечисления.

Чтобы перечислить все свойства, или почти все, в составе объекта Object имеются 2 метода

  1. Object.getOwnPropertyNames(person) — вернёт вам Array, содержащий имена всех свойств, установленных для данного объекта.
  2. Object.keys(person) — вернёт список собственных свойств, которые помечены флагом enumerable
var person = {
    name: "Sara",
    profession: "Student",
    age: 19,
}

Object.defineProperties(person, { name:   {
                                            enumerable:   true }

                                 , age:    {
                                            enumerable:   true }

                                 , gender: {
                                            enumerable:   false }})

console.log(Object.getOwnPropertyNames(person)) // [ 'name', 'profession', 'age', 'gender' ]
console.log(Object.keys(person)) // [ 'name', 'profession', 'age' ]

3. Методы

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

3.1. Динамическое создание методов

Динамическое создание методов ничем не отличается от создания обычных свойств, учитывая, что функции в JavaScript — это те же объекты, которые можно присваивать переменным

var person = {
    name: "Sara Rain",
    age: 19,
}

person['sayHello'] = function(){
    console.log(this.name + ' say hello!')
}
person.sayBye = function(){
    console.log(this.name + ' say bye!')
}

person.sayHello() // Sara Rain say hello!
person.sayBye() // Sara Rain say bye!

3.2. Динамический this

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

Есть 4 способа разрешения this внутри функции:

  • как метод
  • непосредственно
  • явное применение
  • как конструктор

Пока рассмотрим первые 3 способа. Чтобы рассмотреть каждый способ разрешения напишем тестовый код

function add(other) {
    var result = this.value + ' ' + other
    console.log(result)
    return result
}

var objectOne = { value: 'objectOne', add: add }
var objectTwo = { value: 'objectTwo', add: add }

3.2.1. Разрешение this, как метод

Если функция вызывается, как метод объекта, то this внутри функции ссылается на сам объект. Т.е. когда мы явно указываем какой объект выполняет действие, то объект и будет значением this в нашей функции


...
objectOne.add(objectTwo.value) // this === objectOne
// => objectOne objectTwo
objectTwo.add('someValue') // this === objectTwo
// => objectTwo someValue

3.2.2. Непосредственное разрешение this

Когда функция вызывается непосредственно, то this разрешается в глобальный объект движка (window в браузере, global в Node.js)

...
add(objectTwo.value)  // this === global
// => undefined objectTwo"
// В глобальной области нет переменной `value', давайте решим это
value = 'someValue'
add(objectTwo.value)  // this === global
// => someValue objectTwo

3.2.3. Явное разрешение this

Функция может быть явно применена к любому объекту, несмотря на то, есть ли в объекте соответствующее свойство или нет. Эта функциональность достигается с помощью методов call или apply.

Функция call ожидает объект, как первый параметр функции, за которым следуют обычные аргументы исходной функции:

...
add.call(objectTwo, 'someValue1', 'someValue2') // this === objectTwo
// => objectTwo someValue1
add.call(window, 'someValue3') // this === global
// => undefined someValue3
add.call(objectOne, objectOne.value) // this === objectOne
// => objectOne objectOne

Функция apply позволяет описывать вторым параметром массив параметров исходной функции:

...
add.call(objectTwo, ['someValue1', 'someValue2']) // this === objectTwo
// => objectTwo someValue1
add.call(window, ['someValue3']) // this === global
// => undefined someValue3
add.call(objectOne, [objectOne.value]) // this === objectOne
// => objectOne objectOne

Пожалуйста, оцените материал

WebSofter

Web - технологии