js 作用域
# JS 作用域
作用域为可访问变量,对象,函数的集合。
# 作用域分类
- 词法作用域(静态作用域)
词法作用域,即由 函数声明时 所在的位置决定的。词法作用域是指在编译阶段就产生的,一整套函数标识符的访问规则。
目前主流的都是静态作用域,比如 JS,C,C++,Java 这些都是静态作用域
- 动态作用域
动态作用域是在函数执行的时候确认的,使用较少,比如 Bash 脚本、Perl 中的一些模式
JavaScript 除了 this 之外,其他,都是根据词法作用域查找
作用域是分层的,子级作用域可以访问父级作用域,而父级作用域则不能访问子级作用域。
ES6 之前:
- 全局作用域
- 局部作用域
ES6:块级作用域
# 1. 全局作用域
全局变量:
生命周期将存在于整个程序之内。
能被程序中任何函数或者方法访问。
在 JavaScript 内默认是可以被修改的。
# 2. 局部作用域
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!!
# 3. 块级作用域
let 和 const 关键字结合{ }都能形成块级作用域
# JS 作用域链
作用域链:如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
自由变量:当前作用域没有定义的变量
# 执行上下文
执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用 undefined 占个空。
- 变量、函数表达式——变量声明,默认赋值为 undefined;
- this——赋值;
- 函数声明——赋值;
这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”
# 执行上下文类型
执行上下文总共有三种类型:
全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用
# 执行上下文生命周期
- 创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
(1)创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明。
(2)创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
(3)确定 this 指向:全局执行上下文中,this 的值指向全局对象,函数执行上下文中,this 的值取决于函数的调用方式
- 执行阶段
执行变量赋值、代码执行
- 回收阶段
执行上下文出栈等待虚拟机回收执行上下文
# 作用域和执行上下文区别
JavaScript 的执行分为:解释和执行两个阶段
解释阶段:1.词法分析、2.语法分析、3.作用域规则确定
执行阶段:1.创建执行上下文、2.执行函数代码、3.垃圾回收
作用域在定义时就确定,并且不会改变。执行上下文在运行时确定,随时可能改变;
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);
有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。
同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
# 执行栈
执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
首次运行 JS 代码时,会创建一个全局执行上下文并 Push 到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并 Push 到当前执行栈的栈顶。
根据执行栈 LIFO 规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中 Pop 出,上下文控制权将移到当前执行栈的下一个执行上下文。