Javascript 性能优化02
2021-06-01 本文已影响0人
丽__
一、V8 引擎执行流程
V8 引擎是浏览器渲染引擎里的js执行代码的组成部分
-
Scanner 是一个扫描器
-
Parser是一个解析器
-
预解析优点
- 跳过未被使用的代码
- 不生成AST,创建无变量引用和声明的scopes
- 依据规范跑出特定的错误
-
全量解析
- 解析被使用的代码
- 生成AST
- 构建具体scopes信息,变量引用、声明等
- 抛出所有语法错误
//声明时未被调用,因此会被认为是不被执行的代码,进行预解析
function foo(){
console.log('foo');
}
//声明时未调用,因此会被认为是不被执行的代码,进行预解析
function fn(){}
//函数立即执行,只进行一次全量解析
(function bar(){
console.log('bar');
})()
//执行foo,那么需要重新对foo函数进行全量解析,此时foo函数被解析了两次
foo();
- lgnition 是V8 提供的一个解释器
- TurboFan是V8提供的编译器模块
二、堆栈操作
堆栈准备
- JS执行环境
- 执行环境栈(ECStack,execution context stack)
- 执行上下文 EC(G)
- VO(G),全局变量对象
- GO,全局对象
var x = 100
var y = x
y = 200
console.log(x);
/**
*01 基本数据类型是按值进行操作的
*02 基本数据类型值是存放在栈区的
*03 无论我们当前看到的栈内存,还是后续引用数据类型会使用的堆内存都属于计算机内存
*04 GO(全局对象)
*/
image.png
三、引用类型堆栈处理
image.png四、函数类型堆栈处理
var arr = ['zce','alishi']
function foo(obj){
obj[0] = 'zoe',
obj = ['aaa']
obj1[1] = '大前端'
console.log(obj)
}
foo(arr)
console.log(arr);
/**
* 01函数创建
* ---- 可以将函数名称看作是变量,存放在VO当中,同时它的值就是当前函数对应的内存地址
* ---- 函数本身也是一个对象,创建时会有一个内存地址,空间内存放的就是函数体代码(字符串形式)
*02 函数执行
* ---- 函数执行时会形成一个全新私有上下文,它里面有一个AO 用于管理这个上下文当中的变量
* ---- 步骤
* ---- ---- 01作用域链<当前执行上下文,上级执行上下文>
* ---- ---- 02、确定this -->window
* ---- ---- 03、初始化arguments对象
* ---- ---- 04、形参赋值 obj = arr
* ---- ---- 05、变量提升
* ---- ---- 06、执行代码
*/
1、创建函数和创建变量类似,函数名此时就可以看做是一个变量名
2、单独开辟一个堆内存用于存放函数和体(字符串形式代码),当前内存地址也会有一个16进制数值地址
3、创建函数的时候,它的作用域【scope】就已经确定了(创建函数时所在的执行上下文)
4、创建函数之后会将它的内存地址存放在栈区与对应的函数名进行关联
函数执行时做的事情:
1、确定作用域链:《当前执行上下文,上级执行上下文》
2、确定this -->window
3、初始化arguments对象
4、形参赋值 obj = arr
5、变量提升
6、执行代码
五、闭包堆栈处理
var a = 1
function foo(){
var b = 2
return function(c){
console.log(c+b++);
}
}
var f = foo();
f(5)
f(10)
- 1、闭包是一种机制,通过私有上下文来保护当中变量的机制
- 我们也可以认为当我们创建的某一个执行上下文不被释放的时候就形成了闭包
- 保护:当前上下文当中的变量与其他的上下文中变量互不干扰
- 保存:当前上下文中的数据(堆内存)被当前上下文意外的上下文中的变量所引用,这个数据就保存下来
- 2、闭包
- 函数调用形成了一个全新的私有上下文,在函数调用之后,当前上下文不被释放就是闭包
六、闭包与垃圾回收
- 浏览器都自有垃圾回收(内存管理,V8为例)
- 栈空间、堆空间
- 堆:当前的堆内存如果被占用,就能被释放掉,但是我们如果确认后续不再使用这个内存离得数据,也可以自己主动置空,然后浏览器就会对其进行回收
- 栈:当前上下文中是否有内容,被其他上下文的变量所占用,如果有则无法被释放(闭包)
七、循环添加事件实现
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
var aButtons = document.querySelectorAll('button')
for(var i = 0;i <aButtons.length;i ++){
aButtons[i].onclick = function(){
console.log(`当前索引值为${i}`);
}
}
/**
*闭包
*自定义属性
*事件委托
*/
for(var i = 0;i <aButtons.length;i ++){
(function(i){
aButtons[i].onclick = function(){
console.log(`当前索引值为${i}`);
}
})(i)
}
for(var i = 0;i <aButtons.length;i ++){
aButtons[i].onclick = ( function(i){
return function(){
console.log(`当前索引值为${i}`);
}
})(i)
}
for(leti = 0;i <aButtons.length;i ++){
aButtons[i].onclick = function(){
console.log(`当前索引值为${i}`);
}
}
for(var i = 0;i <aButtons.length;i ++){
aButtons[i].myIndex = i
aButtons[i].onclick = function(){
console.log(`当前索引值为${this.myIndex}`);
}
}
八、事件委托实现
<button index='1'>按钮</button>
<button index='2'>按钮</button>
<button index='3'>按钮</button>
document.body.onclick = function(ev){
var target = ev.target,
targetDom = target.tagName
if(targetDom === 'BUTTON'){
var index = target.getAttribute('index');
console.log(`当前点击的是第${index}个`);
}
}
九、JSBench性能测试工具
image.png十一、变量局部化
- 变量局部化(全局、局部)
- 这样可以提高代码的执行效率(减少了数据访问时需要查找的路径)
- 数据的存储和读取
var i, str = ""
function packageDom() {
for (i = 0; i < 1000; i++) {
str += i
}
}
packageDom()
//--------------------------------------------
function packageDom() {
let str = ""
for (let i = 0; i < 1000; i++) {
str += i
}
}
packageDom();
十二、缓存数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="skip" class="skip"></div>
<script>
// 缓存数据:对于需要多次使用的数据进行提前保存,后续进行使用
var oBox = document.getElementById('skip')
function hasClassName(ele, cls) {
// 假设在当前的函数体当中需要对className的值进行多次使用,那么我们可以将它提前缓存起来
return ele.className === cls
}
console.log(hasClassName(oBox, 'skip'));
function hasClassName(ele, cls) {
var clsName = ele.className
// 假设在当前的函数体当中需要对className的值进行多次使用,那么我们可以将它提前缓存起来
return clsName === cls
}
console.log(hasClassName(oBox, 'skip'));
/**
* 01减少声明和语句数(语法 词法)
* 02缓存数据(作用域链查找变快)
*/
</script>
</body>
</html>
image.png
十三、减少访问层级
// 减少访问层级
function Person() {
this.name = 'fds'
this.age = 40
}
let p1 = new Person()
console.log(p1.name)
function Person() {
this.name = 'fds'
this.age = 40
this.getAge = function () {
return this.name
}
}
let p1 = new Person()
console.log(p1.getAge())
image.png
十四、防抖与节流
/**
* 为什么需要防抖和节流
* 在一些高频率事件触发的场景下我们不希望对应的事件处理函数多次执行
* 场景:
* 滚动事件
* 输入的模糊匹配
* 轮播图切换
* 点击操作
* ...
* 浏览器默认情况下都会有自己的监听事件建个(4~6ms)
* 如果检测到多次事件的监听执行,那么就会造成不必要的资源浪费
*
*
* 前置场景:页面上有一个按钮,可以连续多次点击
* 防抖:对于这个高频率的操作来说,我们只希望识别一次点击,可以认为是第一次或者最后一次
*
* 节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,
* 按照我们定义的频率减少触发的次数
*/
十五、防抖函数实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖函数的实现</title>
</head>
<body>
<button id="btn">点击</button>
<script>
var oBtn = document.getElementById('btn')
// oBtn.onclick = function(){
// console.log('点击了');
// }
/**
* handle 最终需要执行的事件监听
* wait 事件触发之后多久开始执行
* immediate 控制执行第一次还是最后一次,false执行最后一次
*/
function myDebounce(handle, wait, immediate) {
// 参数类型判断以及默认值处理
if (typeof handle !== 'function') throw new Error('handle must be an function')
if (typeof wait === 'undefined') wait = 300
if (typeof wait === 'boolean') {
immediate = wait
wait = 300
}
if (typeof immediate !== 'boolean') immediate = false
// 所谓的防抖效果我们想要实现的就是有一个"人"可以管理handle的执行次数
// 如果想要执行最后一次,那就意味着无论我们前面点击了多少次,前面的N-1都没有用
let timer = null
return function proxy(...args) {
let self = this,
init = immediate && !timer
clearTimeout(timer)
timer = setTimeout(() => {
timer = null;
!immediate ? handle.call(self, ...args) : null
}, wait)
// // 如果当前传递进来的是true 就表示我们需要立即执行
// // 如果想要实现只在第一次执行,那么可以添加上timer为null的判断
// // 因为只要timer为null就意味着没有第二次点击
init ? handle.call(self, ...args) : null
}
}
//定义事件执行函数
function btnClick(ev) {
console.log('点击了111', this, ev);
}
// 当点击按钮就会执行返回的proxy
oBtn.onclick = myDebounce(btnClick, 200, true)
</script>
</body>
</html>
十六、节流函数实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流函数的实现</title>
<style>
body {
height: 5000px;
}
</style>
</head>
<body>
<script>
// 节流:这里的节流指的就是在自定义的一段时间内让事件触发
function myThrottle(handle, wait) {
if (typeof handle !== 'function') throw new Error('handle must be an function')
if (typeof wait === 'undefined') wait = 300
// 定义变量记录上一次执行时的时间
let previous = 0
// 用它来管理定时器
let timer = null
return function proxy(...args) {
let now = new Date(); //定义变量记录当前执行的时刻时间点
let selt = this
let interval = wait - (now - previous)
if (interval <= 0) {
clearTimeout(timer)
timer = null
// 此时说明是一个非高频的操作,可以执行handle
handle.call(self, ...args)
previous = new Date();
} else if (!timer) {
// 此时说明操作发生在了我们定义的频次内,
setTimeout(() => {
// 这个操作只是将系统中的定时器清楚了,但是timer中的值还在
clearTimeout(timer)
timer = null
handle.call(self, ...args)
previous = new Date();
}, interval)
}
}
}
// 定义滚动事件监听
function scrollFn() {
console.log('滚动了');
}
// window.onscroll = scrollFn
window.onscroll = myThrottle(scrollFn, 400)
/**
* 01假设当前在5ms的时间点上执行了一次Proxy,我们就
* 可以用这个时间减去上次执行的时间0,此时就会有一个时间差
* 02前置条件:我们自己定义了一个wait,(假设为500)
* 03 wait - (now - previous)
* 04此时如果上述的计算结果是大于0 的就意味着当前的操作是一个
* 高频的触发,不去执行handle,如果结果小于等于0,就意味着
* 当前不是一个高频触发,可以直接执行handle
* 05此时我们就可以在500ms内想办法让所有高频操作在
* 将来都有一次执行就够了,不需要给每个高频操作都添加一个定时器
*/
</script>
</body>
</html>
十七、减少判断层级
function doSomething(part, chapter) {
const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
if (part) {
if (parts.includes(part)) {
console.log('属于当前课程');
if (chapter > 5) {
console.log('您需要提供VIP身份');
}
}
} else {
console.log('请确认模块信息');
}
}
doSomething('ES2016', 6)
// ------------------------------------------------------------
function doSomething1(part, chapter) {
const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
if (!part) {
console.log('请确认模块信息');
}
if (!parts.includes(part)) return
console.log('属于当前课程');
if (chapter > 5) {
console.log('您需要提供VIP身份');
}
}
doSomething1('ES2016', 6)
十八、减少循环体活动
var test = () => {
var i
var arr = ['fds', '456', 'fdsfdsfd']
for (i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
var test2 = () => {
var i
var arr = ['fds', '456', 'fdsfdsfd']
var len = arr.length
for (i = 0; i < len; i++) {
console.log(arr[i]);
}
}
var test2 = () => {
var arr = ['fds', '456', 'fdsfdsfd']
var len = arr.length
while (len--) {
console.log(arr[len]);
}
}
十九、字面量与构造式
var test3 = ()=>{
let obj = new Object()
obj.name = 'fds'
obj.age = 38
obj.slogan = 'just do it'
return obj
}
var test4 = ()=>{
let obj = {
name:'fds',
name:38,
slogan:'just do it'
}
return obj
}