《深入理解JS》| 青训营笔记

106 阅读7分钟

深入理解JS

JS的基本概念

发展:

网景公司——ESMA——Node——npm——ES6

进程:

image.png

JS的特点:

单线程语言,GUI线程 和 JS线程 是互斥的;

动态、弱类型;

面向对象、函数式;

解释类语言、JIT;

安全、性能差。

JS的数据类型

分为基础(原始)数据类型和引用(复杂)数据类型。

基础数据类型:Number、String、Boolean、Undefined、Null;ES6:BigInt、Symbol

引用数据类型: Object、Function、Array等对象

JS的作用域

变量的可访问性和可见性

静态作用域,通过它就能预测代码在执行过程中如何查找标识符。

分为全局、函数、块级作用域。

全局作用域:在整个JS程序中都可访问的作用域。在全局作用域声明的变量和函数可以在任何地方被访问和使用。

函数作用域:在一个函数内部声明的变量和函数只在该函数内部可见与访问的作用域范围。在函数作用域中声明的变量与函数无法在外部访问,但是在函数中可以访问函数外作用域中的变量和函数。

块作用域:

变量提升

let和const声明的变量不会提升,但是var声明的会。

var提升变量后,变量可访问且值是undefined。

function函数的定义也会提升,并且是整个函数进行提升。

2 JS是怎么执行的

image.png

源代码——AST——字节码——机器码

执行上下文

当JS引擎解析到可执行代码片段的时候,会先做一些执行前的准备工作,这个工作就叫做“执行上下文(execution context)”,也叫做执行环境。

执行上下文中包括——变量环境,词法环境,this,可执行代码,其中变量环境还指向外界的环境。

image.png 执行上下文可分为全局执行上下文、函数执行上下文、Eval执行上下文。

全局执行上下文:代码开始执行时就会创建,将其压入执行栈的栈底,每个生命周期内只有一份。

函数执行上下文:当执行一个函数时,这个函数内的代码会被编译,生成变量环境、词法环境等,当函数执行结束的时候该执行环境从栈底弹出。

创建执行上下文时做了什么?—— 绑定this、创建词法环境、创建变量环境

image.png

作用域链:作用域链由多个执行上下文的变量对象组成的链式结构,用于确定标识符的可访问性和解析顺序。当在函数内部引用一个变量或函数时,JS引擎会从当前执行上下文开始,逐级向上查找,直到找到匹配的标识符或到达全局作用域。如果在向上查找过程中找到,则返回该值;否则抛出Reference Error。

3 JS的进阶知识点

闭包

what?

闭包(Closure)是引用了其他函数作用域变量的函数和这些被引用变量的集合。一个函数在创建时会同时创建出相应的闭包,闭包作为函数内部与外部的桥梁。

具体来说,当内部函数引用外部函数的变量时,外部函数的作用域链将被保留在内存中,以便内部函数可以访问这些变量。 这种函数嵌套和变量共享的方式就是闭包的核心概念。当一个函数返回另一个函数时,它实际上返回了一个闭包,其中包含了原函数定义时的词法作用域和相关变量。

how?

闭包可以使用函数的嵌套以及变量引用实现。

why?

闭包通常使用在以下场景:

1. 创建私有变量,以防其被外部访问和修改。

我们在开发过程中会定义全局变量,而一些关键的变量放在全局作用域可能被修改、误操作。因此可以使用闭包,将变量定义在函数内部并在返回的函数中引用对应的变量,外部无法访问。

2. 缓存变量,延长变量的生命周期。

函数执行完毕后变量所在的词法环境会被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时的所在的执行上下文被销毁,但创建时所在词法环境仍然存在,已延长变量的生命周期。

3. 实现函数柯里化

在初始函数中根据参数的长度递归返回闭包。

缺陷

闭包也存在着一个潜在的问题,由于闭包会引用外部函数的变量,但是这些变量在外部函数执行完毕后没有被释放,那么这些变量会一直存在于内存中,总的内存大小不变,但是可用内存空间变小了。 一旦形成闭包,只有在页面关闭后,闭包占用的内存才会被回收,这就造成了所谓的内存泄漏

解决:

1.及时释放闭包:手动调用闭包函数,并将其返回值赋值为null,这样可以让闭包中的变量及时被垃圾回收器回收。

2.使用立即执行函数:在创建闭包时,将需要保留的变量传递给一个立即执行函数,并将这些变量作为参数传递给闭包函数,这样可以保留所需的变量,而不会导致其他变量的内存泄漏。

this

this是JS中的关键字,它表示函数运行时自动生成的一个内部对象,只能在函数内部使用,在函数运行时不可修改。

this指向的绑定需要根据不同的情况确定,但可以说this总指向调用它的对象。

this设置的方式分为(默认绑定、隐式绑定、new绑定、显示修改、箭头函数)

默认绑定:作为普通函数调用的函数,this指向全局对象

隐式绑定:作为对象的方法调用的函数,this指向调用方法的对象

new绑定:用new关键字作为构造函数调用的函数,this指向new新创建的对象

显式修改:this的指向可以通过call、apply、bind函数进行显式修改,this指向函数传递的第一个参数。

绑定的优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

箭头函数:箭头函数的this指向外层作用域,在代码编译时就能确定this指向。箭头函数的this无法被显式修改。

垃圾回收

image.png

新垃圾回收

对象区域;空闲区域

操作:垃圾标记、对象复制、区域反转

老垃圾回收

标记、清除、对空域的区域进行整理。

在老回收里,JS是停滞的。等垃圾标记完备在回收。

事件循环

image.png

JavaScript中,所有的任务都可以分为

  • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

微任务

一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

#宏任务

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

按照这个流程,它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

4 课程总结

image.png