ООП

в

JavaScript

Один из важнейших принципов ООП – отделение внутреннего интерфейса от внешнего.

  • Внутренний интерфейс – это свойства и методы, доступ к которым может быть осуществлен только из других методов объекта, их также называют «приватными» (есть и другие термины, встретим их далее).
  • Внешний интерфейс – это свойства и методы, доступные снаружи объекта, их называют «публичными».

Прототип

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

Связующим звеном выступает специальное свойство __proto__.

Объект, на который указывает ссылка __proto__, называется «прототипом».

Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.

Обычный цикл for..in не делает различия между свойствами объекта и его прототипа.

Вызов obj.hasOwnProperty(prop) возвращает true, если свойство propпринадлежит самому объекту obj, иначе false.

var animal = {
  eats: true
};

var rabbit = {
  jumps: true,
  __proto__: animal
};

alert( rabbit.hasOwnProperty('jumps') );
// true: jumps принадлежит rabbit

alert( rabbit.hasOwnProperty('eats') );
// false: eats не принадлежит

Что делать, если нужен объект БЕЗ прототипа???

Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств.

Чтение: Object.getPrototypeOf(obj)

Возвращает obj.__proto__ (кроме IE8-)

Запись: Object.setPrototypeOf(obj, proto)

Устанавливает obj.__proto__ = proto (кроме IE10-).

 

Создание объекта с прототипом: Object.create(proto, descriptors)

Создаёт пустой объект с __proto__, равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать дескрипторы свойств.

Prototype

let sleep = {
    isLikeSleep: true
};

function Student (name) {
    this.name = name;
}

Student.prototype = sleep;

let ivanov = new Student("Ivan");
console.log(ivanov.isLikeSleep);// true

Странности

let testObj = {};

testObj.toString();//"[object Object]"
let testNumber = 100;

testNumber.valueOf();// 100
testNumber.toFixed(5);// "100.00000"
testNumber.toString();// "100"
testString = "bla-bla";
testString.length; // 7
testString.slice(-4); // "-bla"
let testBoolean = true;

testBoolean.valueOf(); //true
testBoolean.toString(); //"true"
function Animal(name) {
  this.speed = 0;
  this.name = name;

  this.run = function(speed) {
    this.speed += speed;
    console.log( this.name + ' бежит, скорость ' + this.speed );
  };

  this.stop = function() {
    this.speed = 0;
    console.log( this.name + ' стоит' );
  };
};

let testAnimal = new Animal('Зверь');

console.log( animal.speed ); // 0, начальная скорость
animal.run(3); // Зверь бежит, скорость 3
animal.run(10); // Зверь бежит, скорость 13
animal.stop(); // Зверь стоит
function Animal(name) {
  this.name = name;
  this.speed = 0;
}

// методы в прототипе
Animal.prototype.run = function(speed) {
  this.speed += speed;
  console.log( this.name + ' бежит, скорость ' + this.speed );
};

Animal.prototype.stop = function() {
  this.speed = 0;
  console.log( this.name + ' стоит' );
};

var animal = new Animal('Зверь');

console.log( animal.speed ); // 0, свойство взято из прототипа
animal.run(5); // Зверь бежит, скорость 5
animal.run(5); // Зверь бежит, скорость 10
animal.stop(); // Зверь стоит

Достоинства

  • Функциональный стиль записывает в каждый объект и свойства и методы, а прототипный – только свойства. Поэтому прототипный стиль – быстрее и экономнее по памяти.

Недостатки

  • При создании методов через прототип, мы теряем возможность использовать локальные переменные как приватные свойства, у них больше нет общей области видимости с конструктором.
// 1. Конструктор Animal
function Animal(name) {
  this.name = name;
  this.speed = 0;
}

// 1.1. Методы -- в прототип
Animal.prototype.stop = function() {
  this.speed = 0;
  console.log( this.name + ' стоит' );
}

Animal.prototype.run = function(speed) {
  this.speed += speed;
  console.log( this.name + ' бежит, скорость ' + this.speed );
};

// 2. Конструктор Rabbit
function Rabbit(name) {
  this.name = name;
  this.speed = 0;
}

// 2.1. Наследование
Rabbit.prototype = Object.create(Animal.prototype);
Rabbit.prototype.constructor = Rabbit;

// 2.2. Методы Rabbit
Rabbit.prototype.jump = function() {
  this.speed++;
  console.log( this.name + ' прыгает, скорость ' + this.speed );
}

Классы

Синтаксис для классов

class Название [extends Родитель]  {
  constructor
  методы
}
class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

let user = new User("Вася");
user.sayHi(); // Вася

Функция constructor запускается при создании new User, остальные методы записываются в User.prototype.

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // геттер
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  // сеттер
  set fullName(newValue) {
    [this.firstName, this.lastName] = newValue.split(' ');
  }
 
};

let user = new User("Вася", "Пупков");
alert( user.fullName ); // Вася Пупков
user.fullName = "Иван Петров";
alert( user.fullName ); // Иван Петров

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

Для их объявления используется ключевое слово static.

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  static createGuest() {
    return new User("Гость", "Сайта");
  }
};

let user = User.createGuest();

alert( user.firstName ); // Гость

alert( User.createGuest ); // createGuest ... (функция)

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

Также статическими удобно делать константы:

class Menu {
  static get elemClass() {
    return "menu"
  }
}

alert( Menu.elemClass ); // menu
class Child extends Parent {
  ...
}
class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

class Rabbit extends Animal {
  walk() {
    super.walk();
    alert("...and jump!");
  }
}

new Rabbit("Вася").walk();
// I walk: Вася
// and jump!
class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

class Rabbit extends Animal {
  constructor() {
    // вызвать конструктор Animal с аргументом "Кроль"
    super("Кроль"); // то же, что и Animal.call(this, "Кроль")
  }
}

new Rabbit().walk(); // I walk: Кроль
Made with Slides.com