JavaScript- 详细解析变量提升的处理机制

变量提升

变量提升:在当前上下文中(全局/私有/块级),JS代码执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)
会把当前上下文中所有带VAR和FUNCTION关键字的进行提前声明或者定义

var a = 10;
声明declare:var a ; => 声明是创建变量的过程
定义defined:a = 10;=> 定义是复制的过程
** 带VAR的只会提前声明(默认值是undefined)。带FUNCTION的会提前声明加定义,所以在真实项目中建议用函数表达式创建函数,这样在变量提升阶段只会声明FUNCTION,不会赋值,默认值是undefined,提前使用会报错“function is not a function”

变量提升的意义:能够让我们在创建变量之前使用变量而不报错

练习题:1

/*
* 代码执行之前:全局上下文中的变量提升
* var a;    默认值是undefined
*/
console.log(a);// undefined
var a = 12; // 创建值12, 不需要再声明了(变量提升阶段完成了,完成过的事情浏览器不会重新处理)a = 12赋值
a = 13; // a = 13;创建值13,a = 13
console.log(a);  // 13;
//一共输出两次,分别是undefined、 13

练习题:2

/*
* EC(G)的变量提升
*/
console.log(a);//Uncaught ReferenceError: a is not defined
a = 13;
console.log(a);

练习题:3

/*
* EC(G)变量提升 只有VAR/FUNCTION会变量提升(ES6中的LET和CONST不会)
*/
console.log(a);//Uncaught ReferenceError: Cannot access 'a' before initialization   不能LET声明声明之前使用变量
let a = 12;
a = 13;
console.log(a);

练习题:4

var a = 12;
console.log(a);// 12 全局变量
console.log(window.a);// 12 映射到GO上的属性a

window.a = 13
console.log(a) // 13 映射机制是一个修改另一个也会修改
//----------------------
/*
* EC(G)变量提升
* var a 
*/
if(!("a" in window)){
var a = 1;
function func(){};
}
console.log(a);//"undefined"

基于“VAR或者FUNCTION”在“全局上下文中”声明的变量(全局变量)会映射到GO(全局对象window)上一份,作为它的属性;而接下来是一个修改,另外一个也会跟着修改;
条件判断时,不论条件是否成立都要进行变量提升

  • [老版本]:VAR只声明不定义,FUNCTION声明加定义
  • [新版本]:VAR和FUNCTION都是只声明不定义。

练习题:5

/*
*  全局上下文中的变量提升 
*   func = 函数     函数在这个阶段声明加定义都做了
*/
/*func();//"OK"
function func(){
    var a = 12;
    console.log("OK");
}
*/
/*
*  全局上下文中的变量提升 
*   func  函数在这个阶段只声明加未定义
*/
func();//“function is not a function”
var func = function func(){
// 把原本作为值的函数表达式匿名函数具名化,虽然具名化了,但是具名化后的名字外部不可使用=>不会在当前上下文中创建这个名字
// 当函数执行时,在形成的私有上下文中,会把这个具名化的名字作为私有上下文中的变量(值为当前函数)来进行处理
    var a = 12;
    console.log("OK");
    //func();//=> 递归调用,而不用严格模式下不支持的arguments.callee了
}

在真实时项目当中,为了防止变量提升阶段,在创建函数之前调用函数这种不严谨的代码执行顺,一般建议使用函数表达式方式,把函数作为一个值赋给变量,这样的方式在变量提升阶段,只会声明变量,不会赋值,这样就只能在赋值之后使用函数了;为了保证JS语法规范问题,一般处理时会把匿名函数具名化,但是这个名字在函数外部不可以使用,只有在函数执行,在当前执行上下文中把函数名作为私有变量,值为当前函数,可以在当前上下文中,递归时可以使用,这样可以避免使用严格模式下不支持的arguments.callee。

练习题:6

/*
*   EC(G)变量提升
*   fn => 1 声明加定义函数,输出1
*      => 2 重新定义函数,输出2
*   var fn;已经声明过了,不在重复声明
*      => 4 重新定义函数,输出4 
*      => 5 重新定义函数,输出5
* 全局上下文中有一个全局变量fn,值是输出5的函数(此时window.fn => 5)
*/
fn();//=> 5
function fn(){console.log(1);};//变量提升时处理过了,此处不再处理
fn();//=> 5
function fn(){console.log(2);};//变量提升时处理过了,此处不再处理
fn();//=> 5
var fn = function fn(){console.log(3);}//变量提升时声明处理过了,此时只需要进行变量赋值即可,fn = window.fn => 3
fn();//=>  3
function fn(){console.log(4);};//变量提升时处理过了,此处不再处理
fn();//=>  3
function fn(){console.log(5);};//变量提升时处理过了,此处不再处理
f();//=>  3

练习题:7

/*
* EC(G)变量提升:
* var foo
* func bar(){...}
*/
var foo = 1;//foo = 1;
function bar(){
    if(!foo){
        var foo = 10;
    }
    console.log(foo);
};//变量提升处理过,不在此处里了
bar(); 
/*
* 全新的私有上下文
* EC(bar)
* 初始化作用域链:<EC(BAR),EC(G)>
* 初始化THIS:window
* 初始化ARGUMENTS
* 形参赋值:--
* 变量提升:var foo 默认值是 undefined
* 代码执行: 
    if(!foo){ //!undefined =>true
        var foo = 10;// foo = 10 私有变量
    }
    console.log(foo); // 10
*/

图形解析:
在这里插入图片描述
练习题:8

/*[老版本浏览器]
* EC(G)变量提升
* var a
* function a  0X0000已经声明过了,不再重复操作,直接把函数的堆地址赋值给a
*
*/
var a = 0; // a = 0
if(true){
    a = 1// a = 1
    function a(){}; //变量提升时处理过了,此处不再处理
    a = 21; //a = 21;
    console.log(a); // 21
};
console.log(a); // 21

//-------------------------------------------
/*[新版本浏览器]
* EC(G)变量提升
* var a
* function a 
*
*/
var a = 0; // a = 0 window.a = 0
if(true){//遇到大括号,并且里面有函数,则形成一个块级作用域
    /*
    * 块级私有上下文EC(BLOCK)
    * 初始化作用域链:<EC(BLOCK),EC(G)>
    * 初始化THIS:没有自己的THIS,用的是上下文中的THIS,和箭头函数类似
    * 初始化ARGUMENTS:--
    * 形参赋值:--
    * 变量提升:function a  0x0000
    * 代码执行: 
    * (***这里面遇到的a都是私有的)
    */
    a = 1// window.a = 1
    function a(){}; //因为要兼容ES3/6,function a在全局下声明过,也在私有下处理过,遇到此行代码,私有不会再处理,但是浏览器会把当前代码之前,所有对a的操作,映射给全局一份,以此兼容ES3,但是它后面的代码和全局没有任何关系了!
    a = 21; //a = 21;私有变量
    console.log(a); // 21
};
console.log(a); // 1

老版本浏览器图形解析:
在这里插入图片描述
新版本浏览器图形解析:
在这里插入图片描述
最新版本的浏览器需要向前兼容ES3/5规范

  • 判断题和函数体等不存在块级上下文,上下文只有全局和私有。
  • 不论条件是否成立,带function的都要声明加定义 。

-> 向后兼容ES6规范

  • 存在块级作用域,大括号中出现let、const、function…都会被认为是块级作用域。
  • 不论条件是否成立带function的只是提前声明,不会提前赋值了。

代码执行时遇到大括号,并且里面有函数,则形成一个块级作用域,因为要兼容ES3/6,块级作用域中的函数在全局下声明过,也在私有下处理过,遇到当前函数代码,私有不会再处理,但是浏览器会把当前代码之前,所有对函数变量名的操作,映射给全局一份,以此兼容ES3,但是它后面的代码和全局没有任何关系了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值