作用域
RHS查询与LHS查询。
1.对变量进行赋值进行的是LHS查询,而获取变量的时候是RHS查询,注意,形参实参的传递过程中,含有隐形的LHS查询过程。
2.当RHS查询失败时,会抛出reference error,而LHS查询失败时,会隐性的给全局对象创建一个属性,严格模式下会抛出reference error。当然,查询成功的条件下,如果对值进行不合理的操作,会抛出type error。
3.JavaScript 引擎首先会在代码执行前对其进行编译,在这个过程中,像 var a = 2 这样的声明会被分解成两个独立的步骤:
- 首先,var a 在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
接下来,a = 2 会查询(LHS 查询)变量 a 并对其进行赋值。
函数作用域和块级作用域
从传统角度来看,我们声明了函数并向其中添加了代码,但是反过来看,我们划分了一块区域,对一些代码进行了封装,形成了函数作用域,这个作用域隐藏了其中的代码这样做有什么好处?
1.保护内部的变量或函数,避免外部的无意改动造成内部的破坏,将这些变量、函数私有化。
2.避免标识符的冲突。最典型的例子就是在引用多方库的时候,如果每个库的封装做的不够好,过多的将接口暴露在外,可能会导致一些标识符发生冲突。最好的处理方式就是在每个库中私有化变量和函数,而将提供给外界的方法转化成一个全局对象的属性。或者是使用模块化的管理,将标识符显示的导入到另一个作用域中。立即执行函数(IIFE)
尽管我们说声明函数产生了隐藏标识符的函数作用域,但这其实存在一些问题,因为声明函数所用的函数名本身就是对所在作用域的一次污染,使用立即执行函数可以规避这个问题。
块级作用域
产生块级作用域的几种方式
1.with with虽然看上去是对对象处理的简化写法,实际上会在with的括号中生成块级作用域function foo(obj) {with (obj) {a = 2;}}var o1 = {a: 3,};var o2 = {b: 3,};foo(o1);console.log(o1.a); // 2foo(o2);console.log(o2.a); // undefinedconsole.log(a); // 2
这个例子可以这么解释,传入o1时函数希望对o1的a标识符进行赋值(obj.a=2),看上去是给对象的属性进行修改,实际上是进行了LHS的引用,o1中找到了a标识符,因此成功进行了赋值,但是o2传入时,块级作用域中没有找到a标识符,因此随着作用域链向上查找,最终全局对象中也没有,因此为全局对象创建了a变量。
2.try catch语句 每一个catch(){ }的大括号内部都是一个独立的块级作用域
3.let、const会在大括号中创建一个独立的作用域块级作用域在垃圾回收中的作用
function process(data) {//一些代码}var someReallyBigData = {..};//=> {let someReallyBigData = {..}process(someReallyBigData); // process(someReallyBigData);}var btn = document.getElementById("my_button");btn.addEventListener("click",function click(evt) {console.log("button clicked");},false);
注意上面这个例子,给btn添加事件的时候实际上click函数和全局环境之间形成了闭包,导致浏览器很有可能不会对之前的 someReallyBigData这种已经用完的变量进行回收,使用{}和let形成块级作用域,使这部分代码脱离全局环境,这样闭包可能产生的保留不会对这部分作用域生效,因此可以保证这部分数据的回收。
变量提升
注意两点1.函数表达式不会提升,只有声明提升了。
2.变量和函数重名的情况下,函数的声明优先于变量,重复声明变量并没有用,但是再次声明同名函数
会覆盖前面的函数。
