浏览器中JavaScript的执行机制

149 阅读6分钟

变量提升

变量提升是指JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码开头的行为。变量提升后,会给变量设默认值为underfined。

  • 实际上变量和函数声明在代码里的位置是不会改变的,而是在编译阶段被JavaScript引擎放入内存中

下载.png

  • 编译过程中遇到变量声明,js引擎会在环境对象中创建一个同名变量属性,使用undefined初始化

  • 编译过程中遇到函数,js引擎将函数定义存储到堆中,并在环境对象中创建一个函数名同名的属性,并且将该属性指向堆中函数的位置

  • JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译

  • 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。

  • 代码中出现同名变量和函数怎么解析?

    showName()
    var showName = function() {
        console.log(2)  
    }   
    function showName() {
        console.log(1)    
    }
    输出结果 1
    showName()
    function showName() {
        console.log(1)    
    }
    var showName = function() {
        console.log(2)  
    } 
    输出结果也为1
    

    同名变量和函数的两点处理原则: 1、如果是同名的函数,JavaScript编译阶段会选择最后声明的那个 2、如果变量和函数同名,那么在编译阶段,变量的声明会被忽略。

  • js和css阻塞原因和流程

    dom解析器解析header,碰到内联js,先会暂停dom解析,下载内联js,下载并执行完成该js后再继续执行向下解析dom,这就是js为什么会阻塞dom渲染,css同样的道理

  • 同名变量和函数的问题

    demo1:
       var a;
       function a(){
          console.log(1)
       }
       console.log(a); // function a(){....}
    
    demo2:
        function a(){
           console.log(1)
        }
        var a;
        console.log(a); // function a(){..}
    
     结论:函数声明 > 变量声明
    
     demo1:
       var a = 10;
       function a(){
               console.log(1)
       }
       console.log(a); // 10
    demo2:
       var a = function(){
           console.log('变量赋值')
       }
    
       function a(){
            console.log('函数声明')
       }
       console.log(a); // function a(){'变量赋值'}
    
    结论:变量赋值 > 函数声明
    
    var foo=function(x,y){
       return x-y;
    }
    function foo(x,y){
       return x+y;
    }
    var num=foo(1,2); // -1
    

最终结论:变量赋值 > 函数声明 > 变量声明 函数赋值>函数声明

重点来了哦

1、对于同名的变量声明,Javascript采用的是忽略原则,后声明的会被忽略,变量声明和赋值操作可以写在一起,但是只有声明会被提升,提升后变量的值默认为undefined。

2、对于同名的函数声明,Javascript采用的是覆盖原则,先声明的会被覆盖,因为函数在声明时会指定函数的内容,所以同一作用域下一系列同名函数声明的最终结果是调用时函数的内容和最后一次函数声明相同

3、对于同名的函数声明和变量声明,采用的是忽略原则,由于在提升时函数声明会提升到变量声明之前,变量声明一定会被忽略,所以结果是函数声明有效同名函数声明

两个维度的问题:首先把所有的声明都提前,变量提升指的也就是声明提前,并且函数的声明在变量声明之前,然后再进行赋值操作

执行上下文

  1. 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
  2. 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
  3. 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。

为什么JavaScript代码会出现栈溢出?

  • 调用栈是 JavaScript 引擎追踪函数执行的一个机制
  • 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码
  • 如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶
  • 当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈
  • 当分配的调用栈空间被占满时,会引发“堆栈溢出”问题

var缺陷以及为什么要引入let和const?

  • var存在变量提升,但let和const定义就会形成封闭作用域,凡是在声明之前就使用这些变量,就会报错,这就是let和const的暂时性死区

  • 变量提升会带来的问题:

    1、变量容易在不被察觉的情况下被覆盖

    var myname = " 极客时间 "
    function showName(){
        console.log(myname);
        if(0){
            var myname = " 极客邦 "
        }
        console.log(myname);
    }
    showName()
    输出的结果都是undefined 
    
    

    2、本应销毁的变量没有被销毁

    function foo(){ 
        for (var i = 0; i < 7; i++) { 
        }    
        console.log(i);  
    }                           
    foo()
    输出结果是7
    
  • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了

  • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)

  • 在函数的作用域内部,通过 let 声明的变量并没有被存放到词法环境中,其实在词法环境内部维护着一个小型的栈结构,栈底是函数最外层变量,作用域定义的变量查找方式:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找

作用域链和闭包

  • 词法作用域:词法作用域是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符
  • 闭包:在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

this指向问题

  • 当函数作为对象的方法调用时,函数中的this就是该对象
  • 当函数被正常调用时,在严格模式下,this值是undefined,非严格模式下,指向window
  • 嵌套函数中的 this 不会继承外层函数的 this 值