堆/栈内存

原始值类型
+ number:数字
+ string:字符串
+ boolean:布尔类型
+ null:空对象指针
+ undefined:未定义类型
+ bigint:大数类型
+ symbol:唯一值类型
对象类型
+ 标准普通对象{}
+ 标准特殊对象
+ []数组
+ /^ $/正则 RegExp
+ Math
+ Date
+ ……
+ 可执行(调用)对象 funtion(){}
+ 非标准特殊对象

浏览器每打开一个新页面都会从计算机的内存空间当中分配除两块内存

栈内存:
1.提供一个供代码自上而下执行的环境
2.存储原始值类型,值存储=>当栈内存被销毁,存储的基本值也被销毁了
3.声明的变量
堆内存:引用值对应的空间
1.对象类型的值存放在里面
普通对象:键值对
函数:
1.作用域
2.字符串(函数体)
3.内置属性 name:’函数名’ length 形参个数
堆内存释放:当堆内存没有任何变量或者其他东西占用,浏览器会在空闲的时候,自主的进行内存回收,把所有不被占用的堆内存销毁掉(谷歌浏览器) xxx=null

var与let的区别

ES6中又新添两个关键字:let、const。
let、const: 不存在变量提升,且只能声明一次,不能重复声明 。
var 会给window增加一个属性,let 和const 不会。

执行环境栈 EC(Execution)Stack/全局上下文 EC(G) //EC执行上下文

浏览器加载页面 想让代码执行 首先会形成一个栈内存(执行环境栈 ECStack) 然后开始让代码准备执行 =》最开始要执行的一定是全局下的代码 此时形成一个全局代码的执行环境(全局执行上下文 EC(G))把EC(G) 压缩到栈内存中去执行(进栈) 每一个函数的执行也是这样操作的… =>有些上下文在代码执行完后,会从栈内存中移除去(出栈) 但是有些情况是不能移除出去的 例如(全局上下文就不能移除) =>在下一次有新的执行上下文进栈的时候,会把之前没有移除的都放到栈内存的底部 让最新要执行的在顶部执行

VO(G)全局变量对象 VS GO全局对象

Stack栈内存 [ECStack]执行环境栈

  • EC(G)全局执行上下文=> [进栈执行]

    • VO(G)[variable object]全局变量对象:存储全局上下文声明的变量的
      • 为了后期代码执行中,可以访问到GO中的那些属性和方法,浏览器在全局上下文中 EC(G)中默认声明了一个变量 window[存储在VO(G)中] 并且让window存储GO地址,后期我们基于window.xxx就可以访问到那些内置的属性和方法
        • @1 我们经常把window称为全局对象
        • @2 编写代码的时候,可以省略window
          • alert(‘…..’)=>window.alert(‘……’)
    • 代码执行前的操作
      • 变量提升

        Heap 堆内存

  • 一开始加载页面,浏览器就会在开辟的堆内存中,分配一块空间

    用来存储内置的一些属性和方法=》GO(global object) 全局对象
    假设堆内存地址为:0X000
    内置api:alert/confirm /prompt/IsNaN/ParseInt/ParseFloat/setTimeout/SetInterval

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

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

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

  • @1如果是window.xxx访问,直接到GO中找即可

  • @2如果是直接输出一个变量,则先去看VO(G)中是否有,
    • 它里面有,获取的就是全局变量
    • 如果没有,则继续去GO中找如果有就是全局对象中的属性,如果也没有,则直接报错
      • ?? is not defined
        1. var x=12;
        2. let y=13;
        3. z=14; //=>等同于window.z=14
        4. console.log(wondow.x);//12
        5. console.log(wondow.y);//undefined
        6. console.log(x);//12
        7. console.log(y);;//13
        8. console.log(z,window.z);//14 14

        1.png

        断点调试debugger

        设置断点调试,代码运行到这个断点处,就暂停了,接下来我们在控制台基于F10(逐过程)/F11(逐语句)控制代码一步步的执行,并且监测到每一步执行的结果

断点调试.png

变量提升机制

1. 什么是变量提升?

在当前执行的上下文中(不论是全局 还是函数执行私有的),js代码自上而下执行之前,浏览器首先会把所有带有 VAR/FUNCITON关键字的进行提前‘声明’或者‘定义’,这种预先处理机制称之为‘变量提升’。 声明(declare):var a(默认值undefined) 定义(defined):a=12(定义其实就是赋值操作)

【变量提升阶段】

  • 带var的只是提前声明
  • 带function的此阶段声明+定义{赋值}都完成了
  • 只提升等号左边的变量。
  • 不管条件成不成立,都要进行变量提升。
  • 新版标准浏览器,在{ }块级作用域中,对于function只声明不定义
  1. /* Var创建变量
  2. * EC(G) 全局执行上下文
  3. * VO(G)
  4. * window -> GO a:undefined
  5. *
  6. * 变量提升: var a;
  7. */
  8. console.log(a); //undefined
  9. var a = 12; //GO a:12

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

  1. /*let创建变量
  2. * EC(G) 全局执行上下文
  3. * VO(G)
  4. * window -> GO
  5. *
  6. * 变量提升: --不存在
  7. */
  8. console.log(a); //Uncaught ReferenceError: Cannot access 'a' before initialization 代码报错,下面的代码则不会再执行
  9. let a = 12;

3.重复声明的问题

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

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

3.2 对于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
 *     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();
fn2();

[练习题]

1)
/*
*    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

2)
/*
*
*   EC(G)
*         VO(G)/GO
*               num ======>1
*               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

3)

<script>
        /*全局提升 var a function func(){...} */
        console.log(a);
        console.log(func);
        var a=10;
        function func(){
            /*
             *变量提升 b 
             */
            console.log(b);
            var b=20;
            console.log(b);
        }
        func();
        console.log(a);
        console.log(func);
    </script>

1.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); 
if(1==2){
  var a=12;
}
console.log(a);

【答案】


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); //函数体

条件判断下的变量提升到底有多坑

1)在条件判断语句中 遇到function,如果条件成立,会把执行体当成块级私有上下文,再进行变量提升

/*EC(G)
* 变量提升 fn
*/
console.log(fn); //undefined
if(1==1){
     //立马定义
   console.log(fn);//函数本身
   function fn(){
       console.log("ok");
   }
}
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函数

2) 在条件判断下,如果有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

【答案】21 1
var 还是原来理解的变量提升,但是function有改变(在条件语句下)

  • 在新版本浏览器中,function 只声明,不定义
  • 在老版本浏览器中,function 声明和定义
    //变量提升 var fn
      console.log(num);//undefined
    console.log(fn);//undefined
    if([]){
         // 只要进到当前if条件中,会立即对fn进行赋值;
         fn();//a
         var num=100;
         function fn(){console.log("a")}
     }
    console.log(fn);//函数本身
    

    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

image.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)
    }
}

【分析】:
1、变量提升:var fn (只对等号左边的进行变量提升),function sum
2、自上而下执行代码:
sum() ;//2
fn();// 此时fn undefined undefined() 报类型错误

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

function fn(){
   console.log(a);
   return function f1(){
   }
   var a=3;
}
fn();
function fn(){
  console.log(f2);
  return function f1(){
  }
  function f2(){
    console.log("f2")
  }
}
fn();

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);
console.log("a" in window);
delete window.a;
delete window.b;

【思考】:下面的答案是什么?

console.log(a); //undefined
var a=3;
console.log(window.a);
console.log("a" in window);

【 判断一个对象到底有没有一个属性】:用 “属性名” 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 a=b=20;
    console.log(a,b);
};
fn();
console.log(a,b)

[答案]
报错:a is not defined;


let a=10,
b=10;
let fn=function(){
    console.log(a);// 函数执行,形成一个私有作用域,这里没变量提升,但是有自我检测机制,知道用let声明
    let a=b=20;    // 了一个变量,进行了记录,不存在变量提升,不能在let 之前进行获取变量,所以报错:a is not
                   // defined
    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