Конструкторы

Гоголев Сергей

Неделей ранее ...

Объекты студента и предподавателя

var student = {
 name: 'Billy',
 type: 'human',
 getName: function() {},
 sleep: function() {}
};
var lecturer = {
 name: 'Sergey',
 type: 'human',
 getName: function() {}
 talk: function() {}
};

Проблема: дублирование кода

Объект «Личность»


var student = {
 name: 'Billy',
 sleep: function () {}
};

var lecturer = {
 name: 'Sergey',
 talk: function () {}
};

var person = {
 type: 'human',
 getName: function () {}
};

Делегирование


var student = {
 name: 'Billy',
 sleep: function () {}
};

var person = {
 type: 'human',
 getName: function () {}
};

student.getName();
// Billy

Внутреннее поле [[Prototype]]


var student = {
 name: 'Billy',
 sleep: function () {}
 [[Prototype]]: <ссылка на объект>
};

Способы связывания


var student = {
 name: 'Billy',
 sleep: function () {},
 [[Prototype]]: <person>
};
var person = {
 type: 'human',
 getName: function () {}
};

student.__proto__ = person;

Object.setPrototypeOf(student, person);

var student = Object.create(person);

student.name = 'Billy';
student.sleep = function () {};

Цепочка прототипов


student.getName();

var student = {
 name: 'Billy',
 [[Prototype]]: <person>
};
var person = {
 getName: function () { console.info(this.name); },
 [[Prototype]]: <Object.prototype>
};

Object.prototype = {
 toString: function () {},
 [[Prototype]]: null
};

Глобальные прототипы


Object.prototype = {
 toString: function () {},
 hasOwnProperty: function () {},
 [[Prototype]]: null
};

Array.prototype = {
 forEach: function () {},
 [[Prototype]]: <Object.prototype>
};

Function.prototype = {
 call: function () {},
 [[Prototype]]: <Object.prototype>
};

Object.getPrototypeOf()


var student = {
 name: 'Billy',
 sleep: function () {},
 [[Prototype]]: <person>
};
var person = {
 type: 'human',
 getName: function () {}
};

Object.getPrototypeOf(student) === person;
// true

Эффект затенения


var student = {
 name: 'Billy',
 sleep: function () {},
 [[Prototype]]: <person>
};
var person = {
 type: 'human',
 getName: function () {}
};

student.type = 'robot';
var student = {
 type: 'robot',
 name: 'Billy',
 sleep: function () {},
 [[Prototype]]: <person>
};
var person = {
 type: 'human',
 getName: function () {}
};
  1. Чтобы переиспользовать код, любой объект можно сделать прототипом для другого
  2. Для этого во внутреннее поле [[Prototype]] записываем ссылку на прототип одним из способов
  3. У всех объектов есть прототип по умолчанию – Object.prototype с общими методами
  4. Даже у массивов – Array.prototype
  5. И у функций – Function.prototype
  6. Если интерпретатор не находит поля у объекта, он ищет его по всей цепочке прототипов, пока не наткнётся на null во внутреннем поле [[Prototype]]
  7. При попытке зациклить цепочку
    интерпретатор сразу бросит ошибку

Наши дни ...

Один объект


var student = {
 name: 'Billy',
 sleep: function () {}
};
var person = {
 type: 'human',
 getName: function () {}
};

Object.setPrototypeOf(student, person);

Много объектов одного типа


var billy = {
 name: 'Billy',
 sleep: function () {}
};

var willy = {
 name: 'Willy',
 sleep: function () {}
};

Много объектов одного типа

Проблема: дублирование кода
при создании объекта

Решение: использовать
конструктор объектов

Самодельный конструктор


function createStudent(name) {
 return {
 name: name,
 sleep: function () {
 console.info('zzzZZ ...');
 }
 };
}

var billy = createStudent('Billy');

var willy = createStudent('Willy');

Самодельный конструктор

Проблема: каждый раз создаём метод sleep()

Решение: вынести этот метод в прототип

Самодельный конструктор


var studentProto = {
 sleep: function () {
 console.info('zzzZZ ...');
 }
};

function createStudent(name) {
 var student = {
 name: name
 };
 Object.setPrototypeOf(student, studentProto);
 return student;
}

Самодельный конструктор


var billy = createStudent('Billy');
var willy = createStudent('Willy');

billy.sleep();
// zzzZZ ...

willy.sleep();
// zzzZZ ...

Конструктор «из коробки»

Любая функция, вызванная оператором new


var billy = new createStudent('Billy');

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

function createStudent(name) {
 // var this = {};
 this.name = name;
 // return this;
}

this указывает на создаваемый объект

Конструктор «из коробки»


function createStudent(name) {
 this.name = name;
}
var billy = new createStudent('Billy');

Чуть больше семантики


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

var billy = new student('Billy');

Правило именования конструкторов

Чтобы отличить функцию-конструктор от обычной, их именуют с заглавной буквы.


function Student(name) {
 this.name = name;
}
var billy = new Student('Billy');

Зачем отличать конструкторы от обычных?


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

var billy = Student('Billy');

Поле появится в глобальном объекте!


window.name === 'Billy'; // true

use strict;
TypeError: Cannot set property 'name' of undefined

Возвращаем значение из конструктора


function Student(name) {
 this.name = name;
 return {
 name: 'Muahahahahaha!'
 };
}

var billy = new Student('Billy');
console.info(billy.name);
// Muahahahahaha

Возвращаем значение из конструктора


function Student(name) {
 this.name = name;
 return null; // Evil mode on!
}

var billy = new Student('Billy');
console.info(billy.name);

// Billy

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

А как же метод .sleep() в прототипе?


var studentProto = {
 sleep: function () {}
}
function createStudent(name) {
 var student = { name: name };
 Object.setPrototypeOf(student, studentProto);
 return student;
}

Автоматическая привязка прототипа


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

Student.prototype = {
 sleep: function () {}
};

function Student(name) {
 // var this = {};
 this.name = name;
 // Object.setPrototypeOf(this, Student.prototype);
 // return this;
}

Автоматическая привязка прототипа


function Student(name) {
 this.name = name;
}
Student.prototype = {
 sleep: function () {}
};

var billy = new Student('Billy');

var billy = {
 name: 'Billy',
 [[Prototype]]: <Student.prototype>
};

Прямо как Object.prototype!

billy

Student.prototype

Object.prototype

null

Особое поле .prototype

1. Есть у каждой функции


function kawabanga(name) {
 console.info('kawabanga!');
}

2. Хранит объект

  1. Имеет смысл только при вызове функции как конструктора
  1. Имеет вложенное поле .constructor

Особое поле .constructor

неперечисляемое

хранит ссылку на саму функцию


function Student(name) {
 this.name = name;
}
Student.prototype.constructor === Student; // true

var billy = new Student('Billy');
console.info(billy.constructor.name); // ?

// Student

Конструктор «из коробки»


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

Student.prototype = {
 sleep: function () {}
};

Проблема: уничтожаем поле .constructor

Решение: не перезаписывать .prototype

Конструктор «из коробки»


function Student(name) {
 this.name = name;
}
Student.prototype.sleep = function () {
 console.info('zzzZZ ...');
}

var billy = new Student('Billy');
billy.sleep(); // zzzZZ ...

billy.constructor === Student; // true

Много объектов одного типа


var billy = {
 name: 'Billy',
 sleep: function () {}
};

var willy = {
 name: 'Willy',
 sleep: function () {}
};

Object.setPrototypeOf(billy, person);

Object.setPrototypeOf(willy, person);

Строим цепочку прототипов


var personProto = {
 type: 'human',
 getName: function () {
 console.info(this.name);
 }
};

function Person() {
 this.type = 'human';
}
Person.prototype.getName = function () {
 console.info(this.name);
}

Строим цепочку прототипов


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

Student.prototype = Person.prototype;

Student.prototype.sleep = function () {};

var billy = new Student('Billy');

billy.getName();
// Billy

Строим цепочку прототипов


function Student(name) {
 this.name = name;
}
Student.prototype = Person.prototype;
Student.prototype.sleep = function () {};

function Lecturer(name) {
 this.name = name;
}
Lecturer.prototype = Person.prototype;

var sergey = new Lecturer('Sergey');
sergey.sleep(); // zzzZZ ...

billy

Student.prototype === Person.prototype

Object.prototype

null

billy

Student.prototype

Person.prototype

Object.prototype

null

Object.create()


function Student(name) {
 this.name = name;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.sleep = function () {};

function Lecturer(name) {
 this.name = name;
}
Lecturer.prototype = Object.create(Person.prototype);

var sergey = new Lecturer('Sergey');
sergey.sleep();

TypeError: sergey.sleep is not a function

billy

Student.prototype

Person.prototype

Object.prototype

null

sergey

Lecturer.prototype

Person.prototype

Object.prototype

null

Object.create()

Создаёт пустой объект, прототипом которого становится объект, переданный первым аргументом


var fruitProto = {
 isUsefull: true
}

var apple = Object.create(fruitProto);

apple.isUsefull; // true

Object.create()


var apple = Object.create(fruitProto);

Object.create = function(prototype) {
 // Простейший конструктор пустых объектов
 function emptyFunction() {};
 emptyFunction.prototype = prototype;
 return new emptyFunction();
};

Object.create()


Student.prototype = Object.create(Person.prototype);

Object.create = function(prototype) {
 function emptyFunction() {};
 emptyFunction.prototype = prototype;
 return new emptyFunction();
};

Student.prototype = {
 [[Prototype]]: <Person.prototype>
}

Object.create()


var foreverAlone = Object.create(null);

foreverAlone.hasOwnProperty; // undefined

foreverAlone

null

Object.create()


function Student(name) {
 this.name = name;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.sleep = function () {};

Student.prototype

Person.prototype

Object.create()


function Student(name) {
 this.name = name;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.sleep = function () {};

Student.prototype.constructor = Student;

Итак, общее решение


function Person() {
 this.type = 'human';
}
Person.prototype.getName = function () {
 console.info(this.name);
};

function Student(name) {
 this.name = name;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.sleep = function () {};
Student.prototype.constructor = Student;

var billy = new Student('Billy');

billy

Student.prototype

Person.prototype

Object.prototype

null

Поле .name

Метод .sleep

Метод .getName

Общие методы

instanceof


billy instanceof Student;

// true

billy instanceof Person;

// true

billy instanceof Object;

// true

Object.create(null) instanceof Object

// false

instanceof


billy instanceof Person;

billy.__proto__ === Person.prototype;
// false -> Может, там null?

billy.__proto__ === null;
// false -> Идём дальше по цепочке

billy.__proto__.__proto__ === Person.prototype;
// true -> Возвращаем true

instanceof


Object.create(null) instanceof Object;

Object.create(null).__proto__ === Object.prototype;
// false -> Может, там null?

Object.create(null).__proto__ === null;
// true -> Так и есть, возращаем false!

Object.prototype.isPrototypeOf()


Student.prototype.isPrototypeOf(billy);

// true

Person.prototype.isPrototypeOf(billy);

// true

Object.prototype.isPrototypeOf(billy);

// true

Конструкторы студента и предподавателя


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

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

Проблема: дублирование кода

Решение: вынести общий код в Person

Выносим общий код


function Student(name) {
 this.name = name;
}
function Person() {
 this.type = 'human';
}

function Student(name) {}
function Person(name) {
 this.type = 'human';
 this.name = name;
}

Вызов одного конструктора внутри другого


function Person(name) {
 this.type = 'human';
 this.name = name;
}

function Student(name) {}

function Student(name) {
 Person.call(this, name);
}

var billy = new Student('Billy');
console.info(billy.name); // undefined

Вызов метода одного прототипа внутри другого


function Person(name) {
 this.name = name;
}
Person.prototype.getName = function () {
 return this.name;
}

Student.prototype.getName = function () {
 return 'Student ' + this.getName();
};

var billy = new Student('Billy');
billy.getName();

RangeError: Maximum call stack size exceeded

Вызов метода одного прототипа внутри другого


function Person(name) {
 this.name = name;
}
Person.prototype.getName = function () {
 return this.name;
}

Student.prototype.getStudentName = function () {
 return 'Student ' + this.getName();
};

var billy = new Student('Billy');
billy.getStudentName();

Вызов метода одного прототипа внутри другого


function Person(name) {
 this.type = 'human';
 this.name = name;
}
Person.prototype.getName = function () {
 return this.name;
}

Student.prototype.getName = function () {
 return 'Student ' +
 Person.prototype.getName.call(this);
};

new , prototype , Object.create

Можно проще!

Вернёмся к простым объектам


var personProto = {
 getName: function () {
 return this.name;
 }
};

var studentProto = Object.create(personProto);

studentProto

personProto


studentProto.sleep = function () {};

Вернёмся к простым объектам


var billy = Object.create(studentProto);

billy

studentProto

personProto


billy.name = 'Billy';

Вернёмся к простым объектам


var personProto = {};
personProto.getName = function () { return this.name; }

var studentProto = Object.create(personProto);
studentProto.sleep = function () {};

var billy = Object.create(studentProto);
billy.name = 'Billy';

Вернёмся к простым объектам


var personProto = {};
personProto.getName = function () { return this.name; }

var studentProto = Object.create(personProto);
studentProto.sleep = function () {};

studentProto.create = function (name) {
 return Object.create(this, {
 name: { value: name }
 });
}

var billy = studentProto.create('Billy');

Object.create()


var apple = Object.create(fruit, {
 shape: { value: 'round', writable: false },
 color: { value: 'Green' },
 amount: { writable: true }
});

apple.amount = 'half';

new или Object.create?

Некоторое время спустя ...

«Классы»


function Student(name) {
 this.name = name;
}
Student.prototype.getName = function () {
 return this.name;
};

class Student {
 constructor(name) {
 this.name = name;
 }
 getName() {
 return this.name;
 }
}

«Классы»


class Student {
 // ...
}

var billy = new Student('Billy');
billy.getName(); // Billy

Student.prototype.isPrototypeOf(billy); // true

typeof Student.prototype.getName; // function

typeof Student; // function

class или Object.create?

Common Misconceptions About Inheritance in JavaScript, Eric Elliott

Что почитать?

Objects and Inheritance, Dr. Axel Rauschmayer

Прототипы, Илья Кантор

AltStyle によって変換されたページ (->オリジナル) /