ES 6: A New Hope
Wow! I am so excited for ECMAscript 6 I can barely contain myself. Only one month until it is officially released. I just finished watching Aaron Frost’s overview of ECMAscript 6 over at Pluralsight. If you haven’t tuned in yet, check it out here. Over the next three blog posts, I am going to quickly summarize some of the most exciting features of ES6.
Recursion Optimization: Improving Space Complexity
Recursion has always been an elegant and clever way to solve problems. In javascript, however, it can be quite dangerous. Anyone with experience using recursion in javascript is familiar with the call stack exceeded error. This occurs because each stack is stored regardless of whether it is needed or not. In ES6, as long as the stack in question is no longer being referenced, it will be dropped, allowing for a huge improvement in space complexity
Proper Tail Calls (PTC)
The use of a Proper Tail Call is how ES6 will determine whether it can drop a previous stack or not. A PTC occurs when there are no calculations or information that needs to be retained by the function, and all of the information being recursed or passed along is in the tail call, meaning the statement being returned.
return (Anything next to the return statement is the tail position)
Examples of Proper Tail Calls
function foo(n){
return 'hello' //this is a tail position but not a tail call
return bar() //this is a tail call, since it is not using anything
//else in the function, the stack can be deleted
}
Examples of Improper Tail Calls
function foo(n){
return 1 + bar()
//close, but not a PTC because it must get the
//value of calling bar and then add one and then return return foo(n) - foo(n)
// not a PTC because the stack must be maintained to fulfill the
// statement
}
Proper Tail Calls will only work in Strict Mode
LET and CONST
ES6 is going to introduce two new variable declarations that are going to completely redefine the way we structure our programs.
CONST
Const is quite simple. When you declare a CONST and set it to some value, it creates a constant that can never be changed.
CONST a = 5
a=1 //returns an error
LET
LET is the most exciting of the new declarations. Using the keyword LET overrides javascript’s native variable hoisting. It removes any ambiguity between development and execution. It also treats any block statement as it’s own scope. Let’s glimpse at a few examples:
console.log(a) // Error if(something){
let a = 4
console.log(a) // 4
} console.log(a) // Error
Notice the differences here between var and let. With var, the first console.log would have returned undefined, the second and third console.logs would have returned 4. This occurs for two reasons:
- The use of var would have hoisted the variable declaration to the top of the scope (in this case window), and would have defined it where it was defined in the code
• Let declares and defines exactly where you put it in the code
2. With the use of var, only functions provide new scopes for variable declarations.
- Let treats each block as a new scope (that includes for loops, if statements, or even ES6's new floating block/Lambda
- You can now freely reuse i in for loops without worry
Caveats
- You cannot declare the same variable twice:
var a = 3
var a = 4
console.log(a) //4 let a = 3
let a = 4 //error var a = 3
let a = 4 //error
- While it generally won’t matter to us, there is a Temporal Dead Zone when declaring a variable using let. This means that it actually is secretly hoisted, but it is totally inaccessible, so for 99% of you reading this, you can skip over this bit of information
Conclusion for the use of let
I don’t foresee let completely replacing var. Var still has an important purpose in lexical scoping, but the use of let both adds and removes a major piece of complexity when it comes to variable declarations.
REST/SPLAT/Arguments statement
One of the cool things about javascript is that it is very un-opinionated about how we organize our parameters when instantiating and executing a function. I could have 2 parameters in the declaration, yet pass in 4 parameters in the execution, or visa versa. Javascript handles this by giving us an array-like arguments object for each function. The arguments object contains all of the arguments passed in, including those I provided parameter names for. I can call length on it and I can iterate through it, but it doesn’t have any other properties that arrays have in their prototype chain (such as splice, split, etc). This is solved by converting the arguments object into an array in a somewhat hacky way
var args = Array.prototype.slice.call(arguments)
// now I could call any array method on args.
Introducing the REST Parameter
People with experience in Coffeescript or Ruby are already familiar with these sort of parameters. Basically, a rest parameter allows us to separate any extra, undeclared parameters into an array.
var baz = function(a, b, ...rest){
console.log(a)
console.log(b)
console.log(rest)
} baz(5, 6, 10, 11, 12)
//5
//6
//[10,11,12]
The rest array is a fully functional array and can take all methods that are in the array prototype.
Caveats
- Rest arguments must be the very last parameter
- the actual name of the rest argument can be named anything, as long as it is preceded with 3 periods
Spread Operators
Spread operators are awesome and simple. It provides us a way to immediately access or assign elements in an array without having to iterate. This can be most easily explained with examples
Accessing elements
var nums = [1,2,3]
console.log(nums) //[1,2,3]
console.log(...nums) // 1,2,3
combining/concatenating elements
function getNums(){
return [1,2,3]
} var b = [0, ...getNums()]; console.log(b) // [0,1,2,3]
automatic assignment of elements
function returnTwo(a, b) {
return [b,a]
} var a =[1,2,3,4,5] var b = returnTwo(a[0], a[1]) //[2,1] – old way var b = returnTwo(...a); // [2,1] – new way
Destructuring
Destructuring represents a concept of automatic assignment of values of objects and arrays into variables
My favorite part of destructuring is the ability to swap variables really easily, without making a temp variable
[b,a] = [a,b]
boom! We just swapped the variables a and b with each other!
More commonly, destructuring is going to look like this
var {city, state, zip} = {city: "San Francisco", state:"CA", zip: 94133} console.log(city) // San Francisco
console.log(state) // CA
console.log(zip) // 94133
Notice that the variables you are saving them into have to match the keys EXACTLY, or you will get an error. But what if you don’t like the name of the keys? No problem, you can reassign them inline
var {city: c, state: s, zip: z} = {city: "San Francisco",state:"CA", zip: 94133} console.log(c) // San Francisco
console.log(s) // CA
console.log(z) // 94133
console.log(city) //undefined
So, as you can see, one must know exactly what they are expecting from an object to be able to use destructuring. If you ask for a key that doesn’t exist, you will receive an error. So what if you aren’t 100% sure that the key exists in an object? Well fortunately, they created syntax to support the ‘might exist’ concept:
var {?city, state, ?zip} = {state: 'CA'}
Here the program doesn’t crash, state is assigned and the others are unassigned. It even gets better! If you throw the syntax outside of the braces, it will apply to all of the variables
var ?{city, state, zip} = {nothing: "here"}
Well the variables won’t be assigned, but your program won’t break either. Destructuring is going to be a very handy tool, and will open up many doors for data management.
Arrow Functions
Lastly for this section, we have arrow functions. Probably one of the coolest features I’ve learned about so far. Arrow functions create implicit returns and this binding in javascript. It will look like this:
var fn1 = function(){
return 2
} // old way
var fn2 = () => 2 // new way
It is important to note that brackets are optional for arrow functions, but if you have brackets, there will be no implicit return, so arrow functions are really best for one line functions and anonymous functions. Let’s take a look at what those might look like (it’s about to get really cool)
ES5 syntax:
var nums =[1,2,3];
var res = nums.map(function(n){return n*n})
console.log(res) // [1, 4, 9]
Arrow function — ES6 syntax:
var nums = [1,2,3]
var res = nums.map(n => n*n);
console.log(res); //[1,4,9]
How cool is that!!!!! The syntax for anonymous functions and callbacks have always seemed incredibly bulky and superfluous to me. Now we have quick, easy, and elegant syntax.
Speaking of anonymous functions or callbacks, any practitioner of javascript has struggled with asynchronous calls and losing the this keybinding.
For example, let’s assume I have a pseudoclassical car:
Car.prototype.drive = function(){
this.move()
setTimeout(this.turnLeft, function(){
console.log(this) //will log Window
})
}
We should all be familiar with the solutions to this problem. Either we use .bind, to bind the this binding to the turnLeft function, or we set a var context = this. Arrow functions have an awesome automatic this binding that makes the .bind method or setting an outer context unnecessary. It looks like this:
Car.prototype.drive = function(){
this.move()
setTimeout(this.turnLeft, () => console.log(this)) //will log Car }
That will surely save many headaches!
I hope you guys have enjoyed the first installment of my ES6 overview.
Originally published at questhenkart.com on May 19, 2015.