js的应用范围
web开发
原生JavaScript React开发 Vue开发 Angular开发
移动端开发
ReactNative Weex
小程序端开发
微信小程序 支付宝小程序 uniapp taro
桌面应用开发
Electron 比如VSCode
后端开发
Node环境 比如express、koa、egg.js
难点
作用域
作用域的理解、作用域提升、块级作用域、作用域链、AO、GO、VO、执行上下文、执行调用栈
函数
闭包 闭包的访问规则 闭包的内存泄露 函数中this的指向 函数柯里化
面向对象
继承、 原型、原型链等
ES新特性
其他难点
事件循环、微任务、宏任务、 内存管理、Promise、await、 asnyc、防抖、节流等
TypeScript会取代JavaScript吗?
答案是否定的。
TypeScript只是给JavaScript带来了类型的思维,因为JavaScript本身长期是没有对变量、函数参数等类型进行限制的。TypeScript致力于为JavaScript提供类型检查,而不是取代JavaScript;在TypeScript的官方文档有这么一句话:源于JavaScript,归于JavaScript!
TypeScript只是JavaScript的一个超集,在它的基础之上进行了扩展;并且最终TypeScript还是需要转换成JavaScript代码才能真正运行的;当然我们不排除有一天JavaScript语言本身会加入类型检测,那么无论是TypeScript,还是Flow都会退出历史舞台。
认识浏览器
我们经常说的浏览器内核指的是浏览器的排版引擎 排版引擎(layout engine),也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine) 或样版引擎。
| 内核 | 前缀 | 备注 |
|---|---|---|
| Gecko | -moz | 早期被Netscape和Mozilla Firefox浏览器浏览器使用; |
| Trident | -ms | 早期被Netscape和Mozilla Firefox浏览器浏览器使用; |
| Webkit | -webkit | 苹果基于KHTML开发、开源的,用于Safari,Google Chrome之前也在使用; |
| Blink | 是Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Opera等 |
浏览器渲染过程
浏览器会先下载html 浏览器内核通过HTML Parser 转化为DOM树,JavaScript代码通过js引擎可以操作DOM。 css通过CSS Parser 形成 Style Rules 然后和Dom树结合形成Rander Tree 通过Layout布局 之后进行绘制展示。如果遇到JavaScript会停止解析html而去加载JavaScript
为什么需要JavaScript引擎?
高级的编程语言都是需要转成最终的机器指令来执行的;
事实上我们编写的JavaScript无论你交给浏览器或者Node执行,最后都是需要被CPU执行的;
但是CPU只认识自己的指令集,实际上是机器语言,才能被CPU所执行。所以需要JavaScript引擎帮助我们将JavaScript代码翻译成CPU指令来执行;
常见的JavaScript引擎有哪些?
- SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(也就是JavaScript作者);
- Chakra:微软开发,用于IT浏览器;
- JavaScriptCore:WebKit中的JavaScript引擎,Apple公司开发;
- V8:Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出;
浏览器内核和JS引擎的关系
以WebKit为例,WebKit事实上由两部分组成的: - WebCore:负责HTML解析、布局、渲染等等相关的工作;
- JavaScriptCore:解析、执行JavaScript代码; V8引擎的原理
- V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
- 它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32, ARM或MIPS处理器的Linux系统上运行。
- V8可以独立运行,也可以嵌入到任何C ++应用程序中。
JavaScript代码最终由cpu执行 但是cpu不认识JavaScript代码,所以需要借助V8引擎
Parse模块 会将JavaScript代码转换成AST(抽象语法树),
- 这是因为解释器并不直接认识JavaScript代码;要经过词法分析(token对象)和语法分析
- 如果函数没有被调用,那么是不会被转换成AST的;
- Parse的V8官方文档:v8.dev/blog/scanne… Ignition是一个解释器 ,会将AST转换成ByteCode(字节码)
- 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
- 如果函数只调用一次,Ignition会执行解释执行ByteCode;
- Ignition的V8官方文档:v8.dev/blog/igniti… TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
- 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
- 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是 number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
- TurboFan的V8官方文档:v8.dev/blog/turbof…
如何解析:
- 浏览器内核(Blink)解析html的时候遇到js,会把js下载下来以流的方式传递给V8引擎,对编码进行转化经过转化为Stream(扫描器)会进行词法分析(Iexical analysis),词法分析会把代码转化为tokens。
- 由Parse模块将JavaScript代码转换成AST(抽象语法树)
- PreParser称之为预解析,为什么需要预解析呢?
这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会 影响网页的运行效率;
所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂 时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析; - Ignition会将AST转换成ByteCode(字节码),
- TurboFan将字节码编译为CPU可以直接执行的机器码
JS执行原理
JS在被解析(运行代码之前)会在堆内存中创建一个全局对象Global Object(初始化全局对象GO)
- 该对象的Scope所有作用域都可以访问
- 里面会包含Date、String、Array、setTimeout等等。。。
- 其中还包含一个window属性指向自己
-
js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。
-
那么现在它要执行谁呢?执行的是全局的代码块:
-全局的代码块为了执行会构建一个 Global Execution Context(GEC) -全局执行上下文(GEC)会被放入到执行上下文栈(ESC)中执行
-
GEC被放入到ECS中里面包含两部分内容
- 第一部分:在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中但是并不会赋值(变量提升)
- 第二部分:在代码执行中,对变量赋值,或者执行其他的函数
-
GEC被放入到ECS中
- GEC开始执行代码
-
执行过程中遇到函数如何执行?
-
在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中。
-
FEC中包含三部分内容:
-
在解析函数成为AST树结构时,会创建一个Activation Object(AO)
-
作用域链:由VO(variable Object 在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
-
this绑定的值
-
-
- FEC被放入到ECS中
- FEC开始执行代码
变量环境和记录
通过上面的变化我们可以知道,在最新的ECMA标准中,我们前面的变量对象VO已经有另外一个称呼了变量环境 VE(variable Enviroment)
作用域提升习题
var n = 100
function foo() {
n = 200
}
console.log(n) // 100
var n = 100
function foo() {
n = 200
}
foo()
console.log(n) // 200
var a = 100
function foo() {
console.log(a) // undefined
return
var a = 100
}
foo()
function foo() {
console.log(n) // undefined
var n = 200
console.log(n) // 200
}
var n = 100
foo()
function foo() {
// 这里a是局部变量,b是全局变量
var a = b = 100
}
foo()
console.log(a,b) //a is not defined,100
var n = 100
function foo1() {
console.log(n) // 100
}
function foo2() {
var n = 200
console.log(n) // 200
foo1()
}
foo2()
console.log(n) // 100
function fn(a) {
console.log(a) // fn
//1、函数声明会置顶,变量声明会置顶,**函数声明比变量声明更置顶**
//2、变量声明和赋值一起写(var),js引擎解析时会将其拆分成声明和赋值两部分,声明置顶,解析保留在原来位置(这里仅指var方式,let和const具有块级作用域,并且声明的变量不挂载到window上)
//3、声明过的变量不会重复声明
var a = 123
console.log(a) // 123
function a(){}
console.log(a) // 123
var b = function(){}
console.log(b) // fn
}
fn(1)
console.log(test) // fn
function test(){
console.log(test) // fn
var test = 234
console.log(test) // 1234
function test(){}
}
test(1)
var test = 123
function demo() {
console.log(b) // undefined
if(a) {
var b = 100
}
console.log(b) // undefined
c = 234
console.log(c) // 234
}
var a
demo()
a = 10
console.log(c) // 234
内存管理
- 不管什么样的编程语言,在代码的执行过程中都是需要给它分配内存的,不同的是某些编程语言需要我们自己手动 的管理内存,某些编程语言会可以自动帮助我们管理内存:
- 不管以什么样的方式来管理内存,内存的管理都会有如下的生命周期:
- 第一步:分配申请你需要的内存(申请);
- 第二步:使用分配的内存(存放一些东西,比如对象等);
- 第三步:不需要使用时,对其进行释放; 不同的编程语言对于第一步和第三步会有不同的实现:
- 手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函 数);
- 自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存;JavaScript通常情况下是不需要手动来管理的 JS的内存管理
- JS对于基本数据类型内存的分配会在执行时, 直接在栈空间进行分配;
- JS对于复杂数据类型内存的分配会在堆内存 中开辟一块空间,并且将这块空间的指针返 回值变量引用
垃圾回收(Garbage Collection)
因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间。 - 在手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:
- 但是这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率;
- 并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露;所以大部分现代的编程语言都是有自己的垃圾回收机制:
- 垃圾回收的英文是Garbage Collection,简称GC;
- 对于那些不再使用的对象,我们都称之为是垃圾,它需要被回收,以释放更多的内存空间;
- 而我们的语言运行环境,比如Java的运行环境JVM,JavaScript的运行环境js引擎都会内存 垃圾回收器; - 垃圾回收器我们也会简称为GC,所以在很多地方你看到GC其实指的是垃圾回收器; 但是这里又出现了另外一个很关键的问题:GC怎么知道哪些对象是不再使用的呢?这里就要用到GC的算法了
常见的GC算法
- 引用计数 当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销 毁掉; p这个算法有一个很大的弊端就是会产生循环引用;
- 标记清除
这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对 于哪些没有引用到的对象,就认为是不可用的对象;
这个算法可以很好的解决循环引用的问题;