Categories
TypeScript

JavaScript Object Features in TypeScript — Static Methods, Generators, and Collections

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 define static methods and defining, using iterators and generators, and accessing collections.

Defining Static Methods

Static methods are methods that are available for all instances of the class.

They’re accessed through the class rather than the object it creates.

For instance, we can create a static method by writing:

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

  static getType() {
    return 'animal';
  }
}

Then we can write the following code to call getType:

const type = Animal.getType();

We called getType on Animal rather than an instance of it.

Iterators and Generators

Iterators are objects that return a sequence of values.

They’re used with collections and they can be used without them.

An iterator defines a function named next that returns an object with value and done properties.

The value property returns the next value in the sequence and the done property is set to true when the sequence finishes.

Generator functions are functions that return iterators.

We use the yield keyword to return the next item in the sequence.

For instance, we can write:

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

The function* keyword returns a generator that we can use to return 1, 2, and 3 in sequence.

Then we can write the code to return a generator, that we can use to return the next value:

const generator = gen();
let done = false;
while (!done) {
  const next = generator.next();
  console.log(next.value);
  done = next.done;
}

Calling gen returns a generator.

Then we use it to get the items with the next method until done is true .

The JavaScript runtime creates the next function and runs the generator function until it reaches the yield keyword.

yield provides the next value in the sequence.

We can use generators with the spread operator.

For instance, we can write:

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

const generator = gen();
const arr = [...generator];

This will spread all the values that are after yield into an array, so we get:

[1, 2, 3]

as the value of arr .

Defining Iterable Objects

We can define our own iterable objects in addition to generators.

For instance, we can have a generator method in our object.

We can write:

const obj = {
  * gen() {
    yield 1;
    yield 2;
    yield 3;
  }
}

const generator = obj.gen();

We just put the gen generator as a method of obj and the call it.

However, it’s a bit awkward to use.

We can make this cleaner by using the Symbol.iterator symbol as the name of the method instead:

const obj = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

This will make obj an iterable object, then we can write:

const arr = [...obj];

to spread the returned values.

And we get [1, 2, 3] as the value of arr .

JavaScript Collections

JavaScript objects are very flexible. We can get the keys and values of an object to do what we like with it.

With Object.keys , we can get the keys of an object as an array.

And with Object.values , we can get the values of an object as an array.

We can use Object.keys as follows:

const obj = {
  a: 1,
  b: 2,
  c: 3
};

for (const key of Object.keys(obj)) {
  console.log(key);
}

Then we get a , b and c logged.

Object.keys only return the own string keys of an object.

Likewise, we can return an array of values with Object.values .

We can write:

const obj = {
  a: 1,
  b: 2,
  c: 3
};

for (const key of Object.values(obj)) {
  console.log(key);
}

Then we get the numbers 1, 2, and 3 logged in the console.

It also returns the own values of an object for properties with string keys.

Conclusion

We can use generator functions to return a generator to return something in sequence.

To access the keys and values of objects, we can use Object.keys and Object.values respectively.

Static methods can be defined on a class and be available by accessing them directly from the class.

Categories
TypeScript

How Does TypeScript Work?

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 TypeScript makes JavaScript projects better.

What is TypeScript?

TypeScript is a superset of the JavaScript language that lets us produce safe and predictable code.

The code that it produces can be run in any JavaScript runtime.

It provides us with type checks which JavaScript lacks.

Should We Use TypeScript?

TypeScript isn’t the solution to all the problems in JavaScript projects.

We should know what TypeScript is good for.

TypeScript is focused on developer productivity through static typing.

It makes JavaScript type system easier to work with.

It provides us access control keywords and enhances the class constructor syntax.

We can write TypeScript code with JavaScript or TypeScript-exclusive syntax.

All the code goes through the TypeScript compiler, which is built to JavaScript.

The combination of JavaScript and TypeScript provides us with a flexible combination of both.

We can apply TypeScript features selectively.

Limitations of the Productivity Features

Writing TypeScript is mostly writing JavaScript with some extra features.

TypeScript enhances the type system of JavaScript and provides flexible types just like JavaScript.

If we understand JavaScript’s type system, then we understand why TypeScript’s type system is the way it is.

TypeScript provides us not only with a static type system, but also more dynamic types like union types, intersection types, literal types, and more.

JavaScript Version Features

Older JavaScript runtimes don’t support many modern features that are universally supported.

Therefore, we need a compiler like a TypeScript compiler to transform code to the JavaScript runtimes that we’re targeting.

The compiler does a good job of most transforming to JavaScript code that’s more compatible.

Data Types of JavaScript

To understand TypeScript, we’ve to understand the data types of JavaScript.

We won’t be very productive with TypeScript id we don’t understand the type system of JavaScript.

Confusion by JavaScript

The foundation of data storage in JavaScript is variables.

We can declare variables with let or const .

let variables can be reassigned while const ones can’t.

JavaScript Types

JavaScript has a clear type of system. And the rules for it are applied consistently throughout the language.

The most basic JavaScript data types are primitive values and the object compound type.

The primitives are number, string, boolean, symbol, null, undefined.

object is a compound type.

number is a data type used for representing all numeric values. JavaScript doesn’t distinguish between integer and floating-point values.

string is a type used for representing text data.

boolean can either be true or false

symbol represents unique constant values, like keys in an object.

null represents a nonexistent reference.

undefined is used by a variable that’s defined but hasn’t been assigned a value.

object is the type used to represent compound values, formed bu individual properties and values.

Working with Primitive Data Types

In JavaScript, we don’t write out the data type explicitly.

It figures out the type of its variable on its own when we assign it a value.

For instance, we write:

let foo = "bar";

to declare a string variable.

If we need to check the type, we write:

typeof foo

to return the type of the variable.

typeof is an operator that identifies the value type and returns 'string' .

typeof null returns 'object' , which isn’t the correct behavior, but it’s already implemented in all runtimes and code, so we can’t use typeof to check for null .

Type Coercion

JavaScript does data type coercion of its variables when it does certain operations.

It produces consistent results, we just have to know how it works.

If we use the == operator to compare objects, then type coercion will be done before comparison operations are done.

For instance, we may have:

let applePrice = 1;  
let orangePrice = '1';  
if (applePrice == orangePrice) {  
  //...  
}

Then both will be converted to numbers before comparison.

When we write:

let totalPrice = applePrice + orangePrice;

then both will be converted to strings before concatenation, which is probably not what we want.

Avoiding Unintentional Type Coercion

To make our lives easier, we should take steps to avoid unintentional data type coercion.

To do that, we can use the === operator for comparisons and convert types explicitly first before doing concatenation.

Conclusion

TypeScript works by adding enhanced syntax to JavaScript and then transforming it to JavaScript after the TypeScript compiler does its own checks.

It doesn’t change JavaScript’s type system. Instead, it adds more checks to it.

Categories
TypeScript

Setting Up Our TypeScript Project

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.

Installing Packages

We can install packages by using some npm commands.

npm install performs local install of the packages specified in the package.json file.

npm install package@version performs a local install of a specific version of a package and updates the package.json to add the package in the dependencies section.

npm install --save-dev package@version performs a local install of a specific version of a package and updates the package.json file to add the package to the devDepdencies section.

devDepencies section adds the dependencies required for the development of the project but it’s not part of the app.

npm install --global package@version performs a global install of a specific version of a package.

npm list will list all the local packages and their dependencies

npm run runs one or more scripts defined in the package.

npx package runs the code contained in a package.

We should exclude the node_modules folder because it has a large number of files and may contain platform-specific components that don’t work when the project is checked out.

Instead, we should check out the project and let npm install create the node_modules folder.

To maintain consistency in the packages each time it’s checked out, a package.json file is created.

With it, version changes to packages we use for dependencies won’t be affected.

TypeScript Compiler Configuration File

The TypeScript compiler, tsc is responsible for compiling TypeScript files.

It’s the compiler that’s responsible for implementing TypeScript features like static types.

The result of that the compiler outputs would be pure JavaScript with all the TypeScript keywords and expressions removed.

A sample tsconfig.json may have the following:

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

They have the following meaning.

compilerOptions is the section that groups the settings that the compiler will use.

files specifies the files that will be compiled and overrides the behavior where the compiler searches for files to compile.

include is a setting used to select files for compilation by pattern. By default, .ts , .tsx , and .d.ts extensions will be selected.

exclude is a setting is used to exclude files from compilation by pattern.

compileOnSave , when it’s set to true , this setting is a hint to the code editor that it should run the compiler each time a file is saved.

This option isn’t supported bu all editors,

We can set these options is our project doesn’t have a typical TypeScript project structure.

The TypeScript package has type declaration for different versions of Javascript and for the APIs that are available in Node.js and browsers.

We can use tsc --listFiles and list the files that will be included in the build process.

Also, we can use npx to run tsc --listFiles without install tsc globally.

We can use npx tsc --listFiles to do that.

Compiling TypeScript Code

To compile the TypeScript code into a JavaScript bundle, we can use the tsc command to build our project.

This is from the TypeScript compiler that we installed in our package with:

npm install --save-dev typescript

Once we run tsc , we should have a dist folder by default with the built files.

The extension would be .js for JavaScript.

The file won’t have any TypeScript-specific expressions in them.

We shouldn’t edit this folder since it’ll be overwritten next time the compiler runs.

For the same reason, we shouldn’t check in the dist folder.

Compiler Errors

If we write our TypeScript code incorrectly, we may get compiler errors from the TypeScript compiler.

For instance, if we have the following function:

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

Then we would pass a string into it.

However, if we pass in a number to it as follows:

printMsg(5);

Then we would get a type mismatch error.

We would get:

Argument of type '100' is not assignable to parameter of type 'string'.ts(2345)

since we passed in a number.

This error is a benefit that TypeScript brings.

We get an error if the type of the argument doesn’t match what’s specified.

An editor that supports TypeScript would show us this error.

If we run tsc , we’ll see the same error as well if we haven’t corrected it.

However, we can disable emitting compiler errors with the noEmitOnError option set to true .

For instance, we can write:

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

Conclusion

We should see errors in our code if our code has type mismatches.

Also, we’ve to install packages with npm in except for the most trivial projects.

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.