函数执行的几个步骤
- 形成一个私有的执行上下文(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 undefined
var a = 12, //等同于 var a,var b,var c
b = 13,
c = 14;
var fn = function (c) {
console.log(a, b, c);//undefined 13 10
var a = b = c = 20;// c=20,b=20,var a=20
console.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 defined
a = 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); //undefined
var A = 100;
console.log(A); //100
})();
/*
* EC(AA)
* A--------->函数本身
* 作用域链
* 形参赋值 A ===100
* 变量提升:---
*/
(function A(A) {
console.log(A); //100
A = 200;
console.log(A); //200
})(100);
(function A(A) {
console.log(A); //100
function 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(); ```