函数执行的几个步骤

    1. 形成一个私有的执行上下文(AO),然后进栈执行
    1. 初始化作用域链
  • 3.初始化THIS
    1. 初始化ARGUMENTS
    1. 形参赋值
    1. 变量提升
    1. 代码执行
    1. 根据情况决定是否出栈释放


作用域:

创建函数的时候就声明了它的作用域,在哪个上下文中创建的,那它作用域就是哪个上下文

作用域和上下文是一个东西么

是,都是函数执行形成的那个空间,FN的作用域【ECG】ECG 也是全局上下文 从核心来说 都是栈内存
函数自己执行形成的EC(FN) 上下文
函数创建的时候声明了它的作用域

函数执行会形成一个”全新”的”私有”的执行上下文 然后进栈执行

一般情况下,函数执行完,形成的这个私有上下文会出栈释放 来优化内存空间

EC(FN) 私有执行上下文

AO(FN) 私有变量对象 [私有上下文声明的变量都存储在这里=>私有变量]

  • AO[active object]是VO的分支,函数私有上下文中[AO]
  • 私有变量(AO):

    • @1 形参变量
    • @2 函数体中声明过的变量

      代码执行前的步骤

      1.初始作用域链:scope chain

      2.初始化THIS

      3.初始ARGUMENTS—实参集合

      4.形参赋值

      5.变量提升

      作用域链

      <自己的上下文【执行产生的】,函数的作用域【创建时候声明的】>

      作用域链机制

      @1:

      私有上下文中,代码执行阶段,遇到一个变量,我们首先看是否为自己上下文中的私有变量,
      如果是自己的【AO】,则接下来所有操作,都是操作自己的,和外界的变量没有直接的关系

      @ 2:

      如果不是自己私有的变量,则按照作用域链,查找是否为其上级上下文中变量【上级上下文就是函数的作用域】
      如果找到了,则后期操作的都是上级上下文中的变量

      @3

      如果上级上下文也没有这个变量,则继续找其”上上级”上下文,一直到EC(G)全局上下文位置
  • 如果全局上下文中也没有

    • 获取变量值就是报错
    • 设置变量值就是给window设置的属性


      练习题

      1)

      1. var x = [12, 23];
      2. function fn(x) {
      3. x[0] = 100;
      4. x = [100];
      5. x[1] = 200;
      6. console.log(x); //[100,100]
      7. }
      8. fn(x);
      9. console.log(x); [100,23]

      3.png

      2)

      1. console.log(a, b, c);//undefined undefined undefined
      2. var a = 12, //等同于 var a,var b,var c
      3. b = 13,
      4. c = 14;
      5. var fn = function (c) {
      6. console.log(a, b, c);//undefined 13 10
      7. var a = b = c = 20;// c=20,b=20,var a=20
      8. console.log(a, b, c);//20 20 20
      9. };
      10. fn(10);
      11. console.log(a, b, c); //12 20 14
      3-1.png

      如果全局上下文没有变量

      获取:报错
      赋值:为window变设置属性
      1. /*
      2. /!*
      3. * EC(G)
      4. * VO(G) / GO
      5. * fn ---> 0x000 [[scope]]:EC(G)
      6. *!/
      7. var fn = function () {
      8. /!*
      9. * EC(FN)
      10. * AO(FN)
      11. *
      12. * 作用域链:<EC(FN),EC(G)>
      13. * 形参赋值:--
      14. * 变量提升:--
      15. *!/
      16. console.log(a); //获取的话就是报错 Uncaught ReferenceError: a is not defined
      17. a = 100; //window.a=100
      18. };
      19. fn();
      20. console.log(a, window.a); //100 100

      函数重构:

      1. let a = 0,
      2. b = 0;
      3. function A(a) {
      4. // 函数重构:第一次执行A函数,内部把A改为里面的小函数;所以以后再执行A,执行的都是里面的小函数;
      5. A = function (b) {
      6. alert(a + b++);
      7. };
      8. alert(a++);
      9. };
      10. A(1);
      11. A(2);
      1-3.png

      匿名函数具名化【官方推荐规范】

  • 应该是匿名函数的
    • 自执行函数
    • 函数表达式
    • 回调函数 setTimeout(function(){},1000)


匿名函数具名化特点

1.设置的名字,并不会在所处上下文中进行声明

  1. (function A() {
  2. // ...
  3. })();
  4. console.log(A); //Uncaught ReferenceError: A is not defined

2.在函数执行形成的私有上下文中,会把这个名字作为一个私有变量「存储到AO中」,变量值是当前函数本身「堆内存地址」; 并且默认情况下,对这个变量值进行修改是无效的;

  1. (function A() {
  2. A = 100; //无效操作
  3. console.log(A); //函数本身
  4. })();

3.但凡函数内部,这个名字被我们手动的声明过「例如:形参/var/function/let/const…」,都认为这个名字是我们自己玩的私有变量,和默认值是当前函数本身没关系了!! “默认是函数本身的这个点,权重太低”

  1. /*
  2. * EC(AA)
  3. * A--------->函数本身
  4. * 作用域链
  5. * 形参赋值--
  6. * 变量提升:var A
  7. */
  8. (function A() {
  9. console.log(A); //undefined
  10. var A = 100;
  11. console.log(A); //100
  12. })();
  13. /*
  14. * EC(AA)
  15. * A--------->函数本身
  16. * 作用域链
  17. * 形参赋值 A ===100
  18. * 变量提升:---
  19. */
  20. (function A(A) {
  21. console.log(A); //100
  22. A = 200;
  23. console.log(A); //200
  24. })(100);
  25. (function A(A) {
  26. console.log(A); //100
  27. function A(){}
  28. console.log(A); //200
  29. })(100);
  1. /*
  2. /!*
  3. * EC(G)
  4. * b --> 10
  5. *
  6. * 变量提升: var b;
  7. *!/
  8. var b = 10;
  9. (function b() {
  10. /!*
  11. * EC(B) 私有上下文
  12. * b --> 当前函数本身
  13. * 作用域链:<EC(B),EC(G)>
  14. * 形参赋值:--
  15. * 变量提升:--
  16. *!/
  17. b = 20; //无效操作
  18. console.log(b); //当前函数本身
  19. })();
  20. console.log(b); //10

匿名函数具名化作用:

因为我们可以在函数内部,基于这个名字访问到这个函数,这样就可以实现一些原本不具备的能力,例如:递归

“use strict”; 开启JS严格模式「语法要更加严谨,目前我们开发,基于webpack打包后,都是严格模式」

  1. "use strict";
  2. (function () {
  3. // 递归?
  4. // arguments.callee:代表当前函数本身
  5. // arguments.callee.caller:存储函数在哪执行的
  6. // 但是在严格模式下,都嗝屁了,不允许使用....
  7. console.log(arguments);
  8. })();
  1. (function A() {
  2. // 实现递归处理
  3. // console.log(A); //函数本身
  4. A();
  5. })(); //死递归 内存溢出

了解arguments.callee 和arguments.callee.caller

  • arguments.callee:指的是函数本身
  • arguments.callee.caller 指的是函数执行的宿主环境,如果是在函数A中执行,打印出来的就是A,如果是在全局作用域中执行,打印出来的就是null。 ``` function fn(){ console.log(arguments.callee); // 打印出的是fn 函数本身

} fn();

function fn(){ console.log(arguments.callee.caller); }

fn(); // 此时打印出的是 null(在全局作用域中执行)

function fn(){ console.log(arguments.callee.caller); } function A(){ fn(); // 此时打印出的是 A这个函数 } A(); ```

fn()()=》返回个函数,紧接着执行

1-2.png