Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Multiple Inheritance

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at multiple inheritance.

Multiple Inheritance

We can do multiple inheritance easily by merging properties from different properties into one object and then return the object.

For instance, we can write:

function multi(...args) {
  let obj = {};
  for (const arg of args) {
    obj = {
      ...obj,
      ...arg
    };
  }
  return obj;
}

Then we can use it by writing:

const obj = multi({
  foo: 1
}, {
  bar: 2
}, {
  baz: 3
});

obj is then:

{foo: 1, bar: 2, baz: 3}

We pass in multiple objects and then copied the properties with the spread operator.

Mixins

Mixins is an object that can be incorporated into another object.

When we create an object, we can choose which mixin to incorporate into the final object.

Parasitic Inheritance

Parasitic inheritance is where we take all the functionality from another object into a new one.

This pattern is created by Douglas Crockford.

For instance, we can write:

function object(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

const baseObj = {
  name: '2D shape',
  dimensions: 2
};

function rectangle(baseObj, width, height) {
  const obj = object(baseObj);
  obj.name = 'rectangle';
  obj.getArea = function() {
    return this.width * this.height;
  };
  obj.width = width;
  obj.height = height;
  return obj;
}

const rect = rectangle(baseObj, 2, 3);
console.log(rect);
console.log(rect.getArea());

We have the object function which returns an instance of F .

Then we created the rectangle function that incorporates the baseOf , width and height and incorporate that into the object returned by object .

object takes the baseObj into the prototype of F .

And we add own properties to obj , which we return to add more properties.

This way if we log the value of rect , we get:

{name: "rectangle", width: 2, height: 3, getArea: ƒ

And __proto__ for rect has:

dimensions: 2
name: "2D shape"

We can also call the getArea as we did in the last console log, and we get 6.

So we know this is referring to the returned object.

Borrow a Constructor

We can call a parent constructor from a child constructor.

For instance, we can write:

function Shape(id) {
  this.id = id;
}

Shape.prototype.name = 'Shape';
Shape.prototype.toString = function() {
  return this.name;
};

function Square(id, name, length) {
  Shape.apply(this, [id]);
  this.name = name;
  this.length = length;
}

Square.prototype = new Shape();
Square.prototype.name = 'Square';

We have the Shape constructor, which takes the id parameter.

We call the Shape constructor with apply so that we this is set to the Shape constructor.

This will set this.id from Square .

Then we can populate our own properties.

And then we create Square ‘s prototype with the Shape instance.

Then we set the name to 'Square' since this is shared because of all the Square instances.

The way, we copy the properties from Shape to Square by setting Square’s prototype property.

Conclusion

We can create objects with various ways of inheritance.

We can call the parent’s constructor and incorporate various properties from various objects.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Modules

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at JavaScript modules.

Modules

JavaScript modules let us divide our code into multiple small chunks.

Modules in JavaScript are just regular JavaScript files.

There’s no module keyword.

We just use the export keyword to make items in modules available to other modules.

For instance, we can write:

module.js

export const port = 8080;
export function startServer() {
  //...
}
export class Config {
  //...
}
export function process() {
  //...
}

to create a module with various members exported.

We can export variables, functions, classes, etc.

Then we can import it by writing:

import * from './module'

We import all members from a module with the asterisk.

Also, we can import individual members by writing:

import { Config, process } from "./module";

We import the proces function and the Config class from module .

If we only have only one thing to export, we can use the default export syntax.

For instance, we can write:

export default class {
  //...
}

then we export the class in module.js .

Then we import it by writing:

import C from "./module";

Modules have some special properties.

They’re singletons, which means that it’s imported only once in another module.

Variables, fucmtions and other declarations are local to the module.

Only the ones that are marked with export are available for import .

Modules can import other modules.

We did that with the import statement we used earlier.

The path is relative in the examples.

But we can import modules with the module name if they’re in the node_modules folder.

ES5 supports libraries with CommonJS and Asynchronous Module Definition (AMD) module systems.

These aren’t part of the language spec.

They’re 3rd party systems that are adopted for use since ES5 and earlier don’t have a native module system.

CommonJS is the default module system that Node.js uses.

Export Lists

We can export a bunch of items in one line.

For instance, we can write:

const port = 8080;

function startServer() {
  //...
}
class Config {
  //...
}
function process() {
  //...
}

export { port, startServer, Config, process };

Then we export all the variables, classes, and functions that are above it.

We can then import them with import in another module.

The name that we import the members with can be changed with the as keyword.

For instance, we can write:

import { process as processConfig } from "./module";

We import the process function to processConfig with the as keyword.

Also, we can rename exports.

For instance, we can write:

module.js

const port = 8080;
function startServer() {
  //...
}
class Config {
  //...
}
function process() {
  //...
}

export { port, startServer, Config, process as processConfig };

Then we can import it bu writing:

import { processConfig } from "./module";

We used as on an export to rename it.

Conclusion

We can use modules to divide our code into small chunks.

This way, we won’t have to use global variables or have large pieces of code.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Maps, Sets, WeakSets, and WeakMaps

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at maps iteration, sets, WeakSets, and WeakMaps.

Iterating Over Maps

We can iterate over maps with the keys method to iterate over the keys.

For instance, we can write:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

for (const k of m.keys()) {
  console.log(k);
}

Then we get:

one
two
three

from the console log.

We can iterate over the values with the values method:

for (const v of m.values()) {
  console.log(v);
}

Then we get:

1
2
3

from the console log.

We can loop through the entries with the entries method:

for (const [k, v] of m.entries()) {
  console.log(k, v);
}

Then we get:

one 1
two 2
three 3

We restructured the key and value returned from the entries method.

Converting Maps to Arrays

We can convert maps to arrays with the spread operator.

For instance, we can write:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

const keys = [...m.keys()]

to get the keys from the map and convert it to an array.

So we get:

["one", "two", "three"]

We can also spread the map to spread the key-value pairs into a nested array of key-value pairs.

So if we have:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

const arr = [...m]

Then we get:

[
  [
    "one",
    1
  ],
  [
    "two",
    2
  ],
  [
    "three",
    3
  ]
]

as the value of arr .

Set

A Set is a collection of values that can’t have duplicate entries.

For instance, we can write:

const s = new Set();
s.add('first');

Then we create a set with an entry.

add adds an entry.

has checks if a value exists. We can use it by writing:

s.has('first');

delete lets us delete a value:

s.delete('first');

We can also create an array, so we can write:

const colors = new Set(['red', 'green', 'blue']);

to create a set with the strings.

WeakMap and WeakSet

WeakMap and WeakSet are similar to Map and Set but are more restricted.

WeakMaps only have the has(), get(), set(), and delete() methods.

WeakSet s only have the has() , add() , and delete() methods.

Keys of WeakMap must be objects.

We can only access a WeakMap value with its key.

Values of WeakSet must be objects.

We can’t iterate over a WeakMap or WeakSet .

We can’t clear a WeakMap or WeakSet .

WeakMap s are useful when we don’t want any control over the lifecycle of the object we’re keeping with it.

It’ll automatically be garbage collected when the key is no longer available.

Conclusion

Maps, Sets, WeakMaps, and WeakSets are useful data structures that we can use with our app.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Inheritance and Copying Objects

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at copying objects.

Deep Copy

We can deep copy an object by copying an object’s properties recursively from the source object to the target object.

For instance, we can write:

function deepCopy(source, target = {}) {
  for (const key in source) {
    if (source.hasOwnProperty(key)) {
      if (typeof source[key] === 'object') {
        target[key] = Array.isArray(source[key]) ? [] : {};
        deepCopy(source[key], target[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

We loop through each key of the source .

Then we check each property if it’s an own property.

Then we check if the source[key] object is an object.

If source[key] is an array, then we create an array, then we do the copying.

If it’s an object, then we call deepCopy recursively to make the copy.

Otherwise, we assign the value from the source to the target .

Once that’s all done, we return the target .

Then we can use it by writing:

const foo = {
  a: 1,
  b: {
    c: 2
  }
}
const bar = deepCopy(foo, {});

console.log(bar);

deepCopy will copy all the own properties from foo to bar recursively.

So bar is:

{
  "a": 1,
  "b": {
    "c": 2
  }
}

Array.isArray lets us check if something is an array regardless of context.

Using object() Method

We can create an object function to create a function that returns an instance of a constructor.

This suggestion is from Douglas Crockford and it makes setting the prototype easy since it takes the prototype object.

For instance, we can write:

function object(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

We create the object function with the proto property.

Then we set that as the F ‘s prototype .

And we return an instance of F .

This is different from Object.create since we have an instance of the constructor.

We can then use it by writing:

const obj = object({
  foo: 1
});

Then obj would inherit from the object we passed in.

Mix Prototypal Inheritance and Copying Properties

We can mix prototypical inheritance with copying properties.

To do that, we can expand the object function by writing:

function object(proto, moreProps) {
  function F() {}
  F.prototype = proto;
  const f = new F();
  return {
    f,
    ...moreProps
  };
}

const obj = object({
  foo: 1
}, {
  bar: 2
});

We add a moreProps parameter to the object function, which lets us add more properties by passing in the 2nd argument to it.

moreProps is spread into the object we return so that we return the new object.

Therefore, obj is {f: F, bar: 2} since we inherit from F.prototype and moreProps .

Conclusion

We can mix prototypical inheritance with copying properties to get all the properties we want in an object.

A deep copy can be done by recursively copying properties from the source object to the target.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Inheritance

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at inheritance.

Prototype Chaining

An object has a prototype chain.

The inherit properties from them.

For instance, we can create a chain of constructors by writing:

function Shape() {}
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function() {
  return this.name;
};

function Square() {}
Square.prototype = new Shape();
Square.prototype.constructor = Square;
Square.prototype.name = 'Square';

We have the Shape constructor with some prototype properties.

Then we created a Square constructor with the prototype set to the Shape constructor.

Then we set the constructor to the Square to make instanceof sqaure returns true if we use instanceof with a Square instance.

If we want to call the parent constructor within the child constructor to populate its properties, we can change the code.

We can write:

function Shape(name) {
  this.name = name;
}
Shape.prototype.toString = function() {
  return this.name;
};

function Square(name, length) {
  Shape.call(this, name);
  this.length = length
}
Square.prototype = Object.create(Shape.prototype);
Square.prototype.constructor = Square;

to create the Shape constructor with the name property.

Then we have the Square property with the name and length parameters.

We call the Shape constructor with call and we set the first argument to this so it’s called with the Square constructor as this .

name is what we pass into the Shape constructor.

To create the prototype, we call Object.create to inherit the properties from Shape.prototype .

And we set the constructor the same way so that instanceof still reports correctly.

Class Syntax

We can make this easier with the class syntax.

With it, we can rewrite:

function Shape(name) {
  this.name = name;
}
Shape.prototype.toString = function() {
  return this.name;
};

function Square(name, length) {
  Shape.call(this, name);
  this.length = length
}
Square.prototype = Object.create(Shape.prototype);
Square.prototype.constructor = Square;

to:

class Shape {
  constructor(name) {
    this.name = name;
  }

  toString() {
    return this.name;
  };
}

class Square extends Shape {
  constructor(name, length) {
    super(name);
    this.length = length
  }
}

We use the extends keyword to inherit the properties from Shape ‘s properties in the Square class.

super(name) is the same as Shape.call(this, name); .

Prototype

We can get the prototypes in the chain with the __proto__ property.

For instance, we can write:

console.log(square.__proto__);

then we get the Shape constructor.

We can get the __proto__ of the __proto__ property, so we can get:

console.log(square.__proto__.__proto__);

then we get the Shape.prototype object.

Then if we call it again:

console.log(square.__proto__.__proto__.__proto___);

We get the Object.prototype object.

We can confirm this by writing:

console.log(square.__proto__ === Square.prototype);
console.log(square.__proto__.__proto__ === Shape.prototype);
console.log(square.__proto__.__proto__.__proto__  === Object.prototype);

Then they all log true .

Objects Inherit from Objects

Objects can inherit from other objects directly.

To do that, we use the Object.create method to return an object that returns an object with a given prototype object.

For instance, we can write:

const proto = {
  foo: 1
};
const obj = Object.create(proto);

Then we can check the prototype of obj with the __proto__ property:

console.log(obj.__proto__ === proto);

then that logs true .

Conclusion

We can create objects that inherit from other objects.

Also, we can check the prototype chain of an object to see what’s inherited from.

Then class syntax makes inheritance with constructors much more easily.