JavaScript is a bit confusing for developers experienced in class-based languages (like Java or C++), as it is dynamic and does not provide a class implementation per se (the class keyword is introduced in ES2015, but is syntactical sugar, JavaScript remains prototype-based). Ref.
- A constructor makes an object
"based on"its own prototype. (JS doesn't do copy). - A constructor makes an object
linked to
its ownprototype
.
- Function call with a
new
keyword
- It means a object linkage. It comes when we create a
new object
.
- When we call an method/property/object of an object, if it is not found then it is delegated the chaining by
[[Prototype]]
.
ob.__proto__
- Object.getPrototypeOf(ob)
- ob.constructor.prototype
-
__proto__
brings direct access to [[Prototype]]. So, the__proto__
setter allows the[[Prototype]]
of an object to be mutated. -
prototype
is the object that is used to build__proto__
when we create an object withnew
. -
[[Prototype]]
is an object specifies its prototype via the internal property.
const foo = {
getName: function() {
return this.name;
}
};
const a1 = {
[[prototype]]: foo, // __proto__: foo
name: 'sajib',
}
a1.getName(); // sajib
prototype
is not available on the instances themselves (or, other objects), but only on the constructor functions.prototype
is only available on functions since they are copied fromFunction
andObject
, but in anything else it is not. However,__proto__
is available everywhere.- Any method declared directly to
Function
will be considered asstatic method
, which meansit exists as local property of
that Functiononly and won't available to its instances
.
function A() {
function staticMethod() {
// this is static method and can't access from the instance of A
}
}
const ob = new A();
ob.staticMethod();
// TypeError: A.staticMethod is not a function
- Common OO patterns
- Prototype
Inheritance
vsBehavior Delegation
Every single 'object' is built by a constructor function call
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name;
}
const o1 = new User('a1');
const o2 = new User('a2');
o2.sayHello = function() {
alert(`Hello, ${this.getName()}.`);
};
o1.constructor === User;
o2.constructor === o2.constructor;
o1.__proto__ === User.prototype;
o1.__proto__ === o2.__proto__;
When an inherited function is executed, the value of this
points to the inheriting object, not to the prototype object
where the function is an own property.
var ob = {
a: 2,
f1: function() {
return this.a;
}
}
console.log(ob.f1()); // 2, this = ob
// o1 is an object that inherits/linked-to from 'ob'
const o1 = Object.create(ob);
o1.a = 5;
console.log(o1.f1()); // 5, this = o1 not ob!
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, because d doesn't inherit from Object.prototype
class Shape {
constructor(h, w) {
this.height = h;
this.width = w;
}
}
class Rectangle extends Shape {
constructor(h, w) {
super(h, w);
}
getArea() {
return this.height * this.width;
}
}
const o1 = new Rectangle(2, 3);
function Shape(h, w) {
this.height = h;
this.width = w;
}
const Rectangle = Object.create(Shape.prototype);
Rectangle.getArea = function() {
return this.height * this.width;
}
const o1 = Object.create(Rectangle)
console.log(o1);
const ob = {};
ob.__proto__.a = 'Hello!';
console.log(obj); // {}
console.log(obj.a); // Hello!
console.log(ob.hasOwnProperty('a')); // false
console.log(ob.__proto__.hasOwnProperty('a.')); // true
proto = null | | proto === Object.prototype | | ob
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name;
}
const o1 = new User('a1');
o1.getName(); // a1
o1.getName = function() { // shadowing, in class system it is called 'method overriding in polymorphism
// invoking User.prototype.getName but inside that method this = User.prototype
// so, this.name = undefined
// for o1
console.log(`Hello, I'm ${this.__proto__.getName()}.`);
// for o2, but for o1 it will go to Object.prototype & there is not 'name'
console.log(`Hello, I'm ${this.__proto__.__proto__.getName()}.`); // for o2
}
o1.getName();
// if we create a new object from o1 (say, o2) then we need two __proto__ to invoke User.prototype.getName
o2 = Object.assign(o1, {a: 1});
// we can skip prototype chain & invoke User.prototype.getName directly
console.log(`Hello, I'm ${User.prototype.identify.call(this)}.`);
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name;
}
User.prototype.sayHello = function() {
// this = o1, deligate to prototype chain & find getName() into User.prototype
return console.log(`Hello, ${this.getName()}.`);
}
const o1 = new User('a1');
function Shape(height, width) {
this.height = height;
this.width = width;
}
Shape.prototype.getHeight = function() {
return this.height;
}
Shape.prototype.getWidth = function() {
return this.width;
}
function Rectangle(height, width) {
Shape.call(this, height, width);
}
// Rectangle.prototype = new Shape(); // or,
Rectangle.prototype = Object.create(Shape.prototype);
// NOTE: .constructor is broken here, need to fix
Rectangle.prototype.getArea = function() {
return this.getHeight() * this.getWidth();
}
const o1 = new Rectangle(2, 3);
const o1 = new Rectangle(4, 5);
o1.getArea(); // 6
o1.getArea(); // 20
function Shape(height, width) {
this.height = height;
this.width = width;
}
Shape.prototype.getHeight = function() {
return this.height;
}
Shape.prototype.getWidth = function() {
return this.width;
}
function Rectangle(height, width) {
Shape.call(this, height, width);
}
// Rectangle.prototype = new Foo(); // or,
Rectangle.prototype = Object.create(Shape.prototype);
// NOTE: .constructor is broken here, need to fix
Rectangle.prototype.getArea = function() {
return this.getHeight() * this.getWidth();
}
const r1 = new Bar(2, 3);
r1.getArea(); // 6
const Shape = {
init: function(height, width) {
this.height = height;
this.width = width;
},
getHeight: function() {
return this.height;
},
getWidth = function() {
return this.width;
},
}
const Rectangle = Object.create(Shape);
Rectangle.getArea = function() {
return this.getHeight() * this.getWidth();
}
const r1 = Object.create(Bar);
r1.init(2, 3);
r1.getArea(); // 6
- nicer syntax
- it's actually syntactical sugar, cause behind the scene prototype chain is working
- it has some limitations also
class Shape {
constructor(height, width) {
this.height = height;
this.width = width;
}
getHeight() {
return this.height;
}
getWidth() {
return this.width;
}
}
const o1 = new Shape(2, 3);
const o2 = new Shape(4, 5);
o1.getHeight(); // 2
o1.getHeight(); // 4
class Rectangle extends Shape {
getArea() {
return this.getHeight() * this.getWidth();
}
}
const r1 = new Rectangle(6, 7);
const r2 = new Rectangle(8, 9);
r1.getArea(); // ??
r2.getArea(); // ??
const o = {a: 1};
// o --> Object.prototype -->
// o.__proto__ === Object.prototype // true
// o has no own property named 'hasOwnProperty' but the properties exists in Object.prototype so, o will inherits it
const b = [1, 2];
// b --> Array.prototype --> Object.prototype --> null
// b.__proto__ === Array.prototype // true
// indexOf, forEach, etc. will be inherited from Array.prototype
function f() {
return 1;
}
// f --> Function.prototype --> Object.prototype --> null
// f.__proto__ == Function.prototype
Dig into some examples from MDN
var shape = function () {};
var p = {
a: function () {
console.log('aaa');
}
};
shape.prototype.__proto__ = p;
var circle = new shape();
circle.a(); // aaa
console.log(shape.prototype === circle.__proto__); // true
// or
var shape = function () {};
var p = {
a: function () {
console.log('a');
}
};
var circle = new shape();
circle.__proto__ = p;
circle.a(); // a
console.log(shape.prototype === circle.__proto__); // false
// or
function test() {};
test.prototype.myname = function () {
console.log('myname');
};
var a = new test();
console.log(a.__proto__ === test.prototype); // true
a.myname(); // myname
// or
var fn = function () {};
fn.prototype.myname = function () {
console.log('myname');
};
var obj = {
__proto__: fn.prototype
};
obj.myname(); // myname
-
The
__proto__
property has been standardized in the ECMAScript 2015 language specification for web browsers to ensure compatibility, so will be supported into the future. It is deprecated in favor ofObject.getPrototypeOf/Reflect.getPrototypeOf
andObject.setPrototypeOf/Reflect.setPrototypeOf
(though still, setting the[[Prototype]]
of an object is a slow operation that should be avoided if performance is a concern) -
The
__proto__
property can also be used in an object literal definition to set the object[[Prototype]]
on creation, as an alternative toObject.create()
. -
The prototype object has a prototype of its own, and so on until an object with
null
as its prototype. By definition,null
has no prototype, and acts as the final link of prototype chain. -
Nearly all objects in JS are instances of
Object
which sits on the top of a prototype chain. -
The
prototypal inheritance
model itself is, in fact, more powerful than the classic model. It is, for example, fairly trivial to build a classic model on top of a prototypal model. -
We can't extend an
Object
var Ob = {}; class User extends Ob { // <--- Error }
class Foo {
constructor(who) {
this.me = who;
}
identify() {
return this.me;
}
}
class Bar extends Foo {
constructor(who) {
this.x = 1; // <--- error!
super(who); // <-- this must come first
}
}
const b1 = new Bar('b1');