变量提升
举个栗子,如以下代码:
console.log(age)
var age = 10
经过实践可以发现,打印结果是 undefined。那又是为什么会出现这一结果呢?
在代码打印之前会有一个编译的过程。我们定义了 age 变量,经过编译就会出现一个全局的上下文:
{
age: undefined,
String: ,
Number: ,
...
}
这个全局的上下文,包含我们常用的一些方法或者类型以及我们定义的 age。编译完成之后接下来执行js代码,这时会先执行 console.log(age),输出全局上下文中的 age即 undefined。接下来执行 var age = 10给 age 赋值。
{
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。
我们定义了 age、foo、bar,经过编译就出现以下上下文:
// 全局上下文
{
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等
垃圾回收机制
引用计数
如下图所示:当定义obj时,会在内存中为其开辟一块空间,当有变量引用它时,这个引用就会 +1,释放时引用-1,如果引用计数为0,将会释放内存。
但是这种方式在循环引用时,可能永远得不到释放,造成内存泄漏
标记清除
现在标记清除应用更为广泛。
本文是学习王元红老师深入JavaScript高级语法课程的个人总结,素材来源于课程。