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

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

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

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

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

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