作用域
RHS查询失败->ReferenceError异常
非严格模式,LHS查询失败,会在全局作用域创建一个具有该名称的变量。严格模式禁止自动或隐式地创建全局变量->ReferenceError异常
RHS查询成功,当尝试对其进行不合理的操作->TypeError
词法作用域
作用域的两种主要工作模型:词法作用域、动态作用域(Bash脚本、Perl中的一些模式)
词法作用域,即定义在词法阶段的作用域,由写代码时将变量和块作用域写在哪决定。
作用域查找会在找到第一个匹配的标识符时停止。
通过对全局对象属性的引用可以间接访问全局变量,如window.a。但非全局的变量如被遮蔽,则无法被访问到。
在运行时“修改”词法作用域
“欺骗”词法作用域会导致性能下降
JavaScript引擎会在编译阶段进行数项性能优化,其中有些优化基于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。
eval
eval()函数可以接受一个字符串,以动态形式插入当前位置。
在严格模式中,eval()在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域
setTimeout()和setInterval()的第一个参数可以是字符串,不推荐使用。new Function()的最后一个参数可以接受代码字符串,并将其转化为动态生成的函数,也不推荐使用
with
通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身
with可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。但这个块内部正常的var声明并不会被限制在这个块的作用域中,而是被添加到with所处的函数作用域中。
在严格模式下,with被完全禁止。
函数作用域和块作用域
函数作用域指属于这个函数的全部变量都可以在整个函数的范围内使用及复用(在嵌套的作用域中也可以使用)
可以通过把变量和函数包裹在一个函数的作用域中,然后用这个作用域来隐藏它们(最小授权或最小暴露原则)。以此控制变量和函数的访问权限;避免同名标识符之间的冲突,以防变量的值被意外覆盖。
规避冲突
避免同名标识符之间的冲突
第三方库在全局作用域中声明一个名称独特的变量,用作库的命名空间。所有需要暴露给外界的功能都会成为这个对象的属性。
模块管理
函数作用域
包装函数产生函数名,对外部作用域产生影响,并且需要显式调用
函数不需要函数名,并且能够自动运
把函数处理成函数表达式。如果function是声明中的第一个词,那么就是函数声明,否则就是函数表达式。下文中的作为函数表达式意味着foo只能在所代表的位置中被访问,外部作用域则不行。
匿名函数的缺点
在栈追踪中不会显示出有意义的函数名,使得调试很困难
当函数需要引用自身时,只能使用已经过期的引用,如在递归中。另一个函数需要引用自身是在事件触发后事件监听器需要解绑自身。
对代码可读性/可理解性产生重要影响
针对匿名函数缺点,可以改写为:
立即执行函数表达式(IIFE)的使用形式
从外部作用域传递参数
块作用域
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息
JavaScript的或者语句没有直接提供块作用域,声明的变量会污染到整个作用域
with关键字
try/catch
catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效
let关键字
ES6引入了let关键字
let关键字可以将变量绑定到所在的任意作用域中(通常是{ .. }内部)。即let为其声明的变量隐式地创建了所在的块作用域。
为块作用域显式地创建块可以使变量地附属关系变得更加清晰
使用let进行的声明不会在块作用域中进行提升。
提升是指声明会被视为存在于其所出现的作用域的整个范围内。
let的一些使用场景
垃圾收集
对于一些在内存中占用大量空间的数据结构,在不需要时可以被垃圾回收。而不是因存在于整个作用域,无法回收。
2. let循环
for循环头部使用let来取代var
let不仅将i绑定到了for循环的块中,还将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值
块作用域的替代方案
ES3中就存在try/catch语句,可以通过工具将ES6代码转换成兼容ES6之前的环境,如Traceur
显式地使用let
let声明并不包含在ES6中。Traceur编译器也不支持,可以改写为
为什么不直接使用IIFE来创建作用域
try/catch的性能在改进
任意一段代码用函数进行包裹会改变代码的含义,其中的、、、都会发生变化
const
和let一样可用于创建块作用域变量,但其为常量
领取专属 10元无门槛券
私享最新 技术干货