Javascript Prototypes

JS Objects

  • Objects are collection of key/value pairs (Arrays can be looked at as objects with numbers as keys)
  • You can access properties and methods using
    • . notation (only if the key has a valid name)
    • [“”] notation (works for everything)
  • Like objects, functions can have properties (like bind, call & apply) but they can also be invoked using () operator

 

Creating new objects

You can create a new object using:

  1. {} notation var obj = {}
  2. Object.create(proto) method.

Functions are created using the function keyword or with => in ES6. When they are a part of an object we call them methods

  •  

Adding to objects

You can add properties to existing object with any type of value you wish (number, string, object, function, boolean, undefined, null, etc..) this can be done in the following ways:

// Method 1 
// . notation
var x = {}
x.newProperty = 1



// Method 2
// Object.defineProperty(obj, prop, descriptor)
var obj = {};
// this is same as obj.key = "somevalue"
Object.defineProperty(obj, 'key', {
  enumerable: true,
  configurable: true,
  writable: true,
  value: 'somevalue'
});

Question: Why does this work ?

Where is the toString() method coming from ?

 

var obj = { name:"Dan" }
console.log(obj.toString()) // prints [object Object]


The invocation chain - __proto__

  • Each object (functions are objects too) have a __proto__ property

  • __proto__ points to an object, it's __proto__ points to another object until the top

  • The top is the Object.prototype object

  • When accessing an object property, the object is searched first, it not found we search the object in __proto__

  • If we reach the top and still can't find the property, we get undefined. Invoking it as a method will throw an exception (you can’t invoke undefined)

 

Prototypes 

  • Functions have an additional property called prototype

  • this is used in the function constructor pattern:
    When an object is created using a new, the instance __proto__ will point to Ctor.prototype.

  • Remember:

    • every object has the __proto__ property but only functions also have the prototype property.

    • the difference between CtorFunc.prototype and instance.__proto__

 

Prototypes - cont.

function Foo() {
  this.test = 1;
}
Foo.prototype.bar = 2; // adding bar before instantiation 
let foo = new Foo();

foo.test // 1
foo.bar // 2

Foo.prototype.bar2 = 4; // adding bar2 after instantiation also works
foo.bar2 // 4




Prototype vs. this - where to put my property ?

  • Properties that are data are usually defined  on the objects's this .
  • Methods (properties that are functions) are usually defined on the prototype of the constructor function. (unless they need to access private variables of the ctor function) see example in next slide.

Prototypes example

function Foo() {
  var sum = 0;
  this.value = "this is a value";
  // this method needs to access the sum private var
  this.addToSum = function (val) { sum += val; }
}
Foo.prototype.showValue = function () { 
  console.log(this.value) 
} 
let f = new Foo();
f.value // “this is a value”
f.addToSum(4) // works!
f.showValue() // this is a value

Prototypes - cont.

The differences between this.myProperty and Foo.prototype.myProperty are:

  • Access to local variables is possible with this
  • Some memory saving (which may or may not be significant) since the method is not a part of every object.
constructor
function Foo () {
  
}

// instance method
Foo.prototype.moo = function() {}
let f = new Foo();

// static
Foo.moo2 = function () {
  
}

Foo.moo2()
f.constructor.moo2()
f.moo()

points to the function that created the object

Prototype - inheritance ES5

// Shape - superclass
function Shape(x, y) { this.x = x; this.y = y; }
// Superclass method
Shape.prototype.move = function(x, y) { this.x += x; this.y += y; }

// Circle - subclass
function Circle(x, y, r) {
  // Call constructor of superclass to initialize superclass-derived members.
  Shape.call(this, x, y);
  // Initialize subclass's own members
  this.r = r;
}
// Circle derives from Shape
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
// Subclass methods. Add them after Circle.prototype is created with
// Object.create
Circle.prototype.area = function() {
  return this.r * 2 * Math.PI;
}

let shape = new Shape()
let circle = new Circle()

Inheritance - ES6 style :)

class Shape {
  constructor(x, y) { this.x = x; this.y = y }
  move (x, y) { this.x += x; this.y += y }
}

class Circle extends Shape {
  constructor (x,y,r) {
   super(x, y)
   this.r = r;
  }
  
  area () { 
    return this.r * 2 * Math.PI;
  }
}

Conclusion

  • ES6 style inheritance is quite simpler to write and read (although under the hood it’s just syntactic sugar and still uses prototypes)
  • Inheritance in general is not used every day (usually we write logic)
  • Still a lot of legacy code is laying around so it’s good to have a firm grasp of “raw” prototypes
Made with Slides.com