Прототипы

Сага. Часть II

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

ВНИМАНИЕ

Все примеры кода, использованные в данной презентации, вымышлены. Любые совпадения с реальными кодстайлами, живыми или мертвыми, абсолютно случайны. Не повторяйте этого дома!

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

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

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

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

Три не связанных объекта


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

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

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

student.getName();
// Daria

Скрытое поле [[Prototype]]

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

Связываем два объекта через .setPrototypeOf()


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

student.__proto__ = person; // :(

Object.setPrototypeOf(student, person); // :)

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

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


var student = {
    name: 'Daria',
    [[Prototype]]: <person>
}

var lecturer = {
    name: 'Sergey',
    [[Prototype]]: <person>
}

var person = {
    getName: function () {}
}

student.getName();
// Daria

lecturer.getName();
// Sergey

Объект person послужил прототипом для объекта student

Объект student делегировал свои обязанности объекту person

Путешествие по цепочке прототипов


student.toString();

var student = {
    name: 'Daria',
    [[Prototype]]: <person>
}

var person = {
    type: 'human',
    [[Prototype]]: <Object.prototype>
}

Object.prototype


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

Object.prototype – хранилище общих методов для всех объектов

Array.prototype


Array.prototype = {
    concat: function () {},
    slice: function () {},
    filter: function () {},
    map: function () {},
    [[Prototype]]: <Object.prototype>
}

var fruits = ['Apple', 'Banana', 'Cat'];

Object.getPrototypeOf(fruits) === Array.prototype;
// true

Function.prototype


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

function sum(a, b) {
    return a + b;
};

Object.getPrototypeOf(sum) === Function.prototype;
// true
  1. Любой объект можно сделать прототипом другого
  2. У всех объектов есть прототип по умолчанию – Object.prototype
  3. Даже у массивов – Array.prototype
  4. И у функций – Function.prototype
  5. Если интерпретатор не находит поля у объекта, он начинает искать его по всей цепочке прототипов
  6. Если не нашёл – возвращает undefined

Наши дни ...

Один объект


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

Object.setPrototypeOf(student, person);

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


var daria = {
    name: 'Daria',
    sleep: function () {}
}

var petr = {
    name: 'Petr',
    sleep: function () {}
}

var kirill = {
    name: 'Kirill',
    sleep: function () {}
}

Object.setPrototypeOf(daria, person);

Object.setPrototypeOf(petr, person);

Object.setPrototypeOf(kirill, person);

Проблема: создание новых объектов громоздкая операция

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

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


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

var daria = createStudent('Daria');

var petr = createStudent('Petr');

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

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

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


var student = {
    sleep: function () {
        console.log('zzzZZ ...');
    }
}

function createStudent(name) {
    var newStudent = {
        name: name
    };

    Object.setPrototypeOf(newStudent, student)
    return newStudent;
}

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


var daria = createStudent('Daria');

daria.sleep();

// zzzZZ ...

var petr = createStudent('Petr');

petr.sleep();

// zzzZZ ...

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

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


var daria = new createStudent('Daria');

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

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

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

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


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

var daria = new createStudent('Daria');

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


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

var daria = new student('Daria');

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


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

var daria = new Student('Daria');

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


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

var daria = Student('Daria');

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


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

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

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


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

var daria = new Student('Daria');
console.log(daria.name);

// Muahahahahaha

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


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

var daria = new Student('Daria');
console.log(daria.name);

// Daria

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

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


var student = {
    sleep: function () {
        console.log('zzzZZ ...');
    }
}

Object.setPrototypeOf(newStudent, student);

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


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

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

function Student(name) {
    // var this = {};
    this.name = name;
    // this.__proto__ = Student.prototype;
    // return this;
}

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

var daria = new Student('Daria');

var daria = {
    name: 'Daria',
    [[Prototype]]: <Student.prototype>
}

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

daria

Student.prototype

Object.prototype

null

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

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


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

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

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

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

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

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


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

var daria = new Student('Daria');
daria.constructor === Student;

// true

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


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

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

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

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

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


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

Student.prototype.sleep = function () {
    console.log('zzzZZ ...');
};

var daria = new Student('Daria');
daria.sleep();
// zzzZZ ...

var daria = new Student('Daria');
daria.constructor === Student; // true

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


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

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

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

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


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

Student.prototype = Person.prototype;

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

var daria = new Student('Daria');

daria.getName();

// Daria

Опять перезаписали .prototype

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


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 ...

daria

Student.prototype === Person.prototype

Object.prototype

null

daria

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

daria

Student.prototype

Person.prototype

Object.prototype

null

sergey

Lecturer.prototype

Person.prototype

Object.prototype

null

Object.create()

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


var fruit = {
    isUsefull: true
}

var apple = Object.create(fruit);

apple.isUsefull; // true

Ещё один конструктор, более лучший

Object.create()


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

    emptyFunction.prototype = prototype;

    return new emptyFunction();
};

var apple = Object.create(fruit);

Object.create()


var foreverAlone = Object.create(null);

foreverAlone.hasOwnProperty; // undefined

foreverAlone

null

Object.create()


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

apple.color; // Green

apple.shape; // Sphere

Object.create()


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

Student.prototype

Person.prototype

Итак, решение


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

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

var daria = new Student('Daria');

daria

Student.prototype

Person.prototype

Object.prototype

null

Поле .name

 

Метод .sleep

 

Метод .getName

 

Общие методы

instanceof


daria instanceof Student;

// true

daria instanceof Person;

// true

daria instanceof Object;

// true

Object.prototype.isPrototypeOf()


Student.prototype.isPrototypeOf(daria);

// true

Person.prototype.isPrototypeOf(daria);

// true

Object.prototype.isPrototypeOf(daria);

// true

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

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

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

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

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


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

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

function Student(name) {
}

Вызов конструктора прототипа внутри конструктора объекта


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

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

Переопредление метода


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

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

var daria = new Student('Daria');
daria.getName();

RangeError: Maximum call stack size exceeded

Переопредление метода


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

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

Переопредление метода


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 person = {
    getName: function () { return this.name; }
};

var student = Object.create(person);

student = {}

person


student.sleep = function () {};

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


var daria = Object.create(student);;

daria = {}

student

person


daria.name = 'Daria';

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


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

var student = Object.create(person);
student.sleep = function () {};

var daria = Object.create(student);
daria.name = 'Daria';

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


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

var student = Object.create(person);
student.sleep = function () {};

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

var daria = student.create('Daria');

new или Object.create?

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

«Классы»


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

«Классы» – class


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

  getName() {
      return this.name;
  }
}

«Классы» – extend


class Student extends Person {
  sleep() {
      console.log('zzzZZ ...');
  }
}

«Классы» – super


class Student extends Person {
  constructor(name) {
      super(name);
  },

  getName() {
      return 'Student' + super.getName();
  }
}

«Классы» – new


var daria = new Student('Daria');
daria.getName(); // Daria

Домашнее задание

github.com/urfu-2015/javascript-tasks-8


Deadline
24 ноября
02:59:59.999