var 声明及变量提升机制
在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是我们常说的提升机制。
function getValue(condition) {if (condition) {var value = "blue";return value;} else {// 此处可访问变量value,其值为undefinedreturn null;}// 此处可访问变量value,其值为undefined}
并不是在condition的值为true时才会创建变量value。在预编译阶段,JavaScript 引擎会将上面的getValue 修改成下面这样
function getValue(condition) {
var value;
if (condition) {
value = "blue";
return value;
} else {
// 此处可访问变量value,其值为undefined
return null;
}
// 此处可访问变量value,其值为undefined
}
因此es6引入块级作用域来强化对变量声明周期的控制
块级声明(词法作用域)
块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于
- 函数内部
- 块中(字符{和}之间的区域)
let声明
不能在声明变量前访问
把变量的作用域限制在当前代码块中。
let声明不会被提升,因此通常放在封闭代码块的顶部,以便整个代码块都可以访问。
1.不会被提升至函数顶部
2.离开if块,value立刻被销毁
3.如果condition的值为false,就永远不会声明并初始化value
只在当前代码块内生效
function getValue(condition) {
if (condition) {
let value = "blue";
//其他代码
return value;
} else {
// 变量value在此处不存在
return null;
}
// 变量value在此处不存在
}
禁止重复声明
在作用域中已经存在某个标识符,此时再使用let关键字声明它就会抛出错误
var count = 30;
// 抛出语法错误
let count = 40;
//或
let count2 = 30;
// 抛出语法错误
var count2 = 40;
如果当前作用域内嵌另一个作用域,便可在内嵌的作用域中用let声明同名变量
let count = 30, condition = true;
if (condition) {
// 不会抛出错误 内部块中的count会屏蔽全局作用域中的count
let count = 40;
console.log(count); //40
}
console.log(count); //30
const声明
不能在声明变量前访问
const声明常量,其值一旦被设定后不可更改。因此,每个通过const声明的常量必须进行初始化
把常量的作用域限制在当前代码块中。
const声明不会被提升,因此通常放在封闭代码块的顶部,以便整个代码块都可以访问。
常量必须进行初始化
// 有效的常量
const maxItems = 30;
// 语法错误:常量未初始化
const name;
只在当前代码块内生效
if (condition) {
const maxItems = 5;
}
// 此处无法访问 maxItems
禁止重复声明
var message = "Hello!";
let age = 25;
// 这两条语句都会抛出错误
const message = "Goodbye!";
const age = 30;
禁止常量再赋值
const maxItems = 5;
// 抛出语法错误
maxItems = 6;
用const声明对象
const声明不允许修改绑定,但允许修改值。意味着const声明对象后,可以修改该对象的属性值。
const person = {
name: "Nicholas"
};
// 可以修改对象属性的值
person.name = "Greg";
// 抛出语法错误
person = {
name: "Greg"
}
临时死区(Temporal Dead Zone)
let和const不能在声明变量前访问
let和const声明的变量如果在声明之前访问,会触发引用错误
if (condition) {
console.log(typeof value); //引用错误
let value = "blue"; //此时value还位于JavaScript的临时死区中
}
JavaScript引擎在扫描代码发现变量声明时。
- var声明:提升到作用域顶部
- let声明const声明:把声明放到TDZ中
type是在声明变量value的代码块外执行的,此时不value 不在TDZ中
console.log(typeof value); //"undefined"
if (condition) {
let value = "blue"; //此时value还位于JavaScript的临时死区中
}
循坏中的块作用域绑定
for (var i = 0; i < 10; i++) {
}
//此处仍然可访问变量i
console.log(i); //10
// js引擎在预编译阶段会将代码转换成
var i;
for (i = 0; i < 10; i++) {
}
//此处仍然可访问变量i
console.log(i); //10
let 与块级作用域绑定
for (let i = 0; i < 10; i++) {
// i只能在此处访问
}
//抛出错误
console.log(i);
循坏中的函数
循坏里每次迭代同时共享着变量i,循环内部创建的函数全部都保留了对相同变量的引用 。循坏结束时变量i的值为10,所以每次调用console.log(i)时就会输出数字10
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 10个10;
})
//在预编译阶段js引擎会解读为 i为全局作用域中的变量
var funcs = [],
i;
for (i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs.forEach(function (func) {
func(); // 10个10;
})
es5解决方案立即执行函数
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
})
循坏中的let声明
es6解决方案
每次循坏的时候let声明都会创建一个新变量i。并将其初始化为i的当前值
var funcs = [];
for(let i = 0;i<10;i++){
funcs.push(function () {
console.log(i);
})
}
funcs.forEach(function(func){
func(); //输出0-9
})
每次循坏创建一个新的key绑定,因此每个函数都有一个变量key的副本,于是每个函数都输出不同的值
var funcs = [],
object = {
a: true,
b: true,
c: true
};
for (let key in object) {
funcs.push(function () {
console.log(key);
});
}
funcs.forEach(function (func) {
func(); // 输出 a、b、和c
})
let声明在循环内部的行为是标准中专门定义的,它不一定与let的不提升特性相关
循坏中的const声明
当i++时 尝试改变i的值时会报错
var funcs = [];
for (const i = 0; i < 10; i++) {
funcs.push(function () {
console.log(i); //报错
});
}
每次不会修改已有绑定,而是会创建一个新绑定
var funcs = [],
object = {
a: true,
b: true,
c: true
};
for (const key in object) {
funcs.push(function () {
console.log(key);
});
}
funcs.forEach(function (func) {
func(); // 输出 a、b、和c
})
全局作用域绑定
var 会覆盖全局属性
var RegExp = "Hello!";
console.log(window.RegExp); //"Hello!";
var ncz = "Hi!";
console.log(window.ncz); //"Hi!"
console.log(window.RegExp === RegExp); //true 此时 window.RegExp被var RegExp 覆盖了原有属性值
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
参考 1.let 和 const 命令 《1.let 和 const 命令》
深入理解es6
小结
块级作用域绑定的let和const为JavaScript引入了词法作用域,它们声明的变量不会提升,而且只可以在声明这些变量的代码块中使用。如此一来,JavaScript声明变量的语法与其他语言更相似了,同时也大幅降低了产生错误的几率,因为变量只会在需要它们的地方声明。与此同时,这一新特性还存在一个副作用,即不能在声明变量前访问它们,就算用typeof这样安全的操作符也不行。在声明前访问块级绑定会导致错误,因为绑定还在临时死区(TDZ)中。let和const的行为很多时候与var一致。然而,它们在循环中的行为却不一样。在for-in和for-of循环中,let和const都会每次迭代时创建新绑定,从而使循环体内创建的函数可以访问到相应迭代的值,而非最后一次迭代后的值(像使用var那样)。let在for循环中同样如此,但在for循环中使用const声明则可能引发错误。当前使用块级绑定的最佳实践是:默认使用const,只在确实需要改变变量的值时使用let。这样就可以在某种程度上实现代码的不可变,从而防止某些错误的产生。
