作用域
作用域是根据名称查找变量的一套规则。如果查找的目的是对变量进行赋值,那么就会使用
LHS查询
,如果是为了获取变量的值,就会使用RHS
查询。
例如:var a=2
在JS引擎进行编译时,会执行下面两个操作:
- 第一步在其作用域中声明新变量
- 接下来
a=2
会查询变量a(LHS查询)并对其赋值
在作用域中查找变量的操作LHS和RHS查询会根据下面作用域链查找顺序进行查找,不成功的RHS引用会抛出ReferenceError
异常,而非严格模式下不成功的LHS引用会导致自动隐式地创建一个全局变量,严格模式下的不成功LHS引用会抛出ReferenceError异常
作用域链:当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError。
let a=1;
function fn1(){
function fn3(){
function fn2(){
console.log(a);//undefined
}
let a;
fn2();
a=4
}
let a=2;
return fn3;
}
let fn = fn1()
fn() //undefined
作用域的预处理:浏览器在解析JS代码之前,会进行“预处理”,将当前JS代码中所有变量定义
和函数定义
放到代码的最前面.
注意:
- 如果没有用var关键字声明变量(直接写
a=1
),则变量不会被声明提前 - 使用函数表达式创建的函数不会被声明提前
- 使用let声明的变量不会在块作用域中进行提升
作用域主要可以分为以下三种:
- 全局作用域:作用于整个script标签内部或作用于一个独立的JS文件
- 函数作用域:作用于函数内的代码环境
- 块级作用域:{}包裹起来的代码块,外部也可以访问块级作用域内var声明的变量,而
let
和const
声明的变量是不能访问的
作用域的访问关系:在内部作用域中可以访问到外部作用域的变量,在外部作用域中无法访问到内部作用域的变量
函数作用域
函数作用域的预处理: 在函数作用域中也有声明提前的特性
- 函数中,使用var关键字声明的变量会在函数中所有的代码执行之前被声明
- 函数中,没有var声明的变量都是全局变量,而且不会提前声明
fun();//保错
var fun = function(){
console.log('hhh')
}
函数预编译的步骤:
- 创建AO对象(Activation Object,执行期上下文)
- 将形参名和变量作为AO的属性名,值为undefined
- 将实参值和形参统一并赋值
- 查找函数声明,将函数名作为AO的预编译,值为整个函数体
词法作用域
词法作用域就是定义在词法阶段的作用域,是在写代码时将变量和作用域写在哪里来决定的,都只由函数被声明时所处的位置决定的。JS中有
eval(...)
和with
这个两个机制来欺骗词法作用域,但是会导致引擎在编译时无法对作用域查找进行优化,导致代码运行变慢,不能使用它们。
闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域哇执行
一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的。闭包在处理速度和内存消耗方面对脚本性能有负面影响,一般不要使用闭包。
参考链接: