前端开发那些事儿大前端

Javascript 性能优化02

2021-06-01  本文已影响0人  丽__
一、V8 引擎执行流程

V8 引擎是浏览器渲染引擎里的js执行代码的组成部分

//声明时未被调用,因此会被认为是不被执行的代码,进行预解析
function foo(){
  console.log('foo');
}

//声明时未调用,因此会被认为是不被执行的代码,进行预解析
function fn(){}

//函数立即执行,只进行一次全量解析
(function bar(){
  console.log('bar');
})()

//执行foo,那么需要重新对foo函数进行全量解析,此时foo函数被解析了两次
foo();
二、堆栈操作

堆栈准备

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)
六、闭包与垃圾回收
七、循环添加事件实现
<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性能测试工具

https://jsbench.me/

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
}
上一篇 下一篇

猜你喜欢

热点阅读