作用域
JS作用域机制
参于到其中的有:
引擎
从头到尾负责整个JS程序的编译及执行过程
编译器
引擎的好朋友之一,负责语法分析以及代码生成等脏活累活
作用域
引擎的另一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
以var a =2为例
变量的赋值会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果它之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值
LHS查询与RHS查询
RHS查询等同于找到某个变量的值
而LHS查询试图找到变量的容器本身,从而可以对其赋值
例如:
1 |
|
作用域嵌套
在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或者抵达最外层的作用域(全局作用域)为止
异常
在变量还没有声明的情况下,LHS和RHS的行为是不一样的
1 |
|
欺骗词法
欺骗词法作用域会导致性能下降
eval
接受一个字符串为参数,将其内容视为书写在那个位置的代码
1 |
|
实际情况中,可以非常容易地根据程序逻辑动态地将字符拼接在一起之后再传递进去
eval(..)通常被用来执行动态创建的代码
在严格模式下,eval(..)在运行时由其自己的词法作用域,意味着其中的声明无法修改所在的作用域
with
通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身
1 |
|
1 |
|
eval会修改其所处的词法作用域,而with根据传递的对象凭空创建了一个新的词法作用域
运用eval和with会影响到JS引擎对代码的优化(因为不知道优化之后是否会被改变,所以就不做任何优化),故而运行速度会比较缓慢,而且在严格模式下也不适用
函数作用域
一般情况下,在函数内部声明的变量和函数在外部是无法访问到的
这种”隐藏内部实现“的方法也符合开发中最小限度地暴露必要内容的API设计思想,即将具体内容私有化
同时也能规避冲突
1 |
|
可以选择一个对象用作全局命名空间,所有需要暴露给外界的功能都会成为这个命名空间的属性
或者用模块管理的机制将库的标识符显示地导入到另一个特定的作用域中
添加包装函数虽然可以隐藏一些细节,但是具名函数这个名称也会污染作用域,而且必须显式调用
所以可以利用IIFE及匿名函数解决这个问题
1 |
|
区分函数声明和函数表达式最简单的方法是看Function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果它是声明中的第一个词,那么就是函数声明,否则就是一个函数表达式
函数表达式可以匿名,而函数声明不可以省略函数名—在JS中这是非法的
下面来看立即执行函数表达式的应用
1 |
|
块作用域
1.上面提到的with也是块作用域的一个例子(块作用域的一种形式),用with从对象中创建出的作用域仅在with声明中而非外部作用域中有效
2.try/catch中的catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效
1 |
|
3.let
let是在ES6中引入的用来解决JS中没有块作用域的问题的
使用let在用{}包裹之后,外面的部分不能访问到里面的信息,形成暂时性死区
学会用let也能避免一些由于闭包导致元素无法被回收的情况
let定义的变量不会挂载在window上
不能进行重复声明
4.const
const与let基本相似,但是const定义的值是一个常量,并不能对它进行修改
否则会报错