YDKJS
While JavaScript is perhaps one of the easiest languages to get up and running with, its eccentricities make solid mastery of the language a vastly less common occurrence than in many other languages. Where it takes a pretty in-depth knowledge of a language like C or C++ to write a full-scale program, full-scale production JavaScript can, and often does, barely scratch the surface of what the language can do.
Initial idea of this blog post was to cover all books from YDKJS series, I realized it would be "reinventing the wheel" most of the time so I decided to cover only Up & Going with some extra examples. Following text represents brief summary of topics covered deeply in other YDKJS books, article is not substitute for reading all YDKJS books, main purpose is to make you more interested in YDKJS book series and javascript itself.
Types
JavaScript is loosely typed, this doesn't mean JavaScript has no types, you just don't need to write them.
The following built-in types are available:
string
number
boolean
null
andundefined
object
function
array
date
regExp
symbol
You can check type of variable with typeof
operator. You can call it as typeof(variable)
as well.
let a;console.log(typeof a) // "undefined"a = 'Some text'console.log(typeof a) // "string"a = 42console.log(typeof a) // "number"a = trueconsole.log(typeof a) // "boolean"a = nullconsole.log(typeof a) // "object" <- Caution!a = undefinedconsole.log(typeof a) // "undefined"a = { b: 'c',}console.log(typeof a) // "object"
This probably work as you expect, but again, be careful typeof(null)
returns an object, so for an example if you want to check if some variable is an object you can do it like this:
let object = {}let falseObject = nullfunction isObj(arg) { return typeof of === 'object' && arg !== null}console.log(isObj(object)) // trueconsole.log(isObj(falseObject)) // false
Objects
In JavaScript, an object is a standalone entity, with properties and type. Compare it with a car, for example. A car is an object, with properties. A car has a color, a design, a type, number of doors etc. The same way, JavaScript objects can have properties, which define their characteristics.
You can access object properties in two way, . ( dot notation ), [ ] ( brackets notation )
let ShibaInu = { legs: 4, race: 'Dog', sex: 'Male',}ShibaInu.legs // 4ShibaInu['race'] // "Dog"
Objects are passed by reference, not by value.
let objA = { prop: "Some property value";}let objB = objA; // objB now "points" to objA objectobjA.prop2 = "Another property value";objB.prop2; // "Another property value"
Arrays
An array is an object that holds values (of any type) not particularly in named properties/keys, but rather in numerically indexed positions. For example:
let arr = [ "1", 23, { a : "b", c : "d" }, function() { console.log("Hi!")];arr[0]; // "1"arr[3](); // "Hi!"
Because arrays are objects, they can also have properties, including the automatic updated length property.
// arr from previous exampleconsole.log(arr.length) // 4arr.returnFirst = function() { return this[0]}arr.returnLast = function() { let len = this.length return this[len - 1]}arr.returnFirst() // "1"arr.returnLast() // function () { ... }// returnLast() returns function since last element of arr is a function// we can invoke it with another set of ()arr.returnLast()() // "Hi!"
Functions
JavaScript implements a first-class functions. This basically means that you can treat functions as any other type. You can pass them around, you can declare them inline, you can return them from other functions etc..
Functions, as many other things in JS, are objects. So like in case with Array they can have properties too. More details about functions later on, for now here is small example:
function ultimateQuestionOfLife() { return 42}ultimateQuestionOfLife() // 42
Comparing values
You can compare values with one of following operators:
==
!=
===
!===
Object.is()
(ES6)
Result of any comparison is boolean value, true or false. Main difference between ==
and ===
is coercion. ==
allow coercion and ===
don't.
It's handy to know what evaluates to true and false before comparing values.
Falsy values:
""
- empty string0
,-0
,NaN
null
,undefined
false
Truthy values:
"hello"
42
true
[]
{}
function bar() { ... }
Variables
Valid name: must start with a-z
, A-Z
, $
or _
, It can contain any of those characters plus the numerals 0-9
.
Hoisting
Wherever a var appears inside a scope, that declaration is taken to belong to the entire scope and accessible everywhere throughout. Metaphorically, this behavior is called hoisting, when a var declaration is conceptually "moved" to the top of its enclosing scope. Technically, this process is more accurately explained by how code is compiled, but we can skip over those details for now.
Consider this example:
var a = 2foo() // works because foo() declaration// is hoistedfunction foo() { a = 3 console.log(a) // 3 var a // declaration is hoisted // on top of foo()}console.log(a)
Or:
var a = 42function bar() { console.log(typeof a) // "function" a = 23 function a() {} return a}bar()console.log(a) // 42
So function a()
is hoisted on top of function function bar()
, then we give it a value of 23 so it becomes a number, and finally we return it. Because of this behavior the global a
variable stays unchanged.
When you declare a variable it's available anywhere in that scope. JavaScript used to have only function scope, meaning creating function creates new scope. ES6 changed that introducing the let
keyword, using let
one can declare block scope.
function bScope() { var a = 10 if (a >= 10) { let a = 5 } console.log(a)}function fScope() { var a = 10 if (a >= 10) { var a = 5 } console.log(a)}bScope() // 10fScope() // 5
Strict mode
ES5 added "strict mode" to the language. Strict mode tightens the rules for certain behaviors. In general using strict mode, your code will become "safer place", this doesn't mean your code will become error proof or perfect, but it will be one step closer to that.
function foo() { "use strict"; // this code is in strict mode function bar() { // this code is in strict mode }
Or:
'use strict'// this code is in strict modefunction foo() { // this code is in strict mode function bar() {}}
Strict mode is disallowing the implicit auto-global variable declaration from omitting the var
keyword.
function foo() { 'use strict' a = 42 // var missing, ReferenceError}foo()
Immediately Invoked Function Expressions (IIFEs)
IIFEs can be very usefull, let's see some examples:
(function IIFE() { console.log('Hi from IIFE!')})()// "Hi from IIFE!"
The outer ( .. )
that surrounds the function is just a mechanism needed to prevent it from being treated as a normal function declaration. The final ()
on the end of expression is what executes the function.
Creating IIFE you also create new variable scope, so you can use IIFE to do something like this:
var a = 42(function IIFE() { var a = 10 console.log(a) // 10})()console.log(a) // 42
IIFEs can also have return values:
var x = (function IIFE() { return 42})()console.log(x) // 42
Closure
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
Little motivation to learn and understand closures, recently I took couple of interviews and in 90% of them I had task to write function that sums two number, function must be called in this way: sum( arg1 )( arg2 )
Solution:
let sumES6 = x => { return y => { return x + y }}console.log(sumES6(2)(3)) // 5
If you are not familiar with ES6 arrow functions, here is equivalent ES5 example:
let sumES5 = function(x) { return function(y) { return x + y }}console.log(sumES5(2), 3) // 5
Because of this mechanism, we can set one argument, and pass other argument later on. In our example we double invoke over sumES6
function. On first invoke we return reference to inner function, on second invoke we return x + y
value. Closure give us acess to x
value we passed in first invoke.
Module pattern
The most common usage of closure in JavaScript is the module pattern. Modules let you define private implementation details (variables, functions) that are hidden from the outside world, as well as a public API that is accessible from the outside.
Consider the example:
function Employee() { let name, surname function returnSalary(nm, srnm) { name = nm surname = srnm // connect to a database // return salary or smth. similar here ... } let publicAPI = { salary: returnSalary, } return publicAPI}// create a `Employee` module instancelet john = Employee()john.salary('John', 'Doe')
So, the publicAPI
object is returned after Employee is invoked, john
will have access to sallary property of that object, that will again return inner returnSallary function.
this keyword
this
is very specific mechanism in javascript and it's value mainly depends on context of execution.
If a function has a this reference inside it, that this reference usually points to an object. But which object it points to depends on how the function was called. It's important to realize that this does not refer to the function itself, as is the most common misconception.
function foo() { console.log(this.bar)}let bar = 'global'let obj1 = { bar: 'obj1', foo: foo,}let obj2 = { bar: 'obj2',}// --------foo() // "global"obj1.foo() // "obj1"foo.call(obj2) // "obj2"new foo() // undefined
There are four rules for how this gets set, and they're shown in those last four lines of that snippet.
foo()
ends up setting this to the global object in non-strict mode -- in strict mode, this would be undefined and you'd get an error in accessing the bar property -- so "global" is the value found for this.bar.obj1.foo()
sets this to the obj1 object.foo.call(obj2)
sets this to the obj2 object.new foo()
sets this to a brand new empty object.
I will stop here. For further informations consider looking this YDKJS books overview.
I hope you enjoyed this article and that you're more interested in learning javascript than you were before reading this article. Remember: