10月上旬学习笔记(ES新特性,TS,JS性能优化)

457 阅读17分钟

由于该篇幅字数过长js性能优化部分写在下一篇里

js性能优下篇:juejin.cn/post/688341…

TS部分:juejin.cn/post/688342…

第一部分:ECMAScript新特性

一:ES2015-let与代码块

  1. 作用域:某个成员能够起作用的范围,在此之前,ES中只有两种作用域(全局作用域,函数作用域),在ES2015中新增加了块级作用域。
  2. 在以前快没有独立的作用域,因此外界能访问代码块中的变量。
  3. var存在变量声明,let不存在变量声明。

二:ES2015-const

  1. 在let的基础上多了[只读]特效,声明过后不允许在被修改(可以修改属性成员,类如对象中的成员)

  2. const声明的同时必须复制,let不需要

    //被声明过后就不允许修改
    const name = 'xjq'
    name = 'x' // 报错因为被声明过后就不允许被修改
    
    const = a
    a = 'c'
    console.log(a) // 报错,因为const声明的同时必须赋值
    
    const obj = {}
    obj.age = 22
    console.log(obj) // {age:22}  因为const不允许重新修改内存地址指向,但是可以修改属性成员
    

三:ES2015-数组的解构

const arr = [100,200,300]
//以前的做法
const f1 = arr[0]
const f2 = arr[1]
const f3 = arr[2]
console.log(f1, f2, f3) // 100 200 300

//ES2015数组的解构
const [f4, f5, f6] = arr
console.log(f4, f5, f6) // 100 200 300

//只要获取个别成员的话,需要逗号补上保证一致格式
const [ , , f7] = arr
console.log(f7) = 300

//...的用法只能放到最后一个成员上
const [foo , ...rest] = arr
console.log(foo) // 100
console.log(rest) // [200,300]

//只有一个成员默认获取第一个
const [f8] = arr
console.log(f8) // 100

//超出成员数量   undefined
const [f9 ,f10 , f11, f12] = arr
console.log(f12) // undefined

//成员默认值(没有提取到数组对应的值才用默认值)
const [f13, f14 = 400, f15 = 500, f16 = 600] = arr
console.log(f13, f14, f15, f16) // 100 200 300 500

四:ES2015-对象的解构

  1. 对象的提取需要根据名字获取,与数组解构不同是因为数组元素存在下标,存在顺序规则,而对象里的成员没有规定顺序
  2. 如果当前作用域有相同的变量名就会产生冲突,解决方法是在解构的时候修改变量名
  3. 其他特性与数组解构基本一致

五:ES2015-模板字符串

const str = console.log`hello world`  // ['hello world']

const name = 'tom'
const gender = true
function tarFunc(strings, name, gender){
console.log(strings, name, gender)
//带标签的模板字符串可以打印静态成员["hey," , "is a" , "" , raw: Array(3)] "tom" true
//后面两个参数接收动态传入的值,函数内部的返回值就是下面带标签模板字符串的值
return '这是模板字符串的返回值'
}

const retult = tagFunc`key,${name} is a ${gender}`
console.log(result) //这是模板字符串的返回值

六:ES2015-字符串的扩展方法

  1. includes() ---字符串包含什么,返回布尔值

  2. stratsWith() ---字符串以什么开头,返回布尔值

  3. endWith() ---字符串以什么结尾,返回布尔值

    const message = "Error: foo is not defined."

    console.log(message.stratsWith('Error')) // true

    console.log(message.endWith('.')) // true

    console.log(message.includes('foo')) // true

七:ES2015-参数的默认值

function foo(enable = true){
console.log(enable)
}
foo()          //不传参数取默认值
foo(false)     //传递参数,取传递参数值

八:ES2015-剩余参数

  1. 以前用arguments,但是arguments是一个伪数组

九:ES2015-展开数组

const arr1 = ["f1", "f2", "f3"]
const arr2 = ["f2","f3","f4","f5","f6"]
console.log(...arr1)          //f1 f2 f3
console.log(...arr1 , arr2)   //f1 f2 f3 f2 f3 f4 f5 f6

十:ES2015-箭头函数

  1. 箭头函数不会改变this的指向

十一:ES2015-对象字面量增强

const bar = 123
const obj = {
foo: 123,
//bar:bar
//key和value相同时,可以使用对象字面量增强写法
bar,

//method : function (){}, 这也可以使用对象字面量的写法
method1(){
console.log('增强写法')
retuen '增强写法'
}
//动态添加属性名
[Math.random] : 13
}

//以前动态添加属性
obj[Math.random()] = 147
console.log(obj)
console.log(obj.method1())

十二:ES2015-对象扩展方法-Object.assign

  1. 将多个源对象中的属性赋值到一个目标对象中,如果有相同属性九覆盖
  2. 支持输入多个对象参数,第一个为目标对象

十三:ES2015-对象扩展方法-Object.is

  1. 用于判断两个值是否相等
  2. 基本很少用,常用的是 ===

十四:ES2015-代理对象proxy基本语法

  1. 用于监听对象的读写过程
  2. 功能比defineProperty更强大,更方便
  3. 支持两个参数,第一个为代理目标,第二个代理的处理对象

十五:ES2015-代理对象proxy对比defineProperty

  1. Proxy更为强大,因为defineProperty只能监视属性的读写,Proxy能够监视到更多对象操作。
  2. Proxy更好的支持数组对象的监视,以前defineProperty最常见的方式是重写数组的操作方法。
  3. Proxy是以非侵入的方式监管了对象的读写,不需要对对象本身进行操作,而Object.defineProperty需要对对象本身进行操作。

十六:ES2015-Reflect统一的对象操作API

  1. Reflect属于一个静态类,不能提供new Reflect创建一个实例对象,只能调用静态类的静态方法,比如Reflect.get()
  2. Reflect内部提供了一系列对对象的底层操作
  3. Reflect成员方法就是proxy处理对象的默认实现
  4. 存在的意义就是提供了一套api,一共存在13个方法

十七:ES2015-promise

  1. 这里之前笔记整理过,可以跳转下面的链继
  2. juejin.cn/post/687787…

十八:ES2015-class类

  1. constructor是构造函数
  2. 类的方法中有实例方法和静态方法
  3. 静态成员--static(静态方法)
  4. 因为静态方法是挂载类上的,所以this方法不会指向实例方法

十九:ES2015-class类的继承

  1. 关键词:extends
  2. super关键字:始终指向父类,调用它就是调用父类的构造函数

二十:ES2015-Set数据结构

  1. Set内部成员不允许重复出现,值是唯一的
  2. add方法会返回集合对象本身,所以可以链式调用
  3. 常见的场景是用来数组去重

二十一:ES2015-Map数据结构

  1. 该对象的键可以是任意类型,普通对象只能是字符串
  2. 普通对象如果键使用了其他类型的值,会被转为字符串,map不会
  3. Map数据结构与普通对象最大区别是可以用任意类型作为键,对象只能使用字符串

二十二:ES2015-Symbol一种全新的数据结构

  1. 作用是表示独一无二的值,通过Symbol创建的值都是独一无二的
  2. 目前最主要的作用就是为对象添加一个独一无二的额属性名
  3. 全局复用使用相同的Symbol值可以使用Symbol的静态方法for,传入for方法的值会自动转为字符串

二十三:ES2015-for...of循环

  1. 可以作为遍历所有数据结构的统一方式
  2. for...of方法可以使用return方法终止循环
  3. 但是循环普通对象会报错,因为没有实现可迭代接口,而其他数据类是实现了可迭代接口

二十四:ES2015-可迭代接口

  1. 实现可迭代接口就是for...of的前提
  2. 所有被for..of循环的数据结构都实现了Interable

二十五:ES2015-实现可迭代的接口

const obj = {
store: ['foo','bar','baz'],

[Symbol.iterator]: function(){
let index = 0
const self = this
return {
next : function(){
const result = {
value : self.store[index],
done : index >= self.store.length
}
index ++
return result
}
}
}
}
for (const item of obj){
console.log(item)  // foo  bar  baz
}

二十六:ES2015-迭代器模式

  1. 意义就是对外提供统一的接口,外部不需要知道内部的数据结构及实现原理

二十七:ES2015-生成器Generator

  1. 避免异步编程中回调嵌套过深
  2. 生成器函数会自动生成一个生成器对象,需要调用next方法执行
  3. 执行过程中遇到yield关键字,函数的执行就会被暂停,需要再次调用next才会再次执行

二十八:ES2015-生成器Generator的应用

  1. 最重要的目的是解决异步编程中过渡嵌套的问题

二十九:ES2015-Modules

  1. 这个知识点放在下次编写工程化笔记的时候在详细介绍

三十:ES2016

  1. includes,以前的indexOf方法不能查找NaN,但是includes方法可以
  2. 指数运算符

三十一:ES2017

  1. Object.values与ES2015的Object.key类似,一个返回值,一个返回键
  2. Object.entries是以数组的形式返回对象的所有键值,用了这个方法就可以不用自己写迭代器九可以遍历普通对象,也可以将对象转换为Map对象
  3. Object.getOwnPropertyDescriptors可以获取对象中的属性的完整描述
  4. String.prototype.padStart / String.prototype.padEnd--两个字符串填充方法
  5. 函数在参数中添加逗号
  6. async / await

第二部分:JS性能优化

一:内存管理

  1. 内存:由可读写单元组成,表示一片可操作空间

  2. 管理:人为的取操作一片空间的申请,使用和释放

  3. 内存管理:开发者主动申请空间,使用空间,释放空间

  4. 管理流程:申请-使用-释放

  5. JavaScript中的内存管理--申请内存管理--使用内存空间--释放内存空间

    //下面这段代码存在内存泄漏,内存监控持续升高 function fn(){ arrList = [] arrList[100000] = 'xjq' } fn()

    //申请 const obj = {} //使用 obj.name = 'xjq' //释放 obj = null

二:JavaScript中的垃圾回收

  1. JavaScript中的内存管理是主动的

  2. 对象不再被引用式是垃圾

  3. 对象不能从根上访问到时是垃圾

  4. 可达的理解

  5. 在一个作用域上,只要提供根可以有路径查到的对象都是可达对象

  6. 可以访问到的对象就是可达对象(引用,作用域)

  7. 可达的标准就是从根出发是否能被找到

  8. javascript的根可以被理解为全局变量引用

    //javascript中的引用和可达 //{}对象空间被obj引用,同时可以被访问到,所以是可达的 let obj = {name : 'xjq'} //此时ali也引用对象,即使下面obj=null,该对象也是可达的 let ali = obj obj = null

    //下面的代码是相互引用的关系,如果断了o1和prve引用obj1的话,那么obj1就会被当成垃圾被回收 function objGroup(obj1, obj2){ //obj1的next引用了obj2 obj1.next = obj2 //obj的prve引用了obj1 obj2.prve = obj1 return { //o1引用了obj1 o1: obj1, //o2引用了obj2 o2: obj2 } }

    //obj引用了objGroup let obj = objGroup({name: 'obj1'}, {name: 'obj2'})

三:GC算法介绍

  1. GC就是垃圾回收机制的简写
  2. GC可以找到内存中的垃圾,并释放和回收空间
  3. 程序中不需要再使用的对象就是GC里的垃圾
  4. 程序中不能再访问到的对象就是GC里的垃圾
  5. GC是一种机制,垃圾回收器完成具体的工作
  6. 工作的内容就是查找垃圾释放空间,回收空间
  7. GC算法就是工作时查找和回收所遵循的规则
  8. 常见的GC算法--引用计数
  9. 常见的GC算法--标记清除
  10. 常见的GC算法--标记整理
  11. 常见的GC算法--分代回收(v8常见)
//GC里的垃圾是什么
//程序中不再需要使用的对象
function func1(){
name: 'xjq' //函数调用完成后不再使用该全局变量,此时应该是垃圾
return `${name}`
}
func1()

//程序中不能再访问的对象
function func2(){
const name = 'xjq' //函数调用完毕,该局部变量不再使用,外部也不能调用,此时是垃圾

return  `${name}`
}

四:引用计数算法实现原理

  1. 核心思想:设置引用数,判断当前引用数是否为0,为0-GC就开始工作,回收该对象的内容,释放空间

  2. 引用计数器

  3. 引用关系改变时修改引用数字(例如一个对象空间,有一个变量指向就加一,不引用就减一)

  4. 引用数字为零时立即回收

    const user1 = { age: 11} const user2 = { age: 22} const user3 = { age: 33}

    //这里一直引用上面的3个对象,所以引用计数器·不为零,不会被回收 const nameList = [user1.age, user2.age, user3.age]

    function fn(){ //当前函数调用完毕后这两个变量不再被调用,此时引用计数器为0,应该被回收 const num1 = 1 const num2 = 2 }

四:引用计数算法的优缺点

  1. 优点:发现垃圾立即回收

  2. 优点:最大程度减少程序暂停(内存占满就会·暂停)

  3. 缺点:无法回收循环引用的对象

  4. 缺点:事件开销大(因为计数需要维护计数。需要时刻监视数字修改)

    //循环引用对象 function fn(){ const obj1 = {} const obj2 = {}

    //这里使用了循环引用,导致计数不为零 obj1.name = obj2 obj2.name = obj1 } fn()

五:标记清除算法实现原理

  1. 核心思想:分标记和清除两个阶段完成
  2. 遍历所有对象找标记活动对象
  3. 遍历所有对象清除没有标记对象(同时将之前的标记抹除)
  4. 回收相应的空间(回收的空间会放在空闲列表上,方便后续持续申请空间使用)

六:引用计数算法优缺点

  1. 优点:可以回收循环引用的对象
  2. 缺点:存放再空闲列表的空间碎片化,因为之前回收释放的空间是不连续的,后续申请内存的话。可能空间大小不合适

七:标记整理算法实现原理

  1. 标记这里可以看做是标记清除的增强
  2. 标记阶段的操作和标记清除一致
  3. 清除阶段会先执行整理,移动对象位置(以达到空间连续的目的)

八:认识v8

  1. v8是一款主流的javascript执行引擎
  2. v8采用即时编译(直接将源码转换为当前可执行的机器码)
  3. v8内存设限(64位不超过1.5G,32位不超过800M)

九:v8垃圾回收策略

  1. 采用分代回收的思想
  2. 内存分为新生代和老生代
  3. 针对不同对象采用不同算法
  4. v8中常见的GC算法--分代回收
  5. v8中常见的GC算法--空间复制
  6. v8中常见的GC算法--标记清除
  7. v8中常见的GC算法--标记整理
  8. v8中常见的GC算法--标记增量

十:v8如何回收新生代对象

v8的内存分配

  1. v8空间一分为二
  2. 小空间用于存储新生代对象(64位操作系统32M |  32位操作系统16M)
  3. 新生代指的是存活时间较短的对象

新生代对象回收实现

  1. 回收过程采用空间复制+标记整理
  2. 新生代内存分为两个等大空间
  3. 使用空间为From状态,空闲空间为To状态
  4. 活动对象存储在From空间(当空间到达一定上限后就会触发相应操作)
  5. 标记整理之后将活动对象拷贝至To(把活动对象变得连续方便后续不会产生碎片化空间)
  6. From与To交换空间完成释放(回收释放From空间)

细节说明

  1. 拷贝过程中可能出现晋升(拷贝时发现某一个对象的所指向的空间,在老生代也会出现)
  2. 晋升就是将新生代对象移动至老生代
  3. 一轮GC还存活的新生代需要晋升(触发晋升移动到老生代)
  4. To空间的使用超过25%(触发晋升移动到老生代)

十一:v8如何回收老生代对象

老生代对象说明

  1. 老生代对象存放在右侧老生代区
  2. 64位操作系统1.4G,32位操作系统700M
  3. 老生代对象就是指存活时间较长的对象(全局存放的变量,闭包等)

老生代对象回收实现

  1. 主要采用标记清除,标记整理,增量标记
  2. 首先使用标记清除完成垃圾空间的回收
  3. 采用标记整理进行空间优化(新生代移动的时候,如果出现空间不足的时候就会进行标记整理)
  4. 采用增量标记进行效率优化(就是将一整段的垃圾回收拆分成很多小段,组合着完成这个垃圾回收)

细节对比

  1. 新生代区域垃圾回收使用空间换时间(因为空间小)
  2. 老年代区域垃圾回收不适合空间复制算法(因为空间大,使用空间复制算法会导致浪费大量空间,还有老年代存储的数据较多)

十二:Perfromance工具介绍

为什么使用Performance

  1. GC的目的是为了实现内存空间的良性循环
  2. 良性循环的基石就是合理使用
  3. 时刻关注才能确定是否合理
  4. Performance提供多种监控方式
  5. 提供Performance时刻监控内存

Performance使用步骤

  1. 打开谷歌浏览器输入目标网址
  2. 进入开发人员面板,选择性能(Performance)
  3. 开启录制功能,访问具体界面
  4. 执行用户行为,一段时间后停止录制
  5. 需要勾选内存选项,才会显示内存曲线图,分析界面中记录的内存信息

十三:内存问题的体现

  1. 页面出现延迟加载或者继承暂停(除了网络环境)(出现此问题说明持续在内存回收)(频繁垃圾回收)
  2. 页面持续性出现糟糕的性能(内存膨胀)
  3. 页面的性能随着时间延长越来越差(内存泄漏)

十四:监视内存的几种方法

界定内存问题的标准

  1. 内存泄漏:内存使用持续升高
  2. 内存膨胀:在多数设备上都存在性能问题
  3. 频繁垃圾回收:通过内存分析图进行分析(持续上涨,没有下降的趋势)

监视内存的几种方法

  1. 浏览器任务管理器
  2. Timeline时序图记录
  3. 堆快照查找分离DOM

十五:任务管理器监控内存

  1. 打开方式:点击谷歌浏览器右上角--更多工具--任务管理器
  2. 快捷键---shift + esc
  3. 右击目标,开启JavaScript内存
  4. 内存指BOM节点占据的内存--如果不断增大,表示不断在创建新DOM
  5. JavaScript内存指js堆(看括号内的值)

十六:Timeline记录内存

  1. f12打开开发者工具
  2. 选择Performance--打开录制--打开页面进行操作--操作完停止录制--勾选内存选项,查看内存走向图

十七:堆快照查找分离DOM

什么是分离DOM

  1. 界面元素存活在DOM树上
  2. 垃圾对象时的DOM节点(如果节点从当前DOM树上分离,但是js代码还引用着)
  3. 分离状态的DOM节点(如果节点从当前dom树上分离,但是js代码还引用着)
  4. 这些就是存在内存泄漏

快照的使用

  1. 打开谷歌浏览器,打开开发者工具,选择Memory(内存),勾选读一个快照
  2. 操作完界面。点击Take snapshot(拍照)
  3. 检索Detached(筛选)

十八:判断是否存在频繁GC

  1. GC工作时应用程序是停止的
  2. 频繁且过长的GC会导致应用假死
  3. 用户使用中感知应用卡顿
  4. 确定频繁垃圾回收--Timeline中频繁的下升下降
  5. 确定频繁垃圾回收--任务管理器中数据频繁的增加减少