函数执行的几个步骤
- 形成一个私有的执行上下文(AO),然后进栈执行
- 初始化作用域链
- 3.初始化THIS
- 初始化ARGUMENTS
- 形参赋值
- 变量提升
- 代码执行
- 根据情况决定是否出栈释放
作用域:
创建函数的时候就声明了它的作用域,在哪个上下文中创建的,那它作用域就是哪个上下文
作用域和上下文是一个东西么
是,都是函数执行形成的那个空间,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)
var x = [12, 23];function fn(x) {x[0] = 100;x = [100];x[1] = 200;console.log(x); //[100,100]}fn(x);console.log(x); [100,23]

2)
console.log(a, b, c);//undefined undefined undefinedvar a = 12, //等同于 var a,var b,var cb = 13,c = 14;var fn = function (c) {console.log(a, b, c);//undefined 13 10var a = b = c = 20;// c=20,b=20,var a=20console.log(a, b, c);//20 20 20};fn(10);console.log(a, b, c); //12 20 14
如果全局上下文没有变量
获取:报错
赋值:为window变设置属性/*/!** EC(G)* VO(G) / GO* fn ---> 0x000 [[scope]]:EC(G)*!/var fn = function () {/!** EC(FN)* AO(FN)** 作用域链:<EC(FN),EC(G)>* 形参赋值:--* 变量提升:--*!/console.log(a); //获取的话就是报错 Uncaught ReferenceError: a is not defineda = 100; //window.a=100};fn();console.log(a, window.a); //100 100
函数重构:
let a = 0,b = 0;function A(a) {// 函数重构:第一次执行A函数,内部把A改为里面的小函数;所以以后再执行A,执行的都是里面的小函数;A = function (b) {alert(a + b++);};alert(a++);};A(1);A(2);
匿名函数具名化【官方推荐规范】
- 应该是匿名函数的
- 自执行函数
- 函数表达式
- 回调函数 setTimeout(function(){},1000)
匿名函数具名化特点
1.设置的名字,并不会在所处上下文中进行声明
(function A() {// ...})();console.log(A); //Uncaught ReferenceError: A is not defined
2.在函数执行形成的私有上下文中,会把这个名字作为一个私有变量「存储到AO中」,变量值是当前函数本身「堆内存地址」; 并且默认情况下,对这个变量值进行修改是无效的;
(function A() {A = 100; //无效操作console.log(A); //函数本身})();
3.但凡函数内部,这个名字被我们手动的声明过「例如:形参/var/function/let/const…」,都认为这个名字是我们自己玩的私有变量,和默认值是当前函数本身没关系了!! “默认是函数本身的这个点,权重太低”
/** EC(AA)* A--------->函数本身* 作用域链* 形参赋值--* 变量提升:var A*/(function A() {console.log(A); //undefinedvar A = 100;console.log(A); //100})();/** EC(AA)* A--------->函数本身* 作用域链* 形参赋值 A ===100* 变量提升:---*/(function A(A) {console.log(A); //100A = 200;console.log(A); //200})(100);(function A(A) {console.log(A); //100function A(){}console.log(A); //200})(100);
/*/!** EC(G)* b --> 10** 变量提升: var b;*!/var b = 10;(function b() {/!** EC(B) 私有上下文* b --> 当前函数本身* 作用域链:<EC(B),EC(G)>* 形参赋值:--* 变量提升:--*!/b = 20; //无效操作console.log(b); //当前函数本身})();console.log(b); //10
匿名函数具名化作用:
因为我们可以在函数内部,基于这个名字访问到这个函数,这样就可以实现一些原本不具备的能力,例如:递归
“use strict”; 开启JS严格模式「语法要更加严谨,目前我们开发,基于webpack打包后,都是严格模式」
"use strict";(function () {// 递归?// arguments.callee:代表当前函数本身// arguments.callee.caller:存储函数在哪执行的// 但是在严格模式下,都嗝屁了,不允许使用....console.log(arguments);})();
(function A() {// 实现递归处理// console.log(A); //函数本身A();})(); //死递归 内存溢出
了解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()()=》返回个函数,紧接着执行

