「JS篇」JavaScript 执行上下文和提升

2019-07-13 admin

我们通常将 JavaScript 归类为动态或解释执行语言,但实际上它也是一门编译语言,它有自己的编译器形式,运行在 JavaScript 引擎中。

每个 Web 浏览器都有自己的 JavaScript 引擎形式:Chrome 有 V8,Mozilla 有 SpiderMonkey 等。这些 JavaScript 引擎的共同点都是将 JavaScript 代码转换为编译器可以理解的语言,然后执行它。

执行上下文 Execution Context

当 JavaScript 代码运行的时候,运行 JavaScript 代码的环境形成了执行上下文 ,执行上下文决定代码可以访问哪些变量、函数、对象等。

我们将执行上下文简单视为运行当前代码的 environment / scope,我们知道作用域分为 global scopelocal scope

类似的,执行上下文也分为不同的类型:

全局执行上下文 - 代码首次执行时候的默认环境,在代码的整个执行过程中,只用一个全局执行上下文。

函数执行上下文 - 每当执行流程进入到一个函数体内部的时候,就会创建一个函数执行上下文,可以有任意数量的函数执行上下文。

clipboard.png

执行栈/调用栈

JavaScript 是单线程的,浏览器只分配给 JavaScript 一个主线程,一次只能执行一个任务(函数),因此它在执行栈中对其他操作(事件和函数执行)形成一个任务队列,排队等候执行。

clipboard.png

每当在浏览器中加载脚本时,栈 stack 中的第一个元素就是全局执行上下文。当有函数执行时,将创建一个函数执行上下文,并将其置于全局执行上下文之上。一旦函数执行完成,它就会从执行堆栈中弹出,并将控制权交给它下面的上下文中。结合上面说到的,我们看一个例子:

var name = "global variable";
console.log(name)

function func1() {
  console.log("func1 被调用了。")
  func2();
}
function func2() {
  console.log("func2 被调用了。");
}
func1();

clipboard.png

当上述代码在浏览器中加载时:

  • Javascript 引擎创建一个全局执行上下文 global execution context 并将其推送到当前执行栈。
  • 接着进行 func1() 被调用,然后 Javascript 引擎为该函数创建一个新的函数执行上下文 function execution context 并将其推送到全局执行上下文的顶部。
  • 在执行 func1() 过程中,发现 func2() 被调用,Javascript 引擎为该函数创建一个新的执行上下文,并将其推送到 func1() 执行上下文的顶部。
  • func2() 函数完成时,其执行上下文从当前堆栈弹出,将控制权交给其下面的执行上下文,即 func1() 函数执行上下文。
  • func1() 完成后,其执行堆栈将从堆栈中删除,将控制权交给全局执行上下文。一旦执行了所有代码,JavaScript 引擎就会从当前堆栈中删除全局执行上下文。

执行上下文阶段

执行上下文主要有两个阶段:创建阶段执行阶段,接下来我们将逐一进行介绍。

创建阶段

在函数执行发生之前,JavaScript 引擎会做如下事情:

  • 首先,为每个函数或变量创建与外部环境的连接,这些函数形成作用域链 scope chain。作用链告诉执行上下文它应该包含什么,以及它应该在哪里查找解析函数的引用和变量的值。(对于全局环境,外部环境为 null。在全局作用域内的所有环境都将把全局环境作为其外部环境)。
  • 扫描作用链后,将创建一个环境存储器,其中全局上下文的创建和引用(Web浏览器中的窗口),变量、函数和函数参数的创建和引用在内存中完成。
  • 最后,在第一步中创建的每个执行上下文中确定 this 关键字的值(对于全局执行上下文,this 指向 window)。

我们可以将创建阶段使用伪代码这样表示:

creationPhase = { // 创建阶段
  'outerEnvironmentConnection': { // 创建外部连接
        // 形成作用域链
   },    
   'variableObjectMapping': {
        // 变量、函数和函数参数的创建和引用在内存中完成。
   },
   'valueOfThis': {},  // 确定 this 的值
}

执行阶段

这是代码在创建阶段形成的执行上下文中的运行的阶段,并且逐行分配变量值。

当执行开始时,JavaScript 引擎在其创建阶段对象中查找执行函数的引用。如果在当前对象中没有找到,它将沿着作用域链继续向上查找,直到它到达全局环境。

如果在全局环境中找不到函数引用,则将返回错误。如果找到了引用并且函数正确执行,那么这个特定函数的执行上下文将从栈中弹出,接着 JavaScript 引擎将移动到下一个函数,它们的函数执行上下文将被加入到栈中并执行,以此类推。

让我们通过示例来看看上面的两个阶段,以便更好地理解它。

let name = "webinfoq";
var title = "execution context";
const message = "hello world";

function func1(num) {
  var author = "deepak";
  let value = 3;
  let func2 = function multiply() {
    return num * value;
  }
  const fixed = "Divine";
  function addFive() {
    return num + 5;
  }
}
func1(10);

因此,全局执行上下文将如下表示:

globalExecutionObj = {  // 全局执行s上下文
    outerEnvironmentConnection: null,  // 全局上下文外部环境为 null
    variableObjectMapping: { 
        name: uninitialized,  // 在创建阶段,let声明的变量是未初始化状态
        title: undefined,   // var 声明的变量表示为未定义
        date: uninitialized, // 在创建阶段,const声明的变量是未初始化状态
        func1: <func1 reference>,  func1 地址引用
    },
    this: window //Global Object  
}

注意:letconst 定义的变量在创建阶段没有任何与之关联的值,但 var 定义的变量在创建阶段为 undefined, 这就是为什么可以在 var 声明之前访问变量,(得到的是 undefined), 在 letconst 声明之前访问会报错(暂时性死区)。

这就是所谓的变量提升,所有使用 var 声明的变量都会被提升到作用域的顶部。

执行阶段,完成对变量的赋值等操作。因此,在执行阶段,全局执行上下文global execution 看起来像这样:

globalExectutionObj = {  // 全局执行上下文
    outerEnvironmentConnection: null,
    variableObjectMapping: {
        name: "webinfoq",
        title: "execution context",
        message: "hello world",
        func1: pointer to function func1, // 指向func1的指针
    },
    this: window //Global Object
}

当执行到 func1() 时,将形成新的函数执行上下文 function execution global,创建阶段如下所示:

func1ExecutionObj = {  // func1 函数执行上下文
    outerEnvironmentConnection: Global,  // 外部环境为全局环境
    variableObjectMapping: {
       arguments: {
            0: 10,
            length: 1
        },
        num: 10,
        author: undefined,  // var 声明的
        value: uninitialized,  // let 声明的
        func2: uninitialized,  // let 声明的
        fixed: uninitialized,  // const 声明
        addFive: pointer to function addFive()  // 指向函数addFive的指针
    },
    this: Global Object or undefined  
}

执行阶段:

func1ExecutionObj = {
    outerEnvironmentConnection: Global,  
    variableObjectMapping: {
       arguments: {  // 先处理 arguments 参数
            0: 10,
            length: 1
        },
        num: 10,
        author: "deepak",  //变量f赋值
        val: 3,
        func2: pointer to function func2() 
        fixed: "Divine"
        addFive: pointer to function addFive()
    },
    this: Global Object or undefined
}

Javascript 引擎创建执行上下文调用栈。当有函数执行时,引擎就会创建一个新的函数执行上下文。最后所用函数执行完成后,将更新全局环境,然后全局代码完成,程序结束。

了解更多请关注微信公众号:webinfoq

clipboard.png

[转载]原文链接:https://segmentfault.com/a/1190000019752177

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-69671.html

文章标题:「JS篇」JavaScript 执行上下文和提升

相关文章
vue 数组遍历方法forEach和map的原理解析和实际应用
一、前言 forEach和map是数组的两个方法,作用都是遍历数组。在vue项目的处理数据中经常会用到,这里介绍一下两者的区别和具体用法示例。 二、代码 1. 相同点 都是数组的方法 都用来遍历数组 两个函数都有4个参数:匿名函数中可传3...
2018-11-15
为什么要选择Nodejs?Nodejs有什么好处?
什么?JavaScript还能用作服务器编程! Caleb Madrigal是来自美国密尔沃基市的一名软件顾问。四年前,他在听说“将JavaScript用作服务器端语言”这样的说法时,认为那是一个荒唐的想法。有那么多服务器端语言可供选择,为...
2015-11-12
JavaScript编辑器推荐
主流编辑器有SublimeText,Notepad++,webstorm等,是使用最广泛的编辑器,但也有一些JavaScript编辑器提供有着各自的特性和功能,适应不同人的需求,以下是几款优秀的编辑器,相信你一定能找到自己喜欢的。 1. W...
2015-11-12
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
2015年JavaScript或“亲库而远框架”
2014年过去了,作为一个JavaScript开发者很难满怀信心的去“挽回”一个特定的库或技术,即便是强大的Angular,似乎也因为最近的一些事情而动摇。 2014年10月的ng-europe会议上,Angular开发者团队透露了一个关于...
2015-11-12
Node.js 2014这一年发生了什么
Node.js 的 2014 年充满了不幸和争议. 这一年 Noder 们经历了太多的伤心事, 经历了漫长的等待, 经历了沉重的分裂之痛. 也许 Noder 们不想回忆14年 Node.js land 发生的事情, 但正因为痛才更有铭记的价...
2015-11-12
JavaScript实现PC手机端和嵌入式滑动拼图验证码三种效果
PC和手机端网站滑动拼图验证码效果源码,同时包涵了弹出式Demo,使用ajax形式提交二次验证码所需的验证结果值,嵌入式Demo,使用表单形式提交二次验证所需的验证结果值,移动端手动实现弹出式Demo三种效果 首先要确认前端使用页面,比如...
2017-03-17
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
Vue获取DOM元素样式和样式更改示例
在 vue 中用 document 获取 dom 节点进行节点样式更改的时候有可能会出现 ‘style’ is not definde的错误,这时候可以在 mounted 里用 $refs 来获取样式,并进行更改: &lt;template...
2017-03-13
React.js编程思想
JavaScript框架层出不穷,在很多程序员看来,React.js是创建大型、快速的Web应用的最好方式。这一款由Facebook出品的JS框架,无论是在Facebook还是在Instagram中,它的表现都非常出色。 使用React.j...
2015-11-12
回到顶部