Class super - garevna/js-course GitHub Wiki

Наследование

super

Методы родительского класса доступны в дочернем классе посредством ключевого слова super

Расширим метод drawLine родительского класса,
добавив аргумент lineWidth ( толщину линии )

Для этого определим "расширенный" метод drawLine
внутри дочернего класса,
который будет вызывать метод drawLine
родительского класса
с помощью ключевого слова super:

super.drawLine ( points, lineColor )

Теперь код будет таким:

const Canvas = class {
    constructor () {
        this.canvas = document.createElement ( 'canvas' )
        document.body.appendChild ( this.canvas )
        this.canvas.style.border = "1px solid #000000"
        this.area = this.canvas.getContext ( "2d" )
    }
    drawLine ( points ) {
        this.area.beginPath()
        this.area.moveTo( points[0].x, points[0].y )
        this.area.lineTo( points[1].x, points[1].y )
        this.area.stroke()
    }
}

class ExtendedCanvas extends Canvas {
    drawLine ( points, lineColor, lineWidth ) {
        this.area.lineWidth = lineWidth || 3
        this.area.strokeStyle = lineColor
        super.drawLine ( points )
    }
}

Создадим экземпляр дочернего класса:

let newCanvas = new ExtendedCanvas ()

и вызовем его метод drawLine

newCanvas.drawLine (  [ { x: 20, y: 20 }, { x: 300, y: 400 } ], "#ffaa00", 10 )

Теперь линия будет отрисовываться заданной толщины


super ()

В предыдущих примерах мы не использовали конструктор наследующего класса

☝ Когда нужно добавить свойства 
 экземпляру наследующего класса,
 без конструктора это сделать невозможно

✋ Первое, что нужно выполнить в конструкторе наследующего класса - вызвать метод super ()

const Canvas = class {
    constructor () {
        this.canvas = document.createElement ( 'canvas' )
        document.body.appendChild ( this.canvas )
        this.area = this.canvas.getContext ( "2d" )
    }
    drawLine ( points ) {
        this.area.beginPath()
        this.area.moveTo( points[0].x, points[0].y )
        this.area.lineTo( points[1].x, points[1].y )
        this.area.stroke()
    }
}

class ExtendedCanvas extends Canvas {
    constructor () {
        super ()
        this.history = []
    }
}

В противном случае будет сгенерировано исключение:

⛔️ Uncaught ReferenceError: 
Must call super constructor in derived class before accessing 'this' or returning from derived constructor

super в литералах объектов

Ключевое слово super можно использовать без объявления классов
Он является просто ссылкой на прототип объекта
Поэтому можно использовать его для доступа к свойствам и методам объекта-прототипа

В примерах далее мы будем использовать объекты, объявленные в литеральной форме
В качестве прототипа объекта person будет выступать объект human
Назначать объект human прототипом объекта person мы будем с помощью метода

Object.setPrototypeOf ( person, human )

После такого назначения внутри объекта person
свойства и методы объекта human будут доступны
с помощью ключевого слова super

В следующем примере вызовем методы place () и say ()
прототипа human
в методах getPlace () и talk ()
объекта person
с помощью ключевого слова super :

☕ 1️⃣
let human = {
    place () {
        var x = document.createElement ( "p" )
        document.body.appendChild ( x )
        x.id = "demo"
        return x
    },
    say ( text ) {
        this.place.innerHTML = text
    }
}

let person = {
    getPlace () { this.place = super.place () },
    talk ( text ) {
        super.say ( text )
    }
}

Object.setPrototypeOf ( person, human )
person.getPlace ()
person.talk ( "привет!" )
☕ 2️⃣

✍ В этом примере метод place() прототипа ( объекта human )
проверяет наличие элемента с id === "demo",
и если такой элемент найден,
возвращает ссылку на него,
в противном случае создает такой элемент,
добавляет его на страницу и возвращает ссылку на него

✍ Объект person изначально не имеет свойства place,
но имеет собственный метод getPlace (),
который создает такое свойство,
вызывая с помощью ключевого слова super
метод place() прототипа ( объекта human ),
и присваивая возвращенное этим методом
значение собственному свойству place

✍ Метод talk( text ) объекта person
вызывает метод getPlace()
до вызова метода say ()
прототипа ( объекта human )

let human = {
    place: () => 
        document.getElementById ( "demo" ) ? 
        document.getElementById ( "demo" ) :
        document.body.appendChild ( 
            document.createElement ( "p" ) 
        ).id = "demo",

    say ( text ) {
        this.place.innerHTML = text
    }
}

let person = {
    getPlace () { this.place = super.place() },
    talk ( text ) {
        this.getPlace ()
        super.say ( text )
    }
}

Object.setPrototypeOf ( person, human )

person.talk ( "привет!" )
setTimeout ( () => person.talk ( "Hello, baby!" ), 2000 )

☝ Обратите внимание,
что при объявлении метода place() объекта human
мы использовали стрелочную функцию,
а при объявлении метода say ее использовать нельзя,
поскольку внутри методов,
объявленных с помощью стрелочных функций,
контектом вызова будет глобальный объект window

☕ 3️⃣

В этом примере свойство place прототипа ( объекта human )
уже не является методом
Его значение ( ссылка на элемент )
будет установлено при инициализации объекта human

В этом примере демонстрируется взаимозаменяемость
ключевых слов super и this
при ссылках на свойства прототипа

👉 Метод talk() объекта person
вызывает метод say () прототипа
без ключевого слова super
( с ключевым словом this )

👉 Когда метод say() в объекте person не будет найден,
поиск будет продолжен в прототипе,
где и будет благополучно найден

👉 Внутри метода say(), вызванного из метода talk(),
контекстом вызова будет объект person
( т.е. this будет указывать на объект person )
но тем не менее ссылка this.place
будет благополучно разрешена по цепочке прототипов

let human = {
    place: ( () => {
        if ( document.getElementById ( "demo" ) ) 
            return document.getElementById ( "demo" )
        var x = document.body.appendChild ( 
            document.createElement ( "p" )
        )
        x.id = "demo"
        return x
    })(),
    say ( text ) {
        this.place.innerHTML = text
    }
}

let person = {
    talk ( text ) {
        this.say ( text )
    }
}

Object.setPrototypeOf ( person, human )
person.talk ( "привет!" )
setTimeout ( () => person.talk ( "Hello, baby!" ), 2000 )

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

let person = {
        say ( text ) { console.log ( text ) },
        talk ( text ) {
                this.__proto__.say ( text )
        }
}

👉 Очевидно, в таком случае код:

super.say ( text )

короче, чем

this.__proto__.say ( text )

а результат идентичный 😉


☕ 4️⃣

👉 В этом примере мы используем геттеры и сеттеры свойств объектов

Для вычисляемых свойств это наиболее корректный способ доступа к их значениям

let human = {
    id: "",
    get place () {
        if ( this.id ) return document.getElementById ( this.id )
    },
    set place ( newId ) {
        this.id = newId
        if ( document.getElementById ( this.id ) ) return
        var x = document.createElement ( "p" )
        document.body.appendChild ( x )
        x.id = this.id
    },
    get message () {
        return this.place.innerHTML
    },
    set message ( val ) {
        this.place.innerHTML = val
    }
}

let person = {
    talk ( text ) {
        super.message = text
    },
    get place () {
        return super.place
    },
    set place ( newId ) {
        super.place = newId
    }
}

Object.setPrototypeOf ( person, human )
person.place = "demo"
person.talk ( "привет!" )
setTimeout ( () => person.talk ( "Hello, baby!" ), 2000 )

⚠️ **GitHub.com Fallback** ⚠️