前端 js 之 代码执行的一个过程 02

嘿,欢迎你来 !💕

文章目录

  • 前言
  • 一、运行一个 js 文件
  • 二、运行环境
  • 三、js执行代码的过程(普通变量)
  • 四、打印 window
  • 五、js执行代码的过程(函数变量)
  • 六、函数调用函数的过程
  • 注意!!!
  • 最后、作用域与作用域链面试题


前言


在上一篇文章里,我们简单说了浏览器的工作原理和v8引擎的原理



浏览器渲染

  1. 首先先下载 index.html ,遇到css 或者script 文件就下载,
  2. 通过浏览器内核进行解析 index.html
  3. 如果解析过程中遇到 js 代码就立即执行 js 代码,因为js代码属于高级语言,cpu不能直接执行,所以借助js引擎
  4. 对于v8 引擎来说,通过parse模块把我们的代码解析成 AST 树型结构,再通过ignation转化为字节码,再编译成低级语言,cpu就可以执行这些指令了

那今天我们研究的就是 js源代码到AST抽象语法树他进行解析,是如何解析的。


上篇文章整理了很久,多多少少有了点概念,今天这篇文章是基于上篇基础上的加深,希望我表达的足够清晰,如果错处,还望指正,感谢,biubiu ~💕


一、运行一个 js 文件


我们以下的讲解都是基于早期的ECMA的版本规范,也就es5之前的。也发现了所有的变量定义都是用的 var。

在早期的版本规定里是这样定义的:每个执行上下文都会被关联到一个变量环境(VO: variable Object,它是一个对象),源代码中的变量和函数都会 被作为属性 添加到VO中,对于函数来说,参数也会被添加到VO中

在最新的的ECMA 版本规范中,每个执行上下文会关联到一个变量环境中,在执行代码中变量和函数的声明会 作为环境纪录 添加到变量环境中,对于函数来说,参数也会被作为环境记录添加到环境变量中

const name = "haha";const name2 = "haha2";console.log(name);

假设这是一个 js 文件, 想在浏览器上运行,首先得有一个index.html ,通过script 引入才能运行。js引擎怎么运行的呢?


二、运行环境


我们编写的JS代码都是需要在一个环境中运行的:比如

  1. 浏览器(引擎):例如webkit,gecko,Trident, blink
  2. node*(基于V8渲染js)
  3. webview(v8引擎)
  4. JS之所以能够在浏览器中运行,是因为浏览器给JS提供了执行的环境 => 栈内存

先说几个概念:(不太理解也没关系)

  1. 栈内存 ( ECStack(Execution Context Stack):执行环境栈) :它是代码执行环境;它是浏览器分配的内存空间,用来执行代码 。
  2. 全局对象GO(Globle Object)): 该对象所有作用域都可以访问, 它用来存放当前环境的内置方法,类,变量等,他还有个window属性指向自己;这就是为什么我们可以直接使用Math,String,setTimeout 等方法 ,这里请注意!!对于方法,类这种复杂类型数据存放的是对应的内存地址,堆内存才是真正的存放代码
  3. 堆内存(Heap) :存放东西(属性和方法(例:isNaN:function…))
  4. 任何开辟的内存都有一个16进制的内存地址,方便后期找到这个内存,ox开头
  5. 执行上下文ECS(Execution Context stack) :在编程语言中,代码执行中,为了区分全局变量和函数执行所处的不同的作用域(目的是为了区分每个词法作用域下代码的独立性),会产生执行上下文栈,一个是全局执行上下栈 GEC,一个是函数执行上下文栈FEC,都会在ECS里运行
  6. 全局执行上下栈 GEC: 包括两部分,第一部分,在代码执行前,在paese转化为AST的过程中,会将全局定义的变量,函数等加入到 GO里,但是不会赋值(也称为变量的提升);第二部分,是代码的执行,变量赋值,或者执行函数等等
  7. 函数执行上下文栈FEC:包括三部分,第一部分,在解析函数成为AST树结构时,会创建一个AO,AO中包括形参、arguments、函数定义和指向函数对象、定义的变量;第二部分,作用域,由当前AO对象和父级VO组成,查找是会一层层查找;第三部分,this的绑定值(本篇先不说)

三、js执行代码的过程(普通变量)

const name = "haha";const name2 = "haha2";console.log(name);var golbalObject={String:'类',Date:'类',setTimeout:'方法',window:golbalObject,name:undefined,name2:undifined}
  1. 再代码被解析之前,运行之前,v8 引擎内部会帮助我们创建一个对象,全局变量(GlobalObject,又称为GO),
  2. 这个GlobalObject 存放着当前环境的全局对象,该对象所有的作用域都可以访问,
  3. 里面包括 Math,Date,settimeout…等 方法 ,还有一个 window属性 指向自己
  4. 在代码编译的时候,它可以确定我们定义了哪些变量,然后将这些变量添加到全局对象里面
  5. 因为我们还没有真正的执行,所以这些变量都是undefined(变量存放在GO) ,
  6. v8 为了执行代码,v8引擎内部会有一个全局执行上下文栈(全局代码需要被执行的时候才会创建,称为EGC),有且只有一个,EGC 包括两部分: 第一部分,称为VO,在代码执行前,在paese转化为AST的过程中,会将全局定义的变量,函数等加入到 GO里,但是不会赋值(也称为变量的提升),所以这个VO对应的是GO;第二部分,是代码的执行,变量赋值,或者执行函数等等
  7. 比如现在要执行 var name=30 这个代码;它就会去VO里面找,VO对应GO;再把GEC放到调用栈里执行

请添加图片描述


四、打印 window

const name = "haha";const name2 = "haha2";console.log(name);var golbalObject={String:'类',Date:'类',setTimeout:'方法',window:golbalObject,name:undefined,name2:undifined,}console.log(window)

当打印window时,发现这个对象包括很多东西,很多内置方法,包括name,name2等属性,是因为这个window指向时golbalObject,正如上面所说他一个window属性 指向自己

五、js执行代码的过程(函数变量)

1.   foo();2.   function foo() {3.      console.log("开心快乐向前冲");4.  }

提前声明 ps:

  1. 我们所有代码在执行前都要加载到内存里,(代码存放在磁盘,再进入内存,转化为机器语言,最后才能被cpu执行)
  2. 在内存里,会划分为栈结构和堆结构,简单类型数据放在栈里,复杂类型数据地址放在栈,值放在堆

运行的不仅仅是普通变量,那对于函数来说,他是一个怎么样的过程呢?

  1. 根据上面所说,在编译阶段,会把变量添加到全局对象里,在未运行之前值为undefined,
  2. 但是foo 在定义之前执行,但是能执行所有的代码,为什么可以打印出来foo呢?
  3. 因为是在编译,所以第一行代码没有执行;第二行发现声明了一个函数,他也会定义 foo在GO里,但是不同的是,因为函数较特殊,他会再开辟一个新的内存空间,用来存放函数, 函数执行上下文栈FEC(先理解这一块,下面会具体说) ,foo这里存放的就是函数对应的内存空间地址
  4. 理解成指针、引用,因为一个地址可以对应多个变量,也是为什么复杂类型数据会互相影响
  5. 以上是编译的过程,当执行到第二行时 foo() ,【请留意,他其实是两部分,foo 和()】,他会再VO (对应的是GO) 里面查找Foo,他发现是个内存地址,就会根据地址找到这个空间;()表示函数的执行,就会把我们 FEC 放入到 调用栈 里面执行。


请添加图片描述



为了不饶晕乎,我们先把以上理解清除,再往下看

1.   foo();2.   var name=1233.   function foo(num) {4.    	num=15.     console.log("开心快乐向前冲");6. 	console.log(name)7.  }
  1. 我们把对应的函数空间放到调用栈不是直接运行,而是创建一个函数执行上下文 :包括三部分,第一部分,在解析函数成为AST树结构时,会创建一个AO,AO中包括形参、arguments、函数定义和指向函数对象、定义的变量;第二部分,作用域,由当前AO对象和父级VO组成,查找是会一层层查找;第三部分,this的绑定值(本篇先不说);
  2. 在第六行的时候,我们发现在Ao 里面并没有name这个值,为什么使用了全局的name呢?
  3. 是这样的,当我们查找一个变量时,真实的查找路径是沿着作用域链来查找的
  4. 函数执行上下文不仅仅包括 **VO** ,还有一个 **scope chain** ,叫做作用域链 (看图二)
  5. 一个函数执行上下文的作用域链有两部份组成,当前 AO 的作用域 + 父级作用域
  6. (父级作用域在函数编译时就确定了)
  7. 他的执行顺序是:先在自己的函数执行上下文找,再向他的上级作用域找 所以你理解为什么可以使用name了吗 ?
  8. 一旦函数执行完毕,这个函数执行上下文就会弹出调用栈,就会销毁了,这个 ao 也因为没有指向也会被销毁掉
  9. 当再执行时,是重新创建了一个函数执行上下文,把以上的过程再走一遍



请添加图片描述




六、函数调用函数的过程

我们在上面看了普通变量的执行过程和函数的执行过程,其实发现很简单,调用栈帮我们执行代码,为了区分变量还是函数,我们使用全局执行上下文栈函数执行上下文栈帮助我们执行,编译过程中定义了,真正运行时才是会有赋值等操作。只有函数才有作用域,当我们在一个函数里使用了当前作用域没有定义的变量,而全局变量存在的变量时,也可以使用,是因为沿着作用域链来查找的。在函数定义的时候,他的内存地址里存放两个东西,一个是他的父级作用域和他的执行块

那我们现在看看函数调用函数的过程
其实也很简单,在函数里调用函数时,他发现有函数定义执行时,会再次创建一个函数执行上下文栈,用来存放新的函数,这个函数的VO 同样存储两个东西,一个是作用域(父级作用域和当前作用域)和代码块,如上,所以在函数里调用函数 ,也是按照作用域链查找变量,所以与定义的位置无关

注意!!!


        我们以上的讲解都是基于早期的ECMA的版本规范,也就es5之前的。也发现了所有的变量定义都是用的 var。

在早期的版本规定里是这样定义的: 每个执行上下文都会被关联到一个变量环境(VO: variable Object),再源代码中的变量和函数都会被作为属性添加到VO中,对于函数来说,参数也会被添加到VO中

在最新的ECMA版本规范中,进行了新的改动:每一个执行上下文会关联到一个变量环境中(variableEnvironment),在执行代码中变量和函数的声明会作为环境记录添加到变量环境中,对于函数来说,参数也会被作为环境记录添加到变量环境中

        通过上面的变化,我们可以得知,在最新的ECMA标准中,我们前面的变量对象VO已经有另外一个称呼环境变量,VE



在这里插入图片描述

最后、作用域与作用域链面试题

函数表达式遵循变量提升规则,
今天我的表达还可以吗?🤣

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/140674.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于LSTM-Adaboost的电力负荷预测(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

Python 网络爬虫

爬虫原理 计算机一次Request请求和服务器端的Response回应,即实现了网络连接。 爬虫需要做两件事:模拟计算机对服务器发起Request请求。 接受服务器的Response内容并解析、提取所需的信息。 多页面爬虫流程 ​​​​​​​多页面网页爬虫流程

VR航天科普主题公园模拟太空舱体验馆vr航天模拟体验设备

VR航天航空体验馆巡展是一项非常受欢迎的展览活动,可以让公众在现场体验到航天飞行的乐趣。 普乐蛙VR展览组织者会设计一个航天航空主题的VR体验馆,并在馆内设置各种航天航空相关的展示内容,如太空舱、火箭发射、星际航行等。 其次&#xff0…

这三个方法轻松实现Excel表格名翻译不求人

在进行大量的文件处理时,我们往往需要借助一些工具来提高效率。比如在需要对Excel文档名进行批量翻译时,一个方便快捷的工具可以让我们省去许多麻烦。这里我们介绍一款软件——固乔文件管家,它可以帮助我们实现Excel文档名的批量翻译&#xf…

PTE-精听学习(四)

目录 精听练习方法 精听与数量无关,与质量有关 听到一两个单词,就可以默写整句话 出现了过去时,有时间的指示代词 表示原本以为 精听练习方法 精听与数量无关,与质量有关 听到一两个单词,就可以默写整句话 突然…

easyexcel操作之名称匹配

简单说下需求 现在我有一个excel表格,里面有两张表,分别是a_name表,b_name表,我要在这两张表的基础上新建一张a_b_name表,这张表匹配a,b表的名称,品牌名一样则放在同一行。 示例: a_name表 …

Mysql高级——锁(1)

锁 1. 概述 在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对并发操作进行控制,因此产生了锁。同时锁机制也为实现MySQL的各个隔…

Windows下定时下载Linux服务器的数据库备份文件(pscp+bat脚本+定时任务)

下载传输软件pscp Download PuTTY: latest release (0.79) 创建bat执行脚本 echo 删除旧的备份文件 del D:\db_bk\*.dbecho 下载新的备份文件 D:\Programs\pscp -P 22 -pw youPassword youName192.168.1.1:/home/backup/test.db D:\db_bk\ 设置定时任务 1.使用任务计划程…

【数据结构】Java对象的比较

作者主页:paper jie_博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力…

React +AntD + From组件重复提交数据(已解决)

开发场景: react Hooks andt 提交form表单内容给数据库(使用antd的form组件) 问题描述 提交是异步的,请提交方式是POST 方式 提交表单内容给后端,却产生了两次提交记录(当然,数据新增了两条数据)。可以…

GitKraken for Mac:强大且跨平台的Git客户端

如果你正在寻找一个强大、直观且跨平台的Git客户端,那么GitKraken绝对值得你考虑。在本文中,我们将详细介绍GitKraken的各项功能和优势,以帮助你了解它如何帮助你更有效地管理和提交代码。 GitKraken是一款真正的跨平台工具,可以在…

UE4/5:通过Blender制作BlendShape导入【UE4/5曲线、变形目标,blender形态键】

UE4/5里面,我们经常可以在一些骨骼模型上面看到相关的曲线,如Metahuman里面就是通过这个曲线来改变人物的脸部表情。 而这里笔者将教导如何去制作这种曲线。 这种曲线都是存在于骨骼模型上的,所以我们要么直接制作骨骼模型导入ue&#xff0…