1、执行上下文的分类
执行上下文主要分为三类:
全局上下文 —— 全局代码所处的环境,不在函数中的代码都在全局执行上下文中
函数上下文 —— 在函数调用时创建的上下文
Eval 执行上下文 —— 运行 Eval 函数中的代码时所创建的环境
2、全局上下文的创建和组成
当我们的 JS 脚本跑起来之后,第一个被创建的执行上下文就是全局上下文。
当我们的脚本里一行代码也没有的时候里,全局上下文里会比较干净,只有两个东西:
1.全局对象(浏览器里是 Window, Node 环境下是 Global)
2.this 变量。这里的 this ,指向的还是全局变量
每一个执行上下文都会经历这样一个生命周期:
创建阶段 —— 执行上下文的初始化状态,此时一行代码都还没有执行,只是做了一些准备工作;
执行阶段 —— 逐行执行脚本里的代码。
创建阶段JS引擎做的事情:
- 创建全局对象(Window 有了)
- 创建 this ,并让它指向全局对象
- 给变量和函数安排内存空间
- 默认给变量赋值为undefined
- 将函数声明放入内存 创建作用域链
执行上下文在执行阶段里其实始终是处在一个动态。
结合我们的上下文创建过程,你会知道,其实根本不存在任何的 “提升”,变量一直在原地。所谓的 “提升”,只是变量的创建过程(在上下文创建阶段完成)和真实赋值过程(在上下文执行阶段完成)的不同步带来的一种错觉。执行上下文在不同阶段完成的不同工作,才是 “变量提升 “的本质。
3、函数上下文的创建和组成
两者之间的不同主要体现在以下方面上:
- 创建的时机 —— 全局上下文在进入脚本之初就被创建,而函数上下文则是在函数调用时被创建
- 创建的频率 ——全局上下文仅在代码刚开始被解释的时候创建一次;而函数上下文由脚本里函数调用的多少决定,理论上可以创建无数次
- 创建阶段的工作内容不完全相同 —— 函数上下文不会创建全局对象(Window),而是创建参数对象(arguments);
- 创建出的 this不再死死指向全局对象,而是取决于该函数是如何被调用的 —— 如果它被一个引用对象调用,那么 this 就指向这个对象;否则,this的值会被设置为全局对象或者 undefined(在严格模式下)
4、调用栈
我们看到函数执行完毕后,其对应的执行上下文也随之消失了。这个消失的过程,我们叫它” 出栈 “—— 没错,在 JS 代码的执行过程中,引擎会为我们创建” 执行上下文栈 “(也叫调用栈)。
因为函数上下文可以有许多个,我们不可能保留所有的上下文。当一个函数执行完毕,其对应的上下文必须让出之前所占用的资源。因此上下文的建立和销毁,就对应了一个” 入栈 “和” 出栈 “的操作。当我们调用一个函数的时候,就会把它的上下文推入调用栈里,执行完毕后出栈,随后再为新的函数进行入栈操作。
作用域其实就是当前所处的执行上下文。
一般来说,函数出栈后,我们都没有办法再访问到函数内部的变量了。但闭包可不是这样。
在执行上下文的创建阶段,跟着被创建的还有作用域链!这个作用域链在函数中以内部属性的形式存在,在函数定义时,其对应的父变量对象就会被记录到这个内部属性里。闭包正是通过这一层作用域链的关系,实现了对父作用域执行上下文信息的保留。
注意!这里是沿着作用域链找,可不是沿着调用栈一层一层往上找哦!调用栈是在执行的过程中形成的,而作用域链可是在书写阶段就决定了。
5、原型编程范式
原型编程范式的核心思想就是利用实例来描述对象,用实例作为定义对象和继承的基础。在 JavaScript 中,原型编程范式的体现就是基于原型链的继承。