All Articles

WIP: Modern JavaScript for C# Developers

Work in progress as of Jan 14.

JavaScript has significantly evolved over the past few years with new languages features introduced every year (2015-2020). If you have been in an out of JavaScript development, it can be intimidating to get back into it given the constant change in language features, toolsets, frameworks and libraries. Nonetheless, the JavaScript ecosystem is pushing modern application development forward with new techniques and ideas (e.g. React, Redux, GraphQL, etc.) It is well worth the time to keep up to date with the latest development.

Who should read this post

I wrote this post to catch up my colleagues at Microsoft on the latest the JavaScript world has to offer. My colleagues are primarily professional C# developers with occasional exposure and development experience with JavaScript and TypeScript. This article is aimed for any practicing C# developer looking to understand everything new in JavaScript. I will not be covering programming basics but will be offering a bit of a refresher on JavaScript - the difficult and new parts.

Variable Declaration in JavaScript

As a C# developer, you may be familiar with three ways to declare variables (int x, var x, dynamic x) where x is the variable name. The second and third ways are recent-ish additions to the C# language and are closest to the way JavaScript variables are declared. You may be familiar with var in JavaScript or using window.x to declare variables. Since ES6 (aka ES2015), there are now two new ways to declare variables: const and let. If you recall; JavaScript had a weird way to scope variables called function-scoped. (If you need a refresher, the next section is for you). let and const introduce a way to declare variables that are block-scoped. Block scoping is what C# developers are used to and should probably use. const is exactly like let but makes the variable immutable. It is equivalent to the const keyword in C#.

Variables declared with var are hoisted to the top of the function whereas variables declared with let and const remain in their block.

{
  const x = 42;
  let y = 100;
}

To avoid unintentional logical errors; use let and const exclusively.

Primitives and Types in JavaScript

JavaScript primitives are fairly straightforward but do have a distinction from C# primitives. JavaScript is loosely typed.

There are 7 primitive data types in JavaScript: string, number, bigint, boolean, null, undefined and a weird one called symbol. There is also of course object.

let mynumber = 1;
mynumber = 3.14;
mynumber = 123e-4;

JavaScript Numbers are always 64-bit floating point. A Number can also be NaN or Infinity.

In C#, all data types are objects. In JavaScript, ALMOST everything is an object. Primitives are immutable so JavaScript silently creates object wrappers String, Number and Boolean around these primitives. You’d think because JavaScript is creating wrapper objects that you’d be able to use them like a regular object: that is not true.

For example:

let s = "rami";
console.log(s.toUpperCase()); // RAMI
s.magic = () => {return 'magic'};
s.magic; //undefined

The other interesting thing is that functions and arrays are full-fledged objects. Yes. That means you can manipulate functions like objects: e.g. attaching new properties, etc. This is different from C#.

null and undefined can be confusing to understand. They are different and should not be used interchangeably. undefined is kind of a generic error; it describes the absence of a value. null means a value is blank or empty.

As a general rule; always use null and not undefined to represent the value of a variable. Always check your function arguments for undefined since arguments not passed into a parameter will be set to undefined.

BigInt <- New

BigInt is a new primitive added to the JavaScript language meant for use to store arbitrarily large integers. You define them by adding a n to the end of the number: let bigggint = 100n;.

Do not use BigInts: Safari and Edge do not support them.

Symbol <- New

Symbols were introduce in ES2015 and are now implemented in all major browsers. They’re kind of weird. Symbol() is used to create a unique identifier that can’t be changed and is kept private. You can use them to add new properties to objects or as a kind of unique id that isn’t visible to anyone. Unless you’re building JavaScript platform libnraries, they should rarely ever make an appearance in your code.

Truthy, Falsyness

== vs ===. JavaScript implements two kinds of equality comparisons and each value has an inherent boolean value. The former is called a loose equality comparison ==. The loose equality is used extensively in JavaScript code with valid reason so you should understand how they work. The latter is a strict equality comparison where no type coercion takes place: always use it when you need to compare exact values.

The following values are always falsy: false, '', null, undefined, NaN, 0.

Everything else is truthy including weird things like: '0', 'false', [], {}, function(){}.

You’ll see a lot of JavaScript code that checks if a value is truthy vs explicitly checking for true. This is correct; in fact, we have lint errors for direct comparisons.

if (myvar) {
  // myvar is truthy
}
else {
  // myvar could be false, 0, '', null, undefined or NaN
}

I find this table from Truthy and Falsy: When All is Not Equal in JavaScript on SitePoint to be incredible useful.

BigInt == Number vs BigInt === Number is also weird.

If you need a value to be converted to a real Boolean simply use a double negative e.g. !!falsey_value === false.

Looping in JavaScripts

JavaScript has multiple ways to execute for loops that are builtin to the language. Some may be more familiar than others.

First; there is the classical C-style for loop. This is pretty much identical to for loops in C#. for (initialization; condition; final expression) All three expressions are optional and you can omit any of them.

// initialization; condition; final expression
for (let i = 0; i < 4; i++) {
    console.log(i);
}

Second; there is a for…in loop statement to help you iterate over the properties of an object. Note: we will cover prototypical inheritance in a later section but know that a for…in loop will also iterate over all the attributes of an object including it’s prototype. If you want to iterate only over the objects own properties you will have to use getOwnPropertyNames() or check if an attribute is it’s own property using hasOwnProperty(). This looping method is mostly useful for debugging. Do not use this method for looping arrays; there is no ordering guarantee.

for (attr in obj){
  console.log(attr)
}

Third; the for…of loop. This is a new feature of ES2015 and it lets you iterate over iterable objects like arrays and strings.

for (let val of arr) {
  console.log(val)
}

You can also use for…of with the new array function entries(), also introduced in ES2015, to get the index.

for (let [i, val] of arr.entries()) {
  console.log(i, val);
}

Note: we sneakily introduced destructuring assignment up there. We’ll cover that in a later section.

Fourth; the Array object has a prototype called forEach which takes a callback that will be executed on each element. The callback function needs to at least take a parameter for the currentValue and can also take an optional index and array parameter: cb(currentValue, index, array). You can also specific the context of this as the last parameter. There is no way to break a forEach() loop without throwing an exception. If you need early termination; use any of the above for loops or use the prototype functions Array.prototype.every(), Array.prototype.some(), Array.prototype.find(), Array.prototype.findIndex() which allow the callback to return a falsy value to end the loop.

If you care about performance, the fastest loop is a reverse variation of the traditional for-loop. for…in is much slower than anything else so avoid it in performance critical scenarios.

Scoping in JavaScript

Scoping in JavaScript is a trap for most C# developers used to block scoping. There is one global scope that you should avoid polluting in general.

Local Scope = Function Scoping

The most common scope level in JavaScript is Function scope. All variables, objects and functions declared using varjs± in a function belong to that function’s scope.

function foo(){
    var bar ='foobar';
    console.log('inside function: ',bar);
}
// baronly exists inside foo() and can't be accessed outside of it.

It doesn’t matter where you declare a variable in a function; the variable declaration is always brought to the top of the function scope. This is called hoisting. Even the Function is hoisted to the top of the global or function scope. Only the declaration is hoisted and not the assignment.

foo("bar"); //calling foo is valid.

function foo(bar) {
  console.log(bar);
}

Lexical Scoping

JavaScript also follows lexical scoping. It means childrens’ scope have access to variables defined in the parent scope. Yes…

Here’s an example from DevTo:

function foo1(){
    var fruit1 = 'apple';
    const fruit2 = 'banana';
    let fruit3 = 'strawberry';
    function foo2(){
        console.log(fruit1);
        console.log(fruit2);
        console.log(fruit3);
    }
    foo2();
}

foo1();

//result:
//apple
//banana
//strawberry

Lexical scoping even applies to variables declared with let and const.

this Context

You must be confused by now because function scoping does not exist in C#. You must be wondering how it is possible that these things exist like this. In JavaScript; there is a difference between scope and context. This is a unique feature of JavaScript that makes the language extremely flexible.

Every function invocation has both a scope and a context associated with it. Scope is specific to a function while the context is specific to an object. Ryan Morr explains it well:

Every function invocation has both a scope and a context associated with it. Fundamentally, scope is function-based while context is object-based. In other words, scope pertains to the variable access of a function when it is invoked and is unique to each invocation. Context is always the value of the this keyword which is a reference to the object that “owns” the currently executing code.

The context (this) is always set to the object that executed the current code. The code below relies on implicit binding to set the context of the function.

// global scope

var dog = "ruby";

function bark() {
  console.log(this.dog + " barked.");
}

bark(); // output: 'ruby barked.'

var newContext = {
  dog: "roger",
  bark: bark
}

newContext.bark(); // output: 'roger barked.'

You can explicitly set the context by calling a function using call() or apply(). The first parameter of both functions binds this to the object passed.

// Continuing the above example

bark.call(newContext); // output: "roger barked"

You can use the new keyword to turn a function call into what amounts to a “constructor” call. By using new; you are creating an empty object that is linked to that functions prototype and is bound as the this keyword.

Closures

In the lexical scoping section; I showed how a function inside a function (nested function) can access variables in the parent’s scope. Every time a nested function is defined inside a function; a closure is created. The general feature of closures is implemented in C# by anonymous methods and lambda expressions.

Closures are common in JavaScript. For the longest time, it was the only way to associate a set of variables with a function that operates on them. Further; by returning the inner function; you can effectively make the private variables in the closure - simulating object-oriented programming.

function simulateObjectOrientedProgramming(constructorVar) {
  var privateVariable = constructorVar;
  return function() {
    console.log(privateVariable);
    return privateVariable;
  };
}

var oo = simulateObjectOrientedProgramming("Hello");
oo(); // Hello

You can extend this even further by returning an object instead of a function thus completing a simulated C# object.

function simulateObjectOrientedProgramming(constructorVar) {
  var privateVariable = constructorVar;
  var publicVariable = "World";
  var doSomething = function() {
    console.log(privateVariable);
    return privateVariable;
  };
  return {
    doSomething: doSomething,
    publicVariable: publicVariable
  }
}

var oo = simulateObjectOrientedProgramming("Hello");
oo.doSomething(); // Hello
console.log(oo.publicVariable); // World

Arrow Functions

Arrow functions were introduced in ES2015. They are a new syntax for writing functions in JavaScript that have a specific relationship with the this keyword. They are similar in concept to C# lambda functions.

// ES5
var sum = function (a, b) {
  return a + b;
};

// ES2015
var sum = (a, b) => a + b;
// or
var sum = (a, b) => { return a + b; };

They do have a specific property that is not like regular JavaScript functions. Arrow functions do not have a developer-settable this. The this is always the lexical scope enclosing the function.

function newThis(){
  this.foo = "bar";

  setInterval(() => {
    console.log(this.foo); // output: bar
  }, 1000);
}

Since arrow functions do not have a this, you can not use them as “object” function, you can not invoke them through call() or apply(), you can not use them as constructors or with the new operator, they do not have a prototype (covered later).

There is a shortcut in ES2015 to returning an object in an arrow function.

// This doesn't work.
var newObj = () => { key: val };
// This is fine.
var newObj = () => ({key: val});

Random note; the expression () is equivalent to (function () {})() which is a immediately-invoked function expression.

Prototypes

JavaScript does not follow typical object-oriented inheritance as in C#. In JavaScript, inheritance is achieved through a prototype object which acts like a template for the new object. An object’s prototype may also have its own prototype hence a chain of inheritance. However! Unlike typical object-oriented inheritance, the methods and properties are not copied from one object to another in the prototype chain; when you call a method if it’s not available on the object itself, JavaScript will walk up the prototype chain to find it.

function DOGGO(doggoName) {
  this.name = doggoName;
}

let newDoggo = new DOGGO("rex");
console.log(newDoggo.name); // rex
newDoggo.toString();

Something odd happened in the above code sample; we never defined a function in DOGGO called toString(). That is because the constructor prototype of DOGGO is Object. newDoggo’s prototype is DOGGO and DOGGO’s prototype is Object.

You can try to see an object’s prototype by calling newDoggo.prototype which is undefined (what!). Since we used the new keyword, new DOGGO() is a constructor function. The constructor prototype of newDoggo is accessible in most browsers through a property __proto__.

newDoggo.__proto__ === DOGGO.prototype; // true

If you use Object.create() to create an object, it will assign the new object’s prototype to the same prototype as the object you passed in.

newDoggo = Object.Create(DOGGO);
newDoggo.prototype == DOGGO.prototype; // true

this does not refer to the prototype as we’ve discussed in the this section.

newDoggo contains a constructor property which refers to the function DOGGO itself.

newDoggo = new DOGGO("rex");
newDoggo.constructor == DOGGO; // true
newDoggo.constructor.prototype == DOGGO.prototype // true
DOGGO.constructor.prototype == DOGGO.__proto__; //true

Modifying prototypes is another interesting way to inject or change the behavior of objects all the way down the prototype chain even after they’ve been created.

function DOGGO(doggoName) {
  this.name = doggoName;
}

let newDoggo = new DOGGO("ruby");

DOGGO.prototype.bark = function() {
  alert(this.name + ' barked!');
};

newDoggo.bark(); // alert("ruby barked!")

ES2015 Classes

Classes are a new feature introduced in ES2015. Classes are just syntatic sugar over the existing prototypical object inheritance; they do not introduce a new inheritance scheme. You can think of classes as functions but with a critical difference: they are not hoisted like a function. There are two ways to make classes: class expressions and class declarations.

Class declarations will appear familiar to you but again classes are just syntatic sugar. You can have a constructor, getters and setters, methods and static methods.

class Dog {
  // private field declaration
  #dog_name = '';
  // public field declaration;
  animal = 'Dog';
  // this is a constructor
  constructor(name) {
    this.dog_name = name;
  }
  // getter
  get name() {
    return this.dog_name;
  }
  set name(name){
    this.dog_name = name;
  }
  // method
  bark() {
    alert(this.dog_name + " barked!")
  }
  // static methods
  static createRuby() {
    // Don't actually add create functions to your class - this is just an example
    return new Dog("ruby");
  }
}

let ruby = new Dog("ruby");
ruby.name; // output: "ruby"
let ruby2 = Dog.createRuby();

You can also extends classes and reference the parent class with super().

class FancyDog extends Dog {
  #trick = "high-five";

  constructor(name, trick) {
    super(name);

    this.trick = trick;
  }

  bark() {
    super.bark();
    alert(this.trick + " trick performed by " + this.name);
  }
}

You can also create classes using a class expression.

let MyNewClass = class {
  constructor(thing) {
    this.thing = thing;
  }
};

New Operators (Spreads, Exponentiation, Optional Chaining, Nullish Coalescing)

Destructuring Assignments

Default Parameters

Modules

Native Promises

Async and Await

Generators

New “Types” and Object Methods

Map and Set

String Methods and New String Format Syntax

Template Literals

Object Methods

Bless Lodash

Proposed Features: Static Class Features

Proposed Features: Private Methods and Fields

Appendix

Map of EcmaScript Versions to Years

References