var 声明与变量提升
使用var 关键自声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部,如果声明不在任意函数内,则视为全局作用域的顶部,这就是所谓的变量提升。
function getValue(condition){
if(condition){
var value = 'red';
return value;
}else{
// value 在此处可以访问,值为 undefined
return null;
}
// value 在此处可以被访问,值为 undefined
}
// value 变量的声明被提升到了顶部,而初始化工作则保留在原处。
// 在else 分支内 value 变量可以访问,此处的值是 undefined,因为它没有被初始化
块级声明
块级声明就是让所声明的变量在指定块的作用域外无法被访问,块级作用域又被称为词法作用域,在如下情况被创建:
- 在一个函数内部
- 在一个代码块内部
let 声明
let 声明的语法与var 的语法一致,但是let 会将变量的作用域限制在当前代码块中。由于 let 声明并不会被提升到当前代码块的顶部,因此需要手动将 let 声明放置在顶部,以便让变量在整个代码块内部可用。
function fun(condition){
if(condition){
let value = 'red';
return value;
} else {
// value 在此处不可访问
return null;
}
// value 在此处不可用
}
// 由于变量 value 声明使用的是let, 声明没有被提升到函数定义的顶部
// 变量在if 代码块外部无法被访问
// 在condition的值为false时,该变量永远不会被声明初始化
禁止重复声明
如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行let 声明会抛出错误
var count = 30;
// 语法错误
let count = 20;
// count 被变量声明了两次
// let 不能在同一作用域内重复声明一个已有标识符
var count = 30;
// 不会抛出错误
if(condition){
let count = 20;
}
// 在嵌套的作用域内使用let 声明一个同名的新变量,不会抛出错误
// 此处的let 在if语句内部创建了一个新的count 。不是在同一级再次创建
// 在if 代码块内部,这个新变量会屏蔽全局的count
常量声明
使用 const 声明的变量被认为是常量(constant),它们的值在被设置完成后就不能再被改变
// 有效的常量
const value = 30;
// 语法错误,未进行初始化
const name;
// value 变量被初始化, 它的const 声明能正常起效
// name 变量没有被初始化,导致在试图运行这段代码时抛出了错误
常量声明与 let 声明
常量声明与let 声明都是块级声明,在语句块外部都无法访问,并且不会被提升
if(condition){
const value = 5;
// do something
}
// value 在此处无法访问
// value 在if 语句中被声明,value 在代码块外无法被访问
const 声明会在同一作用域(全局或是函数作用域)内定义一个已有变量时会抛出错误
var message = 'Hello';
let age = 30;
// 均会抛出错误
const message = '你好';
const age = 25;
// const 声明前面添加了 var 与 let 声明的情况下,二者都会出问题
使用const 声明对象
const 声明会阻止对于变量绑定与变量自身值的修改,这意味着const 声明不会阻止对变量成员的修改
const person = {
name: 'xiaoming'
};
// 正常
person.name = 'lisi';
// 抛出错误
person = {
name: 'wangwu'
}
// person 初始化时被绑定了带有一个属性的对象,修改person.name是可能的,该操作只修改了 person 对象的成员,没有修改person 的绑定值
// 当代码试图为person 对象自身赋值时,这会改变变量绑定,会抛出错误
// const 阻止的是对于变量绑定的修改,不会阻止对成员值对修改
暂时性死区
使用 let 或 const 声明对变量,在达到声明处之前都是无法访问的,试图访问会导致一个引用错误,暂时性死区经常被用于描述 let 或 const 声明的变量为何在声明处之前无法被访问。
if(condition) {
console.log(typeof value); // 引用错误
let value = 'red';
}
// value 变量使用了let 进行定义与初始化,但该语句不会被执行,因为声明之前 抛出了错误
// value 位于被JS 社区称为暂时性死区的区域内
// 当JS引擎检视代码发现变量声明时,在面对var 的情况下将声明提升到函数或全局作用域顶部,而 let 或 const 会将声明放在暂时性死区
循环中的块级绑定
在for 循环内,想让一次性的循环计数器仅能在循环内部使用
for (var i = 0; i<10; i++) {
process(item[i]);
}
// 此处i 不能访问,会抛出错误
console.log(i);
// i 只能在for 循环内部可用,一旦循环结束,变量在任意位置都不可访问
循环内的函数
var 的特点使得循环变量在循环作用域之外仍然可被访问,于是在循环内创建函数就变得有问题。
var funcs = [];
for(var i=0; i<10; i++){
funcs.push(function(){console.log(i);});
}
funcs.forEach(function(func){
func(); // 输出数值 ‘10’ 10次
})
// 变量i 在循环的每次迭代中都被共享了,意味着循环内创建的那些函数都拥有对于同一变量的引用
// 循环结束后,变量 i 的值会是10
在循环内使用立即调用函数表达式(IIFEs),以便在每次迭代中强制创建变量的一个新副本
var funcs = [];
for(var i=0; i<10; i++) {
funcs.push((function(value){
return function(){
console.log(value)
}
}(i)));
}
funcs.forEach(function(func){
func(); // 从 0 到 9 依次输出
})
// 变量 i 被传递 IIFE ,从而创建了value 变量作为自身副本并将值储存于其中
// value 变量的值被迭代中的函数所使用,因此在循环从0 到9 的过程中调用每个函数都返回了预期的值
// 使用let 与 const 的块级绑定可以在ES6 中为你简化这个循环
循环内的let 声明
let 声明通过有效模仿IIFE的作用简化了循环。在每次迭代中,都会创建一个新的同名变量并对其进行初始化
var funcs = [];
for(let i=0; i<10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func){
func(); // 从 0 到9 一次输出
})
// 循环中let 声明每次都创建了一个新的 i 变量,
// 在循环内部创建的函数获得了各自的 i 副本,
// 每个 i 副本的值都在每次循环跌打声明变量时被确定
// 这种方式在 for - in 和 for -of 中同样适用
循环内的常量声明
在常规的for 循环中,可以在初始化时使用 const ,但循环会在试图改变该变量的值时抛出错误
var funcs = [];
// 在一次迭代后抛出错误
for(const i = 0; i<10; i++){
funcs.push(
function(){
console.log(i);
}
)
}
// i 被声明为一个常量,循环第一次迭代成功执行,此时 i 的值为0
// i ++ 执行时,错误会被抛出
// 循环中只能使用const 来声明一个不会被改变的变量
// const 在for -in 和 for -of 循环中与let 变量效果相同
全局块级绑定
let 与 const 不同于 var 的另一个方面是在全局作用域上的表现,当在全局作用域上使用 var 时,它会创建一个新的全局变量,并成为全局对象的一个属性
var RegExp = 'Hello';
console.log(window.RegExp); // "Hello"
// 尽管全局的RegExp 是定义在window 上的,它仍不能防止被var 重写
// 声明了一个新的全局变量 RegExp 覆盖了原有的对象
若在全局作用域上使用let 或const , 虽然全局作用域上会创建新的绑定,但不会有任何属性被添加到全局对象,这意味着不能使用let 或 const 来覆盖一个全局变量,只能将其屏蔽。
let RegExp = 'Hello';
console.log(RegExp); // 'Hello'
console.log(window.RegExp === RegExp); // false
const ncz = "Hi!";
console.log(ncz); // "Hi!"
console.log("ncz" in window); // false
// let 声明创建了RegExp 的一个绑定,并屏蔽了全局的RegExp
// 这表示 window.RegExp 与 RegExp 是不同的,全局作用域没有被污染
// const 声明创建了一个 ncz 的绑定,但未在全局对象上创建属性,
// 若不想在全局对象上创建属性时,这种特性会让 let const 在全局作用域中更安全
块级绑定新的最佳实践
在默认情况下使用const 并且只在知道变量值需要被更改的情况下才使用let