块级作用域
两个{ }之间的区域我们就称之为块级作用域
为什么需要块级作用域
在只有var声明变量的时候,只存在全局作用域和函数作用域,导致会出现一些违背我们一般逻辑的现象出现,比如:
1、内层变量覆盖了外层变量
var a = 'outside'
function f (){
console.log(a)
if(true){
var a='inside'
}
}
f()
这段代码本来想要实现的是在if块内使用内部的a变量,在if块外使用全局的a变量,但是因为没有块级作用域+var的变量提升特性,最终执行的代码如下,输出结果为undefined
var a = 'outside'
function f (){
var a=undefined
console.log(a)
if(true){
a='inside'
}
}
f() // undefined
2、变量被泄漏为了全局变量
for(var i=0;i<3;i++){
}
console.log(i) // 3
在上面代码中,i只是想作为循环的变量,循环结束后就没有用处了应该消失,但是被暴露成了全局变量,导致在循环的外面还是可以访问到
块级作用域和函数声明
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
函数声明也是会被提升的,提升到当前作用域的顶部(函数提升优先级 > 变量提升)
ES5中,当前作用域是函数作用域,提升后的代码如下,执行结果:'I am inside'
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
// 重复声明一次函数f
}
f();
}());
ES6中,当前函数声明所处的作用域是if的块级作用域,本来就在首部,不会有任何变化,理论上应该执行外层作用域的f函数,输出'I am outside',但是这样就直接改变了老代码的逻辑,为了兼容,所以在ES6中这段代码在执行的时候会报错
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f(); // TypeError: f is not a function
}());
这是因为ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
- 允许在块级作用域内声明函数。
- 函数声明类似于
var,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
按照这个规定,实际执行的代码如下
function f() { console.log('I am outside!'); }
(function () {
if (false) {
var f
// 重复声明一次函数f
f = { console.log('I am inside!'); }
}
f(); // TypeError: f is not a function
}());
因为var没有块级作用域,所以f存在于那个自调用函数作用域中,f的值是undefined,所以报错
注意事项
块级作用域必须要有{},没有的话,不会被认定为块级作用域名,书写代码时,对于if判断,只有一条语句的时候,可能会省略{}
if( true) let b = 2 // SyntaxError: Lexical declaration cannot appear in a single-statement context
if(true){
let a = 1 // 正常
}
let
不存在变量提升
变量提升:将函数声明(但不赋值)提到当前作用域的顶部
变量提升导致的奇怪现象:变量提升造成了可以在声明该变量前使用该变量的奇怪现象,按照一般逻辑,使用某东西前得先保证这个东西存在
console.log(a) //a
var a = 1
console.log(b) // 谷歌浏览器报错:抛出 ReferenceError: b is not defined
let b = 2
console.log(x); // safari浏览器报错:Cannot access uninitialized variable.
let x = 10;
好像对于let是否有变量提升是有争议的,按照谷歌浏览器的报错,b没有被定义,肯定是没有变量提升的,但是按照safari浏览器的报错:不能使用未初始化的变量,那x应该是被声明了但是没有初始化为undefined,个人觉得是否存在变量提升,就看你对变量提升的概念,如果变量提升=声明变量+初始化undefined的话,那let就没有变量提升,他没有初始化undefined这个过程,如果变量提升=声明变量的话,那他就有变量提升。
暂时性死区
在块级作用域中,用let声明的变量,变量会绑定到这个块级作用域,不会受到外部的影响,在let声明该变量之前。不可以使用该变量。
不可重复声明
在同一作用域内,不可以重复声明同名变量
{
let a= 1
var a= 2 // SyntaxError: Identifier 'a' has already been declared
}
{
let b= 1
let b= 2 // SyntaxError: Identifier 'b' has already been declared
}
所以不可以在函数体内部再次声明形参
function f(a){
let a=1 // SyntaxError: Identifier 'a' has already been declared
}
但是使用var是可以的
function f(a){
var a=1
}
函数形参与函数体是否是同一个作用域?
从上面代码的执行结果来看,在函数体内,用let声明和参数同名的变量报错,说明函数参数和函数体是在同一作用域内
函数参数到底是用let声明的还是用var声明的?
从上面代码的执行结果来看,用var声明和参数同名的变量不会报错,说明函数参数是由var声明的
const
初始化后不可修改
const声明的是只读常量,一经初始化就不可以修改
const A = '你好'
A = '拜拜了你嘞' // TypeError: Assignment to constant variable.
声明时初始化
因为const声明的变量声明后就不可以修改,所以必须在声明时就初始化
const A //SyntaxError: Missing initializer in const declaration
A = '拜拜了你嘞'
不存在变量提升
const和let一样,不存在变量提升
{
console.log(A) //Cannot access 'A' before initialization
const A = 'hello world'
}
暂时性死区
声明变量前的区域不可以使用变量
{
console.log(A) //Cannot access 'A' before initialization
const A = 'hello world'
}
不可重复声明
{
const A = 'hello world'
let A = 'bai' //SyntaxError: Identifier 'A' has already been declared
}
声明变量的方式
- var
- function
- let
- const
- class
class A{
name='xf'
age=12
}
console.log(A) //[class A]
- import
import {aFunc} from './index.js
顶层对象属性和全局变量
浏览器环境下,全局作用域内用var和function声明的会被作为window的属性
let、const、class声明的不会被作为window的属性
node环境下,var声明的变量不会被当作global的属性,其声明变量的作用域为函数作用域
globalThis
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
浏览器:window
node:global
通用:globalThis(ES2020提出)