المعرفة:: JavaScript الحالة::مؤرشفة المراجع:: The Complete JavaScript Course 2022 From Zero to Expert, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
What is Object Oriented Programming (OOP)?
- Object-oriented programming (OOP) is a programming paradigm based on the concept of objects.
- We use objects to model (describe) real-world or abstract features.
- Objects may contain data (properties) and code (methods). By using objects, we pack data and the corresponding behavior into one block.
- In OOP, objects are self-contained pieces/blocks of code.
- Objects are building blocks of applications, and interact with one another.
- Interactions happen through a public interface (API): methods that the code outside of the object can access and use to communicate with the object.
- OOP was developed with the goal of organizing code, to make it more flexible and easier to maintain (avoid spaghetti code).
4 Fundamental OOP Principles
1. Abstraction
Ignoring or hiding details that dont matter, allowing us to get an overview perspective of the thing were implementing, instead of messing with details that dont really matter to our implementation.
2. Encapsulation
- Keeping properties and methods private inside the class, so they are not accessible from outside the class. Some methods can be Encapsulation as a public interface (API).
- Prevents external code from accidentally manipulating internal properties/state.
- Allows to change internal implementation without the risk of breaking external code.
3. Inheritance
Making all properties and methods of a certain class available to a child class, forming a hierarchical relationship between classes. This allows us to reuse common logic and to model real-world relationships.
4. Polymorphism
A child class can overwrite a method it inherited from a parent class [its more complex that that, but enough for our purposes].
Classical OOP vs Prototypes
-
JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not have static types.
-
In JavaScript we have something called prototypes and all objects in JavaScript are linked to a certain prototype object.
-
The prototypical inheritance model is more powerful than the classic model even it’s a little cofusing.
Classical OOP | Prototypes |
---|---|
Objects (instances) are instantiated from a class, which functions like a blueprint. | Objects are linked to a prototype object |
Behavior (methods) is copied from class to all instances. | Prototypal inheritance: The prototype contains methods (behavior) that are accessible to all objects linked to that prototype. |
Behavior is delegated to the linked prototype object. |
Prototypes Introduction
-
- JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with
null
as its prototype. By definition,null
has no prototype, and acts as the final link in this prototype chain.
- JavaScript only has one construct: objects. Each object has a private property which holds a link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with
-
For example,
Array.prototype
is the prototype of all array objects we create in JavaScript. Therefore, all arrays have access to themap
method.map
method isn’t defined in the array object itself but in its prototype.
3 Ways Of Implementing Prototypal Inheritance
1. Constructor Functions
- Technique to create objects from a function.
- This is how built-in objects like Arrays, Maps or Sets are actually implemented.
2. ES6 Classes
- Modern alternative to constructor function syntax.
- Syntactic sugar: behind the scenes, ES6 classes work exactly like constructor functions.
- ES6 classes do NOT behave like classes in classical OOP (last lecture).
3. Object.create()
- The easiest and most straightforward way of linking an object to a prototype object.
Constructor Functions and new
Operator
- We can use constructor functions, to build an object using a function.
- A constructor function is actually a completely normal function. The only difference between a regular function, and it, is that we call a constructor function with the
new
operator.
- In the code above we didn’t technically create a class here because JavaScript doesn’t really have classes in the sense of traditional OOP. However, we did create 3 objects from a constructor function.
How does Constructor Functions work?
Here’s what happen by using new
operator:
- An empty object is created.
this
keyword in constructor function call is set to the new object.- The new object is linked (proto property) to the constructor function’s prototype property.
- The new object is returned from the constructor function call. (Unless we’re explicitly return something else)
instanceof
- We can use
instanceof
to check if an object is an instance of a constructor function.
Methods in Constructor Functions
- We can add methods to constructor functions. But we should never create a method inside of a constructor function because each object created would have its copy of the same function which would be terrible for the performance.
- Instead to solve this problem, we will use prototypes and prototype inheritance.
Prototypes
- Each and every function in JavaScript automatically has a property called prototype, and that includes constructor functions.
- Every object that’s created by a certain constructor function will get access to all the methods and properties that we define on the constructors prototype property.
- Another example of coding challenge: here.
__proto__
- Each object has a special property called
__proto__
which equals the prototype object of its constructor function.
isPrototypeOf()
- The constructor function’s
prototype
object doesn’t refer to its ownprototype
but to one of objects created by this constructor function. - The
isPrototypeOf()
method checks if an object exists in another object’s prototype chain.
Prototype Properties
- We can also set properties on the prototype and not just methods.
hasOwnProperty()
- Prototype properties are not available directly in the object but in it’s prototype, so they aren’t object’s own properties.
- Own properties are only the ones that are declared directly on the object itself.
- We can check for own properties using
hasOwnProperty
method.
How Prototypal Inheritance / Delegation Works
- Everything starts with the
Person
the constructor function. - This constructor function has a
prototype
property which is an object. - Inside that object, we defined the
calcAge
method.Person.prototype
itself has a reference back toPerson
which is the constructor property. - Remember,
Person.prototype
is not the prototype ofPerson
but of all the objects that are created through the Person function .
How Prototypal Inheritance works with function constructors and ES6 classes
Object.create()
doesn’t work the same way.
How does Constructor Functions work?
Here’s what happen by using
new
operator:وصلة للملاحظة الرئيسة
- An empty object is created.
this
keyword in constructor function call is set to the new object.- The new object is linked (proto property) to the constructor function’s prototype property.
- The new object is returned from the constructor function call. (Unless we’re explicitly return something else)
- When we attempt to call the
calcAge
function on thejonas
object it can’t be found directly in the object itself. If a property or a method cannot be found in a certain object, JavaScript will look into its prototype. - This behavior is what we call prototypal inheritance or delegation.
graph BT const_person[Constructor function<br/>Person] --.prototype--> person person[Prototype<br/>Person.prototype<br/>calcAge: function] --.constructor --> const_person jonas[Object<br/>jonas<br/>name: 'jonas'<br/>birthYear: 1990<br/>__proto__:<br/>Person.prototype] --.__proto__--> person
The Prototype Chain
Series of links between objects, linked through prototypes (Similar to the Scope Chain)
Explanation:
jonas
object is linked toperson
function constructorprototype
via the__proto__
property.Person.prototype
itself is also an object and all objects in JavaScript have a prototype.- Prototype of
Person.prototype
isObject.prototype
.Person.prototype
is just a simple object which means that it has been built by the built-inObject
constructor function and this is actually the function that gets called behind the scenes whenever we create an object literal using{...}
syntax ({...} === new Object(...)
) .Person.prototype
itself needs to have a prototype and since it has been created by theObject
constructor function its prototype is gonna beObject.prototype
..
- This entire series of links between the objects is what is called the prototype chain.
Object.prototype
. is usually the top of the prototype chain which means that it’s prototype is null.
graph BT jonas[Object<br/>jonas<br/>__proto__:<br/>Person.prototype] --.__proto__--> person[Prototype<br/>Person.prototype<br/>__proto__:<br/>Object.prototype] person --.__proto__--> object[Prototype<br/>Object.prototype<br/>__proto__: null] object --.__proto__--> null[null] const_person[Constructor function<br/>Person] --> person const_object[Constructor function<br/>Object] --> object
Prototypal Inheritance on Built-In Objects
- If we checked the prototype of the
Array
object, we can see that it has all these array’s methods (filter, find, find, etc.) that we already know. - Each array doesn’t contain all of these methods but instead, each array will inherit these methods from its prototype.
- Any array prototype is exactly its constructor function.prototype
arr.__proto__ === Array.prototype // true
. - Once more, we can see that the Prototypal Inheritance is really a mechanism for reusing code. All of these built-in methods have to exist only once somewhere in the JavaScript engine and then all the arrays we create in the code get access to the functions through the Prototype Chain and Prototypal Inheritance.
Extending Built-In Objects Functionality
- Knowing the last point, we can use that knowledge to extend the functionality of arrays even further. We can add any new method to Array’s prototype and all the arrays will then inherit it.
- Warning: Extending the prototype of a built-in object is generally not a good idea.
- The next version of JavaScript might add a method with the same name that we are adding. The code will then use that new method which works differently and that will probably break the code.
- When you work on a team of developers, if multiple developers implemented the same method with a different name then that’s just going to create so many bugs that it’s just not worth to do this.
ES6 Classes
- Allows us to do the same thing as constructor function and setting method on prototype property but using a nicer and more modern syntax.
- Classes are in fact “special functions”, and just as we can define Function Expressions and Function Declarations, the class syntax has two components: Class Expressions and Class Declarations.
- A function created by class is labelled by a special internal property
[[IsClassConstructor]]: true
. The language checks for that property in a variety of places. For example, unlike a regular function, it must be called withnew
keyword. - Classes are first-class citizens, they can be passed into functions and also returned from them.
- The body of a class is always executed in strict mode.
- Instance properties must be defined inside of class methods. Computed Property Keys is also supported.
Class Declarations
- To declare a class, you use the
class
keyword with the name of the class.
- An important difference between function declarations and class declarations is that while functions can be called in code that appears before they are defined, classes must be defined before they can be constructed. This occurs because while the class is hoisted, its values are not initialized.
Class Expressions
- A class expression is another way to define a class.
- Class expressions can be named or unnamed. The name can be accessed via the
name
property. - Class expressions must be declared before they can be used as well.
Methods Definition
Constructor
- The constructor method is a special method for creating and initializing an object created with a class.
- There can only be one special method with the name “constructor” in a class.
- A
SyntaxError
will be thrown if the class contains more than one occurrence of a constructor method. - A constructor can use the
super
keyword to call the constructor of the super class.
Prototype (Instance) Methods
- Methods written outside the constructor will be added to
.prototype
property directly. - Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced.
- It is a shorthand for a function assigned to the method’s name.
- Prototype methods behave exactly as adding the function to prototype object manually.
Getters and Setters
- Every object in JavaScript can have setter and getter properties.
- These special properties are called assessor properties, while the more normal properties are called data properties.
- Getters and setters are basically functions that get and set a value, but on the outside they still look like regular properties. This can be very useful when we want to read something as a property, but still need to do some calculations before.
- It is not mandatory to specify a setter when we have a getter for the same property, just a getter or just a setter would be enough.
- Getters and setters work for any regular object in JavaScript, classes also have them, and they work in the exact same way.
- Setters and getters can be very useful for data validation.
Static Methods
- Static properties and methods are called without instantiating their class and cannot be called through a class instance.
- Static methods are often used to create utility functions for an application, whereas static properties are useful for caches, fixed-configuration, or any other data you don’t need to be replicated across instances.
- For example,
Array.from
method which converts any array like structure to a real Array, is attached to the Array constructor and not the prototype property of the constructor, so we could not use it on an Array because Arrays don’t inherit this method.
- Creating a static method with Constructor functions:
- Static methods in classes:
Object.create()
- The
Object.create()
method creates a new object, using an existing object as the prototype of the newly created object. - With
Object.create()
, there is still the idea of prototypal inheritance. However, there are no prototype properties involved, no constructor functions, and no new operator. - We can use
Object.create()
to essentially manually set the prototype of an object, to any other object that we want. - In the real world, this is the least used way of implementing prototypal inheritance.
- We can create a constructor-like method to set properties on creating a new object, for example, the following
init
method:
Inheritance
Inheritance Between Classes: Constructor Functions
- When we build a “child” constructor function, instead of having duplicate lines for setting properties, we can use
Object.call
method of parent constructor function with this keyword and other parameters. - To link prototypes of parent and child we use
Object.create()
and doChild.prototype = Object.create(Parent.prototype);
. This must happen before adding any new method to the prototype, otherwise prototype will be overridden. - When two methods or properties have the same name in a prototype chain, the first one that appears in the chain is the one that will be used.
- We can check if prototype chain is set up correctly.
- If we inspected the
Student
object in console we can see that prototype is set toPerson
. We can fix that by setting.prototype.constructor
toStudent
object.
- Here’s what prototype chain looks like:
graph BT mike[Object<br/>mike<br/>__proto__:<br/>Student.prototype] student[Prototype<br/>Student.prototype<br/>__proto__:<br/>Person.prototype] person[Prototype<br/>Person.prototype<br/>__proto__:<br/>Object.prototype] object[Prototype<br/>Object.prototype<br/>__proto__: null] mike --.__proto__--> student --.__proto__--> person --.__proto__--> object --.__proto__--> null[null] const_student[Constructor function<br/>Student] --> student const_person[Constructor function<br/>Person] --> person const_object[Constructor function<br/>Object] --> object
Inheritance Between Classes: ES6 Classes
- The
extends
keyword is used in class declarations or class expressions to create a class as a child of another class. - If there is a constructor present in the subclass, it needs to first call
super()
before using “this
” to call the super class constructor and pass in parameters. - If there’s no constructor, the super function will be automatically called with all arguments that are passed into child class.
Inheritance Between Classes: Object.create()
- First, we create an object that will be the prototype of the child objects.
StudentProto
- Second, we can add an init method to the prototype of the child objects so we don’t have to manually specify the properties on any new child object.
- Inside init, we can use
Object.call
method of parent constructor function with this keyword and other parameters to pass the parameters without having duplicate code, similar as we did in Inheritance Between Classes Constructor Functions.
- Here’s what prototype chain looks like, where Student inherits from Person:
graph BT jay[Object<br/>Jay<br/>__proto__:<br/>StudentProto] student[Prototype<br/>StudentProto<br/>__proto__:<br/>PersonProto] person[Prototype<br/>PersonProto<br/>__proto__:<br/>Object.prototype] object[Prototype<br/>Object.prototype<br/>__proto__: null] jay --.__proto__--> student --.__proto__--> person --.__proto__--> object --.__proto__--> null[null]
Protected Properties and Methods
- Remember that Encapsulation is:
2. Encapsulation
وصلة للملاحظة الرئيسة
- Keeping properties and methods private inside the class, so they are not accessible from outside the class. Some methods can be Encapsulation as a public interface (API).
- Prevents external code from accidentally manipulating internal properties/state.
- Allows to change internal implementation without the risk of breaking external code.
- JavaScript classes actually do not yet support real data privacy and encapsulation. We can fake encapsulation by simply using the convention of adding an underscore in front of the property name. This doesn’t make the property truly private and it’s still can be accessed, but at least now everyone on your team and that includes yourself will know that this property is not supposed to be touched outside of the class.
Class Fields and Methods
- Class Fields Declaration is a ECMAScript proposal which is currently at Stage 3 in the TC-39 process. This proposal will allow to add instance properties directly as a property on the class without having to use the constructor method.
- Why is this proposal actually called Class fields? In traditional OOP languages like Java and C++, properties are usually called fields.
- The important difference of class fields is that they are set on individual objects, not
.prototype
. That’s why we can also call this it instance field. - Class Fields can be static also, whether they are public or private.
Public field declarations
- We don’t need keywords like
let
,const
, orvar
to declare fields. By declaring fields up-front, class definitions become more self-documenting, and the fields are always present. The fields can be declared with or without a default value.
Public static fields
- Public static fields are useful when you want a field to exist only once per class, not on every class instance you create. This is useful for caches, fixed-configuration, or any other data you don’t need to be replicated across instances.
- Fields without initializers are initialized to undefined.
- Public static fields are not reinitialized on subclasses, but can be accessed via the prototype chain.
Private Class Fields and Methods
- Class fields are public by default, but private class members can be created by using a hash
#
prefix. The privacy encapsulation of these class features is enforced by JavaScript itself. - Trying to access the private field outside of the class will generate a
SyntaxError
. Trying to access Private field from a subclass will result inSyntaxError
as well. - Private instance methods are methods available on class instances whose access is restricted in the same manner as private instance fields.
- Private static fields are only accessible on the class itself or on the this context of static methods, but not on the this context of instance methods.
- Private static methods are called on the class itself, not instances of the class, like their public equivalent, and like private static fields, they are only accessible from inside the class declaration.
Example
Methods Chaining
- We can implement the ability of chaining methods in the methods of our class, similar to array methods. All we have to do is to return the object itself
this
at the end of a method that we want to be chainable.
ES6 Classes Summary
- Classes are just “syntactic sugar” over constructor functions.
- Classes are not hoisted.
- Classes are first-class citizens.
- Class body is always executed in strict mode.