变量提升机制

111 阅读4分钟

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

  • 声明(declare):var a (默认值undefined)
  • 定义(defined):a = 12(定义其实就是赋值操作)

变量提升阶段 带“var”的只声明未定义 带“function”的声明和赋值都完成了

  • 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,

  • 因为此时函数中存储的都是字符串而已)

  • 在全局作用域下声明的函数或者变量都是“全局变量”,同理,在私有作用域下声明的变量是“私有变量”(带var或function的才是声明)

  • 当代码执行遇到创建函数这部分代码后,直接跳过即可(因为在提升阶段就已经完成函数的赋值操作了)

var a = 12;
var b = a;
b = 13;
console.log(a);

function sum() {
    var total = null;
    for (var i = 0; i < arguments.length; i++) {
        var item = arguments[i];
        item = parseFloat(item);
        !isNaN(item) ? total += item : null;
    }
    return total;
}
console.log(sum(12, 23, '34', 'AA'));

变量提升.png

带var和不带var的区别

加var是变量(全局变量或私有变量) 不加var本质是window的属性

  • 全局变量和window中的属性存在"映射机制"

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

console.log(a); // undefined
console.log(window.a); // undefined
console.log('a' in window); // true => 在变量提升阶段,在全局作用域中声明了一个变量a,此时就已经把a当做属性赋值给window了,只不过此时还没有给a赋值,默认undefined(in:检测某个属性是否隶属于这个对象)
var a = 12; // 全局变量值修改,window的属性也跟着修改
console.log(a); // 全局变量a
console.log(window.a); // window的一个属性名a
console.log(a); // Uncaught ReferenceError: a is not defined => 浏览器首先会看这个a是不是变量,并没有var声明;接着看window下有无这个属性,此时window下也没有('a' in windowfalse可以证明),就按照没有声明变量来处理,因此这行会报错
console.log(window.a); // undefined => 'a' in windowfalse,说明window下没有属性a
a = 12; // => 代码简写,等同于window.a = 12;
console.log(a); // 12 => 因为上一行的执行,这里也是代码简写,等同于打印window.a
console.log(window.a); // 12
var a = 12, b = 13; // 这样写b是带varvar a = b = 12; // 这样写b是不带var
console.log(a, b); // undefined, undefined => 变量提升
var a = 12, b = 12;
function fn () {
    console.log(a, b); // undefined, 12 // => a变量提升,b是全局变量
    var a = b = 13;
    console.log(a, b); // 13, 13 // a私有变量,b全局变量
}
fn();
console.log(a, b); // 12, 13 // => a全局变量,b全局变量
  • 条件判断下的变量提升 在当前作用域,不管条件是否成立都要进行变量提升
    • 带var的还只是声明
    • 带function的在老版本浏览器渲染机制下,声明+定义都出汗里,但是为了迎合es6的块级作用域,新版浏览器对于【在条件判断中的函数】,不管条件是否成立,都只是声明,没有定义
console.log(a); // undefined
console.log('a' in window); // true
if (1 === 2) {
    var a = 12;
}
console.log(a); // undefined
console.log(fn); // undefined => 即使条件不成立,
if (1 === 2) {
    console.log(fn);
    function fn () {
        console.log('ok');
    }
}
console.log(fn); // 函数本身 => fn() { console.log('ok') }
console.log(fn); // undefined => 变量提升 function fn;
if (1 === 1) {
    console.log(fn); // 函数本身:当条件成立,进入判断体中(在es6中它是一个块级作用域)第一件事并不是代码执行,而是类似于变量提升一样,先把fn声明和定义了,也就是判断体中代码执行之前,fn就已经赋值了
    function fn () {
        console.log('ok');
    }
}
console.log(fn); // 函数本身 => fn() { console.log('ok') }
  • 变量提升机制下的重名处理
    • 带var和function关键字声明相同的名字,这种也算是重名
    • 关于重名的处理:如果名字重复了,不会重新声明,但是会重新定义(赋值)
var fn = 12;
function fn () {}
console.log(fn); // 函数本身 fn () {}
  • let和const创建的变量或函数不存在变量提升机制
    • 切断了全局变量和window属性的映射机制;
    • 在相同作用域中,基于let不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错)
    • 虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测);自上而下查找当前作用域下所有变量,一旦发现重复的,直接抛出异常,代码也不会再执行了(虽然没有把变量提前声明定义,但是浏览器知道当前作用域下有哪些变量)
console.log(a); // Uncaught ReferenceError: a is not defined
let a = 12;
console.log(window.a); // undefined
console.log(a); // 12
  • 变量提升之全局变量与私有变量
var a = 12, b = 13, c = 14;
function fn(a) {
    console.log(a, b, c); // 12, undefined, 14
    var b = c = a = 20;
    console.log(a, b, c); // 20, 20, 20
}
fn(a);
console.log(a, b, c); // 12, 13, 20 // => 这里a为什么是12