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.

Categories
TypeScript

Ways to Write Better JavaScript — Use TypeScript

The way we write JavaScript can always be improved. As the language evolves and more convenient features are added, we can also improve by using new features that are useful.

In this article, we’ll look at some ways to write better JavaScript by using TypeScript.

Use TypeScript

TypeScript is a natural extension to JavaScript. It lets us write JavaScript code that’s type-safe. Therefore, we can use it to prevent lots of data type errors that would otherwise occur if we didn’t use TypeScript.

Also, it provides autocomplete for things that otherwise wouldn’t have the autocomplete feature like many libraries. They use TypeScript type definitions to provide autocomplete for text editors and IDEs to make our lives easier.

TypeScript doesn’t turn JavaScript into a different language. All it does is add type checking to JavaScript by various type-checking features.

Therefore, all the knowledge that is used for JavaScript all apply to TypeScript.

For instance, we can create a function with TypeScript type annotations as follows:

const foo = (num: number): number => {
  return num + 1;
}

In the code above, we have the foo function with a num parameter that’s set to the type number . We also set the return type to number by specifying the type after the : .

Then if we call the function with a number, the TypeScript compiler will accept the code.

Otherwise, it’ll reject the code and won’t build the code. This is good because JavaScript doesn’t stop this from happening.

Interfaces

TypeScript provides us interfaces so that we know the structure of an object without logging the object or checking the value otherwise.

For instance, we can create one as follows:

interface Person {
    name: string;
    age: number;
}

Then we can use it as follows:

const person: Person = { name: 'jane', age: 10 }

If we miss any of these properties, then we’ll get an error as the TypeScript compiler is looking for them.

We can also use it to enforce a class implementation as follows:

interface PersonInterface {
    name: string;
    age: number;
}

class Person implements PersonInterface {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

In the code above, we have both the name and age fields. If we skip any of them, then we’ll get an error from the TypeScript compiler.

If we want to embrace the dynamic typing nature of JavaScript, we can add dynamic index signatures to JavaScript. Also, there’re union and intersection types to combine different types into one.

For instance, we can use it as follows:

interface PersonInterface {
    name: string;
    age: number;
    [key: string]: any;
}

In the code above, we have:

[key: string]: any;

to allow dynamic keys in anything that implements PersonInterface that has anything as a value.

Then we can have any property in addition to name and age in any class that implements PersonInterface or an object that’s cast to the PersonInterface type.

Union types let us join different types together. For instance, we can use it as follows:

interface Person {
    name: string;
    age: number;
}

interface Employee {
    employeeId: string;
}

const staff: Person | Employee = {
    name: 'jane',
    age: 10,
    employeeId: 'abc'
}

In the code above, the | is the union type operator. It lets us combine both the keys from both interfaces into one without creating a new type.

Another good thing about TypeScript is nullable properties. We can make properties optional with the ? operator.

For instance, we can use the following code:

interface Person {
    name: string;
    age?: number;
}

With the ? operator, we made age an optional property.

typeof Operator

Another great feature of TypeScript is the typeof operator, which lets us specify that something has the same type as something else.

For instance, we can use it as follows:

const person = {
    name: 'jane',
    age: 10,
}

const person2: typeof person = {
    name: 'john',
    age: 11,
}

In the code above, we have the person2 object, which has the same type as person since we specified that with typeof person . Then person2 must have the name and age properties or we’ll get an error.

As we can see, we don’t need to specify any interfaces or classes explicitly to specify types. This is handy for getting the types of imported libraries that don’t come with type definitions.

Conclusion

With TypeScript, we made refactoring easy since it’s harder to break the existing code with the type and structure checks that it provides.

It also makes communication easier because we know the type and structure of our objects, classes, and return value of functions.