今天我们来聊一聊,变量提升到底是个啥…

复习

首先,我们来复习一下,什么是堆栈内存?

1.png
前提:

Stack:栈内存

  • 也叫ECStack执行环境栈
  • 为了后期代码执行中,可以访问到GO中的那些属性和方法,浏览器在全局上下文EC(G)中,默认声明了一个变量:”window”,存储到VO(G)中,并且让window存储GO的地址,后期基于window.xxx就可以访问到那些内置的属性和方法,所以经常把window称为全局对象
  • 编写代码的时候,可以省略window
    • alert(‘…’)=>window.alert(‘…’)

Heap:堆内存

  • 一开始加载页面,浏览器就会在开辟的堆内存中,分配一块空间,用来存储一些内置的属性和方法,也称为GO(global object)全局对象
    • alert confirm prompt isNaN…

在全局上下文中【这个前提很重要】

  • 基于var/function声明的变量,是直接存储到GO中的,相当于给window设置了相关的属性
  • 而基于let/const/class声明的变量是存储到VO(G)中的,他们才是纯正血统上的全局变量,和window没啥关系
  • 没有基于任何关键字修饰/声明的变量,其实是省略了window,核心也是给GO增加了一个属性

在全局上下文,代码执行的过程中:

  • 如果是window.xxx访问,直接到GO中找即可
  • 如果是直接输出一个变量,则先去看VO(G)中是否有,他里面有,获取的就是全局变量;如果没有,则继续去GO中找,如果有就是全局对象中的属性,如果没有就直接报错

变量提升机制

1.什么是变量提升?

在“当前上下文”中,代码执行之前,浏览器首先会把所有带var/function关键字的进行提前声明或者定义,这种机制就叫做变量提升

【变量提升阶段】

  • 带var的只是提前声明
  • 带function的此阶段声明+定义{赋值}都完成了
  • 只提升等号左边的变量。
  • 不管条件成不成立,都要进行变量提升。
  • 新版标准浏览器,在{ }块级作用域中,对于function只声明不定义

2.let/const/import/class声明的变量不存在变量提升

//分析过程
/*
 * EC(G)
 *   VO(G) / GO
 *     fn1 ----> 0x001 [[scope]]:EC(G)    
 *     fn2 
 * 
 * 变量提升: 
 *    function fn1(){console.log('OK');} 声明declare+定义defined
 *    var fn2;
 */
----------------------------------------------------------------------- 
fn1(); //'OK'
fn2(); //Uncaught TypeError: fn2 is not a function   fn2===undefined
function fn1() {
   console.log('OK');
 }
 var fn2 = function () {
   console.log('NO');
 }; //fn2 ---> 0x002 [[scope]]:EC(G)
 fn1();//'OK'
 fn2();//'NO'
复制代码

3.重复声明的问题

var

对于var的不会进行重复声明,但是会重新赋值

  • 在变量提升阶段,看到第一行var num ,会声明一个变量num,此时看到第二行还有一个就不用再声明了
/
* EC(G)
*   VO(G)/GO
*	num: 当第二行变量提升时就先判断存在不,看到第二行有就不在声明了
*/
var num=2;
var num=3;
console.log(num);
复制代码

function

对于function的在变量提升阶段是声明和定义一起完成的,【先定义】如果遇到重复声明定义的,会进行重新赋值

函数创建 会开辟堆地址 0X001
分为三部分

  • 1 作用域:[scope]:EC(G) 函数在哪个上下文中创建的,那么它的作用域就是谁
  • 2 代码字符串
  • 3 键值对 name:’fn’ //函数名 length:0 形参个数
//EC(stack)
//   EC(G)
//	VO(G)/GO
//	  变量提升 function fn=>AAFF000=>AAFF001=>12   var fn=>不会在重新声明  
---------------------------------------------------------------------------------
console.log(fn);//函数
function fn(){ console.log(1); }//不执行
console.log(fn);//函数
var fn = 12;
console.log(fn);//12
function fn(){ console.log(2); }//不执行
console.log(fn); //12
复制代码

2.png

4.推荐使用函数表达式,确保函数执行只能放在“创建函数代码”的下面,保证逻辑的严谨性

 匿名函数的 函数的表达式 :把一个匿名函数作为值赋给一个变量 或者赋给一个事件绑定  btn.onclick=fn;
复制代码

普通函数和*匿名函数的函数表达式区别:

变量提升区别

  • 普通函数:在变量提升阶段 声明+定义
  • 匿名表达式:在变量提升阶段 只声明

[练习题]

/*
*  EC(G)
*     VO(G)/GO
*	 fn======》xxx01=》2
*   变量提升阶段 fn
*/
-------------------------------------------------------------------------------
console.log(fn); //函数本身
function fn(){    
    console.log(1);//此步略过
}
var fn=2;//更改fn的值 重新赋值      
console.log(fn) //2
复制代码
/*
*
*   EC(G)
*      VO(G)/GO
*           num ======>1======>2
*           fn =====>ex001
*              =====>ex002
*              =====>ex003
*              =====>ex004  console.log(4)
*              100
*               
*    fn堆内存  ex001  作用域 scope([EC(G)]),函数体,name:fn,length:0 //形参的个数
* 
*   变量提升
*       num 
*       fn
*/
----------------------------------------------------------------------------------------
console.log(num); //undefined
var num = 1;//此步变量提升略过
console.log(num);//1
var num = 2; //此步变量提升略过
console.log(num);//2
fn();// 4
function fn() {
    console.log(1);
}
function fn() {
    console.log(2);
}
fn();//4
function fn() {
    console.log(3);
}
fn = 100;
function fn() {
    console.log(4);
}
fn();//报错 fn is not a function
复制代码
<script>
        console.log(a);//undefined
        console.log(func);//[Function: func]
        var a=10;
        function func(){
            console.log(b);//undefined
            var b=20;
            console.log(b);//20
        }
        func();
        console.log(a);//10
        console.log(func);//[Function: func]
    </script>
复制代码

3.png

5.变量提升的特殊性

1)条件判断

不论判断条件是否成立,都会进行变量提升

在当前上下文中,变量提升阶段,不论条件是否成立,都要进行变量提升「条件是否成立,是执行代码阶段确定的事情,变量提升的时候,代码还没执行呢」

  • var :还是和之前一样只声明不赋值
  • function:
    • 新版本浏览器中的 “判断体中出现的function”在变量提升的时候 也只是声明 但不赋值了
    • 在IE 10 及以前以及谷歌等浏览器低版本的状态下还是声明和定义(仅限判断语句)

[练习题]

/*
 * EC(G)
 *   VO(G) / GO
 *     a「window.a」
 * 
 * 变量提升:
 *   var a;
 */
 ---------------------------------------------------------------------
console.log(a); //undefined
if (!('a' in window)) {  //'a' in window===true
  var a = 13;
}
console.log(a); //undefined
复制代码

console.log(a);//undefined:不管条件是否成立,都会进行变量提升,var a;
if(1==2){
  var a=12;// 条件不成立,所以进不来
}
console.log(a);//undefined
复制代码
console.log(fn);// 在新版本浏览器中,判断条件中的function相当于只是声明(跟var一样),所以undefined
if(1===1){
	 //此时条件成立 进来的第一件事情还是先定义函数(也是为了迎合ES6中的块作用域)
   console.log(fn);//函数体 
   function fn(){
       console.log(1)
   }
  console.log(fn);//函数体 
}
console.log(fn); //函数体
复制代码

在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升

console.log(fn);// undefined 在新版本浏览器中,不管条件是否成功,都会进行变量提升,function 只声明,
if(1==1){
    console.log(fn);// fn 函数:在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升   
                    // 再从上往下执行代码,此时fn 定义完成。
   function fn(){
       console.log("ok");
   }
}
console.log(fn) // 条件成立,给fn进行了赋值,打印出fn函数
复制代码

在条件判断下,如果有function定义的变量,在这个function这个函数后面的更改变量的值,更改的都是私有变量。

 /*
 *EC(G)
 *	VO(G)/GO
 * 	 	a-----------1
 *EC(block)
 *  a:21
 *
 * 变量提升 var a;function a
 */
 ---------------------------------------------------------------------
 var a=0;
 if(true){
    a=1;
    function a(){}
    a=21;
    console.log(a);//21
}
console.log(a);//1
复制代码

2)只对等号左边的做变量提升

//变量提升 fn
console.log(fn);//undefined
console.log(fn(1,2));//报错
var fn=function (n,m){
    return n+m;
 }
console.log(fn(3,4));//7
复制代码

4.png

/*
*	EC(G)
* 	VO(G)/GO
*   	fn:undfiend
*			sum===>x001
*
*变量提升
*		var fn,sum/obj
*
/
--------------------------------------------------------------------------
sum();//2
fn();//报错 is not function
var fn=function(){
    console.log(1);
};

function sum(){
    console.log(2);
}

fn();//1
sum();//2

console.log(obj.f1);//TypeError: Cannot read property 'f1' of undefined
var obj={
    f1:function(){
       console.log(1)
    }
}
复制代码

3)return

return 下面的代码虽然不能执行,但是可以进行变量提升,return 后面的代码不进行变量提升

4) 自执行函数

自执行函数在当前所在的作用域中不进行变量提升(自执行函数自己所形成的私有作用域照常进行)

function f2(){
   console.log("f2");
}

 
// 自执行函数在此处不进行变量提升
(function (){
   console.log(a);// undefined, 照常进行变量提升
   var a=3;
})();
复制代码

5)带var 和不带var的区别

  • 带var 的时候就是声明变量,不带var的时候,没有变量提升,
  • 带var 声明的变量(是不可配置的),用delete 删除不掉,不带var 的变量 可以删除掉(是可配置的)

在全局作用域下,带var 还是不带var 都是给全局window添加了一个属性,属性名就是此变量,属性值就是变量值

console.log(a); //undefined
var a=3;
b=6;
console.log(window.a);//3
console.log("a" in window);//true
复制代码

【 判断一个对象到底有没有一个属性】:用 “属性名” in 对象,如果返回值是false 说明就是不存在,如果是true说明就是存在。

obj={"name":"lili"}; 

console.log(“name” in obj )// true  说明name就是obj的属性
console.log("age" in obj)//false    说明age 不是obj的属性
复制代码

6.in 操作符

属性名in对象,验证这个对象有没有这个属性,有 返回 true ;没有 返回 false 。

//验证 name或者age是不是obj的属性
var obj={
   name:'东方淼淼'
}

 if('age' in obj){
     console.log('age存在');
 }
//AGE是OBJ属性返回TRUE 不是它的属性返回False
复制代码

es6 中let

1、es6不存在变量提升

console.log(a);//Uncaught ReferenceError: a is not defined
let a=2;
复制代码

2、阻断了与window的关系

let a=2;
console.log(window.a);// undefined
复制代码

3、不能重复声明

es6中没有变量提升,但是有一个自我检测的一个机制,在代码自上而下执行前,会先进行检测,看是否有重复声明的变量,如果有的话,就先报错

let a=2;
console.log(a);
var a=3; // 不能进行重复的声明:Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(3);
复制代码
let a=2;
console.log(a);// 这里不会输出,在代码执行前先进行语法检测,发现重复声明变量,直接报错。此时代码还没从上而下执行
var a=3;
console.log(3);
复制代码

let a=10,
b=10;
let fn=function(){
    console.log(a);
    // 函数执行,形成一个私有作用域,这里没变量提升,但是有自我检测机制,知道用let声明了一个变量,进行了记录,不存在变量提升,不能在let 之前进行获取变量,所以报错:a is not defined
    let a=b=20;    
    console.log(a,b);
};
fn();
console.log(a,b)
复制代码
let a=10,
b=10;
let fn=function(){
    //a 是这里的私有变量,在私有作用域中没有b这个变量,向上级查找,b是全局变量,此时更改的也是全局变量b
    let a=b=20;
    console.log(a,b);// 20 20
};
fn();
console.log(a,b);// 10 20
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享