作用域与闭包
作用域
-
什么是作用域
作用域就是一套规则,它负责解决(1)将变量存在哪儿?(2)如何找到变量?的问题 -
作用域工作的前提
- 谁赋予了作用域的权利?——js引擎
- 传统编译语言编译的过程
- 分词/词法分析:字符串 =》 词法单元
var a=2; => var a = 2 ;(共5个单元)
- 解析/语法分析:词法单元流 =》 抽象语法树(Abstract syntax tree,AST)
- 代码的生成: AST =》 可执行代码(机器指令)
- js引擎编译的特点:
- 代码在执行前进行编译(需要用到JIT(just in time)进行延迟编译甚至实施重编译来保证性能最佳)
- 针对 ** var a = 2; **例子的编译流程
- 分词/词法分析:将var a=2; => var a = 2 ; 分成5个词法单元
- 解析/语法分析:解析成抽象语法树
- 代码生成:** 重点来了 **
- var a (声明操作)=》此时编译器会 询问作用域是否有叫 a 的变量存在 ?忽略该声明,继续编译 : 在当前作用域下声明一个新变量,命名为a
- a=2 (赋值操作) =》此时编译器会 询问作用域是否有叫 a 的变量存在 ?使用该变量 : 引擎根据作用域链继续向上查找该变量。
是否能找到叫 a 的变量 ?直接赋值 : 引擎抛出异常。
- 引擎在查找变量a是否被声明的过程中,是如何进行的查找?
- 作用域的协助
- 查找的目的是赋值=》LHS(left hand side)查询 :(1) 当变量出现在赋值操作的左侧时,使用 LHS 查询。类似赋值 a=2; (2)若在顶层作用域内没有找到,严格模式下:Reference异常;非严格模式下:自动隐式创建全局变量
- 查找的目的是取值=》RHS(right hand side)查询 :(1) 当变量出现在赋值操作的右侧时,使用 RHS 查询。类似取值 a; (2) 若在顶层作用域内没有找到,直接抛出Reference异常
-
作用域是如何工作的?词法作用域,动态作用域。
- 词法作用域
- 什么是词法作用域
- 词法作用域就是定义在词法分析阶段的作用域。词法作用域就是在写代码时将变量和块作用域写在哪里决定的。一般是不会变的。
- 词法作用域的查找规则(作用域链)
- 作用域查找从运行时所处的最内部的作用域开始,逐级向上或向外查找,直到遇见第一个匹配的标识符(变量、函数)为止。
- 改变词法作用域(二般情况出现:欺骗词法)
- 2种方法
- eval();接受一个字符串为参数,即动态插入程序代码,伪装成词法期就存在的代码。
function foo(str, a){ eval(str); console.log(a, b); } var b=2; foo('var b=3;',1); //1,3
- with;通常被当做重复引用同一个对象的多个属性的快捷方式,可以不需要重复引用对象本身。
var obj = { a:1, b:2, c:3 }; //使用with with(obj){ a=3; b=4; c=5; }
- 后果
- 引擎无法在编译时对作用域对的查找进行优化
- 在严格模式下,with被完全禁止,eval(...)也被禁止
- 2种方法
- 什么是词法作用域
- 词法作用域
-
常见的作用域单元
- 函数作用域
- 什么是函数作用域
- 属于这个函数的全部变量都可以在整个函数的范围内使用及复用
- 函数的好处:隐藏内部实现,规避同名标志符之间的冲突(有以下两个方法)
- 声明全局命名空间(一个对象,用来存储全局作用域种的变量)
- 函数相关常识
- 函数声明 vs 函数表达式
function是声明中的第一个词 ? 函数声明 : 函数表达式。
- 具名 vs 匿名 :
有无名字的区别 函数表达式可以没有名字,但是函数声明必须有名字 鼓励所有的函数都有名字
- 立即执行函数表达式
两种写法: (function(){}()); (function(){})();//常用,第一个括号( )将函数变成表达式,第二个( )执行这个函数
- 什么是函数作用域
- 块作用域(es5之前并没有该概念)
- 什么是块作用域
变量的声明离使用的地方越近越好,并最大程度的本地化。
- 块作用域的例子
- with关键字 : with从对象中创建出的作用域仅在with声明中而非外部作用域有效
- try/catch : catch分句创建作用域,且声明的变量只能在catch中使用
- let : let为其声明的变量提供块作用域{...},且let进行的声明在块中不会变量提升
- const : const声明常量。
- 函数作用域
-
提升
- 声明提升
- 变量声明提升
- 函数声明提升
- 函数表达式声明不会被提升
foo();//Uncaught TypeError: foo is not a function//foo()对于undefined值进行函数调用而导致非法操作 var foo= function bar(){ console.log('1'); }; foo();//1 foo;//function bar(){} bar;//ReferenceError: bar is not defined bar();//ReferenceError: bar is not defined
- 提升的优先级
- 函数优先,其次才是变量
- 避免在块内声明函数
- 声明提升
- 练习题(1)找出所有的 LHS 查询和 RHS 查询
function foo(a) {
var b=a;
return a+b;
}
var c= foo(2);
答案:
LHS(3处) c=..., a=2(隐式变量分配),b=...,
RHS(4处) foo(2..., =a, a.., ..b
作用域闭包(晦涩难懂,常常搞错的地方)
- 什么是闭包?
- 函数可以记住并访问所在的词法作用域时,就产生了闭包。即使是在当前词法作用域之外的地方执行。
闭包 = 那些能够访问自由变量的函数 = 函数 + 函数能访问的自由变量 自由变量 = 在函数中使用,但既不是函数参数也不是函数的局部变量
- 闭包作用
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中。
- 闭包例子
- 回调函数(定时器,ajax跨域,异步等)
- for循环
for(var i=1;i<=5;i++){ setTimeout( function timer(){ console.log(i); },i*1000); } //以每秒一次的频率输出5个6 for(var i=1;i<=5;i++){ (function(j){ setTimeout( function timer(){ console.log(j); },j*1000); })(i); } //立即执行函数为每一次迭代生成一个新的作用域,以每秒一次依次输出1-5 for(let i=1;i<=5;i++){ setTimeout(function timer(){ console.log(i) },i*1000); }
动态作用域
- 什么是动态作用域
动态作用域是在函数运行时确定的,类似this。
词法作用域关注在何处声明,动态作用域关注在何处调用。