Skip to content
On this page

变量提升机制

变量提升(hoisting)

当栈内存(作用域)形成,JS 代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”

  • 声明 (declare):var a (默认值 undefined)
  • 定义 (defined):a=12 (定义其实就是赋值操作)
  • 带 VAR 的在变量提升阶段只是声明,带 FUNCTION 声明和定义在变量提升阶段都完成了

基本数据类型

  1. 带“VAR”的只声明未定义
  2. 带“FUNCTION”的声明和赋值都完成了
  • 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)

遇到函数如何执行

  • 在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” 【带 VAR/FUNCTION 的才是声明】
  • 浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)

带 VAR 和不带的区别

在全局作用域下声明一个变量,也相当于给 WINDOW 全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和 WINDOW 没啥关系)

用 var 定义 a

javascript
console.log(a); // => undefined
console.log(window.a); // => undefined
console.log("a" in window); //=> TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED  in:检测某个属性是否隶属于这个对象
var a = 12;
1
2
3
4

映射机制

javascript
var a = 12; // => 全局变量值修改,WIN的属性值也跟着修改
console.log(a); // => 全局变量A  12
console.log(window.a); // =>WINDOW的一个属性名A  12
a = 13;
console.log(window.a); // => 13
window.a = 14;
console.log(a); // => 14
// => 全局变量和WIN中的属性存在 “映射机制”
// => 不加VAR的本质是WIN的属性
1
2
3
4
5
6
7
8
9

不带 var 定义

javascript
console.log(a); // => Uncaught ReferenceError: a is not defined
console.log(window.a); // => undefined
console.log("a" in window); // =>false
a = 12; // => window.a=12
console.log(a); // =>12
console.log(window.a); // => 12
1
2
3
4
5
6

定义多个变量

定义多个后,直接调用

javascript
console.log(a, b); // =>undefined undefined
var a = 12,
  b = 13; //=> 这样写B是带VAR的
1
2
3

用等号赋值

javascript
var a = (b = 12); // => 这样写B是不带VAR的 => var a=12;  b=12;
1

私有作用域中的区别

私有作用域中带 VAR 和不带也有区别

  1. 带 VAR 的在私有作用域变量提升阶段,都声明为私有变量,和外界没有任何的关系
  2. 不带 VAR 不是私有变量,会向它的上级作用域查找,看是否为上级的变量,不是,继续向上查找,一直找到 window 为止(我们把这种查找机制叫做:”作用域链”),也就是我们在私有作用域中操作的这个非私有变量,是一直操作别人的
javascript
var a = 12,
  b = 12;
function fn() {
  // 私有作用域
  console.log(a, b); // =>undefined 12
  var a = (b = 13); // => var a=13;  b=13;=
  console.log(a, b); // =>13 13
}

fn();
console.log(a, b); //=>12 13
1
2
3
4
5
6
7
8
9
10
11

等号左边变量提升

javascript
/*
 * 变量提升:
 *   var fn; =>只对等号左边进行变量提升
 *   sum = AAAFFF111;
 */
sum();
fn(); // => Uncaught TypeError: fn is not a function

//=>匿名函数之函数表达式
var fn = function () {
  console.log(1);
}; //=>代码执行到此处会把函数值赋值给FN

fn();

//=>普通的函数
function sum() {
  console.log(2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

条件判断下的变量提升

  • 在当前作用域下,不管条件是否成立都要进行变量提升
    • 带 VAR 的还是只声明
    • 带 FUNCTION 的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合 ES6 中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于 VAR

全局作用域下声明

var a; => 在全局作用域下声明的全局变量也相当于给 WIN 设置了一个属性 window.a=undefined

javascript
console.log(a); // =>undefined
if ("a" in window) {
  var a = 100;
}
console.log(a); //=>100
1
2
3
4
5

在私有作用域下声明

javascript
/*
 * 变量提升:无
 */
f = function () {
  return true;
}; //=>window.f=...(TRUE)
g = function () {
  return false;
}; //=>window.g=...(FALSE)
~(function () {
  /*
   * 变量提升:
   *   function g;  //=>g是私有变量
   */
  if (g() && [] == ![]) {
    // =>Uncaught TypeError: g is not a function (此时的g是undefined)
    //=>[]==![]:TRUE
    f = function () {
      return false;
    }; //=>把全局中的f进行修改 window.f=...(FALSE)
    function g() {
      return true;
    }
  }
})();
console.log(f());
console.log(g());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

重名问题的处理

  • 带 VAR 和 FUNCTION 关键字声明相同的名字,这种也算是重名了(其实是一个 FN,只是存储值的类型不一样)
javascript
var fn = 12;
function fn() {}
console.log(fn); // 12
1
2
3
  • 关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)【不管是变量提升还是代码执行阶段皆是如此】
javascript
/*
 * 变量提升:
 *   fn = ...(1)
 *      = ...(2)
 *      = ...(3)
 *      = ...(4)
 */
fn(); // =>4
function fn() {
  console.log(1);
}
fn(); // => 4
function fn() {
  console.log(2);
}
fn(); //=>4
var fn = 100; // => 带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn(); //=>100() Uncaught TypeError: fn is not a function
function fn() {
  console.log(3);
}
fn();
function fn() {
  console.log(4);
}
fn();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

查找上级作用域

当前函数执行,形成一个私有作用域 A,A 的上级作用域是谁,和他在哪执行的没有关系,和他在哪创建(定义)的有关系,在哪创建的,它的上级作用域就是谁

javascript
var a = 12;
function fn() {
    //=>arguments:实参集合
    //=>arguments.callee:函数本身FN
    //=>arguments.callee.caller:当前函数在哪执行的,CALLER就是谁(记录的是它执行的宿主环境),在全局下执行CALLER的结果是NULL
    console.log(arguments.callee.caller);
}
function sum() {
    var a = 120;
    fn();
}
function aa() {
    fn();
}
aa();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
沪ICP备20006251号-1