美文网首页
面试题解析:考察浏览器对于{}块级作用域的兼容操作

面试题解析:考察浏览器对于{}块级作用域的兼容操作

作者: microkof | 来源:发表于2021-03-27 13:49 被阅读0次

    题目

    求打印结果。

        {
          function foo() {123}
          foo = 1;
          foo = 2;
          function foo() {456}
          foo = 10;
        }
        console.log(String(foo));
    

    你可能说,要么是10,要么是下面的函数声明。很遗憾,结果是2。测试如下:

        {
          console.log(0, String(foo), String(window.foo)) // "function foo() {456}" "undefined"
          function foo() {123}
          debugger;
          console.log(1, String(foo), String(window.foo)) // "function foo() {456}" "function foo() {456}"
          foo = 1;
          debugger;
          console.log(2, String(foo), String(window.foo)) // "1" "function foo() {456}"
          foo = 2;
          debugger;
          console.log(3, String(foo), String(window.foo)) // "2" "function foo() {456}"
          function foo() {456}
          debugger;
          console.log(4, String(foo), String(window.foo)) // "2" "2"
          foo = 10;
          debugger;
          console.log(5, String(foo), String(window.foo)) // "10" "2"
        }
        debugger;
        console.log(6, String(foo), String(window.foo)); // "2" "2"
    

    为什么会这样?

    骑墙的“非严格模式”、变量初始化提升现象

    非严格模式

    ES6引入了块级作用域,明确允许在块级作用域之中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

    但是,浏览器为了兼容老的代码,会宽泛看待块级作用域,也就是说,如果将{}视为包裹语句的关键字,也能解释得通的话,那么,引擎会同时既将{}视为包裹语句的关键字,同时也视为块级作用域,以避免报错。

    当然,浏览器的这个做法是不规范的,所以,ES6也引入了严格模式,即"use strict",在顶部加上"use strict"的话,就会报错了。

    现在我们知道,非严格模式下,{}里面存在函数声明的话,会在全局和块级作用域都出现变量,指向不同的内存地址。同样的,var声明也是这样的处理方式。

    但是,letconst就不是,因为它俩是新语法,才不搞这种恶心的兼容。

    变量提升现象

    变量赋值和函数声明都会提升,原则跟书写顺序无关,原则是:

    • var的变量,会提升到最顶部,隐式生成var a;,此时a等于undefined。
    • 下面是函数声明提升,会变量名和函数内容一起提升,并不会只提升变量名,不提升函数体。
    • 再下面是a = 1234,也就是变量赋值。

    非严格模式+变量提升,就产生下面的结果:

        console.log(String(foo), String(window.foo)); // 全都undefined
        debugger; // 在这个点,你会发现全局作用域有foo变量,为undefined
        {
          debugger; // 在这个点,你会发现局部作用域有foo变量,是函数,全局foo变量依然undefined
          function foo() {123}
          debugger; // 在这个点,你会发现块级作用域和全局作用域全都有foo函数
          console.log(String(foo), String(window.foo)); // 全都打印出函数体
        }
        console.log(String(foo), String(window.foo)); // 全都打印出函数体
    

    可见,非严格模式的不规范兼容操作,发生在显式声明函数之后,也就是function foo() {123}之后,浏览器做的操作是:把内部foo函数复制一份(不同的内存地址),赋值给全局foo。

    如果后续再次遇到显式的函数声明,就再做一次兼容操作。比如function foo() {456}。

    foo = 1是什么作用?

    简单的常识是,未声明、直接赋值的变量,是全局变量。如果题目中没有函数声明,那么它毫无疑问指向全局变量,但是,由于有函数声明,且提升到顶部,所以,foo = 1其实修改的是块级作用域的foo变量,foo自此由指向函数变为指向1

    foo = 2之后,foo指向2。

    在这道题里,由于有隐式函数提升,所以foo = 1这种语句完全合法。

    注意,由于没使用var,所以这里不需要讨论var变量提升。

    全盘过一遍

    1. 函数声明提升且有一次重赋值,所以局部作用域的“第0句”语句就是function foo() {456}

    2. function foo() {123}显式声明会促使浏览器做兼容,给全局foo赋值一次,此时内外变量都是函数456。

    3. foo = 1;foo = 2;重赋值局部的foo,现在外部foo是函数456,内部foo是数字。

    4. function foo() {456}促使浏览器再一次兼容操作,内部foo的值(此时是2)会赋值给外部foo,现在内外foo都是2。

    5. foo = 10让内部foo变成10,外部foo依然是2。

    6. 最后一句console,由于写在全局作用域,它打印的foo都是全局foo,所以都是2。

    相关文章

      网友评论

          本文标题:面试题解析:考察浏览器对于{}块级作用域的兼容操作

          本文链接:https://www.haomeiwen.com/subject/iuaehltx.html