ECMAScript 6 introduced two new ways to declare variables: let and const. const is used for values that should not be reassigned, while let brings something JavaScript had long been missing in practice: block scope.

Anyone who has written JavaScript knows that, unlike many other languages, traditional variable scope in JS is based on functions rather than blocks. That difference becomes especially frustrating once hoisting and closures enter the picture. Even experienced developers have run into bugs caused by variables behaving less intuitively than expected.

Take this example:

var a = 5;
if(true){
    var a = 10;
}
console.log(a);//10

The output is 10. At a glance, it looks as though there are two separate a variables: one outside the if block and one inside it. In many languages, that would mean two separate memory locations. But with var, both declarations refer to the same variable in the same function scope, so the inner assignment overwrites the outer one.

let was added in ES6 specifically to address this kind of problem:

let a = 5;
if(true){
    let a = 10;
}
console.log(a); //5

Here the result is 5, because let is limited to block scope. Whether the block comes from an if, switch, or for, a variable declared with let only exists inside those braces. That makes code easier to reason about and avoids many accidental overwrites.

The other notable addition is const, often thought of as let's sibling. Once a variable is declared with const, it cannot be reassigned later:

const aa = 11;
alert(aa) //11
aa = 22;
alert(aa) //11

As for compatibility, support has varied depending on the environment. Node already supported const and let, and they could be used with node --harmony together with use strict. Some browsers did not support this syntax at the time, but it was still possible to use these features in ES3 environments through a library called defs.js.

That library works by using esprima to parse, compile, and rewrite the code. For example:

"use strict";
function fn() {
    const y = 0;
    for (let x = 0; x < 10; x++) {
        const y = x * 2;
        const z = y;
    }
    console.log(y); // prints 0
}
fn();

After being recompiled by def.js, it becomes:

"use strict";
function fn() {
    var y = 0;
    for (var x = 0; x < 10; x++) {
        var y$0 = x * 2;
        var z = y$0;
    }
    console.log(y); // prints 0
}
fn();

The rewritten version shows the basic idea clearly: newer block-scoped declarations are transformed into older syntax while preserving the original behavior. That made it possible to start using let and const even before full native support was widely available.

For compatibility details, this reference was provided: http://kangax.github.io/es5-compat-table/es6/

More about the transpilation tool can be found here: //github.com/olov/defs