Our startup doesn't have a dedicated front-end engineer, and as someone with some front-end experience the task has fallen to me to introduce Javascript to a junior engineer who has rarely worked on this before.
I only have 60 minutes so I'm obviously not covering everything but I wanted to introduce them to the evolution of JS in recent years, and touch on the differences between native Javascript, first-generation libraries like jQuery, and more recent frameworks like VueJS. I'm not an expert but I'll try my best!
I'm focussing only on client-side (browser) javascript - if you are looking for any NodeJS / module importing etc then this article is not for you...
I'm starting with a quick history of JS, only because it's so common to see references online to ES6 (and ES5, ES7, etc) and/or ES2015 and/or ECMAScript 5 - and have no idea how this all relates to each other.
ECMAScript is the language specification which covers Javascript. (It is a superset since it also covers ActionScript, but ECMAScript and Javascript are effectively interchangeable for practical purposes).
Prior to 2015 there were large gaps between updates to the specifications but in recent years, the specification is updated on an annual basis. Whether or not you can practically use elements of the specification depends on browser vendors implementing them, I recommend CanIUse to make this decision.
Pre-history - anything before ES5!
ES5 or ES2009 are the same specification. Out of the full list of new features, I commonly use:
Array.indexOf()
JSON.Stringify()
ES6 or ES2015 are the same specification and was the first big update in years. From the full list of ES6 features, I commonly use:
`let` and `const` variable declarations
template literals
arrow functions, eg `const add2 = num => num + 2`
Subsequent releases are all on a yearly cycle (eg ES7 = ES2016, ES8 = ES2017, etc). I haven't really worked much with these new features.
1. Firstly, JS is a dynamically typed language. This basically means that you can assign any value to a variable without declaring its type beforehand, and you can change the variable's type at any time (by assigning another value to it).
2. It is object-oriented, with inheritance via prototypes. I find this easiest to demonstrate with code:
function Person(){
this.greeting = 'Hi'
this.sayHello = function(){
console.log(this.greeting)
}
}
function Frenchman(){
this.greeting = "Bonjour"
}
Frenchman.prototype = new Person()
fred = new Person()
fred.sayHello() // outputs 'Hi'
louis = new Frenchman()
louis.sayHello() // outputs 'Bonjour'
** At this point I need to highlight that you'll hear statements like "Javascript is a class-less language" due to the prototype stuff I demonstrated earlier. Which is strictly true, but that ES6 introduced classes as a syntactic sugar over the prototyping - so you do kinda have classes in JS now. I'll go over ES5 / ES6 / ES7 etc later in this article.
3. Primitives pass by value, others pass by reference. Meaning that an object's or array's attributes/elements may be changed when passing into a function. See here:
// Primitives are passed by value
var dog = 'Rover'
function nameMyPet(animal){
animal = 'Fredo'
}
console.log(dog) // outputs 'Rover'
nameMyPet(dog)
console.log(dog) // outputs 'Rover'
// Objects/arrays are passed by referenceright
var fave_fruits = ['Mangos']
function eatFruits(list_of_fruits){
list_of_fruits.push("Bananas")
}
console.log(fave_fruits) // outputs ["Mangos"]
eatFruits(fave_fruits)
console.log(fave_fruits) // outputs ["Mangos", "Bananas"]
4. Variables are available either in a global, function, or block scope.
today = 'Halloween' // declared in global scope without the 'var'
day = 'Wednesday' // also global scope
function woo(){
today = 'Christmas'
var day = 'Thursday'
console.log(day) // outputs 'Thursday', ie function scope
if (true){
day = 'Friday'
}
console.log(day) // outputs 'Friday', mutated by block scope
}
woo()
console.log(today) // outputs 'Christmas', ie global scope
console.log(day) // outputs 'Wednesday', ie only changed within function
I should mention at this point that ES6 introduced the let (mostly replacing var and allowing a variable to be block-scoped), and const keywords (for variables that can't be reassigned). Compare this with the previous snippet:
today = 'Halloween'
day = 'Wednesday'
function wooooo(){
today = 'Christmas'
let day = 'Thursday'
console.log(day) // outputs 'Thursday', ie function scope
if (true){
let day = 'Friday'
}
console.log(day) // outputs ***'Thursday'***, NOT mutated by block scope
}
wooooo()
console.log(today) // outputs 'Christmas', ie global scope
console.log(day) // outputs 'Wednesday', ie only changed within function.
const my_time = '2pm'
my_time = '3pm' // Throws TypeError: Assignment to constant variable.
5. Variable initialization (and not assignment!) and function declarations (and not function expressions!) are hoisted to the top of its scope. Take a look at this:
// function declaration is hoisted, so you can call it before its declaration
console.log(add(2, 4)) // outputs 6
function add(num1, num2){
var my_fave_number = 4
console.log(my_fave_number) // outputs 4
console.log(my_hated_number) // outputs undefined
var my_hated_number = 13
return num1 + num2
}
// function expression is not hoisted so you can only call it *after* it is assigned.
var multiply = function(num1, num2){
return num1 * num2
}
console.log(multiply(3,3)) // outputs 9
// function expression is not hoisted, so this throws a TypeError: subtract is not a function
console.log(subtract(1,2))
var subtract = function(num1, num2){
return num2 - num1
}
6. Namespacing variables using either object notation, revealing module pattern, or IIFE. These techniques are useful for encapsulating all similar code in a namespace and making your JS code more readable. Here are some examples:
// object notation
var Dog = {
sound: 'woof',
speak: function(){
console.log(this.sound)
}
}
Dog.speak() // outputs 'woof'
// revealing module pattern
var Monster = function(){
let sound = 'grrrr'
return {
speak: function(){
console.log(sound)
}
}
}
var shrek = new Monster()
shrek.speak() // outputs 'grrr'
// IIFE
var Avenger = (function(){
let sound = 'boiiing'
return {
speak: function(){
console.log(sound)
}
}
})()
Avenger.speak() // outputs 'boiiing'
Libraries like jQuery, mootools, prototype, YUI, etc were all released from ~2006 and focussed on DOM manipulation. That is, when X event happens, do Y on element Z. jQuery generally became acknowledged as the most widely adopted “standard”. The main benefit of these libraries were a set of consistent methods which worked regardless of the client's browser.
To work with jQuery, you would typically refer to DOM elements and attach various event handlers on them. For example:
$('.btn').click(function(){
alert("I've been clicked!")
})
Key concepts to understand are which events are supported, how events are propagated, and when events are bound.
From 2009 onwards, a number of supplementary libraries were released which helped with data manipulation and templating. The main benefit of these libraries were making it much easier to work with data sets in the browser.
More recently, frameworks like VueJS / React / Angular have redefined how javascript is written. Unlike jQuery, these frameworks focussed on what data has changed, ie X is an attribute of Y (and so automatically change X when Y changes). This was a significant difference to the jQuery approach, and allowed developers to think conceptually of the web app as a collection of data objects, instead of as a collection of DOM elements to be manipulated. That is, a transition from imperative to declarative programming.
VueJS mixes the HTML and javascript elements on the page, like this:
<div id="my-app">
<ol v-if="!loading">
<li v-for="monster in monsters" @click="growl(monster)" > [[monster]] </li>
</ol>
</div>
var MonsterApp = new Vue({
delimiters: ['[[', ']]'],
el: '#my-app',
data: {
loading: false,
monsters: ["Shrek", "Big Bad Wolf"]
},
methods: {
growl: function(monster){
alert(`${monster} says: Grrrrr!`)
}
}
})
And that's all I really had time to discuss!