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.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Generators and Maps

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 generators and maps.

Generators

Generator functions let us create generator objects.

They run line by line, returns the result with yield , and then paused until it’s invoked again.

We can use them to write infinite loops since they’ll pause when they aren’t needed.

For instance, we can create a generator function by writing:

function* generatorFunc() {
  yield 1;
  yield 2;
  yield 3;
}

We have the generatorFunc generator function.

It’s indicated by the function* keyword.

The yield keyword lets us return the value and the pause the value.

Then we can use it by creating a generator with it and then call next :

const gen = generatorFunc();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

Then we get:

{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: undefined, done: true}

next returns an object with the value and done properties.

value has value that’s returned and done indicates whether there’s any other value to return.

done is true indicates there’s no other value to return.

If we use yield without an argument, then we can pass a value into next and get the value.

For instance, we can write:

function* generatorFunc() {
  console.log(yield);
  console.log(yield);
  console.log(yield);
}

const gen = generatorFunc();
console.log(gen.next());
console.log(gen.next('foo'));
console.log(gen.next('bar'));
console.log(gen.next('baz'));

We console log yield in the generatorFunc then when we call next with an argument, it’ll be logged.

The first one should be called with nothing since it’ll just start the generator function instead of running the body.

It’ll return:

{"done": false,"value": undefined}

Generator objects conform to the iterator contract.

So we can get the Symbol.iterator method with it.

If we have:

console.log(gen[Symbol.iterator]() === gen);

we log true .

Iterating Over Generators

Generators are iterators.

Anything that supports iterables can be used to iterate over generators.

For instance, we can use the for-of loop:

function* genFunc() {
  yield 1;
  yield 2;
}

for (const i of genFunc()) {
  console.log(i)
}

Then we get:

1
2

We can also use the destructuring syntax with generators.

For instance, we can write:

function* genFunc() {
  yield 1;
  yield 2;
}

const [x, y] = genFunc();

Then x is 1 and y is 2.

Collections

JavaScript provides us with various kinds of collections like Map , WeakMap , Set , and WeakSet .

Now we don’t have to create hacks to create these data structures.

Map

Map allows us to store key-value pairs.

They let us have fast access to values.

For instance, we can write:

const m = new Map();
m.set('first', 1);

Then we can get it by writing:

console.log(m.get('first'));

and get the value by the key.

We can remove the entry with the given keys with the delete method:

m.delete('first');

And we can set the size with the size property:

console.log(m.size);

We can also create a map with an array of key-value arrays:

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

Conclusion

We can use generators to create iterables.

Also, we can use maps to store key-value pairs.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Class 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 JavaScript subclasses, mixins, and multiple inheritance.

Subclassing

We can create subclasses from a JavaScript class.

For instance, we can write:

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

  speak() {
    console.log(`${this.name} speaks`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} woofs`);
  }
}

const mary = new Dog('mary');

to create the Animal class.

The speak method is in the Animal class.

We created a subclass of Animal called Dog which also has the speak method.

If we create a Dog instance, then the speak method in Dog will be used.

So if we write:

mary.speak();

We see:

mary woofs

There are several ways to access the superclass of a class.

We can call super() to call the superclass and pass in the arguments of the parent constructor.

Also, we can call super.<parentClassMethod> to call a parent class’s method.

super.<parentClassProp> lets us access the parent class properties.

So if we have:

class Parent {}

class Child extends Parent {
  constructor(name) {
    this.name = name;
  }
}

const child = new Child('james')

We’ll get the error:

Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

this is because we need to call super first to invoke the parent constructor before we can set the properties of this .

This can be done implicitly if we omit the constructor in Child .

If we have:

class Parent {}

class Child extends Parent {}

const child = new Child()

Then we won’t get an error since a default constructor is provided.

The default is:

constructor(...args) {
  super(...args);
}

Mixins

JavaScript only supports single inheritance, so we can’t use the standard JavaScript syntax to inherit properties from multiple classes.

Instead, we have to compose multiple classes into one with functions.

We can write:

class Person {}

const BackgroundCheck = Tools => class extends Tools {
  check() {}
};

const Onboard = Tools => class extends Tools {
  createEmail() {}
};

class Employee extends BackgroundCheck(Onboard(Person)) {}

We have the BackgroundCheck class that returns a subclass of the Tools class.

So Person is Tools in the Onboard function,.

And Onboard(Person) is Tools in the BackgroundCheck function.

This way, we return a class that has both the check and createEmail methods and we can use the resulting class to create the Employee subclass.

Conclusion

We can create subclasses with the extends keyword.

Also, we can create functions that returns a class and compose them so that we can inherit from multiple classes.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Class Basics

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 the basics of classes.

Classes

ES6 brings many useful features that helps us create objects more easily.

If we’re familiar with class-based languages like Java, then JavaScript classes should look familiar.

But JavaScript classes are syntactic sugar over its prototypical inheritance model.

Before ES6, we can only create constructors.

function Person(name) {
  if (!(this instanceof Person)) {
    throw new Error("Person is a constructor");
  }
  this.name = name;
};

Person.prototype.eat = function() {
  //...
};

We have the Person constrictor with the this.name instance property.

And Person.prototype.eat is an instance method.

Then we can create a child constructor that inherits from the Person constructor by writing:

function Employee(name, job) {
  if (!(this instanceof Employee)) {
    throw new Error("Employee is a constructor");
  }
  Person.call(this, name);
  this.job = job;
};

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.work = function() {
  // ...
};

We create the Employee constructor that calls the Person constructor with the name argument.

Then we set this.job to job .

And then we create the prototype of Employee with Object.create .

We inherit the properties of the Person.prototype since we used the Object.create method with Person.prototype as the argument.

Then we can create our methods and set the constructor to Employee so using instanceof on an Employee instance would return true .

With the class syntax, we can make our lives much easier:

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

  eat() {
    //...
  }
}

class Employee extends Person {
  constructor(name, job) {
    super(name);
    this.job = job;
  }

  work() {
    //...
  }
}

We create the Person class.

The constructor method has what was in the constructor function itself.

The prototype methods are in the class.

To create a child class, we use the extends keyword and then call super to call the Person constructor.

Then we have the work instance method.

The eat method is inherited from the Person instance also.

Defining Classes

We can define classes with the class keyword.

It’s very similar to class-based languages like Java or C#.

For instance, we can write:

class Car {
  constructor(model, year) {
    this.model = model;
    this.year = year;
  }
}

The Car class is still a function.

So if we log:

console.log(typeof Car);

we see 'function' .

Classes are different from normal functions.

They aren’t hoisted.

Normal functions can be declared anywhere in the scope and it’ll be available.

But classes can only be accessed if they’re defined.

So we can’t write:

const toyota = new Car();
class Car {}

because we’ll get the reference error:

Uncaught ReferenceError: Cannot access 'Car' before initialization

We can’t use commas while separating the members of a class, but we can add semicolons.

So we can’t write:

class C {
  member,
  method() {}
}

we’ll get a syntax error.

But we can write:

class C {
  method2() {};
}

Conclusion

The class syntax makes creating constructors easier.

We can do inheritance with extends and super .