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

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

全局作用域

当浏览器加载HTML的时候,会提供一个供全局JS代码执行的环境 -> 全局作用域(global/window)

预解析(变量提升)

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

  • 理解声明和定义
var num = 12;
// declare: var num; -> 告诉浏览器中全局作用域中有一个num变量了
// 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的作用域下进行预解析,只有函数执行的时候才对会函数中的代码进行预解析。

  • 预解析例子
var num = 12;
var obj = {name: "jex", age: 16}
function fn(num1, num2) {
  var total = num1 + num2;
  console.log(total);
}

局部作用域预解析

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

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

  • 当函数执行时

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

  • 局部作用域预解析例子
console.log("global:", total)
var total = 0;
function fn(num1, num2) {
  console.log("scope:", total);
  var total = num1 + num2;
  console.log("calc:", total)
}
fn(100, 200)
console.log("end:", total)

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

  • 预解析时,不管条件是否成立,都会把带 var 的代码进行提前声明
if(!("num" in window)) {
  var num = 12;
}
console.log(num) // undefined
  • 预解析时只解析 ”=” 左边的,不解析右边
// Uncaught TypeError: fn is not a function
fn();
// fn是函数表达式
var fn = function () {
  console.log("ok")
}

// ----------对比函数声明----------
fn(); // -> "ok"
function fn() {
  console.log("ok")
}
  • 自执行函数在全局作用域下不进行预解析
// 自执行函数:定义和执行一起完成了
(function (num) {console.log(num)})(100)
  • 函数体中return后的代码不执行了,但会先进行预解析;return后的返回值,不会进行预解析。
function fn() {
  console.log(num); // -> undefined
  return function () {
    console.log("test", num);
  }
  var num = 100;
}
fn();
  • 在预解析时,如果变量名已经声明,不会执行重复声明,但是可以重新赋值。
// 预解析步骤如下:
// fn=xxxfff111 -> 声明+定义
// var fn; -> 声明(不会执行重复的声明,所以此时fn=xxxfff111)
// fn=xxxfff222 -> 声明(不会执行重复的声明)+定义(但是可以重新赋值),所以此时fn=xxxfff222
// -> 最终预解析结果是:fn=xxxfff222
fn(); // -> 222
function fn() { console.log(111) };
fn(); // -> 222
var fn = 10; // 会进行赋值操作,fn=10
fn(); // -> TypeError: fn is not a function
function fn() {console.log(222) };
fn()

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

查找上级作用域

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

var num = 12;
function fn() {
  var num = 120;
  return function () {
    console.log(num)
  }
}
var f = fn;
f(); // -> 120

~function () {
  var num = 1200;
  f() // -> 120
}();