由于该篇幅字数过长js性能优化部分写在下一篇里
js性能优下篇:juejin.cn/post/688341…
第一部分:ECMAScript新特性
一:ES2015-let与代码块
- 作用域:某个成员能够起作用的范围,在此之前,ES中只有两种作用域(全局作用域,函数作用域),在ES2015中新增加了块级作用域。
- 在以前快没有独立的作用域,因此外界能访问代码块中的变量。
- var存在变量声明,let不存在变量声明。
二:ES2015-const
-
在let的基础上多了[只读]特效,声明过后不允许在被修改(可以修改属性成员,类如对象中的成员)
-
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-对象的解构
- 对象的提取需要根据名字获取,与数组解构不同是因为数组元素存在下标,存在顺序规则,而对象里的成员没有规定顺序
- 如果当前作用域有相同的变量名就会产生冲突,解决方法是在解构的时候修改变量名
- 其他特性与数组解构基本一致
五: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-字符串的扩展方法
-
includes() ---字符串包含什么,返回布尔值
-
stratsWith() ---字符串以什么开头,返回布尔值
-
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-剩余参数
- 以前用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-箭头函数
- 箭头函数不会改变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
- 将多个源对象中的属性赋值到一个目标对象中,如果有相同属性九覆盖
- 支持输入多个对象参数,第一个为目标对象
十三:ES2015-对象扩展方法-Object.is
- 用于判断两个值是否相等
- 基本很少用,常用的是 ===
十四:ES2015-代理对象proxy基本语法
- 用于监听对象的读写过程
- 功能比defineProperty更强大,更方便
- 支持两个参数,第一个为代理目标,第二个代理的处理对象
十五:ES2015-代理对象proxy对比defineProperty
- Proxy更为强大,因为defineProperty只能监视属性的读写,Proxy能够监视到更多对象操作。
- Proxy更好的支持数组对象的监视,以前defineProperty最常见的方式是重写数组的操作方法。
- Proxy是以非侵入的方式监管了对象的读写,不需要对对象本身进行操作,而Object.defineProperty需要对对象本身进行操作。
十六:ES2015-Reflect统一的对象操作API
- Reflect属于一个静态类,不能提供new Reflect创建一个实例对象,只能调用静态类的静态方法,比如Reflect.get()
- Reflect内部提供了一系列对对象的底层操作
- Reflect成员方法就是proxy处理对象的默认实现
- 存在的意义就是提供了一套api,一共存在13个方法
十七:ES2015-promise
- 这里之前笔记整理过,可以跳转下面的链继
- juejin.cn/post/687787…
十八:ES2015-class类
- constructor是构造函数
- 类的方法中有实例方法和静态方法
- 静态成员--static(静态方法)
- 因为静态方法是挂载类上的,所以this方法不会指向实例方法
十九:ES2015-class类的继承
- 关键词:extends
- super关键字:始终指向父类,调用它就是调用父类的构造函数
二十:ES2015-Set数据结构
- Set内部成员不允许重复出现,值是唯一的
- add方法会返回集合对象本身,所以可以链式调用
- 常见的场景是用来数组去重
二十一:ES2015-Map数据结构
- 该对象的键可以是任意类型,普通对象只能是字符串
- 普通对象如果键使用了其他类型的值,会被转为字符串,map不会
- Map数据结构与普通对象最大区别是可以用任意类型作为键,对象只能使用字符串
二十二:ES2015-Symbol一种全新的数据结构
- 作用是表示独一无二的值,通过Symbol创建的值都是独一无二的
- 目前最主要的作用就是为对象添加一个独一无二的额属性名
- 全局复用使用相同的Symbol值可以使用Symbol的静态方法for,传入for方法的值会自动转为字符串
二十三:ES2015-for...of循环
- 可以作为遍历所有数据结构的统一方式
- for...of方法可以使用return方法终止循环
- 但是循环普通对象会报错,因为没有实现可迭代接口,而其他数据类是实现了可迭代接口
二十四:ES2015-可迭代接口
- 实现可迭代接口就是for...of的前提
- 所有被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-迭代器模式
- 意义就是对外提供统一的接口,外部不需要知道内部的数据结构及实现原理
二十七:ES2015-生成器Generator
- 避免异步编程中回调嵌套过深
- 生成器函数会自动生成一个生成器对象,需要调用next方法执行
- 执行过程中遇到yield关键字,函数的执行就会被暂停,需要再次调用next才会再次执行
二十八:ES2015-生成器Generator的应用
- 最重要的目的是解决异步编程中过渡嵌套的问题
二十九:ES2015-Modules
- 这个知识点放在下次编写工程化笔记的时候在详细介绍
三十:ES2016
- includes,以前的indexOf方法不能查找NaN,但是includes方法可以
- 指数运算符
三十一:ES2017
- Object.values与ES2015的Object.key类似,一个返回值,一个返回键
- Object.entries是以数组的形式返回对象的所有键值,用了这个方法就可以不用自己写迭代器九可以遍历普通对象,也可以将对象转换为Map对象
- Object.getOwnPropertyDescriptors可以获取对象中的属性的完整描述
- String.prototype.padStart / String.prototype.padEnd--两个字符串填充方法
- 函数在参数中添加逗号
- async / await
第二部分:JS性能优化
一:内存管理
-
内存:由可读写单元组成,表示一片可操作空间
-
管理:人为的取操作一片空间的申请,使用和释放
-
内存管理:开发者主动申请空间,使用空间,释放空间
-
管理流程:申请-使用-释放
-
JavaScript中的内存管理--申请内存管理--使用内存空间--释放内存空间
//下面这段代码存在内存泄漏,内存监控持续升高 function fn(){ arrList = [] arrList[100000] = 'xjq' } fn()
//申请 const obj = {} //使用 obj.name = 'xjq' //释放 obj = null
二:JavaScript中的垃圾回收
-
JavaScript中的内存管理是主动的
-
对象不再被引用式是垃圾
-
对象不能从根上访问到时是垃圾
-
可达的理解
-
在一个作用域上,只要提供根可以有路径查到的对象都是可达对象
-
可以访问到的对象就是可达对象(引用,作用域)
-
可达的标准就是从根出发是否能被找到
-
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算法介绍
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾,并释放和回收空间
- 程序中不需要再使用的对象就是GC里的垃圾
- 程序中不能再访问到的对象就是GC里的垃圾
- GC是一种机制,垃圾回收器完成具体的工作
- 工作的内容就是查找垃圾释放空间,回收空间
- GC算法就是工作时查找和回收所遵循的规则
- 常见的GC算法--引用计数
- 常见的GC算法--标记清除
- 常见的GC算法--标记整理
- 常见的GC算法--分代回收(v8常见)
//GC里的垃圾是什么
//程序中不再需要使用的对象
function func1(){
name: 'xjq' //函数调用完成后不再使用该全局变量,此时应该是垃圾
return `${name}`
}
func1()
//程序中不能再访问的对象
function func2(){
const name = 'xjq' //函数调用完毕,该局部变量不再使用,外部也不能调用,此时是垃圾
return `${name}`
}
四:引用计数算法实现原理
-
核心思想:设置引用数,判断当前引用数是否为0,为0-GC就开始工作,回收该对象的内容,释放空间
-
引用计数器
-
引用关系改变时修改引用数字(例如一个对象空间,有一个变量指向就加一,不引用就减一)
-
引用数字为零时立即回收
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 }
四:引用计数算法的优缺点
-
优点:发现垃圾立即回收
-
优点:最大程度减少程序暂停(内存占满就会·暂停)
-
缺点:无法回收循环引用的对象
-
缺点:事件开销大(因为计数需要维护计数。需要时刻监视数字修改)
//循环引用对象 function fn(){ const obj1 = {} const obj2 = {}
//这里使用了循环引用,导致计数不为零 obj1.name = obj2 obj2.name = obj1 } fn()
五:标记清除算法实现原理
- 核心思想:分标记和清除两个阶段完成
- 遍历所有对象找标记活动对象
- 遍历所有对象清除没有标记对象(同时将之前的标记抹除)
- 回收相应的空间(回收的空间会放在空闲列表上,方便后续持续申请空间使用)
六:引用计数算法优缺点
- 优点:可以回收循环引用的对象
- 缺点:存放再空闲列表的空间碎片化,因为之前回收释放的空间是不连续的,后续申请内存的话。可能空间大小不合适
七:标记整理算法实现原理
- 标记这里可以看做是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行整理,移动对象位置(以达到空间连续的目的)
八:认识v8
- v8是一款主流的javascript执行引擎
- v8采用即时编译(直接将源码转换为当前可执行的机器码)
- v8内存设限(64位不超过1.5G,32位不超过800M)
九:v8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代和老生代
- 针对不同对象采用不同算法
- v8中常见的GC算法--分代回收
- v8中常见的GC算法--空间复制
- v8中常见的GC算法--标记清除
- v8中常见的GC算法--标记整理
- v8中常见的GC算法--标记增量
十:v8如何回收新生代对象
v8的内存分配
- v8空间一分为二
- 小空间用于存储新生代对象(64位操作系统32M | 32位操作系统16M)
- 新生代指的是存活时间较短的对象
新生代对象回收实现
- 回收过程采用空间复制+标记整理
- 新生代内存分为两个等大空间
- 使用空间为From状态,空闲空间为To状态
- 活动对象存储在From空间(当空间到达一定上限后就会触发相应操作)
- 标记整理之后将活动对象拷贝至To(把活动对象变得连续方便后续不会产生碎片化空间)
- From与To交换空间完成释放(回收释放From空间)
细节说明
- 拷贝过程中可能出现晋升(拷贝时发现某一个对象的所指向的空间,在老生代也会出现)
- 晋升就是将新生代对象移动至老生代
- 一轮GC还存活的新生代需要晋升(触发晋升移动到老生代)
- To空间的使用超过25%(触发晋升移动到老生代)
十一:v8如何回收老生代对象
老生代对象说明
- 老生代对象存放在右侧老生代区
- 64位操作系统1.4G,32位操作系统700M
- 老生代对象就是指存活时间较长的对象(全局存放的变量,闭包等)
老生代对象回收实现
- 主要采用标记清除,标记整理,增量标记
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化(新生代移动的时候,如果出现空间不足的时候就会进行标记整理)
- 采用增量标记进行效率优化(就是将一整段的垃圾回收拆分成很多小段,组合着完成这个垃圾回收)
细节对比
- 新生代区域垃圾回收使用空间换时间(因为空间小)
- 老年代区域垃圾回收不适合空间复制算法(因为空间大,使用空间复制算法会导致浪费大量空间,还有老年代存储的数据较多)
十二:Perfromance工具介绍
为什么使用Performance
- GC的目的是为了实现内存空间的良性循环
- 良性循环的基石就是合理使用
- 时刻关注才能确定是否合理
- Performance提供多种监控方式
- 提供Performance时刻监控内存
Performance使用步骤
- 打开谷歌浏览器输入目标网址
- 进入开发人员面板,选择性能(Performance)
- 开启录制功能,访问具体界面
- 执行用户行为,一段时间后停止录制
- 需要勾选内存选项,才会显示内存曲线图,分析界面中记录的内存信息
十三:内存问题的体现
- 页面出现延迟加载或者继承暂停(除了网络环境)(出现此问题说明持续在内存回收)(频繁垃圾回收)
- 页面持续性出现糟糕的性能(内存膨胀)
- 页面的性能随着时间延长越来越差(内存泄漏)
十四:监视内存的几种方法
界定内存问题的标准
- 内存泄漏:内存使用持续升高
- 内存膨胀:在多数设备上都存在性能问题
- 频繁垃圾回收:通过内存分析图进行分析(持续上涨,没有下降的趋势)
监视内存的几种方法
- 浏览器任务管理器
- Timeline时序图记录
- 堆快照查找分离DOM
十五:任务管理器监控内存
- 打开方式:点击谷歌浏览器右上角--更多工具--任务管理器
- 快捷键---shift + esc
- 右击目标,开启JavaScript内存
- 内存指BOM节点占据的内存--如果不断增大,表示不断在创建新DOM
- JavaScript内存指js堆(看括号内的值)
十六:Timeline记录内存
- f12打开开发者工具
- 选择Performance--打开录制--打开页面进行操作--操作完停止录制--勾选内存选项,查看内存走向图
十七:堆快照查找分离DOM
什么是分离DOM
- 界面元素存活在DOM树上
- 垃圾对象时的DOM节点(如果节点从当前DOM树上分离,但是js代码还引用着)
- 分离状态的DOM节点(如果节点从当前dom树上分离,但是js代码还引用着)
- 这些就是存在内存泄漏
快照的使用
- 打开谷歌浏览器,打开开发者工具,选择Memory(内存),勾选读一个快照
- 操作完界面。点击Take snapshot(拍照)
- 检索Detached(筛选)
十八:判断是否存在频繁GC
- GC工作时应用程序是停止的
- 频繁且过长的GC会导致应用假死
- 用户使用中感知应用卡顿
- 确定频繁垃圾回收--Timeline中频繁的下升下降
- 确定频繁垃圾回收--任务管理器中数据频繁的增加减少