路漫漫其修远兮
头像

codermjy

A programmer who subconsciously views himself as an artist

will enjoy what he does and will do it better

掌握函数执行过程,作用域链

我们在了解了全局代码的执行和作用域的提升后,我们接下来理解比较特殊的函数执行作用域链

函数的全局代码执行过程

代码被解析,开辟函数内存空间,go对象中引用函数地址。

通过全局代码的变量提升我们知道,在代码解析过程中,会生成全局对象Global Object (GO),并在其中对全局的变量进行定义,函数在这个期间也会被定义,并生成一块该函数专属的内存空间,通过引用的形式访问。

var name = 'mjy'

foo(123)
function foo(num) {
    console.log(m)
    var m = 10
    var n = 20
}

// 代码被解析,浏览器引擎内部会帮助我们创建一个go对象
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
}

// foo函数的激活对象AO
activationObject = {
    num = undefined, // 函数的参数同样也是函数中定义的变量
    m = undefined,
    n = undefined
}

foo(123)
// 当函数执行时,依次为ao对象中的变量赋值
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()  // 输出的是:hello Global

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
    // 相当于
    // b = 100
    // var a = b
}

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

posted @ 2022-05-09 10:42  不愿染是与非  阅读(29)  评论(0编辑  收藏  举报