别高估本身,这道题,有点难!

学无止境,不会的为啥老是那么多?chrome

今天在一个技术群里面,有朋友丢了一道题目,问这个输出是啥?浏览器

粗略一看,输出都是 21 啊,你觉得加个 if(true),大伙就不知道了?函数

而后,就没有而后了…………ui

“光荣”的答错了!spa

正确答案:内部是 21,外部是 1;3d

这个玄妙之处确实就在这个块级做用域 if 里面。指针

假如咱们去掉 if 看题。code

var a = 0;
// if(true){
 a = 1;
function a(){}
a = 21;
console.log("里面",a);
// }
console.log("外部",a);
复制代码

这道题估计没人好意思去问了,毫无疑问,输出的 a 都是 21 啊。cdn

然而在 JS 里面都说没有块级做用域,那为啥 if 这个块会影响最终结果呢?blog

如下为chrome浏览器下的实现

本文主要点:

  • 神奇的 let
  • 函数块级做用域

什么是提高(Hosting)?

主要分为变量提高和函数提高。

变量提高

变量的提高是以变量做用域来决定的,即全局做用域中声明的变量会提高至全局最顶层,函数内声明的变量只会提高至该函数做用域最顶层。

入门级

看题:

console.log(a);
var a = 0;
复制代码

这个并不会报: Uncaught ReferenceError: a is not defined。

而是会输出 undefined。

由于变量提高以后的结果是:

var a;
console.log(a);
a = 0;
复制代码

进阶级

例子1:

var x = 0;
function a(){
    console.log(x);
    let x = 1;
}
a();
复制代码

若是 let x 不会变量提高的话,那么应该 x 输出 0,其实是:

VM296:3 Uncaught ReferenceError: Cannot access 'x' before initialization at a (:3:14) at :1:1

它也不是报错 x not defined,而是 Cannot access。

那这个报错是啥缘由呢?

例子2:

let a = a;
let a = a;
复制代码

你以为会报什么错误呢?

“ a not defined ” 或者 “Cannot access 'a' before initialization” ?

实际上都不是,而是报错: a has already been declared。

这里看起来是:let 也会 “变量提高”,若是不会提高的话,例子1 的 x 应该输出 0 ,例子2 应该报错 a not defined。

可是若是会变量提高,那也说不过去呀, 那上面的例子1 应该输出 undefined 啊。

因而咱们管这叫 “暂时性死区”。

实际上这个既不是咱们理解的变量提高,也不是没有变量提高。那什么是暂时性死区呢?

let 定义变量是有一个“特殊声明”的过程,JS 预解析的时候,先将定义的 let ,const “特殊声明”提早,相似“举手”,JS 引擎规定了同一个做用域,同一个变量只能被一次“举手”。

这里不一样于 var 的定义和赋值,var 的声明是若是已经声明了,后者直接忽略声明。

咱们继续回到本题目来看。

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);// undfined
var a = function (){}

console.log(a); // function a
function a(){}
复制代码

函数表达式不会声明提高,第一个例子输出的是 undefined 而不是 not defined,是由于中了变量 var a 的变量提高。

块级做用域

console.log(a);// undefined
if(true){
    console.log(a); // function a
    function a(){}
}
复制代码

若是是变量提高,是不存在块级做用域的,可是函数提高是存在的,这个预解析以下:

var a; // 函数 a 的声明
console.log(a);// undefined
if(true){
    function a(){} // 函数 a 的定义
    console.log(a); // function a
}
复制代码

其实函数 function a(){} 在通过预解析以后,将函数声明提到函数级做用域最前面,而后将函数定义提高到块级做用域最前面。

注意:这里的函数定义是提高到块级做用域最前面。

再看一题:

try{
    console.log(a);// undefined
    aa.c;
}catch(e){
    var a = 1;
}
console.log(a);// 1
console.log(e);// Uncaught ReferenceError: e is not defined
复制代码

在 catch 里面定义的 a,被声明提早了。可是 catch 里面的 e 在外部没法访问。 若是你认为 catch 是一个“函数做用域”,那么里面的 a 不该该被提高到最外层。实际上 catch 里面遵循的是块做用域。

在 JS 领域内,自己是存在块级做用域的(let const 以外)。

再看原题

为了方便阅读,我再贴一下题目:

var a = 0;
if(true){
    a = 1;
    function a(){}
    a = 21;
    console.log("里面",a);
}
console.log("外部",a);
复制代码

结合上面咱们了解到的知识。首先,if 里面的 function a(){} 会声明提高,将声明" var a" 移到函数级做用域最前面,将函数定义移到块级做用域最前面,预解析以下:

var a;// 函数 a 的声明提早
var a = 0;  // 已经声明了 a,这里会忽略声明 ,直接赋值为 0
if(true){
    function a(){} // 函数定义 a 声明提高到块级最前面
    a = 1; // 这里将 块级做用域最前面的函数 a 重置为 1了。
    // function a(){}; how do ?
    a = 21;
    console.log("里面",a);
}
console.log("外部",a);
复制代码

函数自己是【 定义函数名变量 指针指向 函数内存块】。

函数提高是在块级做用域,可是函数名变量是函数级别的做用域。因此在块级的函数定义(原始代码函数的声明位置)的时候,会将函数名变量同步到函数级做用域,实际上,只有这个时候,在块级做用域外才能访问到函数定义。

预解析以下:

var a = 0;
console.log(a,window.a); // 输出 0 和 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,window.a); // 输出为函数提高的块级做用域的 a, 输出 21,1
    console.log("里面",a);
}
console.log("外部",a);
复制代码

到此为止,应该都理解了吧,不再会答错了吧!若是不理解,再多看几遍试试。

相关文章
相关标签/搜索