如图所示,粗略看一下 应该输出都是21。
其实,正确答案是内部:21,外部:1。
在JS中没有块级作用域,为什么if就会影响最终的结果呢。
本文要点
1、神奇的let
2、函数块级作用域
什么是变量提升(Hosting)
主要分为变量提升和函数提升
变量提升
变量的提升是以变量作用域来决定的,即全局作用域中声明的变量会提升至全局最顶层,函数内部声明的变量只会提升到该函数作用域的最顶层。
入门级
console.log(a);
var a = 0;
这个并不会报:Uncaught ReferenceError: a is not defined。
而是输出:undefined
因为变量提示后的结果是:
var a;
console.log(a);
a = 0;
进阶级
var x =0;
function a(){
console.log(x);
let x = 1;
}
a();
如果let x不存在变量提升,那应该输出0。实际上:
image.png
它不是报错x not defined 而是Cannot access。
例子2:
let a =a;
let a = a;
image.png
这里看起来 好像let 也会"变量提升",如果不提升的话 例子1的x应该输出0,例子2应该报错a not defined。
但是如果变量提升,貌似也不对啊。那上面的例子1应该输错undefined。
我们管着这叫"暂时性死区"。
实际上这个既不是理解的变量提升,也不是没有变量提升,那什么是暂时性死区呢?
let定义变量是有一个"特殊声明"的过程,JS预解析的时候,先将定的 let ,const "特殊声明"提前,类似"举手",JS引擎规定了同一个作用域,同一个变量只能被一次"举手"。
这里不同于vue的定义和赋值,var的声明式如果已经声明了,后者直接忽略声明。
回看例子2:
let a =a; //定义了变量a,我暂时标识为a1
let a = a; // 定义了变量a,我暂时标记为a2
预解析,将a1声明,然后准备将a2声明,这时,JS引擎发现,声明a2的时候,已经有a1声明了。
于是违反了"同一个作用域,同一个变量只能被声明一次"的规定,直接报错。实际上代码中赋值的a变量还没读取。(在读取变量的时候才可能抛变量未定义的错误)。
所以,报错。错误提示:a2已经被声明了(被a1声明了a)。
所以,上述例子1,代码在读取x的时候,发现已经let声明的x,但是并未初始化,才直接报错x无法访问。
那么let变量"特殊声明"是一个什么样神奇的东西呢。
实际上JS引擎是为了解决这个let变量提升时引入的 declareData,在预解析的时候,里面存储了作用域里面所有的let 和 const声明的数据。
事实上,作用域内所有的函数和变量的创建都需要校验是否与declareData的值冲突。
例子3:
var a = 1; // 定义了变量a,暂时标记为a1
let a =2; //定义了变量 a,暂时标记为a2
declareData 声明变量a2,然后准备定义变量a1,发现declareData已经有声明a2了,直接报错,a1被声明了,因为已经由a2声明了变量a。
函数提升
函数提升,类似于变量提升,但是确有些许不同。
函数表达式
console.log(a); //undefined
var a = function(){}
console.log(a); // function a
function a(){};
函数会声明提升,函数表达式不会声明提升,第一个例子输出的是undefined。而不是not undefined。是因为中了var a的变量提升。
块级作用域
console.log(a); // undefined
if(true){
console.log(a); // function a
function a();
}
如果是变量提升,是不存在变量作用域的,但是函数声明提升存在的。这个预解析如下:
var a; // 函数a的声明。
console.log(); //undefined
if(true){
function a(){} //函数a的定义
console.log(a) // function a
}
其实函数function a() 在经过预解析之后,将函数声明提升到函数级作用域最前面,然后将函数定义提升到块级作用域最前面。
注意:这里的函数定义是提升到块级作用域最前面。
再看原题
var a; // 函数a的声明提前
var a = 0; // 已经声明了 a,这里会忽略声明,直接赋值为0
if(true){
function a(){} // 函数定义a声明提升到块级最前面
a = 1; //这里将块级作用域最前面的函数a 重置为1了。
a = 21;
console.log("里面",a);
console.log("外面",a);
}
综合上述,首先 if里面的function a(){}会声明提升,将声明"var a"移到函数级作用域最前面,将函数定义移到块级作用域最前面。
这里有一个问题、函数本身是【定义函数名变量 指针方向 函数内存块】。
函数提升是在块级作用域,但是函数名变量是函数级别的作用域。所以在块级的函数定义(原始代码函数的声明位置)的时候,会将函数名变量同步到函数级作用域,实际上,只有这个时候,在块级作用域外才能访问到函数定义。
预解析如下:
var a = 0;
if(true){
console.log(a,window.a); //函数提升 是块级作用域 输出function a 和 0;
a = 1; // 取作用域最近的块级作用域的function a ,且 被重置为1 本质又是一个 变量的赋值。
console.log(a,window.a) // a是指向块级作用域的a 输出1 和 0;
function a(){} // 函数的声明 将执行函数的变量的定义同步到函数级的作用域。
console.log(a,window.a); //输出1 和1;
a = 21; //仍然是函数定义块级作用域的a 重置为21
console.log("里面",a);
}
console.log("外面",a)
网友评论