JS OOP
Prototype Chain
prototype chain - fundamental concept in JS, which allows object to inherit properties and methods from another object and so on, creating a chain
- in classical OOP languages like C++ and Java inheritance is implemented through classes and extends relationships, what makes them class-based
- unlike classical OOP languages, JS is prototype-based. so prototype chain is JavaScript's implementation of inheritance
prototype - object which shares its properties and methods
- every object in JS has a prototype. prototype of the last object in a chain is null
- prototype can't be modified through an instance. e.g. delete child.bar; - deletes "bar" in child, and will never delete "bar" in child's prototype
[[Prototype]] - internal hidden property that exists in every JS object. it holds a link to a prototype
__proto__ - property which gives read and write access to the object's prototype. unlike [[Prototype]], it is not hidden
- e.g. foo.__proto__ = newObject; - set new prototype
- however prototype manipulation through __proto__ is not recommended. special methods should be used instead
- technically __proto__ is computed property, which has both getter and setter
- __proto__ doesn't throw error when wrong type value is assigned. in such cases, it just assigns top-level object, i.e. Object.prototype
Methods
Object.create(prototype) - returns new object, which inherits passed object
Object.setPrototypeOf(object, prototype) - sets prototype
- prototype - accepts any object (array, function, etc.) or null. setting to null will result in plain object without any inherited properties
Object.getPrototypeOf(object) - checks prototype
Constructors
constructor - function, which is used to create objects according to the template
- there is an agreement to uppercase constructor functions names
- Base.call(this, foo) - constructor can call another constructor passing context. properties assigned in Base will be added to the instance
- Base.call(this, foo) is analog of class' super() call
new - operator which is used to invoke constructor function or class
- new operator is responsible for the following things:
- creates a blank object, e.g. newInstance
- points newInstance's [[Prototype]] to the constructor function's "prototype" property. [[Prototype]] -> Foo.prototype
- executes the constructor function with the given arguments, binding newInstance as the "this" context. newInstance -> this
- makes constructor function return newInstance. constructor function result is the result of new operator
- absence of return is default scenario. but still constructor function can return object, which will override newInstance object
- if constructor function returns primitive, it will be ignored and newInstance will be returned instead
instance - object created by constructor function
Foo.prototype - property, which contains reference to an object, which will be inherited by the instances of constructor function
- Foo.prototype === instance.[[Prototype]]
- usually it contains methods. this is useful because we avoid method duplication for all instances. the instance itself stores only the data
- { constructor: Foo } - default prototype object. each function has "prototype" property with default object
- Foo.prototype.constructor - contains a reference to the constructor function. respectively it is accessible from the constructor function instance object
- Foo.prototype.constructor === Foo === instance.[[Prototype]].constructor
- arrow functions don't have "prototype" property
function User(name) {
this.name = name;
}
User.prototype.sayHello = function () {
console.log(`Hello, I'm ${this.name}`);
};
const user = new User('Jack');
user.sayHello();
polyfilling - adding a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine
method borrowing - take a method from one prototype and copy it into another. it allows mixing functionalities
- e.g. object.join = Array.prototype.join;
Classes
class - is a superstructure over constructor function with its prototype, providing more familiar way of creating objects
- JS classes are considered as syntactic sugar
- however classes also own unique features compared to constructor functions. they are:
- class can not be invoked without new
- is it allowed to declare computed properties
- static fields & methods - those fields and methods, which are assigned to the class and not to its objects. e.g. FooClass.fieldStatic
- private properties - e.g. #age = 18;
- practical example of using class can be checked here
class Foo {...} - class declaration
- class declarations are not hoisted like function declarations
const Bar = class {...} - class expression
- const Bar = class MyClass {...} - named class expression
class constructor - method with reserved name. it creates object. this is the same as constructor function
- instance properties, which are not dependent on constructor arguments, can be written over the constructor for simplicity
class methods - functions, which will be placed into FooClass.prototype. these methods will be inherited by instance objects
- typeof FooClass === 'function' and FooClass.prototype === FooFunction.prototype
- previously methods were added separately through assignment to FooFunction.prototype. now everything is done within class declaration
class FooClass {
static fieldStatic = x;
#privateProp = y;
instanceProp = z;
constructor(name) {
this.name = name;
}
get computedProp() { ... }
barMethod() { ... }
}
Classes Inheritance
extends - keyword which is used to implement inheritance. ChildClass can reuse the properties and methods of the ParentClass
- e.g. class ChildClass extends ParentClass { ... } - ChildClass is commonly named Base class. ParentClass is named Derived class
- extends sets two prototypes:
- setPrototypeOf(ChildClass.prototype, ParentClass.prototype)
- setPrototypeOf(ChildClass, ParentClass)
- instance prototype chain: instanceObject -> ChildClass.prototype -> ParentClass.prototype -> Object.prototype -> null
- class prototype chain: ChildClass -> ParentClass -> Function.prototype -> Object.prototype -> null
super() - keyword which is used to call parent class' constructor. "this" will be forwarded and all properties assigned in parent will be added to the instance
- super(foo, bar) - if needed super can accept arguments
- super() must be written before "this" usage
- arrow functions take super from an outer function
- super.barMethod() - super can be used to call parent's method. this is useful when child and parent classes have different methods with same name
default constructor - is automatically generated when custom constructor was not provided
- constructor() {} - for base class
- constructor(...args) { super(...args); } - for derived class
Native Prototypes
native prototype - prototype of JS built-in constructor. e.g. Object, Array, etc.
- all standard objects inherit properties and methods from native prototypes
- native prototype can be modified, but this is considered a bad practice because of conflicts. yet polyfilling is approved
- when primitive's method is accessed, temporary wrapper object is created using built-in constructor, provides all the methods and disappears
- null and undefined have no wrapper objects. methods and properties are not available for them
Object.prototype - native prototype for all objects
- Object.prototype is the top-level object, thereafter the final link in the objects prototype chain
- all other native prototypes directly inherit Object.prototype by default
- Object - built-in constructor
- new Object() - Object constructor call. creates an object
- {} - object literal. "foo = {}" is a short notation for "foo = new Object()"
Wrapper Objects
new String() - string constructor call. creates the string object, which is used to represent and manipulate a sequence of characters
new Number() - number constructor call. is used to create number wrapper object
- e.g. new Number('97')
new Boolean()
String, Number, Boolean objects and respective primitive values is not the same
this
this - reference to special object. it can reference to different objects depending on where is it used
- "this" is also called a context
script this === window regular function this === undefined / window (when used without "use strict") arrow function this is taken from closure constructor function this === Object.create(Foo.prototype), i.e. instance object object method this references an object through which method was called, i.e. object before the dot this in callback of array method this references value of second parameter. e.g. array.forEach(callback, thisArg) setTimeout this === window event handler this === event.currentTarget. i.e. this references an element on which addEventListener is set
Context Forwarding
call(thisArg, arg1, argN) - calls the function with a given "this" value and provided arguments
apply(thisArg, argsArray) - calls the function with a given "this" value, and arguments provided as an array or collection
- parameters after array will be ignored
bind(thisArg, arg1, argN) - creates a new function that has a given "this" value set
- arguments are optional. if provided, they will also be available in a function
- bind doesn't make function call
- foo.bind(thisArg, bar) equals () => foo.call(thisArg, bar)