JavaScript Objects
POJO
(Plain Old JavaScript Objects)
Объект - это набор неупорядоченных свойств, где каждое свойство это ассоциация между ключом и значением
Object creation
var calculator;
calculator = {}; // object literal
calculator = new Object(); // function-constructor
calculator = Object.create(); // meta programming
Property accessors
calculator.lastValue = 5;
calculator.maxNumber = 1502528;
calculator.add = function () {};
calculator.binaryCalculator = {};
Dot notation
calculator['lastValue'] = 5;
calculator['maxNumber'] = 1502528;
calculator['add'] = function () {};
calculator['binaryCalculator'] = {};
Brackets notation
var propertyName = 'lastValue';
calculator[propertyName] = 5;
Dot notation vs brackets notation
Initializing object
var calculator = {
lastValue: 5,
binaryCalculator: {
cache: 43
}
};
properties
methods
// new ES6 way
var calculator = {
add() {}
};
// old ES5 way
var calculator = {
add: function () {}
};
Getter/Setter
var calculator = {
_total: 0,
set total(value) {
this._total = value;
},
get total() {
return this._total;
}
};
calculator.total = 14;
calculator.total; // 14
Practical task 1
- Напишите функцию compareObjects, которая принимает 2 объекта и название числового свойства, по которому нужно выполнить сравнение объектов, и выводит в консоль свойство name того объекта, у которого значение переданного свойства больше.
- Создайте два объекта с помощью литерала
- Вызовите написанную функцию и передайте два созданных объекта и свойство для сравнения
Practical task 2
- Create object Car with the method `drive` and the property `mileage`
- Method `drive` accepts distance as a parameter and logs to console `The car has driven ${distance} miles`
- Property `mileage` has a getter that returns total mileage after several calls of `drive`
- Make several calls to `drive` and output total mileage
Practical task 3
- Modify method `drive` to log mileage after `distance` seconds
- Implement container to collect callbacks to call when `drive` is finished
- Make several calls to `drive` and output total mileage
Accessing non-existent properties
var calculator = {};
calculator.someNonExistentProperty
// undefined
calculator.someNonExistentProperty();
//throws `TypeError: calculator.someNonExistentProperty is not a function`
Checking if property exists
var f = function() {};
'apply' in f // true
Own properties
Inherited properties
var f = function() {};
f.hasOwnProperty('apply') // false
Valid property names
Late binding
var calculator1 = {
value: 'I am calculator 1',
add: function () {
return this.value;
},
};
var calculator2 = {
value: 'I am calculator 2'
};
calculator2.add = calculator1.add;
calculator2.add(); // 'I am calculator 2'
Practical task 4
Create a mixin object that has the `list` method which loops over `props` object property and outputs all existing properties and their respective values, if a property doesn't exist on the current object, it outputs `Property ${name} doesn't exist`
Create two objects with `props` property as an array of strings, where each string denotes a property on an object and other properties listed in the `props` property
Mix in `list` method to these objects and call it
Enumerating object properties
for...in loop
var calculator = {
lastValue: 5,
maxNumber: 1502528,
add: function () {},
binaryCalculator: {
add: function () {}
}
};
for (var property in calculator) {
console.log(property, calculator[property]);
}
// lastValue 5
// maxNumber 1502528
// add funtion() {}
// binaryCalculator Object {}
Comparing
> var obj1 = {}; // an empty object
> var obj2 = {}; // another empty object
> obj1 === obj2
false
> obj1 === obj1
true
objects
primitives
> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true
Property value shorthands
const x = 4;
const y = 1;
const obj = { x, y };
The line is equivalent to
const obj = { x: x, y: y };
const obj = { x: 4, y: 1 };
const {x,y} = obj;
console.log(x); // 4
console.log(y); // 1
Work well with destructing
Computed property keys
There are two ways of specifying a key when you set a property:
const propKey = 'foo';
const obj = {
[propKey]: true,
['b'+'ar']: 123
};
Works well with destructing
// fixed name
obj.foo = true;
// expression
obj['b'+'ar'] = 123;
Practical task 5
- Create array of properties
- Add all properties in the array to an object using computed property syntax
- Loop through the properties and output their name and value
Constructor function
Why are they needed?
// Creating rectangle object
var smallRect = {
width: 3,
height: 5,
area: function() { return this.width*this.height }
};
// Creating another rectangle object
var largeRect = {
width: 10,
height: 10,
area: function() { return this.width*this.height }
};
// Calculating area
smallRect.area(); // 15
largeRect.area(); // 100
Constructor function
Create class\type definition
function Rectangular(width, height) {
this.width = width;
this.height = height;
this.area = function() { return this.width * this.height };
}
Instantiate class instance using `new` keyword
// Creating rectangle object
var smallRect = new Rectangular(3,5);
// Creating another rectangle object
var largeRect = new Rectangular(10,10);
// Calculating area
smallRect.area(); // 15
largeRect.area(); // 100
Public properties
// Defining Rectangular class
function Rectangular(width, height) {
// public properties
this.width = width;
this.height = height;
this.area = function() { return this.width * this.height };
this.getWidthValue = function() { return this.width };
}
// Instantiating class instance
var rect = new Rectangular(3,5);
// Calculating area
rect.area(); // 15 = 3 * 5
// Modifying value of the public property `width`
rect.width = 8;
// Calculating area again
rect.area(); // 40 = 8 * 5
// Checking value of the public `width` property
rect.width; // 8
this.getWidthValue(); // 8
Private properties
// Defining Rectangular class
function Rectangular(_width, _height) {
// private properties
var width = _width;
var height = _height;
// public methods
this.area = function() { return width * height };
this.getWidthValue = function() { return width };
}
// Instantiating class instance
var rect = new Rectangular(3,5);
// Calculating area
rect.area() // 15 = 3 * 5
// Modifying value of the public property `width`
rect.width = 8
// Calculating area again
rect.area() // still 15 !!!
// Checking value of the public and private `width` properties
rect.width // 8
this.getWidthValue() // 3
Static properties
// Defining Rectangular class
function Rectangular(width, height) {
// Incrementing the number of Rectangular instances
Rectangular.count++;
// public properties
this.width = width;
this.height = height;
// public methods
this.area = function() { return this.width * this.height };
this.getWidthValue = function() { return this.width };
}
// Defining static `count` property
Rectangular.count = 0;
// Defining static `factory` method
Rectangular.factory = function(width, height) {
return new Rectangular(width, height);
};
// Instantiating class instances
var smallRect = new Rectangular(3,5);
Rectangular.count; // 1
var largeRect = Rectangular.factory(3,5);
Rectangular.count; // 2
smallRect.width; // 3
Rectangular.width // undefined
smallRect.area(); // 15
Rectangular.area(); // throws type error `Rectangular.area is not a function`
Practical task 6
- Опишите конструктор объектов (класс) Calculator с двумя методами: add - принимает число и прибавляет его к предыдущему, getCurrentSum - принимает индекс и возвращает результирующее число на шаге указанном в индексе (если индекса нет, возвращает текущую сумму)
- Создайте два экземпляра класса Calculator
- Добавьте в первый объект числа 3,8,11 и во второй 5,12,17.
- Выведите в консоль сумму:
- всех чисел всех объектов, убедитесь (56)
- сумму чисел всех объектов на втором шаге (28)
- для одного объекта сумму после третьего шага и общую результирующую сумму (должна совпадать)
Class/Prototype
Why use prototypes?
function Rectangular(width, height) {
this.width = width;
this.height = height;
this.area = function() { return this.width * this.height };
}
// Creating rectangle object
var smallRect = new Rectangular(3,5);
// Creating another rectangle object
var largeRect = new Rectangular(10,10);
// Calculating area
smallRect.area(); // 15
largeRect.area(); // 100
What's the problem with this code?
Shared object (prototype)
Setting prototype
using Object.setPrototypeOf()
// Defining shared object - prototype
var rectangularPrototype = {
area: function() {
return this.width*this.height;
}
}
// Defining instances
var largeRect = {width: 10, height: 10};
var smallRect = {width: 3, height: 5};
// Setting prototype - inheriting area method
Object.setPrototypeOf(largeRect, rectangularPrototype);
Object.setPrototypeOf(smallRect, rectangularPrototype);
// Calling inherited `area` method
smallRect.area(); // 15
largeRect.area(); // 100
Practical task 7
Modify solution for practical task 4 to share `list` method through prototype
Setting prototype
using constructor function
var rectangularPrototype = {
area: function() {
return this.width*this.height;
}
}
function Rectangular(width, height) {
this.width = width;
this.height = height;
}
Rectangular.prototype = rectangularPrototype;
var smallRect = new Rectangular(3,5);
var largeRect = new Rectangular(10,10);
smallRect.area(); // 15
largeRect.area(); // 100
Practical task 8
Create an object `animal` with shared method `run` that outputs '[name] is running', where [name] is placeholder for animal name
Create a function constructor for Rabbit that when instantiated accepts the name of the animal and stores it on the instance
Set `animal` as a prototype for instances of the Rabbit
Instantiate Rabbit instance and call `run` method
Setting prototype
using class inheritance
class Rectangular {
constructor(width, height) {
this.width = width;
this.height = height;
}
area: function() {
return this.width*this.height;
}
}
var smallRect = new Rectangular(3,5);
var largeRect = new Rectangular(10,10);
smallRect.area(); // 15
largeRect.area(); // 100
Practical task 9
Modify task 8 to use classes instead of a function constructor
Calling parent constructor/methods
class Animal {
run() {
console.log(`${this.name} is running`);
}
}
class Rabbit extends Animal {
constructor(name) {
// calling parent constructor
super();
this.name = name;
}
run() {
// calling parent run method
super.run();
}
}
let r = new Rabbit('Rabbit');
r.run();
Prototype chain
class Figure {}
class Rect extends Figure {}
class Circle extends Rect {}
Prototype chain
class Figure {}
class Rect extends Figure {}
class Circle extends Rect {}
checking inheriinstanceOf
var s = new Square();
s instanceof Rect // ?
s.__proto__ === Rect.prototype // false
new Rect() new Figure()
s.__proto__.__proto__ === Rect.prototype // true
new Figure() new Figure()
s instanceof Rect // true
-----------------------------------------------
var c = new Circle();
c instanceof Rect // ?
c.__proto__ === Rect.prototype // false
new Ellipse() new Figure()
c.__proto__.__proto__ === Rect.prototype // false
new Figure() new Figure()
c instanceof Rect // false
Accessing properties
var rectangularPrototype = { unit: 'cm' };
function Rectangular() {};
Rectangular.prototype = rectangularPrototype;
var smallRect = new Rectangular();
var largeRect = new Rectangular();
smallRect.unit // 'cm'
largeRect.unit // 'cm'
smallRect.unit = 'mm';
largeRect.unit; // 'cm'
Reading
Writing
Dynamic prototype property change
smallRect.unit; // 'cm'
Rectangular.prototype.unit = 'mm';
smallRect.unit; // 'mm'
Practical task 10
Наследуемся правильно в JavaScript
Создадим класс PositionedRectangle более специфический чем Rectangle и наследующий его свойства и методы.
// Этот код нам знаком...
function Rectangle(w, h) {
this.width = w;
this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }
// Далее идет определение подкласса
function PositionedRectangle(x, y, w, h) {
// Вызываем конструктор надкласса
// _super()
Rectangle.call(this, w, h);
// Далее сохраняются координаты верхнего левого угла прямоугольника
this.x = x;
this.y = y;
}
Если мы будем использовать объект-прототип по умолчанию,который создается при определении конструктора PositionedRectangle(),
был бы создан подкласс класса Object.
// Чтобы создать подкласс класса Rectangle, необходимо явно создать объектпрототип.
PositionedRectangle.prototype = new Rectangle();
// Мы создали объектпрототип с целью наследования, но мы не собираемся
// наследовать свойства width и height, которыми обладают все объекты
// класса Rectangle, поэтому удалим их из прототипа.
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;
// Переопределяем конструктор
PositionedRectangle.prototype.constructor = PositionedRectangle;
// можно приступать к добавлению методов экземпляров.
PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width &&
y > this.y && y < this.y + this.height);
}
var r = new PositionedRectangle(2,2,2,2);
console.log(r.contains(3,3)); // true // Вызывается метод экземпляра
console.log(r.area( )); // 4 // Вызывается унаследованный метод экземпляра
// Работа с полями экземпляра класса:
console.log(r.x + ", " + r.y + ", " + r.width + ", " + r.height); // "2, 2, 2, 2"
// Наш объект может рассматриваться как экземпляр всех 3 классов
console.log(
r instanceof PositionedRectangle &&
r instanceof Rectangle &&
r instanceof Object
); //true
Создание и работа с экземпляром
Сложности итд...
-
необходимость вызова конструктора надкласса из конструктора подкласса, причем
конструктор надкласса приходится вызывать как метод вновь созданного объекта.
-
потребоность явно создать объект-прототип как экземпляр надкласса
-
после чего - явно изменить свойство constructor объекта-прототипа
-
есть желание удалить любые свойства, которые
создаются конструктором надкласса в объекте-прототипе
Решения
// Вместо использования метода call() или apply()
// для вызова конструктора надкласса как метода данного объекта
Rectangle.call(this, w, h);
// можно упростить синтаксис конструктора, добавив
// свойство superclass в объектпрототип подкласса:
PositionedRectangle.prototype.superclass = Rectangle;
function PositionedRectangle(x, y, w, h) {
this.superclass(w,h);
this.x = x;
this.y = y;
}
Упрощение функции конструктора, отказ от call()
!!! Не работает для многоуровневого наследования
Установка прототипа
PositionedRectangle.prototype = new Rectangle();
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;
PositionedRectangle.prototype = Rectangle.prototype;
PositionedRectangle.prototype = Object.create(Rectangle.prototype);
Object.create() создаёт новый объект с указанными объектом прототипа и свойствами.
Вызов переопределенных методов
PositionedRectangle.prototype.toString = function() {
return "(" + this.x + "," + this.y + ") " + // поля этого класса
Rectangle.prototype.toString.apply(this); // вызов надкласса по цепочке
}
PositionedRectangle.prototype.toString = function( ) {
return "(" + this.x + "," + this.y + ") " + // поля этого класса
this.superclass.prototype.toString.apply(this);
}
Classes ES6
Базовый класс
class Rectangle {
constructor(w, h) { //class constructor
this.width = w;
this.height = h;
}
area() { //class method
return this.width * this.height;
}
}
var rect = new Rectangle(5, 10);
rect.width // 10
rect.area() // 50
rect.hasOwnProperty("area") //false
Добавления метода класса
class Rectangle {
constructor(w, h) { //class constructor
this.width = w;
this.height = h;
}
static max(r1, r2) { // Rectangle.max
if (r1.area() > r2.area()) {
return r1;
}
return r2;
}
}
Наследование классов
class Rectangle {
constructor(w, h) { //class constructor
this.width = w;
this.height = h;
}
toString() {
return '(' + this.width + ', ' + this.height + ')';
}
}
class PositionedRectangle extends Rectangle {
constructor(x, y, w, h) {
super(w, h);
this.x = x;
this.y = y;
}
toString() {
return super.toString() + this.x + ', ' + this.y;
}
}
var pRect = new PositionedRectangle(2, 2, 2, 2);
Расширение без наследования
Заимствование методов одного класса для использования в другом
function borrowMethods(borrowFrom, addTo) {
var from = borrowFrom.prototype; // прототип источник
var to = addTo.prototype; // прототип приемник
for(m in from) { // Цикл по всем свойствам прототипа-источника
if (typeof from[m] != "function") continue; // Игнорировать не функции
to[m] = from[m]; // Заимствовать метод
}
}
Классы-смеси, или просто смеси (Mixin) -
классы, разрабатываемые специально с целью заимствования, обычно ничего особо полезного не
делающие, зато реализующих методы, которые могут быть заимствованы другими классами.
function GenericToString() {}
GenericToString.prototype.toString = function( ) {
//реализация универсального метода toString
}
function GenericEquals() {}
GenericEquals.prototype.equals = function(compareTo) {
// реализация универсального метода equals
// для сравнения 2х объектов
}
// Заимствование некоторых методов
borrowMethods(GenericEquals, Rectangle);
borrowMethods(GenericToString, Rectangle);
// Эта смесь содержит метод, зависящий от конструктора. Оба они,
// и конструктор, и метод должны быть заимствованы.
function Colored(c) { this.color = c; }
Colored.prototype.getColor = function() { return this.color; }
// Определение конструктора нового класса
function ColoredRectangle(w, h, c) {
this.superclass(w, h); // Вызов конструктора надкласса
Colored.call(this, c); // и заимствование конструктора Colored
}
// Настройка объектапрототипа на наследование методов от Rectangle
ColoredRectangle.prototype = Object.create(Rectangle.prototype);
ColoredRectangle.prototype.constructor = ColoredRectangle;
ColoredRectangle.prototype.superclass = Rectangle;
// Заимствовать методы класса Colored в новый класс
borrowMethods(Colored, ColoredRectangle);
"Множественное наследование"
Реализации на уровне библиотек
// jQuery
// jQuery.extend( [deep ], target, object1 [, objectN ] )
// Returns target
var object1 = {
apple: 0,
banana: { weight: 52, price: 100 },
cherry: 97
};
var object2 = {
banana: { price: 200 },
durian: 100
};
$.extend(object1, object2) // Merge object2 into object1
object1 //{"apple":0,"banana":{"price":200},"cherry":97,"durian":100}
_.assign, _.merge from lodash, angular.extend
Спасибо!
Остались вопросы?
Objects
By maximk
Objects
- 1,183