我们在了解了全局代码的执行和作用域的提升后,我们接下来理解比较特殊的函数执行和作用域链。
函数的全局代码执行过程
代码被解析,开辟函数内存空间,go中引用函数地址。
通过全局代码的变量提升我们知道,在代码解析过程中,会生成全局对象Global Object (GO),并在其中对全局的变量进行定义,函数在这个期间也会被定义,并生成一块该函数专属的内存空间,通过引用的形式访问。
var name = 'mjy'
foo(123)
function foo(num) {
console.log(m)
var m = 10
var n = 20
}
var globalObject = {
String: "类",
Date: "类",
seTimeount: "函数",
window: globalObject
name: undefined,
foo: 0xa00
}
在函数开辟的空间中:存放函数的作用域 scope和函数执行体(代码块),作用域为当前函数的父级作用域,也就是说:函数的作用域在函数还没执行前就在内存中定义好了。这个点很重要
图示:
函数执行前,产生FEC,AO对象,FEC入栈
在函数代码执行时,js会创建一个 函数执行上下文FEC(Functional Execution Context), 并将函数执行上下文FEC,**加入到执行上下文栈ECStack(EXecution Context Stack)**中。
在FEC函数的执行上下文栈中,和全局执行上下文GEC一样,同样有着vo对象,此时的vo对象指像的是函数创建的**AO(ACtivation Object)**对象。和全局执行上下文不同的是,函数的FEC函数的执行上下文栈中还有着作用域链,其值为当前的作用域AO + GO
function foo(num) {
console.log(num)
var m = 20
var n = 10
}
activationObject = {
num = undefined,
m = undefined,
n = undefined
}
foo(123)
activationObject = {
num = 123,
m = 20,
n = undefined
}
函数调用函数的执行过程
当函数中出现了嵌套函数时,在外层函数的AO中,也是通过存放地址的引用形式来引用函数的 。函数在内存堆中的示意图是这样的
函数中变量的查找过程
当我们在函数中查找一个变量时, 查找的路径是在自身的FEC中沿着作用域链 scope chain 来查找的,
作用域链的值为scopechain:自身作用域AO + 父级作用域ParaentScope
例如下面这段代码
var message = "hello Global"
function foo() {
console.log(message)
}
function bar() {
var message = "hello Bar"
foo()
}
bar()
foo函数的AO对象中并没有message变量,此时它就会沿着作用域链scopechain去父级作用域中查找,而foo函数的父级作用域在foo函数开辟内存空间时,就已经是定义好的了,其值就是全局作用域GO。从而就找到了GO中的message。
由此得出:函数作用域跟定义位置有关系,跟调用的位置没有关系
图示:
总结:
**代码解析时:**代码中定义了函数,为其开辟自己的函数空间,函数空间中存放着父级作用域和函数体。
函数执行前(函数只有将要被执行才会有这一步):创建函数执行上下文FEC,AO函数活跃对象,AO对象中存放着函数中定义的变量。FEC中存放着VO对象指向AO,和函数的作用域链条scopechain,作用域链值为AO+函数内存中定义的父级作用域。
**函数执行时:**函数执行上下文FEC入栈ECS。代码依次执行,为AO对象中定义的变量赋值。
**函数执行后:**开辟的函数内存空间销毁,释放内存。函数的AO对象与内存空间不复存在。
若函数中嵌套了函数,在父级函数运行前,就说父级函数AO创建时会被读取到,在堆中开辟出新的函数内存空间,通过址引用的方法关联。当嵌套函数运行时,运行上面的4步骤。
当我们在函数中查找变量时:会根据函数的作用域链来查找,先查找自身AO对象,再去作用域中查找。函数的作用域跟定义位置有关系,跟调用的位置没有关系(函数的作用域在函数还没执行前就在内存中定义好了)。
面试题:
1.函数的作用域
var n = 100
function foo() {
n = 200
}
foo()
console.log(n)
2.函数的执行过程
function foo() {
console.log(n)
var n = 200
console.log(n)
}
var n = 100
foo()
3.函数的作用域链
var n = 100
function foo1() {
console.log(n)
}
function foo2() {
var n = 200
console.log(n)
foo1()
}
foo2()
console.log(n)
4.函数的返回值
var a = 100
function foo() {
console.log(a)
return
var a = 200
}
foo()
5.函数的作用域与作用域链与全局变量
function foo() {
var a = b = 100
}
foo()
console.log(a)
console.log(b)
题解:
1.沿着作用域链scopechain查找到GO中的变量n,并且为其赋值为200
200
2.代码还没执行,AO对象中变量n的值为初值undefined,当执行到了赋值语句,n的值才会改变。
undefined 200
3.foo1的AO对象中并没有变量n,去作用域链中的父级作用域GO中查找。 foo2中AO对象中有变量n,且输出语句前已经赋值。
200 100 100
4.函数的返回值在AO中也是有定义的
200
5.全局对象GO中并没有定义变量a,所以输出a报错。
在函数中没有定义的 b 赋值为100, b = 100,严格来说这是一个错误的语法,但js允许这么写,它会沿着作用域去查找,最终到GO中定义一个变量b,并将其赋值。
报错:a is not defined
100
|