内存
- 内存的生命周期
- 内存分配:声明变量、函数
- 内存使用:调用、使用
- 内存回收
// 申请
let obj = {}
// 使用
obj.name = '111'
// 释放
obj = null
- 常见内存泄漏
- 全局变量
- 未被清除的定时器和函数
- 闭包
- DOM的引用
- 如何避免
- 减少不必要的全局变量
- 使用完数据后及时解除引用
const element = {
image: document.getElementById('image');
}
document.body.removeChild(document.getElementById('image'));
element.image = null
垃圾回收与GC算法
- 垃圾:对象不再被引用或者不能从根(当前全局变量对象)上访问
- GC:垃圾回收机制,工作内容是查找空间、释放空间、回收空间
- 引用与可达
let obj = {name: 'ztt'} // 可达
let all = obj // 引用
obj = null // 由于引用未断,依旧是可达
function func(obj1, obj2) {
obj1.name = obj2
obj2.name = obj1
return {
o1: obj1,
o2: obj2
}
}
let obj = func({val: 1}, {val:2})
console.log(obj)
- 引用计数
- 设置引用数,判断当前引用数是否为0
- 优点:发现垃圾立即回收、减少程序暂停
- 缺点:无法回收循环引用的对象、时间开销大
const user1 = {name: 'ztt'} const user2 = {name: 'zz'} const user3 = {name: 'xx'} const list = [user1, user2, user3] // 不会被回收 function fn(){ // 会被回收 const num1 = 1 const num2 = 2 } fn() - 标记清除
- 递归遍历找到可达对象做标记、清除没有标记的对象和第一次所做的标记
- 优点:解决循环引用不能回收的问题
- 缺点:垃圾回收的对象地址不连续,导致空间碎片化
- 标记整理
- 清除阶段会整理对象位置,再进行回收
- 优点:解决空间碎片化
V8引擎
- 一款主流的JavaScript执行引擎,采用即时编译,内存设限
- 垃圾回收策略:分代回收思想,将内存分为新老生代,针对不同对象采用不同GC算法
- 小空间存储新生代对象(存活时间较短),采用复制算法+标记整理,继续分为两个等大空间(活动对象存储在使用空间From,标记整理后将活动对象拷贝到空闲空间To,From与To交换空间完成释放)
- 老生代指的是存活时间较长的对象(全局变量、闭包数据等),采用标记清除(垃圾回收)+标记整理(空间优化)+标记增量(提升效率)
performance工具
- 监控程序的内存变化
- 使用步骤 ·打开浏览器输入目标网址 ·进入开发人员工具面板,选择性能 ·开启录制功能,访问具体界面 ·执行用户行为,一段时间后停止录制 ·分析界面中记录的内存信息 ·勾选内存,观察蓝色线条走势
- 内存问题
- 内存泄漏:内存使用持续升高
- 内存膨胀:在大多数设备上都存在性能问题
- 频繁垃圾回收
- 内存监控
- 浏览器任务管理器:根据JavaScript内存小括号的内存是否持续增加,判断是否存在问题
- Timeline时序图:根据Performance工具蓝色走势图定位到具体操作
- 堆快照查找分离DOM:Memory工具
- 判断是否存在频繁GC(GC工作时应用程序是停止的,频繁且过长的GC会导致应用假死,用户使用中感知应用卡顿):Timeline中频繁的上升下降、任务管理器中数据频繁的增加减小
代码优化
- 慎用全局变量
- 缓存全局变量
let obj = document
obj.createElement...
- 通过原型增加方法
// before
const fn = function(){
this.foo = function(){}
}
// after
const fn = function(){}
fn.prototype.foo = function(){}
- 避开闭包陷阱
function foo() {
let el = document.getElementById('el')
el.onclick = function(){
console.log(el.id)
}
el = null;
}
- 避免属性访问方法的使用
function foo() {
this.name = 'foo';
this.getName = function(){
return this.name; // 会增加一层重定义,没有访问的控制力
}
}
- 优化for循环
for(var i = 0;len = arr.length, i <len;i++){}
- 采用最优的循环方式
- forEach > for > for in
- 节点添加优化
- 节点操作会有重绘和回流
// before
for(var i = 0; i < 10; i++) {
var oP = document.createElement("div");
oP.innerHTML = i;
document.body.appendChild(oP);
}
// after
// 1、文档碎片
const fragEle = document.createDocumentFragment();
for(var i = 0; i < 10; i++) {
var oP = document.createElement("div");
oP.innerHTML = i;
fragEle.appendChild(oP);
}
document.body.appendChild(oP);
// 2、克隆
var oldP = document.createElement('box1');
for(var i = 0; i < 10; i++) {
var newP = oldP.cloneNode(false);
newP.innerHTML = i;
document.body.appendChild(newP);
}
- 直接量替换Object
// brfore
var a1 = new Array();
a1[0] = 1;
// after
var a = [1,2,3]
- 工具:JSBench
- jsbench.me/
- 其他工具:jsperf(不再维护)
其他优化
- 栈:存放执行上下文,由主线程进行回收
- 堆:存放引用类型作用域,由GC垃圾回收机制回收
- 堆栈执行过程
let a = 10;
function foo(b) {
let a = 2;
function baz(c){
console.log(c + b + a);
}
return baz
}
let fn = foo(2)
2. 使用事件委托
3. 减少声明及语句数
4. 减少循环体中活动
5. 减少数据读取次数
// before
function hasEle(ele, cls){
return ele.className == cls
}
// after
function hasEle(ele, cls){
var clsname = ele.className
return classname == cls
}
- 减少作用域链查找层级
- 多次创建执行上下文,上下文有自己的作用域,函数执行过程中,每遇到一个变量都会搜索作用域
- 减少判断层级:if
JS性能优化
目的
- 首屏时间
- 首次交互时间
- 首次有意义内容渲染时间
页面性能检测
- https://developers.googLe.com/speed/pagespeed/insights/
方法
- 只请求当前需要的资源:
- 异步加载
- 懒加载
- 按需加载:优化polyfill(针对低版本浏览器的转义)https://polyfill.io/v3/url-builder/
- 缩减资源体积:
- 打包压缩(webpack4已内置、gzip)
- 图片格式优化:(tinypng.com/)、根据屏幕动态适配图…
- 控制cookie大小(request header),同域名请求携会带所有cookie
- 时序优化:
- promise.all
- ssr (而且还方便seo)
- prerender prefetch preload
<!-- 让文件会立马解析DNS(DNS预解析) -->
<link rel="dns-prefetch" href=''>
<!-- 预加载 -->
<link rel="dns-preload" as="image" href=''>
<!-- 预链接 -->
<link rel="dns-preconnect" href=''>
- 合理利用缓存:
- cdn预热和刷新
通过刷新功能,您可以强制CDN节点回源并获取最新文件;通过预热功能您可以在业务高峰期预热热门资源,提高资源访问效率。