ES5及以前版本的JavaScript采用var声明变量,且不支持块级作用域。
JavaScript中,变量实际创建的位置取决于与如何声明该变量。
ES6新增了两个关键字let和const用于控制作用域。
let命令
类似于var,但所声明的变量只在let命令所在的代码块有效。
{
let a=10;
var b=1;
}
console.log(b);
console.log(a);
1
Uncaught ReferenceError: a is not defined
let可以防止循环变量变成全局变量
for(var i=0;i<10;i++){
console.log(i);
}
console.log(i);//输出10
for(let i=0;i<10;i++){
console.log(i);
}
console.log(i);
Uncaught ReferenceError: i is not defined
var a=[];
for(var i=0;i<10;i++){
a[i]=function(){
console.log(i);
};
}
a[6]();
//10
var a=[];
for(let i=0;i<10;i++){
a[i]=function(){
console.log(i);
};
}
a[6]();
//6
for循环在设置循环变量的部分是一个父作用域,循环体内部又是一个独立的子作用域。
for(let i=0;i<3;i++){
let i='abc’;
console.log(i);
}
//输出三次abc
变量提升
var声明的变量无论其实际声明位置在何处,都会被是为声明于所在函数(或全局)的顶部
function getValue(c){
if(c){
var value='blue';
return value;
}else{
return null;
}
}
js自动调整为=>
function getValue(c){
var value;
if(c){
value='blue';
return value;
}else{
return null;
}
}
暂时性死区
ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。
在代码块内,使用let命令声明变量之前,该变量都是不可用的,这个区域被称为“暂时性死区”。
var temp=123;
if(true){
temp='abc';
let temp;
}
不允许重复声明
function fn(){
var a=10;
var a=1;
}
//不报错
以下代码都报错
function fn(){
let a=10;
let a=1;
}
function fn(){
let a=10;
var a=1;
}
function fn(){
var a=10;
let a=1;
}
作用域:变量或函数在起作用的区域。
全局作用域
在所有函数之外定义的变量拥有全局作用域,该变量为全局变量。
全局变量可以在当前页面中任何JavaScript代码中访问。
函数作用域
在函数中声明的变量(包括函数参数)指定在其所声明的函数内被访问。
块作用域
由{ }界定的代码区域,let声明的变量具备可访问块作用域。
作用域链
每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。
当JavaScript查找变量x的时候( 变量解析,variable resolution),会从当前作用域开始跟随作用域链向上查找,直到找到x变量的声明,若到达全局作用域中仍未找到,则抛出一个引用错误(ReferenceError)异常。
没有块级作用域的缺点:
内层变量可能会覆盖外层变量
var tmp=new Date();
function f(){
console.log(tmp);
if(false){
var tmp='hello';
}
}
f();
用来计数的循环变量泄露为全局变量
var s='hello';
for(var i=0;i<s.length;i++){
console.log(s[i]);
}
console.log(i);
块作用域
ES6允许块级作用域任意嵌套,一对{}即为一个块级作用域。
内层作用域可以定义与外层作用域同名变量。
块级作用域的出现使得ES5中惯用的IIFE(立即执行匿名函数)不再必要了。
function fl () {
let n = 5;
if (true) {
let n = 10;
}
console.log(n);
}
fl();
//5
const命令
声明一个只读的常量,一旦声明,其值不能改变且必须立即初始化。
除此之外,与let用法一致。
当常量保存的不是一个值,而是一个地址的时候,该常量所引用的对象是可以更改成员的,只是不能更改该常量保存的地址。
const foo={y:10};
foo.x=100;
console.log(foo.x);
foo={n:1000};
100
Uncaught TypeError: Assignment to constant variable.
浏览器的顶层对象为window,Node的为global。
var定义的变量会关联到顶层对象中,let和const不会。
练习:
1.let不能重复声明
var count = 10;
let count = 20;
console.log(count);
Uncaught SyntaxError: Identifier 'count' has already been declared
//count已经被声明
2 .块级作用域
var count = 10;
if( count > 0){
let count = 40;
}
console.log(count);
10
//一对{}就是一个块级作用域,JS查找变量x会从当前作用域开始跟随作用域链向上查找,直到找到x变量的声明,count当前作用域的值为10
3 .var声明的变量只支持全局作用域和函数作用域,let声明的变量还是支持块级作用域。
let和const都支持块级作用域。
const声明的是常量,只能在声明时赋值,其他地方不能更改其值。
let声明的变量不支持变量提升。
let声明的变量都不可以被重复声明。
4 .浏览器的顶层对象为window,Node的为global。
var定义的变量会关联到顶层对象中,let和const不会。
var name="es6";//var会关联
console.log(window.name);
es6
let x = 100;//let不关联
console.log(window.x);
undefined
5 .
TDZ: 暂时性死区 Temporal Dead Zone
ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。
在代码块内,使用let命令声明变量之前,该变量都是不可用的,这个区域被称为“暂时性死区”。
IIFE:立即执行匿名函数
(function () {
statements
})();
//这个匿名函数拥有独立的词法作用域。
//这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
经典面试题:
输出为10
a[6]执行的时候,for已经执行完成,此时全局变量i= 10;调用函数此时i= 10
变量i是全局的,函数执行时输出的是全局作用域下的值
把var变成let后 输出为6
因为每次循环都会产生一个块级作用域,每个块级作用域的变量是不同的,函数执行的时候输出的是自己父亲作用域的i
此时全局作用域下没有i,执行a6; 进入函数,此时的i是第七次循环的i,为6