Categories
TypeScript

JavaScript Object Features in TypeScript — Inheritance and Classes

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at accessing overridden prototype methods and the class syntax.

Accessing Overridden Prototype Methods

Even if we override methods in a constructor function that inherits from a parent constructor, we can still access the parent constructor’s implementation of the constructor.

For instance, if we have:

const Animal = function(name) {
  this.name = name;
};
Animal.prototype.toString = function() {
  return `name: ${this.name}`;
};

const Dog = function(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
};
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
Dog.prototype.toString = function() {
  return `name: ${this.name}, breed: ${this.breed}`;
};

Then we can call the parent constructor’s method to replace part of Dog‘s prototype’s toString method by writing:

const Animal = function(name) {
  this.name = name;
};

Animal.prototype.toString = function() {
  return `name: ${this.name}`;
};

const Dog = function(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
};

Object.setPrototypeOf(Dog.prototype, Animal.prototype);

Dog.prototype.toString = function() {
  const name = Animal.prototype.toString.call(this, this.name);
  return `${name}, breed: ${this.breed}`;
};

We reused the Animal.prototype ‘s toString method to form part of Dog.prototype ‘s toString method.

This way, we don’t have to repeat any code.

Defining Static Properties and Methods

We can define static methods and properties as properties on the function itself.

We can do that because functions are just ordinary objects.

For instance, we can write:

const Animal = function(name) {
  this.name = name;
};
Animal.type = 'animal';

console.log(Animal.type);

Then we defined a static type property on Animal.

We can then access it by referencing Animal.type .

JavaScript Classes

JavaScript class is a syntax that eases the transition from other popular programming languages.

However, behind the scenes, it’s just a combination of constructors and prototypes.

There are some differences between a JavaScript class and other class-based languages like Java.

All instance variables are public.

Also, we can return any object we want in the constructor and we return that object when we invoke the class with new .

Like constructor functions, it’s invoked with the new keyword.

For instance, we can define a class by writing:

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

  toString() {
    return `name: ${this.name}`;
  }
}

We have an Animal constructor that returns an Animal instance, with the name property and the toString method.

Then if we create an Animal instance by writing:

const animal = new Animal('joe');

If we look at the content of animal , we see the name property in animal .

In the __proto__ property of it, we see the toString method.

To create a subclass that inherits from a parent class, we use the extends keyword.

For instance, we can write:

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

  toString() {
    return `name: ${this.name}`;
  }
}

class Dog extends Animal {}

We create a subclass of Animal which inherits from the Animal parent class.

Then when we write:

const dog = new Dog('joe');

and call toString:

console.log(dog.toString());

We get:

name: joe

from the console log.

The toString method is inherited from the Animal class.

To call the parent constructor, we use the super keyword.

And if we want to call a parent constructor’s methods, we use the same keyword followed by a dot plus the method name.

For instance, if we want to add constructor and a toString method to Dog , we can write:

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  toString() {
    const name = super.toString();
    return `${name} breed: ${this.breed}`;
  }
}

Given the Animal class we have before, we can call Animal‘s toString by writing calling super.toString(); .

We call the constructor of the parent by using the super keyword as we did in Dog ‘s constructor .

The super call must be before anything else in the constructor body.

The extends keyword indicates that our Dog class inherits members from the Animal class.

So when we create a new Dog instance by writing:

const dog = new Dog('joe', 'lab');
console.log(dog.toString());

and then call toString on it, we get:

name: joe breed: lab

from the console log.

Conclusion

The class syntax makes transitioning from other object-oriented languages easier.

Also, it cleans up the constructor code by putting them in one neat package.

We can also call the parent constructor in a much cleaner fashion.

Categories
TypeScript

JavaScript Object Features in TypeScript — Inheritance

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at some features of JavaScript that should be used in TypeScript projects, including object inheritance.

JavaScript Object Inheritance

JavaScript objects can have a link to another object.

The object is called the prototype.

Objects inherit properties and methods from the prototype.

This allows us to implement complex features to be defined once and used consistently.

When we create an object using the literal syntax, then its prototype is Object .

Object provides some basic features they inherit like the toString method that returns the string representation of an object.

For instance, if we have the following object:

const obj = {
  foo: 1,
  bar: "baz"
};

console.log(obj.toString());

If we define obj and run:

console.log(obj.toString());

We get [object Object] logged.

That’s what the default toString method does in the Object ‘s prototype.

Object’s Prototype

Object is the prototype for most objects.

We can also use some methods directly.

There’s the getPrototypeOf , setPrototypeOf , and getOwnPropertyNames methods.

getPrototypeOf returns the prototype of an object.

For instance, we can write:

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

let objPrototype = Object.getPrototypeOf(obj);

Then we get that objPrototype is Object .

proto is the prototype of obj since we passed it into Object.create .

Creating Custom Prototypes

Also, we can use the setPrototypeOf method to set the prototype of an existing object.

For instance, we can write:

const proto = {};
const obj = {
  foo: 1
};

Object.setPrototypeOf(obj, proto);
const objProto = Object.getPrototypeOf(obj);
console.log(proto === objProto);

The code above has 2 objects, obj and proto .

Then we call setPrototype with obj and proto to set proto as the prototype of obj .

In the last line, we check is proto refers to the same object as the prototype object returned from getPrototypeOf .

Console log returns true so we know that the prototype of obj is actually proto .

Constructor Functions

A constructor function is used to create a new object, configure its properties, and assign its prototype.

For instance, we can create one by writing:

let Dog = function(name, breed) {
  this.name = name;
  this.breed = breed;
};

Dog.prototype.toString = function() {
  return `name: ${this.name}, breed: ${this.breed}`;
};

We have a Dog constructor function with the name and breed fields.

Then we created an instance method called toString to return the string representation of a Dog instance.

Then we can create a Dog instance and call toString as follows:

console.log(new Dog('joe', 'labrador').toString());

And we get:

'name: joe, breed: labrador'

in the console log output.

We invoked our constructor function with the new keyword.

The arguments are passed in and set as properties of this in the constructor function.

The constructor function configures the object own properties using this , which is set to a new object.

The prototype of the object returned by the constructor has its __proto__ set to Dog.prototype .

So toString is from the Dog instance’s prototype property.

Chaining Constructor Functions

We can chain the constrictor functions of more than one function by writing:

const Animal = function(name) {
  this.name = name;
};

Animal.prototype.toString = function() {
  return `toString: name: ${this.name}`;
};

const Dog = function(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
};

Object.setPrototypeOf(Dog.prototype, Animal.prototype);
Dog.prototype.bark = function() {
  return "woof";
};

const dog = new Dog("joe", "labrador");
console.log(dog.toString());
console.log(dog.bark());

We have a parent Animal constructor which has a toString method in its prototype.

Then we added a Dog constructor, which inherits from the Animal constructor.

In the Dog constructor, we call the call method on Animal to call the parent Animal constructor but with this set to the Dog constructor.

Dog inherits from Animal by calling setPrototypeOf with the child constructor in the first argument and the parent as the 2nd.

Then we add another method to the prototype called bark to Dog ‘s prototype, so we can only be called that on a Dog instance.

toString is available to both Dog and Animal .

Conclusion

JavaScript’s inheritance system isn’t like the other object-oriented language’s inheritance system.

JavaScript has constructor functions and prototypes rather than classes.

Objects can inherit from other objects directly. Constructors can inherit from other constructors.

Categories
TypeScript

How to Create Projects with the TypeScript Compiler

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at how to create a project with the TypeScript compiler and write some code.

We also look at how to install packages, what the version number means.

Also, we look at the project structure of a typical TypeScript project.

Getting Started

We can create a folder by creating a project folder.

Then inside it, we run:

npm init --yes

to create a package.json file.

Then we install Node packages by running:

npm install --save-dev typescript
npm install --save-dev tsc-watch

to install the TypeScript compiler and a program to reload our program as it changes respectively.

Everything will be installed into the node_modules folder.

Then we add a tsconfig.json to add our compiler options and add:

{
  "compilerOptions": {
    "target": "es2018",
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Then our built code will be in the dist folder and our code should be in the src folder.

The target is the JavaScript version that our build artifacts will target.

Writing Code

We can then create an index.ts file in the src folder and start writing some code.

For instance, we can write:

const print = (msg: string): void => {
  console.log(msg);
};

print("hello");

We created a print function that takes a msg parameter.

The word string after the colon is the parameter type.

void is the return type of the function. It means that our function returns nothing.

Project Structure

A TypeScript project folder consists of the following items:

  • dist — the folder that has the output from the compiler
  • node_modules — the folder containing the packages that the app and dev tools require
  • src — the folder containing the source code files that will be compiled by the TypeScript compiler
  • package.json — the folder containing the top-level package dependencies for a project
  • package.json — a file containing the complete list of package dependencies of a project
  • tsconfig.json — a file having the config settings of the TypeScript compiler

Node Package Manager

Most TypeScript needs dependencies from the outside.

NPM has the most TypeScrtipt packages for JavaScript and TypeScript project.

Since TypeScript is a superset of JavaScript, we can use any JavaScript package in TypeScript code.

NPM follows the chain of dependencies to work out which version of each package is required and download everything automatically.

Anything that’s saved with the --save-dev or -D option is saved in the devDependencies section of package.json .

Global and Local Packages

Package managers can install packages so they’re specific to a single project.

This is the default option.

Some packages that are needed to run on the computer can be installed as global packages.

For instance, the TypeScript compiler would be a global package since we would need it on the whole project.

Node packages mostly use semantic versioning.

There are also a few symbols to denote the package version in different ways.

For instance, we can denote the version number exactly with 1.0.0 .

The * accepts any version of the package to be installed.

>1.0.0 or >=1.0.0 means that we accept any version of a package that’s greater than or greater than or equal to a given version.

<1.0.0 or <=1.0.0 means that we accept a version that’s less than or less or equal to the given version.

~1.0.0 means that we accept a version to be install even if the patch level number doesn’t match.

So 1.0.1 is considered equivalent to 1.0.0 is a ~ is prefixed to a version number.

^1.0.0 will accept any version even if a minor release number or the patch number doesn’t match.

So 1.0.0 and 1.1.1 are considered equivalent if a ^ is prefixed to a version number.

Conclusion

Node packages go back semantic versioning. We can specify whether we want an exact match or not in package.json .

We can add the TypeScript compiler to get started with a TypeScript project.

Once we have that, we can add TypeScript-specific code to our projects.

Also, we can specify the build target version of our TypeScript project.

Categories
TypeScript

JavaScript Object Features in TypeScript — Methods and this

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at some features of JavaScript that should be used in TypeScript projects, including methods and this.

Defining Methods

Methods are properties of objects that are function valued.

They’re also part of a class, where we can call them after we instantiate the class.

For instance, we can define methods in objects by writing:

let obj = {
  greet() {
    console.log("hello");
  }
};

In the code above, we defined an obj object that has the foo method.

We can then call it by running:

obj.greet();

Now we get 'hello' displayed on the console log.

The this Keyword

this is a confusing keyword for many JavaScript developers.

This is because this can refer to anything depending on its location.

If it’s in an object, then this refers to the object.

If it’s in a class, the this refers to the class that it’s in.

If it’s in a traditional function, then this refers to the function.

For instance, if we have:

let obj = {
  name: "joe",
  greet() {
    console.log(`hello ${this.name}`);
  }
};

obj.greet();

Since this refers to obj in the greet method.

Then this.name refers to 'joe' .

Therefore, we see ‘hello joe’ in the console log.

If it’s in class, then this would be the class. For instance, if we have:

class Foo {
  constructor() {
    this.name = "joe";
  }

  greet() {
    console.log(`hello ${this.name}`);
  }
}

new Foo().greet();

Then we have this.name set to 'joe' again since we did that in the constructor.

So, we get the same result when we run:

new Foo().greet();

this Keyword in Stand-Alone Functions

We can use the this keyword in a traditional function.

For instance, if we write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

Then we can set the value of this by using the call method and call the function:

greet.call({ greeting: "hello" }, "joe");

We change the value of this to { greeting: “hello” } by passing it into the first argument of the call method.

A value assigned names without using the let , const or var keyword is assigned to the global object.

For instance, we can write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

greeting = "hello";
greet("joe");

Then we see that this.greeting is 'hello' since greeting is a property of the global object.

this is the global object as we can see.

Changing the Behavior of this

The call method can change the behavior of this .

Also, we can use the bind method to return a function with a different value of this .

For instance, we can write:

function greet(msg) {
  console.log(`${this.greeting}, ${msg}`);
}

const helloGreet = greet.bind({ greeting: "hello" });
helloGreet("joe");

By calling the bind function, we change this to an object, we passed in as an argument.

Then we can call the returned function and see 'hello, joe’ in the console output.

bind and call are both available to traditional functions only.

Arrow Functions

Arrow functions don’t work the same way as regular functions.

They don’t have their own this value and inherit the closet value of this tey can find when they’re run.

For instance, if we have:

let obj = {
  greeting: "Hi",
  getGreeter() {
    return msg => console.log(`${this.greeting}, ${msg}`);
  }
};

const greeting = obj.getGreeter()("joe");

Then when we call getGreeter , we get the arrow function returned.

this would be the object since the value of this inside getGreeter would be obj .

The second parentheses call the returned function with 'joe' .

Arrow functions don’t have their own value of this , so it takes the value of this from getGreeter .

Conclusion

We can add methods to objects and classes and use them in our TypeScript code.

The value of this varies depending on location. To make sure our code refers to the right value of this , we’ve to know what this takes on at the location that it’s in.

The value of this can be overridden with bind and call , which are available to traditional functions.

Categories
TypeScript

JavaScript Object Features that we can use in TypeScript Code

TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.

However, not everyone knows how it actually works.

In this article, we’ll look at more features that can be used within our TypeScript code.

Working with Objects

JavaScript objects are collections of properties, each of which has a name and value.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith"
};

We have a person object that have firstName and lastName properties.

Then we can access the properties by using the dot notation as follows:

const firstName = person.firstName;

Adding, Changing, and Deleting Object Properties

We can add properties to an object dynamically.

They can also be updated and removed on the fly.

To add a property to an object, we can write:

person.age = 20;

Given that we have the persons object, we can add a property to by using the dot notation and assigning a value to it.

Likewise, we can change it by using the same dot notation as we did above.

Deleting object properties can be done with the delete operator.

For instance, we can use the delete operator as follows:

delete person.age;

Then the age property will be removed from the person object.

Guarding Against Undefined Objects and Properties

We got to check our code against undefined object properties.

To do that, we can use the || operator.

For instance, we can write:

let price = apple.price || 0;

to see if the apple.price is undefined . If it is, then we set 0 to price.

Otherwise, we set apple.price to price .

This also works with null .

Using the Spread and Rest Operators on Objects

The spread operator can also be used with objects.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith"
};

Then we can make a shallow clone of an object by using the spread operator.

For instance, we can write:

let clone = { ...person };

All the own properties will be copied over.

We can also add additional properties to the cloned object by writing:

let clone = { ...person, gender: 'female' };

We can also replace an existing property with new ones in the cloned object.

If we have the person object, we can create a clone of it and replace a property of the clone by writing:

let person = {
  firstName: "jane",
  lastName: "smith"
};

let clone = { ...person, firstName: "may" };

Now we replaced 'jane' with 'may' in the clone object while keeping the person object as-is.

We can also use the spread operator on the left side to assign properties to variables.

The spread operator will have an object that doesn’t have variables assigned to them in an object.

For instance, we can write:

let { firstName, ...props } = person;

This will assign the value of person.firstName to firstName and the remaining properties will be assigned to props .

Therefore, props will have { lastName: “smith” } as its value.

Getters and Setters

Objects can have getters and setters.

To define them, we use the get and set keywords to define getters and setters respectively.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith",
  get name() {
    return `${this.firstName} ${this.lastName}`;
  },
};

to define a getter called name . Then we can access the getter’s return value by writing:

const name = person.name;

To define a setter, we can use the set keyword to let us set the value the way we want.

For instance, we can write:

let person = {
  firstName: "jane",
  lastName: "smith",
  _age: 20,
  get name() {
    return `${this.firstName} ${this.lastName}`;
  },
  set age(age) {
    this._age = age;
  }
};

We can then set age by running:

person.age = 26

Which will make person._age have the value 26.

Conclusion

JavaScript objects are just a collections of key-value pairs.

We can use getters and setters to let us get and set the values of an object.

Also, we can clone an object with the spread operator and assign values to variables.

Finally, we can add, update, and delete properties from objects dynamically.