Skip to content
Nasimi Mamedov edited this page Aug 7, 2023 · 1 revision

🎓 Классы

ES6 ( ECMAScript 2015 )

Синтаксический сахар на поверхности прототипной модели наследования

class declaration class expression
Потеря контекста static
get & set extends
super Пример

⚠️ Код внутри тела класса всегда выполняется в strict mode
( даже если вы не использовали директиву **_'use strict'_** )

⚠️ "Тело" класса всегда заключено в фигурные скобки { }

Внутри фигурных скобок объявляются конструктор ( constructor ) и методы класса

Метод constructor создает и инициализирует экземпляра класса

⚠️ Все собственные свойства экземпляра должны быть объявлены в конструкторе constructor()

"Под капотом" работает все та же прототипная модель наследования JS

Создаваемые в конструкторе класса свойства и методы могут быть приватными и публичными
( как и в обычном конструкторе )

В обычном конструкторе контекстом вызова приватных методов будет глобальный объект window

В конструкторе класса приватные методы теряют контекст ( undefined )

class User {
    constructor ( name ) {
        var privateVar = prompt ( "Set privateVar value: " )
        function showPrivate () {
            console.log ( `Ай-яй-яй, у меня контекст вызова ${this}` )
            console.log ( `Зато я вижу приватную переменную: ${privateVar}` )
        }
        this.name = name || "Бегемот"
        this.show = function () {
            showPrivate ()
        }
    }
}

var user = new User ( "Крокодил" )
user.show()
Result
Ай-яй-яй, у меня контекст вызова undefined
Зато я вижу приватную переменную: 789

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

function User ( name ) {
    var privateVar = prompt ( "Set privateVar value: " )
    function showPrivate () {
        console.log ( `Ай-яй-яй, у меня контекст вызова ${this}` )
        console.log ( `Зато я вижу приватную переменную: ${privateVar}` )
    }
    this.name = name || "Бегемот"
    this.show = function () {
        showPrivate ()
    }
}

var user = new User ( "Крокодил" )
user.show()
Result
Ай-яй-яй, у меня контекст вызова [object Window]
Зато я вижу приватную переменную: 789

Выведем в консоль оба варианта user и найдем те косметические отличия, которые там должны быть 😉

  • constructor: class User / constructor: ƒ User( name )

🎓 class declaration

🚫 hoisting

объявление класса дожно быть раньше первого обращения к нему

Классы - это специальные функции-"обертки", в которые "заворачивают" конструктор

☕ 1️⃣
class Picture {
    constructor ( url, width ) {
        this.elem = document.createElement ( 'img' )
        this.elem.src = url
        this.width = width
    }
}

typeof Picture  // "function"

⚠️ объявленный класс невозможно удалить динамически, без перезагрузки страницы
В примере 1️⃣ идентификатор Picture уже занят, и никакие магические заклинания не помогут переопределить его содержание

⚠️ если обычный конструктор JS можно вызвать и как функцию, и как конструктор ( с ключевым словом new), то конструктор класса вызвать без ключевого словаnew нельзя - будет сгенерировано исключениеTypeError`

let x = new Picture ( "http://www.radioactiva.cl/wp-content/uploads/2018/05/pikachu.jpg", 200 )
document.body.appendChild ( x.elem )

🎓 class expression

class expression может быть именованным или аниномным

Примеры именованных классов

☕ 2️⃣
let Picture = class {
    constructor ( url = "https://cdn.pastemagazine.com/www/articles/GrinchPOster_header.jpg" ) {
        this.elem = document.body.appendChild ( 
            document.createElement ( 'img' )
        )
        this.elem.src = url
    }
}

console.dir ( Picture )
Результат в консоли:
▼ class Picture
    arguments: (...)
    caller: (...)
    length: 0
    name: "Picture"
  ► prototype: {constructor: ƒ}
  ► __proto__: ƒ ()

Однако если мы создадим экземпляр этого класса, и посмотрим на него в консоли, то мы увидим, что имя класса отсутствует

let sample = new Picture

console.log ( sample )
Результат в консоли:
▼ Picture {elem: img}
    elem: img
  ▼ __proto__:
      ► constructor: class
      ► __proto__: Object
☕ 3️⃣
let Picture = class Canvas {
    constructor ( url = "https://cdn.pastemagazine.com/www/articles/GrinchPOster_header.jpg" ) {
        this.elem = document.body.appendChild ( 
            document.createElement ( 'img' )
        )
        this.elem.src = url
    }
}

console.dir ( Picture )
Результат в консоли:
▼ class Canvas
    arguments: (...)
    caller: (...)
    length: 0
    name: "Canvas"
  ► prototype: {constructor: ƒ}
  ► __proto__: ƒ ()

А теперь создадим экземпляр этого класса и выведем его в консоль:

let sample = new Picture

console.log ( sample )
Результат в консоли:
▼ Canvas {elem: img}
    elem: img
  ▼ __proto__:
      ► constructor: class Canvas
      ► __proto__: Object
sample instanceof Picture   // true
sample instanceof Canvas
❌ Uncaught ReferenceError: Canvas is not defined

Итак, при использовании class expression имя класса становится недоступным извне

Точнее говоря, достучаться до него можно только так:

pict.constructor.name

☕ 4️⃣
const Sample = class Canvas {
    constructor () {
        this.canvas = document.createElement ( 'canvas' )
        document.body.appendChild ( this.canvas )
        this.resizeCanvas()
        this.canvas.style.border = "1px solid #000000"
        this.area = this.canvas.getContext ( "2d" )
    }
    resizeCanvas ( event ) {
        this.canvas.width = window.innerWidth - 30
        this.canvas.height = window.innerHeight - 20
    }
    drawLine ( points ) {
        this.area.moveTo( points[0].x, points[0].y )
        this.area.lineTo( points[1].x, points[1].y )
        this.area.stroke()
    }
}

let pict = new Sample ()
window.onresize = pict.resizeCanvas.bind ( pict )
pict.drawLine ( [ { x: 50, y: 50 }, { x: 250, y: 250 } ] )
pict.drawLine (  [ { x: 250, y: 250 }, { x: 100, y: 250 } ] )

📌 Чтобы получить имя класса, нужно использовать его свойство name:

console.log ( Sample.name ) // "Canvas"

🎓 Потеря контекста

📌 В строгом режиме не происходит неявной передачи контекста вызова

⚠️ Потеря контекста происходит всегда, если ссылка на метод передается в новую переменную:

☕ 5️⃣
     let drawLine = pict.drawLine
     drawLine ( [ { x: 50, y: 50 }, { x: 250, y: 250 } ] )

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

⛔️ Uncaught TypeError: Cannot read property 'area' of undefined

Передачу контекста вызова нужно сделать явным образом:

let drawLine = pict.drawLine.bind ( pict )

📌 Потеря контекста ( undefined ) происходит вследствие того, что весь код внутри тела класса выполняется в strict mode, хотя явного указания 'use strict' в коде класса нет

При отсутствии явного указания на объект, вызывающий метод,
в строгом режиме this не будет ссылкой на глобальный объект window
В строгом режиме this будет undefined


☕ 6️⃣

В этом примере контекст теряется в функции getProp(), объявленной внутри метода addSomeInfo

( внутренняя функция не наследует контекст вызова родительской )

class User {
    constructor ( name ) {
        this.name = name || 'unknown'
    }
    addSomeInfo ( props ) {
        if ( !Array.isArray ( props ) ) return
        function getProp ( prop ) {
            this [ prop.name ] = prop.value
        }
        for ( var prop of props ) {
            getProp ( prop )
        }
    }
}

Создадим экземпляр user класса User и вызовем метод addSomeInfo в контексте объекта user

var user = new User ( "Grig" )
user.addSomeInfo ([
    { name: "age", value: 25 },
    { name: "hobby", value: [ "football", "fishing" ] }
])
🙀 Результат
⛔️ Uncaught TypeError: Cannot set property 'age' of undefined

❗ Внутри функции getProp контекст вызова ( this ) оказался undefined

Теперь используем стрелочную функцию getProp, которая не теряет контекст 😉

class User {
    constructor ( name ) {
        this.name = name || 'unknown'
    }
    addSomeInfo ( props ) {
        if ( !Array.isArray ( props ) ) return
        var getProp = prop => this [ prop.name ] = prop.value
        for ( var prop of props ) {
            getProp ( prop )
        }
    }
}

Создадим экземпляр user и вызовем метод addSomeInfo

var user = new User ( "Grig" )
user.addSomeInfo ([
    { name: "age", value: 25 },
    { name: "hobby", value: [ "football", "fishing" ] }
])
console.log ( user )
🙀 Результат
▼ User {name: "Grig", age: 25, hobby: Array(2)}
    age: 25
  ► hobby: (2) ["football", "fishing"]
    name: "Grig"
  ▼ __proto__:
      ► addSomeInfo: addSomeInfo ( props ) { if ( !Array.isArray ( props ) ) return var getProp = prop => {…}
      ► constructor: class User
      ► __proto__: Object

© Nasimi Mamedov 2018

Курсы были созданы для студентов A-Level Ukraine.

Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав.

A-Level Ukraine


1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19

Занятие 1

⤵️

Занятие 2

⤴️ ⤵️

Занятие 3

⤴️ ⤵️

Занятие 4

⤴️ ⤵️

Занятие 5

⤴️ ⤵️

Занятие 6

⤴️ ⤵️

Занятие 7

⤴️ ⤵️

Занятие 8

⤴️ ⤵️

Занятие 9

⤴️ ⤵️

Занятие 10

⤴️ ⤵️

Занятие 11

⤴️ ⤵️

Занятие 12

⤴️ ⤵️

Занятие 13

⤴️ ⤵️

Занятие 14

⤴️ ⤵️

Занятие 15

⤴️ ⤵️

Занятие 16

⤴️ ⤵️

Занятие 17

⤴️ ⤵️

Занятие 18

⤴️ ⤵️

Занятие 19

⤴️ ⤵️

⤴️

ico20 Дополнительно
dir-20 Справочная инфо

Clone this wiki locally