全局作用域

当浏览器加载HTML的时候,会提供一个供全局JS代码执行的环境 -> 全局作用域(global/window)
04-JS预解析(变量提升) - 图1

预解析(变量提升)

在当前的作用域中,JS代码执行之前,浏览器会把所有带var/function的代码进行提前声明(declare)或定义(defined)

  • 理解声明和定义

    1. var num = 12;
    2. // declare: var num; -> 告诉浏览器中全局作用域中有一个num变量了
    3. // defined: num = 12; -> 给变量进行赋值
  • var 和 function 预解析的区别

    • var -> 在预解析时只是提前声明
    • function -> 在预解析时提前声明+定义
  • 全局作用域下带 var 声明和不带 var 的区别

    • 不带var:num1 = 12; -> 相当于给 window 增加了一个叫 num1 的属性,属性值是12
    • 带var:num2 = 12; -> 相当于给全局作用域增加了一个全局变量 num2,但是不仅如此,它也相当于给 window 增加了一个叫 num2 的属性,属性值是12
    • 在局部作用域中,设置一个全局变量: function () { total = 100; } -> 相当于给 window 增加一个属性名叫 total,其属性值是100
  • 预解析只发生在当前作用域下

一开始只对window的作用域下进行预解析,只有函数执行的时候才对会函数中的代码进行预解析。

  • 预解析例子

    1. var num = 12;
    2. var obj = {name: "jex", age: 16}
    3. function fn(num1, num2) {
    4. var total = num1 + num2;
    5. console.log(total);
    6. }

    04-JS预解析(变量提升) - 图2

    局部作用域预解析

  • 如何区分局部变量和全局变量

    • 在全局作用域下声明(预解析的时候)的变量是全局变量
    • 在局部作用域中声明的变量和函数的形参

判断一个变量的值,首先要确认它是否为局部变量。如果是局部变量,和外面全局变量没有任何的关系;如果不是局部变量,则往当前作用域的上级作用域进行查找,一直找到window为止(作用域链)。

  • 当函数执行时

首先会形成一个新的局部作用域,然后按照如下步骤执行:
1) 如果有形参,先给形参赋值;
2) 进行局部作用域中的预解析;
3) 作用域中的代码从上到下执行;
函数形成了一个新的局部作用域,保护了里面的私有变量不受外面变量的干扰(外面的修改不了私有变量,私有变量也修改不了外面的变量 -> “闭包”)

  • 局部作用域预解析例子
    1. console.log("global:", total)
    2. var total = 0;
    3. function fn(num1, num2) {
    4. console.log("scope:", total);
    5. var total = num1 + num2;
    6. console.log("calc:", total)
    7. }
    8. fn(100, 200)
    9. console.log("end:", total)
    04-JS预解析(变量提升) - 图3

预解析机制中一些注意情况

  • 预解析时,不管条件是否成立,都会把带 var 的代码进行提前声明

    1. if(!("num" in window)) {
    2. var num = 12;
    3. }
    4. console.log(num) // undefined
  • 预解析时只解析 “=” 左边的,不解析右边 ```javascript // Uncaught TypeError: fn is not a function fn(); // fn是函数表达式 var fn = function () { console.log(“ok”) }

// —————对比函数声明————— fn(); // -> “ok” function fn() { console.log(“ok”) }

  1. - 自执行函数在全局作用域下不进行预解析
  2. ```javascript
  3. // 自执行函数:定义和执行一起完成了
  4. (function (num) {console.log(num)})(100)
  • 函数体中return后的代码不执行了,但会先进行预解析;return后的返回值,不会进行预解析。

    1. function fn() {
    2. console.log(num); // -> undefined
    3. return function () {
    4. console.log("test", num);
    5. }
    6. var num = 100;
    7. }
    8. fn();
  • 在预解析时,如果变量名已经声明,不会执行重复声明,但是可以重新赋值。

    1. // 预解析步骤如下:
    2. // fn=xxxfff111 -> 声明+定义
    3. // var fn; -> 声明(不会执行重复的声明,所以此时fn=xxxfff111)
    4. // fn=xxxfff222 -> 声明(不会执行重复的声明)+定义(但是可以重新赋值),所以此时fn=xxxfff222
    5. // -> 最终预解析结果是:fn=xxxfff222
    6. fn(); // -> 222
    7. function fn() { console.log(111) };
    8. fn(); // -> 222
    9. var fn = 10; // 会进行赋值操作,fn=10
    10. fn(); // -> TypeError: fn is not a function
    11. function fn() {console.log(222) };
    12. fn()

    所以结果是输出两次222后报错,停止运行程序。

查找上级作用域

上级作用域只跟函数在哪定义有关,跟函数在哪里执行没有任何关系。

  1. var num = 12;
  2. function fn() {
  3. var num = 120;
  4. return function () {
  5. console.log(num)
  6. }
  7. }
  8. var f = fn;
  9. f(); // -> 120
  10. ~function () {
  11. var num = 1200;
  12. f() // -> 120
  13. }();

04-JS预解析(变量提升) - 图4