JavaScrit概述
本文章来源于王红元老师(coderwhy)的 JS高级课程
附上链接:https://ke.qq.com/course/3619571
谁能拒绝一个*100%好评还加课的老师呢
目录
前端需要掌握的三大技术
前端开发最主要需要掌握的是三个知识点:HTML、CSS、JavaScrit
JavaScrit的重要性
著名的Atwood定律
Stack Overflow的创立者之一的 Jeff Atwood 在2007年提出了著名的 Atwood定律:
- Any application that can be written in JavaScript, will eventually be written in JavaScript.
- 任何可以使用JavaScript来实现的应用都最终都会使用JavaScript实现。
JavaScript让人迷惑的知识点
TypeScript会取代JavaScript吗?
- TyeScrit只是给JavaScrit带来了类型的思维?
- 因为JavaScrit本身长期是没有对变量、函数参数等类型进行限制的;
- 这可能给我们的项目带来某种安全的隐患;
- 在之后的JavaScrit社区中出现了一系列的类型约束方案:
- 2014年,Facebook推出了flow来对JavaScrit进行类型检查;
- 同年,Microsoft微软也推出了TyeScrit1.0版本;
- 他们都致力于为JavaScrit提供类型检查,而不是取代JavaScrit;
- 并且在TyeScrit的官方文档有这么一句话:源于JavaScrit,归于JavaScrit!
- TyeScrit只是JavaScrit的一个超级,在它的基础之上进行了扩展;
- 并且最终TyeScrit还是需要转换成JavaScrit代码才能真正运行的;
- 当然我们不排除有一天JavaScrit语言本身会加入类型检测,那么无论是TyeScrit,还是Flow都会退出历史舞台。
JavaScrit是一门编程语言
- 为什么这里我要强调JavaScrit是一门编程语言呢?很多同学想,我还不知道JavaScrit是一门编程语言吗?
- 事实上我们可以使用更加准备的描述是这样:JavaScrit是一门高级的编程语言。
- 那么有高级编程语言,就有低级编程语言,从编程语言发展历史来说,可以划分为三个阶段:
- 机器语言:1000100111011000,一些机器指令;
- 汇编语言:mov ax,bx,一些汇编指令;
- 高级语言:C、C++、Java、JavaScrit、ython;
- 但是计算机它本身是不认识这些高级语言的,所以我们的代码最终还是需要被转换成机器指令:
-
浏览器的工作原理
JavaScrit代码,在浏览器中是如何被执行的?
认识浏览器的内核
- 我们经常会说:不同的浏览器有不同的内核组成
- Gecko:早期被Netscae和Mozilla Firefox浏览器浏览器使用;
- Trident:微软开发,被IE4~IE11浏览器使用,但是Edge浏览器已经转向Blink;
- Webkit:苹果基于KHTML开发、开源的,用于Safari,Google Chrome之前也在使用;
- Blink:是Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Oera等;
- 等等…
- 事实上,我们经常说的浏览器内核指的是浏览器的排版引擎:
- 排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)或样版引擎。
浏览器渲染过程
-
但是在这个执行过程中,HTML解析的时候遇到了JavaScrit标签,应该怎么办呢?
- 会停止解析HTML,而去加载和执行JavaScrit代码;
- 会停止解析HTML,而去加载和执行JavaScrit代码;
-
那么,JavaScrit代码由谁来执行呢?
- JavaScrit引擎
认识JavaScrit引擎
- 为什么需要JavaScrit引擎呢?
- 我们前面说过,高级的编程语言都是需要转成最终的机器指令来执行的;
- 事实上我们编写的JavaScrit无论你交给浏览器或者Node执行,最后都是需要被CU执行的;
- 但是CU只认识自己的指令集,实际上是机器语言,才能被CU所执行;
- 所以我们需要JavaScrit引擎帮助我们将JavaScrit代码翻译成CU指令来执行;
- 比较常见的JavaScrit引擎有哪些呢?
- SiderMonkey:第一款JavaScrit引擎,由BrendaEich开发(也就是JavaScrit作者);
- Chakra:微软开发,用于IT浏览器;
- JavaScritCore:WebKit中的JavaScrit引擎,Ale公司开发;
- V8:Google开发的强大JavaScrit引擎,也帮助Chrome从众多浏览器中脱颖而出;
- 等等…
浏览器内核和JS引擎的关系
-
这里我们先以WebKit为例,WebKit事实上由两部分组成的:
- WebCore:负责HTML解析、布局、渲染等等相关的工作;
- JavaScritCore:解析、执行JavaScrit代码;
-
看到这里,学过小程序的同学有没有感觉非常的熟悉呢?
- 在小程序中编写的JavaScrit代码就是被JSCore执行的;
- 在小程序中编写的JavaScrit代码就是被JSCore执行的;
-
另外一个强大的JavaScrit引擎就是V8引擎。
V8引擎的原理
-
我们来看一下官方对V8引擎的定义:
-
V8是用C ++编写的Google开源高性能JavaScrit和WebAssembly引擎,它用于Chrome和Node.js等。
-
它实现ECMAScrit和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32,ARM或MIS处理器的Linux系统上运行。
-
V8可以独立运行,也可以嵌入到任何C ++应用程序中。
-
-
拓展知识
- TurboFan可以收集函数的一些执行信息,当一个函数执行频率非常高的时候,会将这个函数标记为hot,当一个函数称为热函数的时候,就被转换成为机器码再保存下来,重复执行,减少字节码—>机器码的转换的过程
- 当函数中类型发生变化的时候,进行Deoptimization的过程,机器码再转换回字节码
V8引擎的架构
- V8引擎本身的源码非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道它是如何对JavaScrit执行的:
- Parse模块会将JavaScrit代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScrit代码;
- 如果函数没有被调用,那么是不会被转换成AST的;
- Parse的V8官方文档:htts://v8.dev/blog/scanner
- Ignition是一个解释器,会将AST转换成ByteCode(字节码)
- 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
- 如果函数只调用一次,Ignition会执行解释执行ByteCode;
- Ignition的V8官方文档:htts://v8.dev/blog/ignition-interreter
- TurboFan是一个编译器,可以将字节码编译为CU可以直接执行的机器码;
- 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
- 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
- TurboFan的V8官方文档:htts://v8.dev/blog/turbofan-jit
V8引擎的解析图(官方)
V8执行的细节
- 那么我们的JavaScrit源码是如何被解析(Parse过程)的呢?
- Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;
- Scanner会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;
- 接下来tokens会被转换成AST树,经过Parser和Prearser:
- Parser就是直接将tokens转成AST树架构;
- Prearser称之为预解析,为什么需要预解析呢?
- 这是因为并不是所有的JavaScrit代码,在一开始时就会被执行。那么对所有的JavaScrit代码进行解析,必然会
影响网页的运行效率; - 所以V8引擎就实现了Lazy arsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
- 比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;
- 这是因为并不是所有的JavaScrit代码,在一开始时就会被执行。那么对所有的JavaScrit代码进行解析,必然会
- 生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程(后续会详细分析)。
JavaScrit的执行过程
假如我们有下面一段代码,它在JavaScrit中是如何被执行的呢?
概念补充
- 执行环境栈(Execution Context Stack)
- 浏览器会从计算机内存中分配一块内存,专门用来供代码执行
- javascript是单线程的,这意味着他只有一个调用栈
- 基础数据类型存储在栈中
- 堆内存(Heap)
- 存放属性方法
- 任何开辟的堆内存都有一个16进制的内存地址,存储在栈中,供变量调用
- 引用数据类型存储会开辟一个堆内存,把内容存进去,然后把地址放到栈中供变量关联使用
- 全局对象(Global Object)
- 是一个堆内存,存储的是浏览器内置的属性和方法
- 浏览器中window指向全局对象
- VO(Varibale Object)
- 变量对象,在当前的上下文中,用来存放创建的变量和值的地方
- 每一个执行上下文中都有一个自己的变量对象
- 函数私有上下文中叫做AO(Activation Object)活动对象
- 执行上下文(Execution Context)
- 全局执行上下文:当JavaScript 引擎第一次执行你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈
- 函数执行上下文:函数调用时会创建一个私有上下文,并压入栈的顶部,javascript引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
初始化全局对象
- js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
- 该对象 所有的作用域(scoe)都可以访问;
- 里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
- 其中还有一个window属性指向自己;
执行上下文栈(调用栈)
- js引擎内部有一个执行上下文栈(ExecutioContext Stack,简称ECS),它是用于执行代码的调用栈。那么现在它要执行谁呢?执行的是全局的代码块:
- 全局的代码块为了执行会构建一个 Global ExecutioContext(GEC);
- GEC会 被放入到ECS中 执行;
- GEC被放入到ECS中里面包含两部分内容:
- 第一部分:在代码执行前,在arser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值;
- 这个过程也称之为变量的作用域提升(hoisting)
- 第二部分:在代码执行中,对变量赋值,或者执行其他的函数;
- 第一部分:在代码执行前,在arser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值;
GEC被放入到ECS中
GEC开始执行代码
遇到函数如何执行?
- 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional ExecutioContext,简称FEC),并且压入到EC Stack中。
- FEC中包含三部分内容:
- 第一部分:在解析函数成为AST树结构时,会创建一个ActivatioObject(AO):
- AO中包含形参、arguments、函数定义和指向函数对象、定义的变量
- 第二部分:作用域链:由VO(在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
- 第三部分:this绑定的值:这个我们后续会详细解析;
- 第一部分:在解析函数成为AST树结构时,会创建一个ActivatioObject(AO):
FEC被放入到ECS中
FEC开始执行代码
变量环境和记录
-
其实我们上面的讲解都是基于早期ECMA的版本规范:
-
在最新的ECMA的版本规范中,对于一些词汇进行了修改:
-
通过上面的变化我们可以知道,在最新的ECMA标准中,我们前面的变量对象VO已经有另外一个称呼了变量环境VE。
作用域提升面试
-
var n = 100 function foo() { n = 200 } foo() console.log(n) // 200
-
function foo() { console.log(n) n = 200 console.log(n) } var n = 100 foo() // undefined // 200
-
var n = 100 function foo1() { console.log(n); } function foo2() { var n = 200 console.log(n) foo1() } foo2() console.log(n); // 200 // 100 // 100
-
var a = 100 function foo(){ console.log(a) return var a = 100 } foo() // undefined
-
function foo() { var a = b = 100 } foo() console.log(a) console.log(b) // a未定义 // b = 10