一、作用域是是什么
1. 编译原理:a 分词/词法分析 b 解析/语法分析 c代码生成
2.理解作用域: 引擎(负责js程序编译及执行过程) 编译器(语法分析和代码生成) 作用域(负责收集并维护所有声明的标识符,确定当前执行代码对这些标识符的访问权限)
变量的赋值操作会执行2个动作,编译器在当前作用域中声明一个变量,运行时引擎会在作用域中查找该变量,如果找到就赋值。
3.编译器术语:LRS(查询与查找变量值) RLS(找到变量容器本身赋值)
4.作用域嵌套:作用域是根据名称查找变量的一套规则,当一个块或者函数嵌套在另一个块或者函数中时,就发生了作用域的嵌套,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量。
5. 异常:LRS (如果在顶层中也无法找到该目标变量,全局作用域中就会创建一个具有该名称的变量,前提是程序运行在非严格模式下) RHS(如果在所有嵌套的作用域中遍寻找不到所需的变量,引擎就会抛出ReferenceError)
二、词法作用域
1.词法阶段:词法作用域就是定义在词法阶段的作用域,由你在写代码时将变量和块作用域代码写在哪里来决定的。
2. 查找:作用域查找会在找到第一个匹配的标识符时停止。
3. 欺骗词法:在代码运行时修改词法作用域。eval() 接受一个字符串为参数,并将其中内容视为好像在书写时就存在于程序中这个位置的代码。with()通过将一个对象的引用当作作用域处理,将对象的属性当作作用域标识符来处理,从而创建一个新的词作用域。不仅要使用她们(引擎无法在编译时对作用域查找进行优化)
三、函数作用域和块作用域
1. 函数中的作用域:属于这个函数的全部变量都可以在整个的函数的范围内使用及复用(嵌套的作用域中也可以使用)
2. 隐藏内部实现:传统是先声明一个函数,然后向里面添加代码。反过来从代码里挑出任意片段,然后用函数声明对它进行包装,实际上就是把这些代码隐藏起来了。结果就是在这个代码片段的周围创建了一个作用域气泡,这段代码的任何变量都将绑定在这个新创建的包装函数的作用域中,而不是先前的作用域中。(最小授权或最小暴露原则)
3.避免冲突:“隐藏”作用域中的变量和函数可以避免同名标识符之间的冲突。典型的例子存在于全局作用域中,当程序中加载了多个第三方库时,一般会在全局作用域中声明一个独特的变量,这个变量被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴露在顶级的词法作用域中。
4.函数作用域:函数表达式是可以匿名的,函数声明则不可以省略函数名。(function foo(){..})作为函数表达式意味着foo只能在..做代表的位置中被访问,外部作用域则不行。foo被绑定在函数表达式自身的函数中而不是所在的作用域中。
5.立即执行函数表达式(IIFE):①表达式被包含在()中,然后在另一个()括号中来调用 (function foo(){xxx})() ②用来调用的括号被移进了用来包装的()括号中 (function(){xxx}())
//①把他们当做函数调用并传入参数
(funtion IIFE(g) {
console.log(g.a);
console.log(a)
})(window)
//②倒置代码的执行顺序,IIFE执行之后被当做参数传递进去。
(function IIFE(def) {
def(window);
})(function def(global) {
var a = 3;
console.log(a);
console.log(global.a)
})
6.块级作用域:with try/catch let
四、提升
提升:只有声明本身会被提升,而赋值或其他运算逻辑会留在原地。var a=2 javascript引擎将var a和 a=2当作2个单独的声明,第一个是编译阶段的任务,第二个是执行阶段的任务。这意味着无论作用域中的声明出现在什么地方,都奖在代码本身执行前首先进行处理。这个过程形象的想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程称为提升,函数声明和变量声明都会被提升,函数优先被提升,然后才是变量。