你不知道的JavaScript1(作用域与闭包)
1.编译原理:
首先,JavaScript是解释性语言,编译一行,执行一行
JavaScript运行三部曲:1.语法分析 2.预编译 3.解释执行
语法分析:js引擎来检查代码是否存在语法错误
预编译:简单理解,就是在内存中开辟一些空间来声明存放一些变量与函数。
<script> alert(fun1()); //可以成功显示,说明在执行之前进行了预编译处理 function fun1(){ return 'OK'; } </script>
<script> alert(num); //显示undefined,说明在执行之前进行了预编译,给num分配了内存空间但并不初始化它 var num = 0; </script>
预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译
只有在解释执行阶段才会进行变量初始化
3.解释执行:执行代码
2.作用域:
定义:作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
全局作用域:
//在全局声明的变量都会在window对象下,可以通过window对象来访问,也可以直接访问 var name = 'kiki'; console.log(name); console.log(window.name); //效果一样 //在方法内,给变量加关键词(var)为局部变量 //不加则是全局变量(注意:必须是在方法执行后,否则该变量为未定义!!!) function fun1(){ age = 18; alert(age); }; fun1(); console.log(age); //可以显示age
局部作用域:
//和全局作用域刚好相反,在函数内部定义的变量只存在于函数的作用域中,其生命周期随着函数的执行结束而结束 function fun4(){ var hour = 13; console.log(hour); //输出13 } fun4(); console.log(hour); //报错 ReferenceError: hour is not defined
例子:var a = 2;
编译器的处理过程:
1. 遇到 var a ,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的
集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作
用域的集合中声明一个新的变量,并命名为 a 。
2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值
操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的
变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。
如果引擎最终找到了 a 变量,就会将 2 赋值给它。否则引擎就会举手示意并抛出一个异
常!
总结:变量的赋值操作会执行两个步骤,编译器首先会在当前作用域中声明一个变量(如果该变量之前未被声明)
,在执行代码时,js引擎会在作用域中查找该变量,找到就会给它赋值
而在第二步中,js引擎对于变量的查询有两种:LHS与RHS
LHS:找到变量的容器,并对其赋值,var a = 2;就是LHS查询
RHS:相当于查找某个变量的值,比如:console.log(a);
以下代码:
function fun2(m){ console.log(m); } fun2(6);
首先在函数调用时,会进行RHS查询;在参数传递的过程中,相当于给m一个赋值,需要进行LHS查询;
然后再函数内部,console也进行了一次RHS查询,console是内置对象;最后查到m的值传递进log为RHS查询。
3.闭包
要理解闭包首先要理解JavaScript特殊的变量作用域,全局变量和局部变量
在JavaScript中,在函数内部可以直接读取全局变量,在函数外面反而不可用读取内部变量
注意:在函数中若在定义变量时不加关键字var,则实际上声明了一个全局变量
function hhh(){ n = 1; } hhh(); console.log(n); //可以成功访问到!
当然,有时候需要得到函数内部的变量,在正常情况下,是办不到的,只有变通,那就是:
在函数的内部,再定义一个函数来读取变量
function aaa(){ var x = 2; function bbb(){ //将bbb作为返回值,就可以从外部读取aaa的变量 console.log(x); } return bbb; }
代码中的bbb函数,就是一个闭包
闭包的简单理解:闭包就是能够读取其它函数内部变量的函数,由于在JavaScript中,只有函数内部的子函数才能读取变量,
因此把闭包简单理解为“定义在一个函数内部的函数”。所以,在本质上,闭包就是将函数内部和外部相连接的一座桥。
闭包的用途:
闭包用途广泛,主要用途有两个,一个就是可以读取函数内部的变量,还有一个就是让这些变量的值始终保持在内存中,不会被销毁。
因为与全局变量不同的是,定义的变量只存在于函数作用域中,其生命周期会随着函数的执行结束而结束,即被销毁。
看一个例子
/* fun2被赋予了一个全局变量glo,导致fun2始终在内存中,而fun2又依存于fun1,所以变量a始终在内存中不会被销毁 */ function fun1(){ var a = 0; function fun2(){ console.log(a); } return fun2; } var glo = fun1(); glo();
使用闭包的注意点:
①由于闭包使得函数中的变量都会被保存在内存中,内存消耗很大,所有不能滥用闭包,否则会影响网页的性能。解决方法,在退出函数之前,将不再使用的局部变量删除
②闭包会在父函数的外部,不要随意改变父函数内部变量的值
闭包概念参考来源:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html