浏览器工作原理-js执行机制

浏览器工作原理-js的执行

js变量的提升

console.log(a);
var a = 123;
show();
function show(){
     
  console.log("测试")
};

这段代码打印出来显示undefined在显示测试,为什么执行函数成功了?变量却打印不出来呢?

var a;
function show(){
     
  console.log("测试")
};
console.log(a);
show();
a=123;

打印出来也是一样的。

有点神奇,为什么函数可以执行,变量却不行,

我们的每一个函数或者变量都有两个步骤,声明和赋值。我们的

function show(){
     
  console.log("测试")
};

这个函数其实是一个声明和赋值在一起的。那么久可以解释为什么调用函数在定义函数之前仍然可以得到正确的结果了。

那么js的变量提升到底是怎么回事?

  • js在执行的过程中,js引擎将变量的声明和函数的声明部分提升到整个代码块的顶部,并且会给到一个默认值。

    模拟一下:

      	/**
         * 变量提升部分 
         */
         function show(){
           
            console.log("测试")
        };
        var a= undefined;
     	/**
         * 可执行部分
         */
        console.log(a);
        show();
       a = 123;
    

    这么看,我们的可执行代码依赖于变量,那么提升的变量就是代码的一个运行环境,

js的执行流程

那么我们可以理解到,js的执行在js引擎中,会有一个运行环境,一个可执行的代码,运行环境会有变量的声明和赋值,函数的声明赋值,还有一些this的指向等。可执行代码就按照由上到下的顺序执行,也就是说这部分的动作我们的代码其实是还没有执行的。仅仅只是将我们的代码改造成了这么一个样子。这种应该叫做编译。

  • js的执行流程为 一段代码—》编译—》执行

    那么根据上面的描述我们换一种说法。

      	/**
         * 变量提升部分 
         * 可执行的上下文,因为他可能不止承载变量,还会有其他的依赖等。
         */
         function show(){
           
            console.log("测试")
        };
        var a= undefined;
     	/**
         * 可执行部分
         * 执行阶段。
         */
        console.log(a);
        show();
       a = 123;
    

    找了个案例

    function showName() {
           
    console.log('test1');
    }
    showName();
    function showName() {
            
    console.log('test2');
    }
    showName();
    
    //根据变量提升,编译提取运行环境
    // 变量提升,上下文
    function showName() {
            // 为什么是后一个?
    console.log('test2');
    }
    // 执行阶段
    showName();
    showName();
    
    

    为什么上下文的变量环境只有一个函数声明,因为当js在编译阶段的时候。遇见第一个函数声明。提到变量环境。往下走。第一个函数调用不是声明。不管,遇到第二个函数声明。往变量环境里面塞,发现一个干掉了第一个,留下第二个。那么在执行阶段。就是打印两个test2.

    先编译后执行js的执行流程核心。

调用栈的溢出

栈,是一种先进后出的数据结构。我们调用函数,代码的执行和栈有什么关系?

全局js代码会有一个全局运行环境,也就是全局上下文,调用函数,就会有函数内部上下文。我们的代码有很多,函数调用也会有很多,栈是有数量的,当我们的栈存储的数据超过的最大值,就会出现栈溢出。递归调用容易出现栈的溢出,不当的话。函数的上下文会一直存储在调用栈中。

来个示例(函数的调用):

// 全局执行上下文
var a = 2function add(b,c){
      
return b+c;
}
function addAll(b,c){
     
var d = 10;
result = add(b,c)return a+result+d;
}
// 执行阶段
addAll(3,6)

来看,执行这一整段代码,根据栈的特性,先执行到全局执行上下文,全局上下文在栈底部,在执行addAll,addAll的上下文在中间,在执行add,add的上下文在顶部,当add执行完毕,赋值给addAll上下文的result,将add弹出栈,调用栈剩下两个,全局的和addAll,addAll调用完毕,弹出,剩下全局,执行完毕了。全局弹出。整个执行完成。

那么什么时候会出现栈溢出呢?

递归调用没有设置其归的条件。会一直调下去。栈会一直添加。最后栈溢出。

块作用域 var缺陷,es6为什么要使用let const

上示例

var myname = "name1"
function showName(){
      
console.log(myname); // 按正常逻辑来说,这里是不是应该打印name1
if(0){
     
var myname = "name2"
}
console.log(myname);// 这个也应该打印 name1,因为判断语句不会执行,
}
showName()

但其实不是,第一个console打印undefined,第二也打印undefined,这是由变量的提升造成的污染,如果 将判断里的var改成let,就可以达到我们想要的结果了,let声明的变量只在判断里面生效,let const声明的变量就具备块级作用域。出了这块,就会把这块的变量清除。用var会造成我们出现莫名其妙的错误。

let const的块级作用域又是怎么执行的?

前面有执行上下文。而执行上下文被分成了两个环境,一个是变量环境,语法环境(用于处理块级作用域的)。let const 定义的语句会创建块级作用域并且放在语法环境中,变量使用时候,先从语法环境找,再从变量环境找。

语法环境是一个栈的结构,底部是全局的变量,上面的该块的变量。在块中找到了返回。没找到去到底部。

作用域链和闭包

作用域链

    function bar() {
     
        console.log(myName)
    }
    function foo() {
     
        var myName = "name2"
        bar()
    }
    var myName = "name1"
    foo()

这一段代码最后打印的是什么?按正常理解来说,造foo里面调用bar,应该使用foo的变量,答案是name2,但事实上不是。打印出来的结果是name1,为什么?

  • js的作用域链是词法作用域决定的。而词法作用域是由代码结构来确定的词法作用域是函数声明的位置。

上面的代码举例什么是词法作用域:bar,foo都是在全局下声明的。那么myName的查找先是在bar的内部上下文中的变量环境查找,没找到,沿着作用域链找,读取到的就是全局声明的变量。这个查找的过程叫做作用域链。

闭包

什么是闭包?

// 全局执行上下文    
function bar() {
      // bar上下文
            var myName = "name2";
       return function foo() {
      // foo 上下文
            console.log(myName)
        }
    }

    var myName = "name1";
//全局可执行代码
    bar()();
 console.log(myName)

这是一个最简单的闭包。根据此法作用域的规则。

按照代码的执行顺序来分析,首先产生全局执行上下文,变量环境里面,存放了变量及初始值和函数声明,执行bar() 产生了bar上下文,变量为myName=name2,返回foo函数的声明,释放bar的上下文,由于foo函数内部使用了myName变量,那么myName变量仍然保存在内存中,浏览器的foo上下文中就会有closure块。foo执行结束,释放foo上下文。

内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。

你可能感兴趣的:(浏览器)