let的基本用法
在新的js规范ES6中,新增了let 命令,用来声明变量。用法类似于var,但不同的是所声明的变量,只在let 命令所在的代码块内有效。
{
let a = 10;
var b = 10;
}
//ReferenceError: a is not defined
console.log(a + b);
在之前的js版本中,通过var命令声明的变量可以在声明之前使用,只不过是undefined
console.log(a)// 输出undefined
var a = 10;
这种现象被称为“变量提升”,即变量可以在声明之前使用,值为undefined。而通过let声明的变量,无法在声明之前使用,如果这么做了,则会报xx is not defined错误。
而变量声明之前的代码块,都被称为“暂时性死区”,即变量无法使用的地方。与var声明的全局变量不同,let被设计为一种在局部使用的变量声明命令,使它所声明的变量更具有可操作性。与Java中声明变量的方式非常类似。
一些隐蔽的死区,不容易发现,如:
function bar(x = y, y = 2) {
return [x, y];
}
bar();
输出如下:
for循环中的let变量
for循环的计数器,非常适合使用let命令。
for (let i = 0; i < 10; i++) {
console.log(i);
}
上述代码中,i只在for循环体内有效。而且,应当注意的是,let声明的i在每轮循环都是一个新的变量,每一轮都会重新声明 i ,JavaScript引擎内部会记住上一轮循环的值,初始化本轮的变量i 时,就在上一轮的基础上进行计算。为什么每轮都会声明变量i?这是因为js中的for循环有一个特性,设置循环变量的小括号内是父作用域,而循环体的大括号内是子作用域,这是两个不同的作用域。
不允许重复声明
let不允许在同一个作用域内对同一个变量重复声明。
// 报错
function testRedefined(){
let a = 10;
let a = 20;
}
testRedefined_2();
// 不报错
function testRedefined_2() {
let a = 10;
{
let a = 20;
console.log(a); // 20
}
console.log(a);// 10
}
块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,这会造成诸多问题:
内层变量可能会覆盖外层变量;仅用于计数的循环变量被泄漏为全局变量。
let实际为ES6新增了块级作用域。它允许块级作用域的任意嵌套:
{{{{{{let a = 33;}}}}}}
不同作用域彼此独立,互不影响,因此可以在不同的作用域定义相同的变量名。
const命令基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const声明的变量不得改变值,这意味着,const一旦声明了变量,就必须立即初始化,不能留到以后赋值,如下所示:
const PI;
// SyntaxError: Missing initializer in const declaration
const声明的变量和let有类似的限制,命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用,同样在一个相同的作用域内不能重复声明。
const命令的本质
const实际保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
对于简单的数据类型(数值,字符串,布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。这与Java语言中的final修饰的变量非常相似。因此,在将一个对象或数组生命为const时,应该非常小心。
如果真的希望对象是一个不变的常量,我们可以使用Object.freeze()方法,下面是一个将对象本身及其属性冻结的通用写法:
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
顶层对象
顶层对象可以理解为js代码所运行的宿主环境对象。
在浏览器环境中,这个顶层对象就是window对象,在Node中就是global对象。在ES5中顶层对象的属性就是全局变量(通过var声明的变量)
window.a = 1;
a // 1
a = 2;
window.a // 2
上述代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
这种顶层对象的属性与全局变量挂钩,被认为是JavaScript语言最大的设计败笔之一。
ES6为了改变这一点,一方面为了兼容,规定var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,ES6开始,全局变量将逐步与顶层的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
ES5的顶层对象在各种实现里是不统一的,因此其本身也是个问题。
-浏览器里面,顶层对象是window,但Node和Web Worker没有window
-浏览器和Web Worker里面,self也指向顶层对象,但是Node没有self
-Node里面,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境都能取得顶层对象,现在一般是使用this变量,但是有局限性。
-全局环境中,this会返回顶层对象。但是,Node模块和ES6模块中,this返回的是当前模块。
-函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是严格模式下,这时this会返回undefined
-不管是严格模式还是普通模式,new Function('return this')(),总是会返回全局对象。但是如果浏览器用了CSP内容安全政策,那么eval、new Function这些方法都可能无法使用。