let和var的基本区别是作用域的区别,let可以设置块级作用域,特别适合for循环,在块级作用域之外访问则出错。下面是一个对比:
使用var:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
使用let:
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
对比两个结果,变量i是var声明的时候,在全局范围内有效,因此全局只有一个变量i,变量i是let声明的时候,只在本次循环内有效,因此每次循环都会生成一个变量i。这里有一个问题,既然每次循环都重新声明一个变量i,那它怎么知道上次循环的值,从而计算本次的i值呢?javascript引擎内部会记住上一次的值,下次初始化本轮i时,会在上一轮的基础上进行计算。
ES6和ES5的一个重要区别,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。目的是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
各层级代码块之间不受影响。
function f1(){
let n = 5;
if(true){
let n = 10;
}
console.log(n); // 5
}
f1();
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。但是浏览器没有遵守这个约定,在块级作用域之中声明函数,不会报错。
但是在ES6中,明确允许在块级作用域之中声明函数。函数声明语句的行为类似于let,在块级作用域之外不可引用。
ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
const和let的基本用法是一样的,只在声明所在的块级作用域有效,同样存在暂时性死区。
本质:
const保证的,不是变量的值不能改动,而是变量指向的内存地址所保存的数据不能改动。对于简单的数据类型,比如数值、字符串和布尔值,值就保存在变量指向的内存地址,等同于常量,对于复合类型的数据,比如对象和数组,变量指向的地址实际上是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。
如果真的想将对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES6 声明变量的六种方法:
ES5中的var和function,加上ES6新添加的let、const、import、class命令。
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。
ES6中,为了保持与ES5的兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。