var声明及变量提升(Hoisting)机制
变量提升机制——在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上在哪里声明的,都会被当成在当前作用域顶部声明的变量。
function getValue(condition) {
if(condition) {
var value = "handsome";
console.log(value); // handsome
return value;
} else {
console.log(value); // undefind
return null
}
console.log(value); // undefind
}
块级声明
块级声明用于声明在制定块的作用域之外无法访问的变量(又称词法作用域),一般存在于
- 函数内部
- 块中(字符{和}之间的区域)
let和const的特点
- let和const的声明只在当前代码块内有效,一旦执行到块外会立即销毁,不会被提升至作用域顶部。
function getValue(condition) {
if(condition) {
let value = "handsome";
const constant = "beautiful"
console.log(value); // handsome
console.log(constant); // beautiful
value = "beautiful"; // beautiful
constant = "handsome" // 报错,不能更改
return value;
} else {
console.log(value); // 报错 value不存在
console.log(constant); // 报错 constant
return null
}
console.log(value); // 报错 value不存在
console.log(constant); // 报错 constant
}
- 禁止重声明
假设作用域中已经存在某个标识符,如果此时再使用let声明它就会报错,但如果当前作用域内嵌另一个作用域,可以在内嵌的作用域中用let声明同名变量,内部变量的值会遮掩外部变量的值。
var count = 30;
let count = 18; // 报错
if(condition) {
let count = 18; // 18
}
let和const的区别
let声明的变量声明时可以不赋值,并且可以赋值后可以多次修改,而const声明的是常量,声明时必须进行初始化,且其值一旦被设定后不可更改。
值得注意的时const声明的常量是对象,则const声明不允许修改绑定,但是对象中的值可以修改。
const person = {
name: '张三';
}
person.name = '李四'; //李四
person = {
name: '李四';
} // 报错
临时死区(Temporal Dead Zone)
Javascript引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(var声明),要么将声明放到TDZ中(let和const声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句之后,变量才会从TDZ中移出,方可正常访问。
循环中块作用域绑定
var在循环中的问题
var funcs = [];
for(var i = 0; i < 10; i++) {
funcs.push(function(){
console.log(i)
})
}
console.log(i); // 10
funcs.forEach(function(func){
func(); // 输出10次数字10
});
使用立即调用函数表达式(IIFE)改进后:在循环内部,IIFE表达式为接受的每一个变量i都创建了一个副本并存储为变量value。
var funcs = [];
for(var i = 0; i < 10; i++) {
funcs.push((function(i){
return function(value){
console.log(value);
}
}(i)))
}
console.log(i); // 10
funcs.forEach(function(func){
func(); // 依次输出0-10
});
使用let可以直接实现上述IIFE的应用,每次迭代循环都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。(注意:const不用用于此场景——i++修改常量报错)
var funcs = [];
for(let i = 0; i < 10; i++) {
funcs.push(function(){
console.log(i)
})
}
console.log(i); // 报错
funcs.forEach(function(func){
func(); // 依次输出0-10
});
// 相当于
{
let i = 0
funcs[i] = function() {
console.log(i)
};
}
{
let i = 1
funcs[i] = function() {
console.log(i)
};
}
{
let i = 2
funcs[i] = function() {
console.log(i)
};
}
在for-in和for-of循环中let和const的用法一样。const可以在for-in和for-of循环中应用的原因在于每次迭代不会修改已有的绑定,而是会创建一个新绑定。
全局作用域绑定
当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器中的window对象)的属性。这意味着用var可能会无意间覆盖一个已将存在的全局属性:
var RegExp = 'hello';
console.log(window.RegExp);
如果你在全局作用域使用let或const,会在全局作用域下创建一个新的绑定,。且改绑定不会添加为全局对象的属性。换句话说,用let和const不能覆盖全局变量,只能遮蔽它。
var RegExp = 'hello';
console.log(RegExp); // hello
console.log(window.RegExp === RegExp); // false
最佳实践
在我们开发的时候第一反应可能是默认使用 let 而不是 var,对于需要写保护的变 量要使用const。然而另一种做法日益普及:默认使用const,只有当确实需要改变变量的值的时候才使用let。这是因为大部分的变量的值在初始化后不应再改变,而预料之外的变量之的改变是很多 bug 的源头。