首先我们知道JavaScript引擎包括一个调用栈调用栈是代码实际执行的地方,使用执行上下文(执行环境)来完成;堆是非结构化的内存池,存储了应用程序所需要的所有对象。

执行上下文是什么?

执行上下文包括全局执行上下文和执行上下文。

全局执行上下文:代码编译完成后进入调用栈执行首先创建全局执行上下文(整个项目只有一个全局执行上下文),是用来执行顶层代码(函数除外,函数只在被调用的时候执行)。

执行上下文:执行一段JavaScript的环境,存储了一些代码执行所需要的必要信息,比如传递给函数的局部变量或者参数。打个比方:我们点外卖,送来的袋子(执行上下文)不只有外卖(JavaScript代码),还有餐具(代码执行所需要的必要信息)。

每一个函数调用就会创建执行上下文来执行。

执行上下文分为三个部分,依次为变量环境,作用域和this关键字:

变量环境VE
  • let,const and var变量

  • 函数声明

  • 函数形参

作用域

作用域(scoping):由JavaScript引擎组织和访问,控制我们程序的变量。

主要分为以下三个

  • 全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域。

  • 函数作用域:只能在函数中访问到的对象具有函数作用域,也称局部作用域。

  • 块作用域:ES6新特性,类似于函数作用域,指的是大括号括起来的块,比如if和for循环,块中的变量只能在块中访问,具有块作用域。但只能用let和const,使用var仍能被全局访问。

        if (birthYear >= 1981 && birthYear <= 1996) {
    var millenial = true;
    const str = `oh,you're a millenial,${firstName}`;
    console.log(str);
    function add(a, b) {
    return a + b;
    }
    }
    console.log(str);//str不能被打印
    console.log(add(1, 1));//add函数不能被打印
    console.log(millenial);//var变量能打印,能被全局访问

作用域链:若在当前作用域无法查找到需要变量,则通过作用域链来进行变量查找,子作用域查找使用父作用域的变量。

注:如果子作用域和父作用域存在相同的变量名,则直接查找子作用域的,无需进行变量查找。

this关键字

定义:为每个执行上下文(函数)创建的特殊变量,取的值为该函数的调用者本身,具体取值包括以下四种方法 this为调用该方法的对象

  1. 方法 this指向调用方法的对象

    const luki = {
    name : 'lukirence',
    year : 2002,
    calcAge:function(){
    return 2037-this.year;
    }
    };

    如果在方法内的函数嵌套一个新的函数,该嵌套函数相当于常规函数,this关键字为undefined

    const luki = {
    fullName: 'lukirence',
    year: 2002,
    calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
    const isMillenial = function(){
    console.log(this);//undefined
    console.log(this.year >=1981);
    }
    isMillenial();
    }
    };

    如何解决嵌套函数能够使用this?

    1. 添加self变量=this(ES6之前的旧方法)
    const luki = {
    fullName: 'lukirence',
    year: 2002,
    calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
    const self = this;
    const isMillenial = function(){
    console.log(self);
    console.log(self.year >=1981);
    }
    isMillenial();
    }
    };
    1. 将嵌套函数改成箭头函数
    const luki = {
    fullName: 'lukirence',
    year: 2002,
    calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
    const isMillenial = ()=>{
    console.log(this);
    console.log(this.year >=1981);
    }
    isMillenial();
    }
    };
  2. 常规函数声明 this为undefined

    const calcAge = function (birthYear) {
    console.log(2037 - birthYear);
    console.log(this);//显示undefined
    };
    calcAge(1991);
  3. 箭头函数 箭头函数没有this关键字,箭头函数的关键词会通过查找父函数的关键词,若没有则为全局的关键词,即指向全局窗口。

    console.log(this);//指向全局窗口window
    
    const calcAgeArrow = birthYear => {
    console.log(2037 - birthYear);
    console.log(this);//指向全局窗口window
    };
    calcAgeArrow(1991); const luki = {
    fullName: 'lukirence',
    year: 2002,
    calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
    },
    greet: () => {
    console.log(`hi,${this.fullName}`);
    },
    };
    luki.greet();//显示hi undefined,因为this指向window对象

  1. 事件监听 this为事件处理器所添加的DOM元素

注:箭头函数的执行上下文不包括参数对象和this关键字

执行上下文创建

在详细了解了执行上下文的内容后,我们来看一段代码实际执行时执行上下文如何在调用栈中活动的。

将以以下代码为例,按每一步骤描述执行上下文创建流程:

const name = 'luki';//--------------------------------1
const first =() =>{//---------------------------------2
let a =1;//-------------------------------------2.1
const b =second(1,2);//-------------------------2.2
a=a+b;//----------------------------------------2.3
return a;//-------------------------------------2.4
};
function second(x,y){//-------------------------------3
var c =2;//-------------------------------------3.1
return c;//-------------------------------------3.2
}
const x =first();//-----------------------------------4
  1. 代码被编译后先创建全局执行上下文推入调用栈(call stack);

  2. 执行顶层代码(序号1,2,3):运行1声明name变量; 运行2声明first函数;运行3声明second函数;保存以上变量环境到全局上下文中;

  3. 执行到序号4开始调用first()函数,并创建first()的执行上下文推入调用栈,准备执行first()内部代码跳转到2.1;

  4. 运行到2.1声明变量a保存到first()的执行上下文;

  5. 运行2.2调用新函数second(),创建second()的执行上下文推入调用栈,停止first()的执行跳转到3.1;

  1. 运行3.1声明变量c保存到second()的执行上下文;

  2. 运行3.2return语句表示完成该函数执行,second()的执行上下文将从调用栈中弹出(此处虽然弹出,但是其中变量环境仍可能被使用,涉及到闭包的概念),调用栈重新指向first(),代码重新跳转回2.3;

  1. 运行2.3执行代码内容;

  2. 运行2.4,first()的执行上下文将从调用栈中弹出,调用栈重新指向gloal(),代码重新跳转回4,将返回值最终赋值给x;

  1. 此后调用栈一直保持在这个状态直到我们关闭浏览器来终止程序,最终弹出global()全局上下文;

由此我们可以发现调用栈的执行上下文根据函数调用来出入栈确保了JS引擎能正确执行代码的顺序。

【JavaScript】JS引擎中执行上下文如何顺序执行代码的更多相关文章

  1. 1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

    参考一手资料:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/中文翻译版本:https://zhuanlan.zhihu.com/p ...

  2. Java中如何保证线程顺序执行

    只要了解过多线程,我们就知道线程开始的顺序跟执行的顺序是不一样的.如果只是创建三个线程然后执行,最后的执行顺序是不可预期的.这是因为在创建完线程之后,线程执行的开始时间取决于CPU何时分配时间片,线程 ...

  3. js的并行加载以及顺序执行

    重新温习了下这段内容,发现各个浏览器的兼容性真的是搞大了头,处理起来很是麻烦. 现在现总结下并行加载多个js的方法: 1,对于动态createElement('script')的方式,对所有浏览器都是 ...

  4. js的并行加载与顺序执行

    javaScript文件(下面简称脚本文件)需要被HTML文件引用才能在浏览器中运行.在HTML文件中可以通过不同的方式来引用脚本文件,我们需要关注的是,这些方式的具体实现和这些方式可能会带来的性能问 ...

  5. Javascript在浏览器中的加载顺序详解!

    现在前端用javascript用的比较多,当然真心的说这个语言是一个非常业余的语言,但是用的人很多,所以也比较火.今天想完成一个javascript外部文件自动加载的设计(类似于java或者php的i ...

  6. VBS一键配置VOIP脚本(其中包括VBS操作JS网页中的按钮事件--直接执行确认按钮中的脚本代码)

    Dim ws,fso,IESet IE = WScript.createobject("InternetExplorer.Application")Set ws = WScript ...

  7. javascript在html中的加载顺序

    参考:[1]http://coolshell.cn/articles/9749.html(酷壳) [2]http://shaozhuqing.com/?p=2756 [3]http://www.cnb ...

  8. JS引擎线程的执行过程的三个阶段(二)

    继续JS引擎线程的执行过程的三个阶段(一) 内容, 如下: 三. 执行阶段 1. 网页的线程 永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进事件队列,等待JS引 ...

  9. 【JS】js引擎执行过程

    概述 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍了语法分析和预编译阶段,那么我们先做个简单概括,如下: 语法分析: 分别对加载完成的代码块进行语法检验,语法正 ...

  10. (转载)js引擎的执行过程(二)

    概述 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍了语法分析和预编译阶段,那么我们先做个简单概括,如下: 语法分析: 分别对加载完成的代码块进行语法检验,语法正 ...

随机推荐

  1. 【Linux】个人笔记本安装Centos并开放22端口供外网连接

    〇.参考资料 一.配置及安装内容 (一)配置 [today]低配笔记本(装centos):4+500G 个人电脑(装三个节点的虚拟机集群环境):8+628G (二)所装软件 Linux系统(可选cen ...

  2. 前端开发:4、JavaScript简介、变量与常量、数据类型及内置方法、运算符、流程控制、循环结构、内置方法

    前端开发之JavaScript 目录 前端开发之JavaScript 一.JavaScript简介 二.JS基础 三.变量与常量 四.基本数据类型 1.数值类型 2.字符类型 3.布尔类型 五.特殊数 ...

  3. js迭代循环

    一.for loop for (let i = 0; i < products.length; i++) { console.log(products[i]); } 支持循环中断,可以用brea ...

  4. 小技巧 EntityFrameworkCore 实现 CodeFirst 通过模型生成数据库表时自动携带模型及字段注释信息

    今天分享自己在项目中用到的一个小技巧,就是使用 EntityFrameworkCore 时我们在通过代码去 Update-Database 生成数据库时如何自动将代码模型上的注释和字段上的注释携带到数 ...

  5. SQL语句查询关键字:where筛选、group by分组、distinc去重、order by排序、limit分页、操作表的SQL语句布补充

    目录 SQL语句查询关键字 前期数据准备 编写SQL语句的小技巧 查询关键字之where筛选 查询关键字之group by分组 查询关键字之having过滤 查询关键字值distinct去重 查询关键 ...

  6. java中的杨辉三角

    本文主要介绍如何打印杨辉三角(直角三角形),如下图所示: 规律如下: 第一行全为1,对角线元素全为1,设i表示行标,j表示列标. arr[i][0] = 1; arr[i][i] = 1; 当i &g ...

  7. Django 之swagger中尝试调试post请求时返回 "detail": "JSON parse error - Expecting value: line 1 column 1 (char)

    swagger已配置OK,但发送POST请求时一直显示数据格式错误,怪异.... 经过各种查找资料,才发现setting.py文件中缺少配置 配置完成后重启服务,再次尝试发送POST请求,不会再报格式 ...

  8. Kali Win-KeX Win

    内容: 概述 用法 开始 启动根会话 会话管理 声音支持 多屏支持 停止 概述 窗口模式下的 Win-KeX 将在单独的窗口中运行 Kali Linux 桌面会话. 窗口模式有助于在视觉上将 Wind ...

  9. tempdb数据文件暴增分析

    背景 某客户tempdb数据文件突然暴增,导致磁盘可用空间紧张,让我们找到暴增的原因. 现象 登录到SQL专家云,通过趋势分析进行回溯,在4月12日,tempdb数据文件在3个小时内从10GB涨到了8 ...

  10. 【Linux】TCS34725 颜色传感器设备驱动

    一.概述 此笔记主要是学习 Linux 中的 I2C 驱动,顺便验证一下 TCS34725 传感器的使用,主要内容还是程序记录,方便编写其他 I2C 设备驱动时做参考,所以关于 TCS34725 这里 ...