这是我参与「第四届青训营 」笔记创作活动的的第5天
执行上下文
变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。
每个上下文都有一个关联的变量对象VO(variable object),用于保存上下文执行的变量和函数声明。
全局对象GO(Global Object)和AO(Activition Object)
全局上下文是最外层的上下文.
在浏览器中,全局上下文就是window对象;
全局代码执行的时候,GO对象作为VO对象;函数代码执行的时候,AO对象就作为VO对象。
js引擎在全局代码执行之前,在堆内存创建一个GO对象,该对象可以被所有作用域访问,是作用域链的最外层;当函数被执行的时候,创建一个AO对象;
执行上下文栈
js引擎内部有一个执行上下文栈(ECS,Execution Context Stack);
ECMAScript程序的执行流就是通过这个上下文栈进行控制的。
上下文执行栈的顶部上下文就是掌握执行权的上下文。
全局代码执行的时候,会构建一个全局执行上下文global execution context-GEO,放入栈中执行;当函数代码执行的时候,会创建一个函数执行上下文放入栈中执行,当函数执行完毕就被弹出;函数上下文在其中代码执行完毕之后就被销毁,全局上下文在网页被关闭或退出浏览器的时候被销毁。
作用域链列表
作用域链列表保存了各个作用域的起始地址。函数的作用域链列表在声明的时候就确定了,与运行时候无关。
代码执行的时候内存表现
上下文代码执行的时候,会创建VO对象的一个作用域链,scopes-这个作用域链决定了各级上下文的代码访问变量和函数的时候的顺序。
正在执行的VO对象位于作用域链最前端。如果上下文是函数,AO作为VO对象;如果上下文是全局代码,GO作为VO对象。GO是作用域链的最后一个对象。
通过作用域链查找变量
代码执行的时候查找变量是通过作用域链逐级搜索,从作用域链的最前端开始向后寻找。
内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索。局部上下文首先从自己的变量对象开始搜索变量和函数,搜不到就沿着作用域链或者对象的原型链去查找上级上下文或原型对象。
作用域链增强
某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。
- try/catch语句的catch块
- with语句这两种情况下,都会在作用域链前端添加一个变量对象。
变量声明
使用var关键字的声明
- var关键字声明的变量,会被添加到最近的非临时的上下文。
- 函数内声明的以及with内声明的被添加到最近的函数上下文上。全局声明的或者未声明就初始化的变量被添加到全局上下文。
- 存在变量提升。即在变量声明之前使用变量。
- 所有通过var定义的全局变量和函数都会成为window对象的属性和方法。
使用let的块级作用域声明
- let关键字声明的变量作用域是块级的,且同一个let变量在同一个作用域内不能声明两次;
- 块级作用域由最近的一对包含花括号{}界定;
- let在JavaScript运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的缘故,实际上不能在声明之前使用let变量。
- 使用块级作用域声明并不会改变变量查找流程,只是缩小了层级:
- 使用let和const的顶级声明不会定义在全局上下文中。
使用const的常量声明
- const常量和let的作用域相同。
- const常量声明的同时初始化,并且值不能被更改。
- 使用const声明对象,是可以给它增加属性的,但是不能赋值-赋值是一个浅拷贝的过程,需要改变内存指向的,常量的内存指向不应该被改变。
JavaScript运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的V8引擎就执行这种优化。
测试代码
function buildUrl() {
var name = "yyy"
let qs = "? debug=true";
with (location) {
// let url = href + qs;//抛出错误:url is not defined
var url = href + qs;//ok
}
return url;
//console.log(name)//yy
}
buildUrl()
console.log(name)//无
age = 21;
console.log(age)//21
var age;
console.log(age2)
let age2;//Uncaught ReferenceError: Cannot access 'age2' before initialization