函数执行过程解析
以下图的代码为例,画了一个流程图
代码解析
编译阶段:
- 先创建GO(Global Object)全局作用域,里面包含 变量name 和 函数foo;
// GO(Global Object)
{
name: undefined // 初始值为undefined
foo: 0xa00 // 指向函数内存地址
}
- 碰到函数foo时,会开辟出一块函数存储空间(如:0xa00)用来存储函数作用域以及函数执行体(代码块);
执行阶段:
- 从第一行代码开始执行
var name = "why"
,从GO里面找到 name 属性,给它赋值 “why”
// GO(Global Object)
{
name: "why" // 赋值为why
foo: 0xa00 // 指向函数内存地址
}
- 走到第3行代码
foo(123)
,准备执行之前,创建AO(foo)对象用来存储函数foo内部的变量;遇见函数bar,再开辟出一块函数存储空间(如:0xb00)用来存储函数作用域以及函数执行体(代码块);
// AO (foo)
{
num: undefined // 初始值为undefined
m: undefined // 初始值为undefined
n: undefined // 初始值为undefined
bar: 0xb00 // 指向函数内存地址
}
- 执行第3行代码,调用函数foo,传入参数 123;
从AO(foo)里面找到num属性,赋值 123;
// AO (foo)
{
num: 123 // 赋值123
m: undefined // 初始值为undefined
n: undefined // 初始值为undefined
bar: 0xb00 // 指向函数内存地址
}
走到第5行代码console.log(m)
从AO(foo)取到m属性的值undefined,所以此处打印:undefined;
第6,7行代码给m,n赋值
// AO (foo)
{
num: 123 // 赋值123
m: 10 // 赋值10
n: 20 //赋值20
bar: 0xb00 // 指向函数内存地址
}
- 走到第13行代码
bar()
,准备执行之前,创建AO(bar)对象用来存储函数bar内部的变量;由于函数bar内部没有变量,所以AO(bar)是空的
// AO (bar)
{
}
- 然后执行第13行代码
bar()
,调用函数bar,执行console.log(name)
,
从AO(bar)寻找name属性,但是里面并没有name,所以继续向上从父作用域里查找,
从AO(foo)里面寻找,也没有,继续向上从GO查找,找到了name属性,值是 why ,因此打印结果为:why。
原理:沿着作用域链一层一层查找(若是GO中也不存在该属性,则报错 name is not defined)
bar函数执行完之后,函数执行上下文FEC(bar) 就会从ECStack调用栈中移除,接下来函数执行上下文FEC(foo)也也会移除
作用域提升练习题
var n = 100
function foo() {
n = 200
}
foo()
console.log(n)
打印结果:200;
解析:执行n=200时,先从函数作用域AO中寻找n,没找到,则从父作用域GO寻找,找到n,赋值为200;打印的n作用域是GO,从GO中寻找n,故结果是200
function foo() {
console.log(n)
var n = 200
console.log(n)
}
var n = 100
foo()
打印结果:(1)undefined;(2)200;
解析:因为函数foo声明了n,所以在编译阶段AO:{ n:undefined };
第一次打印从AO中取值,此时是undefined;然后执行 var n = 200,给AO中的n赋值,AO:{ n:200 };
第二次打印从AO中取值,此时是200;
var n = 100
function f001() {
console.log(n)
}
function foo2() {
var n = 200
console.log(n)
foo1()
}
foo2()
console.log(n)
打印结果:从上到下 100;200;100;
var a = 100
function foo() {
console.log(a)
return
var a = 100
}
foo()
打印结果:undefined;
解析:代码编译时,只关心声明的变量,不管return,所以AO:{ a:undefined };
执行时,打印的 a 从AO取值,故结果是undefined;而return后面的代码不会执行
function foo() {
var m = 100
}
foo()
console.log(m)
打印结果:抛异常 m is not defined;
解析:因为var m = 100是定义在函数作用域AO中的,而打印的m作用域是GO,所以找不到m
function foo() {
m = 100
}
foo()
console.log(m)
打印结果:100;
解析:js引擎对这种语法(m = 100)进行特殊处理,默认将 m 定义在全局对象GO中
function foo() {
var a = b = 100
}
foo()
console.log(a)
console.log(b)
打印结果:抛异常 a is not defined;因为前面抛异常,所以console.log(b)不会打印
解析:var a = b = 100
会转成以下两行代码:var a = 10
和b = 10
;赋值顺序是从右到左,先给b赋值,后给a赋值;
所以 a 属于函数作用域AO,b属于全局作用域GO;
而打印的 a 从GO里找不到a,所以抛异常;正常情况下,b会打印100