程序执行过程(重要)
堆和栈
堆栈本身是是数据结构。堆(链表结构)、栈(栈结构)。
程序在运行的时候内存按照逻辑分为了堆内存和栈内存。
-
栈:栈结构中开辟的内存比较小。速度快,操作系统会自动回收内存。
特点:先进后出。
-
堆:开辟的内存通常比较大,速度比较慢。
特点:先进先出
JS中已经封装了垃圾回收机制。
执行环境
每个运行环境我们通常也称为执行上下文。
JavaScript中运行环境分为两种:
- Global,全局的。JavaScript代码开始运行的时候就默认存在的执行环境。
- Function,函数执行环境。进入一个javaScript函数的运行环境(函数调用时才会进入);
代码在执行时开辟一段栈空间来说明代码的执行顺序,这个栈我们叫执行栈
。
- 当JavaScript代码执行时会将全局执行上下文压入到栈底,当执行到函数执行上下文,也会将函数执行上下文压入到栈底。
- 函数执行完成之后将函数执行上下文弹出并且销毁(和执行环境相关的一系列内容都会被销毁)。
- 直到所有代码执行完成才将全局执行上下文销毁。
执行环境的阶段
每个执行环境分为两个阶段:
- 创建阶段(做准备工作的)
- 全局执行环境:一上来就进入了创建阶段。
- 函数执行环境:当函数被调用,但是开始执行函数内部代码之前。
- 代码执行阶段(真正的解释、执行代码)
作用域及作用域链
-
什么是作用域?
作用域:变量起作用的区域范围。
-
作用域的作用?
隔离变量(函数外部定义的和函数内部定义的同名变量没有关系)
-
作用域的确定时机?
作用域是在进入到全局执行环境或函数执行环境时确定好的。
-
作用域种类?
在ES5中作用域只分为两种,全局作用域和函数作用域(局部作用域)。
ES6里面新加了一个块级作用域。
一执行javascript就进入了全局执行环境就有了全局作用域,执行某个函数时进入到函数执行环境就有了函数作用域(局部作用域);
一个执行环境对应着一个作用域。
同一个函数多次调用会产生不同的执行环境,每次调用都会产生一个。
-
全局变量和局部变量
- 全局变量:定义在全局环境中的就是全局变量(函数外部定义的,全局作用域)。
- 局部变量:定义在局部环境中就是局部变量(函数内部定义的,函数作用域)。
局部变量:只能在自己的作用域中使用,只能在函数内部使用,外部不能用。
全局变量:在整个程序所有的地方都会起作用(整个程序当中任何地方都可以操作这个全局变量)
全局变量可以被函数内部进行操作,但是全局当中没有办法操作局部变量(如果真的要操作就需要用到后面讲的闭包)。
-
作用域链
-
函数内部是可以嵌套函数的。
function test(){ function test1(){ console.log('test1'); } test1(); console.log('test'); } test();
作用域链:作用域是变量起作用的范围,作用域链是用来找变量的一系列的过程。
-
一个函数在定义时会自动生成一个[[scope]]
的属性([[]]
都是不能直接通过代码访问的),[[scope]]属性
中存储的是函数定义时作用域的层级。
作用域链是函数执行时创建的,作用域链由:当前执行的函数作用域 + [[scope]]属性中的作用域
组成的。
执行环境如果被销毁的话,其对应的作用域链不会被保留,会被销毁。
函数当前执行的作用域是作用域链的顶端,全局作用域是作用域链的最后端。
- 总结:
- 每个执行环境都会有自己独立的作用域链。
- 作用域链由当前的执行环境的作用域和
[[scope]]
属性组成。在调用时才形成。- 函数调用时形成了执行环境、作用域。
[[scope]]
属性:函数定义时就已经生成了,函数即使不调用也会有这个属性。其中存储的是包含定义它时的各个层级的作用域。
内存中的数据存储
变量的值如果是基本类型(String、Boolean、Number、Undefined、null),会在栈内存开辟一段空间存储标识符,然后将基本类型的值也存储在栈内存中和标识符对应起来。
引用类型的值存储在堆内存里,存储引用类型的时候会给堆内存一个地址。但是标识符还是存储在栈内存中,为了能够让标识符和真正的数据对应起来,栈中存储的值是引用类型的地址。
内存地址使用十六进制0x开头表示。
查找变量是和执行环境有关系(作用域链)和堆没有绝对的关系,堆只是也来存储对象类型的数据的。
程序开始到结束执行了什么:
-
程序一开始就碰见了全局环境,首先会创建全局环境并且进行压栈,全局代码执行的时候依赖的是全局环境当中的东西。
全局变量如果是基本类型,那么这个值直接在栈当中,如果这个变量是对象(函数、数组),那么函数和数组是要在堆结构中开辟自己的空间专门存储数据的。然后把堆里面这块空间的地址,给栈当中对应变量进行保存(他们两个是对应的)
-
当程序执行碰到了函数调用(函数比较特殊,因为函数可以执行),函数执行时候也要有自己的环境,函数执行也是创建自己的函数环境进行压栈。(函数执行环境一定是压在全局环境之上的。)
局部变量,是在函数环境中存在的,只有函数执行,局部变量才会出现。函数执行完成之后函数环境要弹出栈(函数执行环境要将对应的一系列东西销毁,释放内存),局部变量也就不存在了。
-
当函数调用完成之后,会继续执行全局代码,一直到所有的代码都执行完成,代码程序执行结束,程序结束时,全局环境最后出栈。
变量提升(预解析)
在执行环境的第一步(创建阶段
)时,将带var的变量的声明及
函数的声明(function abc(){}
),放在了作用域中,在执行阶段
开始时,代码本身的逻辑和其他逻辑还是在原来的位置。
注意:
- 全局执行环境、函数执行环境,每个执行环境都会有一个变量(变量、函数声明)提升的操作。
- 函数表达式不会被提升。
- 函数名和变量同名。函数声明和变量都会被提升,但是函数会先被提升,然后才是变量。