在 ES6 之前,JavaScript 只有两种作用域: 全局作用域和私有作用域, 没有块级作用域的概念,使用 var 关键字声明的变量在{}
外依然能被访问到(函数内使用 var 声明的变量只能在函数内访问,如果不使用 var 则是全局变量)。
ES6 新增关键字let,可以使用 let 关键字来实现块级作用域。let 声明的变量只在 let 命令所在的代码块 {}
(离let最近的{}
)内有效,在{}
之外不能访问。
实现块级作用域
let声明的变量只在 let 命令所在的代码块{}
内有效。
// 块级作用域
{
let s=3;
console.log(s);//3
}
console.log(s);//s is not defined
不存在变量提升
let声明的变量不会进行变量提升,但是有一个语法检测的机制,会在代码自上而下执行之前先进行语法检测,看是否有语法错误(是否有重复声明的变量、是否有let声明的变量在声明前访问),如果有就直接报错。
console.log(a);//Uncaught ReferenceError: a is not defined
let a=2;
阻断了与window的关系
let声明的变量不会成为window对象的属性。
let a=2;
console.log(window.a);// undefined
不能重复声明
let a=2;
console.log(a);
var a=3; // 不能进行重复的声明:Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(3);
注意:如果在{}
中用function声明变量,代码块内部会进行语法检测,如果发现有重名的变量就直接报错。
{
function fn(){};
var fn = 2;//SyntaxError: Identifier 'fn' has already been declared
}
暂时性死区
如果代码块中存在let和const命令,这个代码块从一开始就对这些命令声明的变量形成了封闭作用域。只要是在声明之前就使用这些变量,就会报错。代码块内,在使用let声明变量之前的那个区域内,该变量都是不可用的(不允许访问),一旦访问就报错。这就是“暂时性死区”(temporal dead zone,简称 TDZ)。
可以理解为,只要一进入当前作用域,经过词法检测就已经知道待使用变量是谁了,但是不可获取,只有等到声明变量的代码执行后,才可以获取和使用该变量。
let a=10, b=10;
let fn=function(){
console.log(a);//报错 a is not defined
let a=b=20;
console.log(a,b);
}
fn();
关于typeof
在ES5中,用typeof判断没有声明过的变量的数据类型,返回值为“undefined”
,这样的结果并不是很合理。
typeof a;//"undefined"
ES6新增let、const关键字之后,用let、const声明一个变量之前,声明前的那块区域就是暂时性死区,不能在声明之前访问变量,如果访问就会报错,这样在理论上更严谨。
typeof a;
let a = 2;//ReferenceError: a is not defined
块级作用域与函数声明
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。ES6 引入了块级作用域,明确允许在块级作用域之中声明函数,但是规定函数声明语句的行为类似于let
,在块级作用域之外不可引用。
故而**ES6的浏览器
**为了兼容旧代码,有自己的行为方式:
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
虽然ES6的浏览器兼容了旧代码,但是其他的环境还是将块级作用域的函数声明当作let
处理,为了避免各个环境中的差异,应该尽量避免在块级作用域内声明函数,或者选择使用函数表达式。
需要注意的是,ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
练习
let a=10;
b=10;
let fn=function(){
let a=b=20;
console.log(a,b);//20,20
}
fn();
console.log(a,b);//10,20