js深入学习之作用域链

545 阅读3分钟

变量提升

举个栗子,如以下代码:

console.log(age)
var age = 10

经过实践可以发现,打印结果是 undefined。那又是为什么会出现这一结果呢? 在代码打印之前会有一个编译的过程。我们定义了 age 变量,经过编译就会出现一个全局的上下文:

{
    age: undefined,
    String: ,
    Number: ,
    ...
}

这个全局的上下文,包含我们常用的一些方法或者类型以及我们定义的 age。编译完成之后接下来执行js代码,这时会先执行 console.log(age),输出全局上下文中的 ageundefined。接下来执行 var age = 10age 赋值。

{
    age: 10,
    String: ,
    Number: ,
    ...
}

函数提升

既然变量提升会输出 undefined,那函数提升的结果又是怎样的呢?以下代码能不能顺利打印出 'foo'?

foo()
function foo() {
    console.log('foo')
}

函数提升和变量提升有所区别。具体在于,在代码编译阶段会给函数开辟出一块内存,用来存放函数体(即代码块)等。经过编译后 foo 对应的是一个地址,这个地址指向存储函数的内存。

{
    foo: 0xa00,
    ...
}

0xa00是函数的存放地址。

graph TD
函数体:代码块,...

接下来执行代码,由上而下执行 foo() 会在全局上下文中找到 foo属性,进而找到其对应的内存地址,执行相关代码,最终输出foo

作用域链

我们一眼看上去,就知道执行以下代码会输出 20,我们并没有在 foo() 函数中定义 age那为什么会输出 20呢?

var age = 20
foo()
function foo() {
    console.log(age)
}

这就涉及到了作用域链。上文我们说到编译代码时,会给函数开服一块内存,用来存放函数体,其实除了函数体,还存放了父级作用域。当执行代码时会现在当前作用域(存放函数的内存中)找 age变量,发现没有,这时就会去其父级作用域(全局上下文)中寻找,找到 age 对应的值并输出。

这里我想请大家看一下以下代码,输出结果又是什么呢:

var age = 20

function foo() {
    console.log(age)
}
function bar() {
    var age = 50
    foo()
}

bar()

答案是:20。 我们定义了 agefoobar,经过编译就出现以下上下文:

// 全局上下文
{
    age: undefined,
    foo: 0xa00, // 0xa00是函数存放地址
    bar: 0xb00, // 0xb00是函数存放地址
    ...
}
// foo函数(0xa00)
{
}
// bar函数(0xb00)
{
    age: undefined
}

接下来执行代码,先给 age赋值20;接下来执行bar函数,给 0xa00 内存中 age赋值50,然后执行foo函数。foo函数作用域中并没有 age变量,沿着作用域链向上查找,这是找到了全局上下文中的age并输出。 简而言之,是因为其父级作用域在编译时就已经确定了,所以才输出 20。

内存

基本数据类型:String,Number,Boolean,null,undefined,Symbol

复杂数据类型(引用类型):Object,Array等 image.png

垃圾回收机制

引用计数

如下图所示:当定义obj时,会在内存中为其开辟一块空间,当有变量引用它时,这个引用就会 +1,释放时引用-1,如果引用计数为0,将会释放内存。

image.png

但是这种方式在循环引用时,可能永远得不到释放,造成内存泄漏

标记清除

现在标记清除应用更为广泛。 image.png

本文是学习王元红老师深入JavaScript高级语法课程的个人总结,素材来源于课程。