0. 引入

1. AO & GO & Function

学习AO,GO是为了解决‘学习作用域、作用域链’所产生的一切问题
AO与function密切相关 -> function 相当于独立的仓库 =? 独立的作用域

2. Function作为对象

  • 函数也是一种对象类型/引用类型/引用值 ```javascript function test(a, b) {}

test.name -> ‘test’ test.length -> 2

  1. - **对象有些属性是我们无法访问的,是JS引擎内部固有的 隐式属性 / 私有属性,如 **`**[[scope]]**`** 属性**
  2. <br />
  3. <a name="GUnLL"></a>
  4. ## 1. 概念
  5. 1. 函数创建时,生成的一个JS引擎内部的隐式属性`[[scope]]`
  6. 1. 函数**存储作用域链**的容器
  7. 1. 作用域链以**链**的形式存储 AO/GO,即函数执行器上下文和全局执行器上下文
  8. 1. 函数执行完成后,AO是要销毁的,再次执行函数时会重新生成AO,即 AO是一个即时的存储容器
  9. <a name="rYhVP"></a>
  10. ## 2. 过程精讲
  11. ```javascript
  12. function a() {
  13. function b() {
  14. var b = 2;
  15. }
  16. var a = 1;
  17. b();
  18. }
  19. var c = 3;
  20. a();

Line: 0 — 全局执行的前一刻,生成GO,此时函数声明已经定义,而函数表达式没有定义

Line:1 — 当a函数被定义时,系统生成函数的[[scope]]属性,[[scope]]保存该函数的作用域链。
该作用域链的第0位存储当前环境下的全局执行期上下文GO,GO里存储全局下的所有对象,其中包含函数a和全局变量c。
*每个函数在定义的时候,作用域链中都保存有GO

Line: 10 — 当函数a被执行时(前一刻),作用域链的顶端(第0位)存储a函数生成的执行期上下文AO,同时第1位存储GO。
*JS引擎查找变量时,到a函数存储的作用域链中,从顶端开始一次向下查找
*函数自身的AO总是存在自己作用域链的最顶端

Line:2 — 当b函数被定义时,是在a函数的环境下,所以b函数这时的作用域链就是a函数被执行期的作用域链

Line: 6 — 当b函数被执行时(前一刻),生成函数b的[[scope]],存储函数b的作用域链,顶端第0位存储b函数的AO,a函数的AO和全局的GO依次向下排列。

Line: 6 — 当b函数执行结束后,b函数的AO被销毁,回归被定义时的状态

Line: 7 — 当a函数执行结束后,a函数的AO被销毁的同时,b函数及其[[scope]]也将不存在。a函数回归到被定义时的状态。

七:作用域、作用域链、闭包基础 - 图1

3. 案例

  1. function a() {
  2. function b() {
  3. function c() {};
  4. c();
  5. }
  6. b();
  7. }
  8. a();
  • a 定义
    • a.[[scope]]
      • 0: GO
  • a 执行
    • a.[[scope]]
      • 0: AO_a
      • 1: GO
  • b 定义
    • b.[[scope]]
      • 0: AO_a
      • 1: GO
  • b 执行
    • b.[[scope]]
      • 0: AO_b
      • 1: AO_a
      • 2: GO
  • c 定义
    • c.[[scope]]
      • 0: AO_b
      • 1: AO_a
      • 2: GO
  • c 执行
    • c.[[scope]]
      • 0: AO_c
      • 1: AO_b
      • 2: AO_a
      • 3: GO
  • c 结束
    • c.[[scope]]
      • 0: AO_b
      • 1: AO_a
      • 2: GO
  • b 结束
    • b.[[scope]]
      • 0: AO_a
      • 1: GO
    • c.[[scope]]
  • a 结束
    • a.[[scope]]
      • 0: GO
      • b.[[scope]]


4. 闭包引入

  1. function test1() {
  2. function test2() {
  3. var b = 2;
  4. console.log(a);
  5. }
  6. var a = 1;
  7. return test2;
  8. }
  9. var c = 3;
  10. var test3 = test1();
  11. test3();

Line: 8 — 当test1函数被执行结束时,由于test2被返回到外部且被全局变量test3接收,因此test1的AO没有被销毁,只是AO_test1被从test1的作用域链中去除,但仍被test2的作用域链保存。

Line: 11 — 当test3执行时,test2的作用域链增加自己的AO,当打印a的时候,在自己的AO上没有查到,则向test1的AO查找。
再次执行test3时,实际操作的仍然是原来test1的AO。

Line: 11 — 当test3函数被执行结束时,test2的AO被销毁,但原来test1的AO仍然存在且被test2连着

七:作用域、作用域链、闭包基础 - 图2
总结:当内部函数被返回到外部并保存时,一定会产生闭包,闭包会导致原来的作用域链不释放,过度的闭包可能会导致内存泄漏或加载过慢

5. 闭包案例

案例1:

  1. function test() {
  2. var n = 100;
  3. function add() {
  4. n++;
  5. console.log(n);
  6. }
  7. function reduce() {
  8. n--;
  9. console.log(n);
  10. }
  11. return [add, reduce]
  12. }
  13. var arr = test();
  14. arr[0](); // 101
  15. arr[1](); // 100

案例2:面包管理

  1. function breadMgr(num){
  2. var breadNum = num || 10;
  3. function supply() {
  4. breadNum += 10;
  5. consol.log(breadNum);
  6. }
  7. function sale() {
  8. breadNum--;
  9. consol.log(breadNum);
  10. }
  11. return [supply, sale]
  12. }
  13. var breadMgr = breadMgr(20);
  14. breadMgr[0]();
  15. breadMgr[1]();
  16. breadMgr[1]();

案例3:日程管理,函数作为对象方法返回

  1. function sunSched() {
  2. var sunSched = '';
  3. var operation = {
  4. setSched: function(thing){
  5. sunSched = thing;
  6. },
  7. showSched: function() {
  8. console.log("My schedule on Sunday is " + sunSched);
  9. }
  10. }
  11. return operation;
  12. }
  13. var sunSched = sunSched();
  14. sunSched.setSched("studying");
  15. sunSched.showSched();

闭包相当于做了数据缓存