Whether you’re a C# (or similar language like Java) developer eager to learn JavaScript or you’ve already been working with JS for some time, I hope you find this article interesting. I collected for you 10 JavaScript features which are/were the most shocking for C# developers who learnt JavaScript.
These features of JavaScript language are the most striking differences compared to C# ecosystem. If you’re about to learn JavaScript, sooner or later you’ll also have to deal with them.
Before we begin, one disclaimer: the goal of this post isn’t stating which language is better or worse. JavaScript and C# are different programming languages and their use cases are also completely different. One is better for some usages, while the other is better for others. The list is my and other readers’ subjective one and you don’t need to agree with all points. I’m myself a C# developer on my JavaScript learning journey, so I’d like to help you grasping these confusing concepts 😉
1. Dynamic Typing
Obviously, the first difference JavaScript newbie notices is that the language is dynamically-typed. It means that the types (of variables, functions, actually of almost everything) are checked at runtime, not at compile time like in C# or Java.
Because of that, there’s not much difference in defining variables and assigning them data of various data types:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var someNumber = 1; | |
var someBoolean = true; | |
var someString = 'Hello JS!'; |
JavaScript’s variables are not associated with any particular data type. That’s why it’s completely legit to write something like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var a = 1; // typeof a => number | |
a = true; // typeof a => boolean | |
a = 'Hello JS!'; // typeof a => string |
Because JavaScript is dynamically-typed, issues with types are detected at runtime (e.g. error is thrown as soon as you try to use a variable in a context which expects another data type), not during compilation time as it would be in C# or Java.
If you have some experience with statically-typed languages, you may now feel how much confusion and troubles dynamic typing can bring. At least at the beginning.
2. Implicit types coercion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var name, isGreatDeveloper; | |
name = 'Dawid'; | |
isGreatDeveloper = true; | |
console.log('Is ' + name + ' a great dev?: ' + isGreatDeveloper); | |
// displays 'Is Dawid a great dev?: true' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var age = 26; | |
if(age == '26'){ | |
// this is executed | |
} | |
if(age === '26'){ | |
// this is NOT executed | |
} |
*shuts laptop forever* pic.twitter.com/3GS1JfNDSP
— Shayna Gentiluomo (@kd2luw) May 3, 2018
3. null and undefined
In JavaScript there are two values which represent kind of unassigned variables: undefined and null. What’s the difference between them? The snippet below clarifies:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var x; | |
console.log(x); // prints 'undefined' | |
x = null; | |
console.log(x); // prints 'null' |
4. Truly and Falsy values
- “falsy” values: undefined, null, 0, ”, NaN
- “truly” values: all NOT falsy values
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var x; // x is 'undefined' | |
if(x){ | |
// this is NOT executed | |
} |
Knowing that you might want to check if the value has been defined in the following way:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if(x){ | |
console.log('x is defined!'); | |
} |
So the proper way of checking whether the number has been defined is as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if(x || x === 0){ | |
console.log('x is defined!'); | |
} |
Of course, for another data types (like text) you need to handle other “falsy” values as well. It depends what you consider a meaningful value.
5. Hoisting
In JavaScript you can legally write such a code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = 'Dawid'; | |
console.log(name); | |
var name; |
As you can notice, name variable is used before it’s declared 2 lines below. This is maybe not that weird, as in JS we can even declare variables without the var keyword, but we can do the same with functions:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
printHello(); // prints 'Hello!' | |
function printHello(){ | |
console.log('Hello!'); | |
} |
So here we call the function before declaring it. Why does this work?
This works thanks to hoisting. It’s a compile-time mechanism which “moves” all variables and functions declarations to the top of our JavaScript code. In fact, during the execution of above-listed code, JavaScript engine processes variables and functions declarations before executing any other code.
It however applies only to declarations, not to assignments:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
console.log(name); // prints 'undefined' | |
var name = 5; |
6. Lexical scoping and this keyword
To create a scope (space in which the variables defined in it are accessible) in JS, we need to create a new function. Variable defined in a function is not accessible outside this function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function a() { | |
var x = 5; | |
} | |
console.log(x); // Uncaught ReferenceError: | |
// x is not defined |
It means that other blocks like the ones associated with if statement don’t introduce a new scope:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if(true) { | |
var x = 5; | |
} | |
console.log(x); // prints '5' | |
// x is a global scope variable |
If a variable is defined outside of any function, it’s assigned to a global scope.
However, in JavaScript we also have something called lexical scoping. It basically makes functions declared within another functions having access to the parent function’s scope, as the following example presents:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function a() { | |
var x = 'Hello'; | |
b(); | |
function b() { | |
var y = ' world!'; | |
console.log(x + y); | |
} | |
} | |
a(); // prints 'Hello world!' |
Forming such “hierarchy” of functions is also referred to as scope chain. You should remember that it refers to the order in which functions are lexically written, not in which they are called:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function a() { | |
b(); | |
function b() { | |
var x = 'Hello world!'; | |
c(); | |
} | |
} | |
function c() { | |
console.log(x); // Uncaught ReferenceError: | |
// x is not defined | |
} | |
a(); |
As you can see above, as soon as we call a(), which then calls b(), within which we define variable x and then call function c(), inside of function c() we don’t have access to x variable (x is not defined). Why does it happen even though function b() in which x variable is declared called function c()? Because function c() is not lexically written as the child function of b(). Parent scope for function c() is always the global scope.
7. this keyword
All these scoping rules examined above somehow lead to another confusing concept with this keyword. As soon as we define a method on some object and try to print the this keyword in this object’s method, the result seems reasonable:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var blogger = { | |
name: 'Dawid', | |
website: 'codejourney.net', | |
aboutMe: function(){ | |
console.log(this); | |
} | |
} | |
blogger.aboutMe(); // prints: {name: "Dawid", website: "codejourney.net", aboutMe: ƒ} |
However, if we just define an inner function in our method and try to print this keyword inside it, weird stuff happens:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var blogger = { | |
name: 'Dawid', | |
website: 'codejourney.net', | |
aboutMe: function(){ | |
function innerFunc(){ | |
console.log(this); | |
} | |
innerFunc(); | |
} | |
} | |
blogger.aboutMe(); // (!!!) prints: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …} |
As you can see, the this keyword in the innerFunc() function doesn’t point to the blogger object anymore. Instead it points to a Window object, which represents the browser’s window (this used in the global scope points to it).
This happens because in a regular function the this keyword points to the global object (Window object in our case). innerFunc() is here considered as a regular function, not as an object’s method. This behavior may be confusing and is often a subject of dispute in JavaScript community.
8. sort() function
You’re a newbie JS developer and you’d like to sort an array of numbers. You found a sort() method on the array, so why not to use it? You write such code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var numbers = [40, 20, 15, -5, 7]; | |
console.log(numbers.sort()); |
What do you expect to be logged in the console? Let’s see:
(5) [-5, 15, 20, 40, 7]
Nice! What happens here? 7 is greater than 40? Well, yes 🙂 It works as expected, because the sort() function sorts array’s elements as strings and alphabetically. Therefore, if our array contains numbers, the final order depends on the Unicode representations values of each number.
Fortunately, there are ways to use sort() function correctly for numbers, but this functionality is still confusing for new JavaScript developers.
9. Prototypes
Every object in JavaScript has a __proto__ property. We can access it directly on our object or by using Object.getPrototypeOf() method. In both cases, having created an empty object and printing its prototype to the console like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let x = {} | |
console.log(x.__proto__); |
produces the same output:
On the top of objects hierarchy, there’s Object. Because of that, our x object is an instance of Object. That’s why the prototype of our x object is actually the Object.prototype, which contains some common methods we can use (like toString()).
If we try to access Object.prototype.__proto__, we’ll see that is contains null.
Prototypes in JavaScript are mainly used to provide some connection between two or more objects (like exposing some common methods).
All this “hierarchy” of prototypes is used to search for fields and methods of objects: the JS engine first searches in the object itself and if it cannot find particular property, it looks in this object’s prototype, and then in its prototype until it finds the first matching occurrence. As soon as it reaches null in the Object.prototype.__proto__ it means that the prototypes chain has ended and undefined is returned.
This kind of prototype inheritance is different than class inheritance, so it’s often confusing for developers willing to learn JavaScript. Actually, I could write a separate post on this, so if you’re interested you can read more for instance here.
10. IIFE
IIFE is an acronym for Immediately-Invoked Function Expression. So what is it and how can it be useful?
Going straight into the code, IIFE can be written as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
console.log('Hey!'); | |
})() |
As a result of executing such script in a web browser, we’ll see the following console output:
Hey!
How does it work? We neither declared a function’s name nor called it, right? Actually, we did 🙂
There are two unusual parts of IIFE implementation: outer parentheses in which we “wrapped” our function and “empty” parentheses together at its end.
Let’s start by “wrapping parentheses”:
Why do we declare them? Because if you don’t, JavaScript parser will give you an error:
script.js:1 Uncaught SyntaxError: Unexpected token (
In such case it thinks that we’re writing a function declaration and we forgot about providing the function’s name after the function keyword. Wrapping this into parentheses (…..) tells the parser that we’re declaring a function expression, not a declaration. After doing this, there are no errors anymore.
The second important part is the “empty” parentheses () just after the function expression:
As you know, () allows to call the function. That’s exactly what we do here here – our function is immediately executed.
Why would we need such construct as IIFE? Developers say that it doesn’t “pollute” the global scope. In effect, we are not leaving the function somewhere after being executed, so no one else can call it accidentally from another place. What’s more, IIFE is even considered a design pattern.
You should know that such construct exists even only to know what it does seeing it in someone else’s code. If you want to know more about its use cases, I recommend reading this article.
Summary
In this post we’ve gone through some of the most shocking features of JavaScript language for C# developers. Most of them are confusing for programmers coming from statically-typed and class-based languages and can bring a lot of issues in the beginning of their JavaScript journey.
Just remember that these are the JavaScript’s language features, not its issues or bugs. That’s just how it is, but it cannot be said that JavaScript is worse or better than other languages like Java or C#.
The list for sure is not exhaustive, so if you have anything to add or correct don’t hesitate to leave a comment! 🙂
For the end, I’ll just leave you with the following meme-but-true (thanks Youenn 😉 ):
Stay tuned!