笔记:JavaScript编译与执行

2023-11-03

1、js的编译与执行、事件循环

单线程语言

JavaScript是单线程语言,即在浏览器中一个页面只有一个线程在执行js代码

进程和线程

假设我们有一家工厂(进程),那么 工厂所拥有的独立资源就相当于系统给我们分配的内存(这是独立的)

如果我们有多个工厂,每个工厂做不一样的事情,那么也就意味着工厂间是互相独立的,也就是说 进程间互相独立

工厂里有一个或多个工人(线程),那么: 一个进程包含了一个或多个线程

多个工人在一个工厂里工作,享受的是这个工厂拥有的独立资源,也就是说: 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

工人们之间互相协作完成工厂布置的任务,也就说明: 多个线程在进程中协作完成任务的特点。

总结

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

WebWorker

H5新增:
可以理解为是 浏览器 申请开辟的一个新线程来帮助JS引擎完成复杂的计算来减少由于JS执行时间过长而导致的阻塞时长

注⚠️: 需要注意这并不影响JS引擎是单线程的。

JS引擎执行过程

解析器:负责将js代码转换为AST抽象语法树
解释器:负责将AST抽转换为字节码,并收集编译器需要的优化编译信息
编译器:利用解释器收集到的信息,将字节码转换为优化的机器码

JavaScript通过 词法分析语法分析 得到语法树后,js引擎开始执行代码,但是JS引擎并不是逐条解释javascript代码,而是通过一个个标签分隔开的代码段进行的解释执行。
简单来说,它就是能够将 JavaScript代码处理并执行的运行环境,不同的浏览器有不同的JS引擎,而我们常接触的V8正是采用C++编写在Chrome浏览器中被使用的解析引擎。

JS引擎执行过程分成三个阶段:

  1. 语法分析
  2. “预编译”阶段(解释阶段)
  3. 执行阶段

浏览器首先按顺序加载由<script></script>标签分割的代码块,加载代码块完毕后,立刻进入以上三个阶段。然后再按顺序查找下一个代码块,再继续执行以上三个阶段,无论是外部脚本文件(不异步加载 即会停止加载后面的内容,停下来解析脚本并对页面进行渲染)还是内部脚本代码块,都是一样的原理,并且都在同一个全局作用域中。

1、语法分析

在js代码加载完毕以后,就会进入到语法分析阶段,在这个阶段中js将会检查代码块的语法是否正确。

  • 若有错误语法则直接抛出错误,并停止接下来的执行,而后继续向后查找并加载下一个代码块。

  • 若语法都没有问题,那么就进入接下来的预编译阶段。

2、“预编译”阶段

在理解这个阶段之前,我们需要优先了解一下js的几种运行环境:

  1. 全局环境
  2. 函数环境
  3. eval

每进入一个不同的运行环境都会创建一个相应的执行上下文(Execution Context),

在一段JS程序中我们会创建很多的执行上下文,而这些执行上下文在JS引擎中会 以栈的方式 执行处理

而这时候形成的栈我们叫它 函数调用栈(Call Stack) ,其中调用栈的 栈底 永远是我们的 全局执行上下文(Global Execution Context)栈顶 则永远是 当前的执行上下文

我们从一个简单的例子出发:

 function bar() {
     var B_context = "Bar EC";
     function foo() {
         var f_context = "foo EC";
     }
     foo()
 }
 bar()
  1. 首先我们进入全局环境,创建全局执行上下文,这时候推入函数调用栈中
  2. 当我们调用bar时,进入bar的运行环境,创建bar的执行上下文推入函数调用栈中
  3. 在运行bar的时候,内部调用foo函数,因此我们再进入foo的运行环境,创建foo的函数执行上下文推入函数调用栈栈中
  4. 此刻栈底是全局执行上下文,栈顶是foo函数执行上下文,由于foo函数内部没有再调用其他函数,因此无需再创建多余的函数执行上下文
  5. foo函数执行完毕后,栈顶foo函数执行上下文首先出栈
  6. 接着bar函数接下来也没有别的语句需要执行,因此也是执行完毕的状态,所以bar函数执行上下文出栈
  7. 最后全局执行上下文则是在浏览器或者该标签页关闭的时候出栈
“预编译”阶段——创建执行上下文
  1.创建变量对象(Variable Object)
  2.建立作用域链(Scope Chain)
  3.确定this的指向

创建变量对象(Variable Object)

  1. 在函数环境(非箭头函数)中进行创建arguments对象,检查当前上下文中的参数,建立该对象的属性与属性值(全局环境没有此过程!!)
  2. 检查当前上下文的函数声明,按代码顺序查找,将找到的函数提前声明,如果当前上下文的变量对象没有该函数名属性,则在该变量对象以函数名建立一个属性,属性值则为指向该函数所在堆内存地址的引用,如果存在,则会被新的引用覆盖。
  3. 检查当前上下文的变量声明,按代码顺序查找,将找到的变量提前声明,如果当前上下文的变量对象没有该变量名属性,则在该变量对象以变量名建立一个属性,属性值为undefined;如果存在,则忽略该变量声明

注:在全局环境中,window对象就是全局执行上下文的变量对象,所有的变量和函数都是window对象的属性方法。

例如:

function fun(a, b) {
    var num = 1;
    function test() {
        console.log(num)
    }
}
fun(2, 3)

当执行fun函数并传入参数2和3时:(暂时不讲解作用域链以及this指向)

funEC = {   //fun的执行上下文
    VO: {   //变量对象
        arguments: {   //arguments对象
            a: undefined,
            b: undefined,
            length: 2
        },
        test: <test reference>,   //test函数,在堆内存中地址的引用
        num: undefined            //num变量
    },
    scopeChain:[],    //作用域链
    this: window   //this指向
}

解析:

  • 创建变量对象是发生在“预编译”阶段,还并没有进入执行阶段,因此这里的变量对象是不可访问的,此时值还是undefined

  • 当我们进入执行阶段开始对其变量属性赋值,变量对象转变为活动对象,此时才可以进行访问,而这个过程就是VO->AO的过程,其中AO就是Active Object活动对象。

  • 注:函数声明提前变量声明提升在创建变量对象中进行的,且 函数声明优先级高于变量声明

通过例子来说明:

    var a = 1;
    function b() {
        a = 10;
        return;  
        function a() {}
    }
    b();
    console.log(a);
  • 解析: 输出: 1,在函数b中优先声明函数a,此后给a赋值为10,这里的a是函数b内的声明变量a,和全局作用域下的a不是同一个变量,因此最后输出a不变
    function foo(){
        function bar() {
            return 3;
        }
        return bar();
        function bar() {
            return 8;
        }
    }
    alert(foo());
  • 解析:输出: 8,执行foo函数,先声明返回3的bar函数,再覆盖了一个返回8的bar函数,执行时即输出被覆盖后的值
    alert(foo());
    function foo() {
        var bar = function() {
            return 3;
        };
        return bar();
        var bar = function() {
            return 8;
        };
    }

解析:输出: 3,优先声明函数foo,因此可以alert出foo函数,此时再foo函数内,先声明变量bar其次,给bar赋值返回值为3的匿名函数,此时执行return语句直接执行输出3,在return后的赋值语句不会再执行

注:不同的运行环境执行都会进入 代码预编译执行 两个阶段,而语法分析则 在代码块加载完毕时 统一检验语法

2 建立作用域链(Scope Chain)
作用域链当前执行环境的变量对象(也就是未进入执行阶段前)与**上层环境的一系列活动对象(AO)**组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

JS引擎在解释过程中,是严格按照作用域机制来执行的, JS的变量及函数 在定义时 就已经决定了它们的作用域范围,

所以解释JS只要通过静态分析就可以确认每个变量,函数的作用域,因此这些作用域也叫作 静态作用域

那么举个具体的例子:

    var num = 30;
    function test() {
        var a = 10;
        function innerTest() {
            var b = 20;
            return a + b
        }
        innerTest()
    }
    test()

此时:

    innerTestEC = {
    VO: { b: undefined },     //变量对象
    scopeChain: [VO(innerTest), AO(test), AO(global)],   //作用域链:注意顺序,链头是当前作用域,链尾一定是全局作用域
    this: window    //this指向
}

3 确定this的指向:
在全局执行上下文中,this是始终指向全局对象的。
而在执行函数的过程中,就需要考虑函数的调用方式,而函数共有4中调用方式:

1. 当它被作为对象的方法调用时,函数的指向为该对象
2. 当它直接作为函数被调用时,this通常指向的就是全局对象
3. 当它被以构造函数的方式调用时,会将新建的对象作为该函数的this值
4. 当它被间接调用也就是被call/bind/apply这样的函数绑定时,this值就是他们要绑定的那个对象。

3、执行阶段——Event Loop

事件循环机制 可以 完成异步操作,事件循环的执行机制,这里涉及到的概念包括:

  1. JS引擎线程、定时器线程、事件触发线程、异步http请求线程
  2. 回调队列Callback Queue
  3. 调用栈Call Stack
  4. macrotask 与 microtask

我们先来了解一下以下几个线程:

JS引擎线程:

  • 也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
  • JS引擎线程负责解析Javascript脚本,运行代码。

事件触发线程:

  • 归属于浏览器而不是JS引擎,用来控制事件循环。
  • 当JS引擎执行代码块如setTimeOut或是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等会将对应任务添加到事件线程中,当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列(回调队列)的队尾,等待JS引擎的处理。

定时触发器线程:

  • 传说中的setInterval与setTimeout所在线程,浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响计时的准确,因此通过单独线程来计时,当计时完毕后将其回调函数添加到回调队列中,等待JS引擎空闲后从回调队列队首取出函数执行。

异步http请求线程:

  • 通过XMLHttpRequest连接后,通过浏览器新开一个线程请求,将检测到readyState状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,再由JS引擎执行。

注:永远只有JS引擎线程在执行JS脚本程序,以上提及的三个线程只负责将满足触发条件的处理函数推进回调队列中,等待JS引擎线程执行。
事件循环图解如下:
在这里插入图片描述

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
Event Loop只做一件事情,那就是负责监听Call Stack和Callback Queue。
当Call Stack里面的内容运行完变成空了, Event Loop就把Callback Queue里面的第一条事件(回调函数)放到调用栈中并执行它,后续不断循环执行这个操作。
⚠️:异步任务又分为:宏任务和微任务(优先级:微>宏)

4、总结:

js执行过程:词法分析 -> 预编译 -> 执行
预编译:js引擎会首先把整个文件进行预处理,以消除一些歧义,这个预处理的过程就叫做预编译

全局预编译:

  • 产生windows对象
  • 查找变量的声明,把变量做为GO对象的属性名,属性值为undefined
  • 查找函数的声明,把函数名作为GO对象的属性名,属性值是function

函数预编译:

  • 在函数被调用时,为当前函数产生AO对象
  • 找到形参和变量声明,并且将它们赋值为undefined
  • 找到形参对应的实参,并且将实参的值赋予形参;
  • 找函数声明并且将函数体赋给函数声明;
    ⚠️:优先级:局部函数 > 实参 > 形参和局部变量
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

笔记:JavaScript编译与执行 的相关文章

  • React useEffect hook 和 Async/await 自己的获取数据函数?

    我尝试创建一个从服务器获取数据的函数 并且它有效 但我不确定这是否正确 我创建了一个函数组件来获取数据 使用useState 使用效果 and 异步 等待 import React useState useEffect from react
  • 每次用户在地址栏中按 Enter 时,Firefox 插件都会执行某些操作

    我正在尝试编写一个扩展程序 用于监视每次有人在使用地址栏时按下回车键时的情况 步骤将类似于 用户在地址栏中输入一堆文本并按 Enter 键 我的插件启动并接收用户输入的内容 然后我的插件决定如何处理用户输入的字符串 我通过使用在步骤 2 中
  • 在 Javascript 中获取文本框的值

    我有这个html代码 table border cellpadding 3 cellspacing 0 tbody tr td Song td td td tr tbody table
  • Javascript:更改浏览器后退按钮的功能

    有没有办法让用户的浏览器上的后退按钮调用 JavaScript 函数而不是返回页面 您无法覆盖这样的行为 如果用户通过链接访问您的页面 则单击 后退 将使他们再次离开该页面 但是 您可以使页面上的 JavaScript 操作将条目添加到历史
  • 从节点使用 TypeScript 编译器

    使用咖啡脚本可以很容易地做到这一点 var coffee require coffee script coffee compile a 1 gt function n var a n n a 1 n n call this n 有没有办法用
  • Node.js 和代码优先

    我使用代码优先方法开发实体框架 现在我正在学习 Node js 我想知道是否有一种方法可以使用 Node js 和一些库来实现相同的代码优先方法 我正在考虑使用MySql作为数据库 你可以看看续集 http docs sequelizejs
  • 如何用方向键移动div

    我想使用 jQuery 用箭头键移动 div 所以右 左 下 上 找到了我想要完成的演示here http atomicrobotdesign com blog htmlcss move objects around the canvas
  • 如何禁用向左滚动?

    I got a div 元素 parent 包含多个子元素 item 我想启用滚动父元素一个方向 left OR正确的 否则什么都不会发生 看我的代码 parent scroll function gt gt gt scroll event
  • 根据复选框显示/隐藏输入字段[重复]

    这个问题在这里已经有答案了 如果单击该复选框 它将显示一个输入字段 到目前为止它正在工作 但如果未选中该复选框 它应该隐藏它 我该怎么做 div class checkbox div
  • 查找 JavaScript 中函数参数的数量[重复]

    这个问题在这里已经有答案了 可能的重复 获取函数的元数 https stackoverflow com questions 4848149 get a functions arity 假设我有 function a x function b
  • EmberJS:如何为 ember-data RESTAdapter 中的模型提供特定的 URL?

    问题一 如果我有一个名为 Company 的余烬数据模型 我如何告诉它点击 businesses and businesses id而是检索记录 有没有办法指定给定模型的 url 更好的是 像 BackboneJS 一样 我可以在运行时计算
  • 使用XMLHttpRequest自动网页刷新内存泄漏

    问候 我一直在为一些使用 8 位微控制器的硬件开发网络界面 该网页使用 HTML javascript JSON 和 XHR XMLHttpRequest 进行通信 我想做的是创建一个页面 使用 setInterval 使用控制器中的新值每
  • ES6 模板文字可以在运行时替换(或重用)吗?

    tl dr 是否可以制作可重用的模板文字 我一直在尝试使用模板文字 但我想我就是不明白 现在我感到沮丧 我的意思是 我想我明白了 但 它 不应该是它的运作方式 或者它应该如何实现 它应该变得不同 我看到的所有示例 甚至标记模板 都要求 替换
  • 图像未显示在从 HTML 创建的 PDF 上

    我想动态创建 PDF 这意味着我将从 Google Drive 获取文件 然后将它们放入 HTML 代码中 并尝试从中创建 PDF 一切工作正常 除了图像没有显示 我现在正在做的是 从 HTML 字符串创建 HtmlOutput 获取该 H
  • ParseFromString 在 IE 中抛出错误,但在 Chrome 中不会抛出错误

    我正在使用传单的 KML 插件 该插件在 Google Chrome 中运行良好 然而 在 IE 中 它会在以下代码中引发错误 parser new DOMParser console log url outputs path to kml
  • 如何使用 Typescript 设置 Material-UI for React?

    我在将 Material UI 添加到我的 React 项目中时遇到了一些问题 该项目是用 Typescript 编程的 根据教程 我首先添加react tab event plugin import injectTapEventPlugi
  • Nodejs 解码 base64 并使用流将它们保存到文件中

    在我的node js应用程序中 我使用以下代码行解码base64编码的图像 const fileDataDecoded Buffer from base64EncodedfileData base64 到目前为止 我可以使用以下代码编写一个
  • ReactJS setState 仅在嵌套在 setState 中时才有效

    问题 当我使用 this setState 并在回调中输出状态时 它根本不会改变 但是当我将 setstate 嵌套在 setstate 中时 它将正常工作 例子 这不行 this setState data newData 这确实有效 t
  • Javascript 最佳实践,为什么使用逗号来链接函数/变量声明?

    我一直在为 jQuery jQueryLog 开发一个插件 以允许调试链选择器和返回值 如果你想检查一下 你可以这样做here http www jquerylog com 这已经是第二个版本了 第一个版本实际上是经过编辑的 jQuery
  • 在哪里放置资源特定逻辑

    您能帮我考虑在 AngularJS 中将资源 服务 特定的业务逻辑放置在哪里吗 我觉得在我的资源上创建一些类似模型的抽象应该很棒 但我不确定如何做 API调用 gt GET customers 1 lt first name John la

随机推荐

  • Android代码实现APK文件的安装与卸载

    Android程序使用代码的安装和卸载 安装 String str CanavaCancel apk String fileName Environment getExternalStorageDirectory str Intent in
  • Zebra-VTYSH源码分析和改造(二):深入代码

    分析Zebra VTYSH的源码 首先从main函数开始 在ztysh main c中找到main函数 来进一步分析流程执行如下图所示 在平时的使用中我们会发现 配置的时候有很多的视图 View 每个视图中有不同的命令可供用户输入进行配置
  • STM32—Flash读写详解

    目录 前言 介绍 STM32 FLASH 闪存的编程和擦除 Flash读写的标准库函数 软件设计 FLASH的读取 直接读取某一地址的内容 读取选定位置的选定大小的内容 FLASH的写入 直接使用标准库写入 写入选定位置的选定大小的内容 如
  • mega328p-ADC,PWM,UART驱动

    ADC驱动 函 数 名 Ai Init 函数功能 Ai端口初始化 输入参数 void 输出参数 void 返 回 值 void 参考文档 void 创 件 人 程强刚 创建日期 2016 02 09 修改历史 void Ai Init vo
  • 身份认证之多因素身份认证(MFA)

    我们大多数人都同意密码是不安全的身份验证形式这一观点 更糟糕的是 它完全不智能 但这引发了一个问题 如果密码不是解决安全问题的答案 那什么是 目前 答案可能是多因素身份验证 MFA 多因素身份验证增加了一层关键的防御 MFA使用两个或多个因
  • Filter过滤器完成验证代码的封装

    Filter过滤器完成验证代码的封装 filter是什么 1 使用filter 2 filter配置到项目中 验证用户权限是需要反复使用的代码块 把他封装到filter中 减少代码冗余 filter是什么 init 方法 初始化方法 在创建
  • 主板上还剩啥?CPU整合GPU/北桥/南桥

    泡泡网主板频道2月6日 众所周知 主板上最重要 成本最高的两颗芯片 被称为北桥和南桥 其中北桥负责与处理器对接 主要功能包括 内存控制器 PCI E控制器 集成显卡 前 后端总线等 都是速度较快的模块 而南桥则负责外围周边功能 速度较慢 主
  • c++ 读写excel_每天10分钟,轻松入门python,json、csv等读写

    JSON的全称是 JavaScript Object Notation 意思是JavaScript对象表示法 它是一种基于文本 独立于语言的轻量级数据交换格式 这种数据在弄爬虫的时候 经常会见到这类型的数据 下面展示一个简单的json数据
  • 利用计数器实现任意分频,占空比为60%(任意占空比)电路 [VHDL]

    本次实验为利用计数器实现分频常数为24000 占空比为60 的电路 也可以设置为任意分频 任意占空比的电路 一 设计思路 设计分析 要将原来的占空比为50 大频率的信号重新设为60 占空比 频率较小的周期信号 其中频率的思想就是分频器 利用
  • Northstar软件下载 以及搭建机器人时遇到的坑

    上个学期学机器人的时候 老师让我们用 innostar 创意之星 做出一个机器人来 但我翻遍全网也没找到创意之星的配套软件 我找了三天也没找到 公司官网也没有 给博创的人发邮件也不回 给我整的心态爆炸 为了方便后来的学弟学妹们 现在把我找到
  • Java 优先队列(PriorityQueue)总结

    PriorityQueue 实现的是 Queue 接口 可以使用 Queue 提供的方法 以及自带的方法 1 PriorityQueue概述 Java PriorityQueue 实现了 Queue 接口 不允许放入 null 元素 其通过
  • LVGL学习笔记

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 硬件要求 二 移植 1 准备工作 2 文件准备 3 加入工程 前言 LVGL 轻巧而多功能的图形库 是一个免费的开放源代码图形库 它提供创建具有易于使用的
  • Shopify Liquid 日期

    Shopify Liquid 日期变量 assign start date now date s assign start date year now date Y assign yoy start start date year minu
  • 基于卷积神经网络的车道线检测

    在本博客中 我们将探讨如何使用卷积神经网络 CNN 在Udacity自动驾驶数据集上进行车道线检测 我们将首先简要介绍自动驾驶的相关知识 然后介绍车道线检测的重要性 接下来 我们将构建一个CNN模型 并在Udacity数据集上对其进行训练和
  • 【Mo 人工智能技术博客】K-means:无监督聚类的经典算法

    K means 无监督聚类的经典算法 作者 郑培 无监督学习是一类用于在数据中寻找模式的机器学习技术 无监督学习算法使用的输入数据都是没有标注过的 这意味着数据只给出了输入变量 自变量 X 而没有给出相应的输出变量 因变量 在无监督学习中
  • 常见的并发模型

    介绍 常见解决并发的策略一般有两种 共享数据和消息传递 基于消息传递的实现有 CSP模型 典型的有Go语言中基于Channel的通讯 Actor模型 典型的有Akka中的Actor模型 CSP模型和Actor模型的简单理解 Don t co
  • linux centos中查看应用进程和杀死进程

    1 ps 表示 查看正在运行中的进程 2 ps ef 表示 查看所有进程的详细信息 3 输入 ps ef grep nmon 表示 搜索nmon相关的进程 4 杀死进程 kill 9 进程号
  • 【常用的反监控(winrdlv3)方法winrdlv3】

    常用的反监控 winrdlv3 方法winrdlv3 方案一 使用silent terminal 禁用 sdhelper2 exe和winrdlv3 exe两个程序进程 加密进程终止或者可以只中止sdhelper2则不会加密也不会被管理员发
  • Python手册(Standard Library)--re

    文章目录 re模块 匹配 返回re对象 MatchObject 查找 检索 替换和分割 flags标志 re 模块使 Python 语言拥有全部的正则表达式功能 compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象
  • 笔记:JavaScript编译与执行

    1 js的编译与执行 事件循环 单线程语言 JavaScript是单线程语言 即在浏览器中一个页面只有一个线程在执行js代码 进程和线程 假设我们有一家工厂 进程 那么 工厂所拥有的独立资源就相当于系统给我们分配的内存 这是独立的 如果我们