一、执行上下文

1-1 什么是执行上下文

执行上下文就是JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行

1-2 执行上下文类型

  1. 全局执行上下文:不在函数中的代码都位于全局执行上下文中,一个程序中只能存在一个全局执行上下文,全局执行上下文会创建一个全局对象(在浏览器中就是window对象),然后将this指针指向这个全局对象
  2. 函数执行上下文:每次函数被调用时,都会为该函数创建一个新的执行上下文,一个程序中可以存在任意数量的函数执行上下文
  3. Eval函数执行上下文

    1-3 变量对象

  • 变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明
  • 在全局上下文中,变量对象就是全局对象
  • 在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象

1-4 执行上下文生命周期

执行上下文的生命周期包括三个阶段:创建阶段 -> 执行阶段 -> 回收阶段
创建阶段:

  • 函数执行上下文

① 初始化函数的参数 arguments ,提升函数声明和变量声明
② 创建作用域链(作用域链是在创建变量对象之后创建的。因为作用域链是用于解析变量,当被要求解析变量时,JavaScript始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一级的父作用域中查找,直到找到该变量)
③ 确定 this 指向

  • 全局执行上下文

在一段JS脚本执行之前,会先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,然后提升函数声明和变量声明。变量先赋值为undefined,函数则先声明好,这一步做完了,然后开始正式执行程序。
执行阶段:执行变量赋值、代码执行
回收阶段:执行上下文出栈等虚拟机回收执行上下文

二、变量提升

参考《变量提升

三、确定this指向

概念:this 的值是在函数执行的时候才能确认,定义的时候不能确认。因为this是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候

3-1 四种基本情况

  1. 情况一:直接调用
  2. function foo() {
  3. console.log(this)
  4. }
  5. foo() // 非严格模式 window 严格模式 undefined
  6. // 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window 或 undefined
  7. 情况二:定义在对象中的函数被调用
  8. let obj = {
  9. name:'hzy'
  10. foo:function(){
  11. console.log(this.name)
  12. }、
  13. }
  14. obj.foo() //hzy
  15. // 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
  16. let obj = { name:'hzy' }
  17. function foo(){
  18. console.log(this.name)
  19. }
  20. obj.foo = foo
  21. obj.foo() //hzy
  22. 情况三:构造函数中的this
  23. function Foo(){
  24. this.name = 'hzy';
  25. }
  26. let f = new Foo()
  27. console.log(f.name) // 构造函数中的this与被实例化的新对象绑定。
  28. 情况四:箭头函数
  29. let p = {
  30. a: function () {
  31. var obj = {
  32. b: () => {console.log(this)},
  33. }
  34. obj.b()
  35. }
  36. }
  37. p.a() // {a: ƒ} 函数执行上下文的this值
  38. // 箭头函数不绑定this, 它会捕获其定义时的位置上下文的this值, 作为自己的this值

image.png

3-2 this指向案例

  1. // 案例1:setTimeout & setInterval 延时器中的普通回调函数的this,指向window
  2. function Person(){
  3. this.age = 0
  4. setTimeout(function(){
  5. console.log(this)
  6. },3000)
  7. }
  8. const p = new Person() //window
  9. // 案例2:
  10. function Person(){
  11. this.age = 0
  12. setTimeout(()=>{console.log(this)},3000)
  13. // 箭头函数捕获其定义时的位置上下文的this值,这里的上下文是构造函数Person
  14. // 构造函数中的this与被实例化的新对象绑定
  15. }
  16. const p = new Person() //Person{age:0}

四、执行上下文栈

每次调用函数创建一个新的执行上下文,JavaScript引擎创建了执行上下文栈来管理执行上下文,可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
例子一:
1210492-20191212134435267-865472671.gif

上述流程图几个重要关键点
① JavaScript 执行在单线程上,所有的代码都是排队执行
② 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部
每当执行一个函数就会创建函数的执行上下文,并且把它压入执行栈的顶部;当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收
④ 浏览器的 JS 执行引擎总是访问栈顶的执行上下文
全局上下文只有唯一的一个,它在浏览器关闭时出栈

例子二:

  1. var color = "blue";
  2. function changeColor() {
  3. var anotherColor = "red";
  4. function swapColors() {
  5. var tempColor = anotherColor;
  6. anotherColor = color;
  7. color = tempColor;
  8. }
  9. swapColors();
  10. }
  11. changeColor();

1210492-20191212135032720-1269449947.png