Class - garevna/js-course GitHub Wiki
Синтаксический сахар на поверхности прототипной модели наследования
| class declaration | class expression |
|---|---|
Потеря контекста |
static |
| get & set | extends |
| super | ☕ Пример |
strict mode
( даже если вы не использовали директиву **_'use strict'_** )
{ }
Внутри фигурных скобок объявляются конструктор ( 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()Ай-яй-яй, у меня контекст вызова 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()Ай-яй-яй, у меня контекст вызова [object Window]
Зато я вижу приватную переменную: 789Выведем в консоль оба варианта user и найдем те косметические отличия, которые там должны быть 😉
-
constructor: class User/constructor: ƒ User( name )
| ⏫ |
|---|
объявление класса дожно быть раньше первого обращения к нему
Классы - это специальные функции-"обертки", в которые "заворачивают" конструктор
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 )| ⏫ |
|---|
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__: Objectlet 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__: Objectsample instanceof Picture // truesample instanceof Canvas❌ Uncaught ReferenceError: Canvas is not defined
Итак, при использовании class expression имя класса становится недоступным извне
Точнее говоря, достучаться до него можно только так:
pict.constructor.nameconst 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"| ⏫ |
|---|
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| ⏫ |
|---|