【面筋烧烤手册】20210326

本文深入探讨前端性能优化策略,包括首屏时间、异步加载、资源压缩等,以及如何分析和避免JavaScript内存泄漏。同时,介绍了HTTP请求的处理,如跨域解决方案、全局请求管理和XMLHttpRequest的hook实现。此外,还涵盖了EventBus的使用和01背包问题的动态规划解法。
摘要由CSDN通过智能技术生成

一、前端加载优化相关

首屏时间

首次可交互时间

首次有意义内容渲染时间

只请求当前需要的资源

  • 异步加载
  • 懒加载
  • polyfill 比如Array.flat方法的往前面的语法版本转义
    (polyfill url-builder)

缩减资源体积

  • 打包压缩 webpack 4
  • gzip on打开 1.2M-300k
  • 图片格式的优化,分辨率和设备适配(根据屏幕分辨率展示不同分辨率的图片),压缩(tinypng),webp的图片
  • 尽量控制cookie大小 request header, cookie

时序优化

  • js promise.all 并行执行promise的方法(没有相互关联没有依赖关系的请求)
  • ssr服务端渲染,某些用户看到的是有相同之处的,通过服务端渲染的时候根据不同的特征渲染出不同的页面做缓存,第二批用户进来的时候就拿缓存了。
  • prefetch,prerender,preload
    <link rel="dns-prefetch" href="xxxx.com>DNS与解析
    <link rel="preconnect" href="xxxx.com>预连接
    <link rel="preload" as="image" href="xxxx.com/p.png>更重要的图片预加载

合理利用缓存

  • CDN:非常大的流量下,cdn预热(预先分发),cdn刷新(强制回源),douyin.com,cdn-douyin.com(减少消耗)

如果一段js执行时间很长,怎么分析

  • 装饰器
  • console.time记录开始时间
  • 执行原有函数
  • 记录函数执行完成的时间
    在这里插入图片描述

在这里插入图片描述

场景题

阿里云oss支持通过链接后面拼接参数来做图片的格式转换,尝试写一下,把任意图片格式转换为webp,需要注意什么?

判断浏览器支不支持格式

  • 创建一个canvas元素,能很简单转换成baseurl64
  • 转成baseurl64,开头能够带上图片的格式
    在这里插入图片描述

如果有巨量的图片要展示,除了懒加载的方式,有没有什么其他方法限制一下同时加载图片数量

代码题

实现promise的并发控制

在这里插入图片描述

在这里插入图片描述

二、前端的内存处理

1、内存的生命周期

  • 内存分配:声明变量、函数、对象的时候,js会自动分配内存
  • 内存使用:调用的时候,使用的时候
  • 内存回收:垃圾回收机制

2、js中的垃圾回收机制

垃圾回收就是当你的内存使用完成了之后,如果使用垃圾回收机制判断内存没有使用了,就会把这块内存权限收回来,不会给原来的变量了

引用计数垃圾回收

a对象对b对象有访问权限,那么成为a引用b对象。
如果没有任何对象引用b对象,就会回收。

  • 缺陷:循环引用,a引用了b,b引用了a,但都没有被其他对象引用,就会内存泄漏

标记清除算法

无法拿到的对象,从根对象进行扫描,如果无法达到了,就说明需要进行回收了

  1. 在运行的时候给存储在内存的所有变量加上标记
  2. 从根部触发,能触及的对象,把标记清除
  3. 哪些有标记的就被视为即将要删除的变量
  4. 内存回收操作

js中,常见的内存泄漏

全局变量

window.a === ‘www’
window.a === null

未被清除的定时器和回调

要清除

在这里插入图片描述

闭包

内部函数有权访问包含它的函数的变量

DOM引用

const elements = {
	image: document.getElementById('image')
}
//即使移除了 之前elements的引用还是存在
document.body.removeChild(document.getElementById('image'))

elements.image = null

4、避免内存泄漏

  • 减少不必要的全局变量
  • 使用完数据后,及时解除引用

实现sizeOf函数,传入一个参数object,计算这个object占用了多少bytes

number: 64位存储,8字节 byte
string: 每个长度2字节
boolean:4字节

在这里插入图片描述

在这里插入图片描述

三、前端HTTP请求相关

1、平时解决跨域问题的

  • jsonp
  • cors 服务端或者nginx设置的头,允许访问的可行域和方法
  • node 正向代理, /api -> 同域的node服务 -> /api -> 前端
  • nginx 反向代理,proxy_pass /api -> /same/api
  • img标签

2、有做过全局的请求处理吗?比如统一处理登陆态?统一处理全局错误?

  • axios 适配器 拦截器

3、代码题,给xhr添加hook,实现在各个阶段打日志?

new XMLHTTPRequest()
open onreadystatechange onload onerror 打日志
110 32

大体实现思路:

保留原生xhr对象
将XMLHttpRequest对象置为新的对象
对xhr对象的方法重写后放入新的对象中
对属性进行重写 然后放到新的对象中

class XhrHook {
    constructor(beforeHooks = {}, afterHooks = {}) {
        this.XHR = window.XMLHttpRequest; // 保存原有的xhr
        this.beforeHooks = beforeHooks;
        this.afterHooks = afterHooks;
        this.init()
    }

    init() {
        let _this = this
        // 像这样,把XMLHttpRequest赋值为一个新的函数,用户全局访问到的就是我们自己写的新的函数。
        window.XMLHttpRequest = function () {
            this._xhr = new _this.XHR();    // 在实例上挂一个保留的原生的xhr实例 
            _this.overwrite(this)
        }
    }

    /**重写方法和属性 */
    overwrite(proxyXHR) {
        for (let key in proxyXHR._xhr) {
            if (typeof proxyXHR._xhr[key] === 'function') {
                this.overwriteMethod(key, proxyXHR);
                continue;
            }
            this.overwriteAttributes(key, proxyXHR);
        }
    }

    /**重写方法 */
    overwriteMethod(key, proxyXHR) {
        let beforeHooks = this.beforeHooks;
        let afterHooks = this.afterHooks;
        proxyXHR[key] = (...args) => {
            // 拦截
            if (beforeHooks[key]) {
                const res = beforeHooks[key].apply(proxyXHR, args);
                if (res === false) {
                    return;
                }
            }

            // 执行原生xhr实例中对应的方法
            const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);

            // 原生方法执行完后执行的钩子
            afterHooks[key] && afterHooks[key].call(proxyXHR._xhr, res);

            return res
        }
    }

    /**重写属性 */
    overwriteAttributes(key, proxyXHR) {
        Object.defineProperty(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
    }

    setProperyDescriptor(key, proxyXHR) {
        let obj = Object.create(null);
        let _this = this

        obj.set = function (val) {
            // 如果属性不是on开头,直接挂载
            if (!key.startsWith('on')) {
                proxyXHR['__' + key] = val;
                return
            }

            // 如果是on开头,查看是否有要执行的钩子
            if (_this.beforeHooks[key]) {
                // 包装一下,先执行钩子,再执行函数
                this._xhr[key] = function (...args) {
                    _this.beforeHooks[key].call(proxyXHR);
                    val.apply(proxyXHR, args);
                }
                return;
            }

            // 如果没有,直接挂载
            this._xhr[key] = val;
        }

        obj.get = function () {
            // 返回重写的属性
            return proxyXHR['__' + key] || this._xhr[key];
        }

        return obj;
    }
}

// 代码测试
new XhrHook({
    open: function () {
        console.log('open')
    },
    onload: function () {
        console.log('onload')
    },
    onreadystatechange: function () {
        console.log('onreadystatechange')
    },
    onerror: function () {
        console.log('onerror')
    }
})

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com', true);
xhr.send();
xhr.onreadystatechange = function (res) {
    console.log(res)
}
xhr.onerror = function (e) {
    console.log(e)
}


四、event bus

  • on 监听

  • emit 传入参数、触发属性

  • off 移除监听的回调

  • once 只触发一次

除了emit以外的参数都是event和callback

on 监听

  • 如果没有事件的话,处理成数组类型
  • 给event push一个callback
  • return this 为了链式调用

off 移除监听

  • 如果没callback 就移除—null
  • 如果有,就把传进来的event给去掉,filter
  • return this 为了链式调用

once

  • 执行一次的方法然后取消掉
  • 移除掉事件监听 然后再执行callback

emit

  • 默认是一个数组
  • 如果没有
  • 如果有 就遍历调用

要设置最大监听数呢???

直接在on里面拦截最大监听

  • 如果不等于初始值,并且大于等于最大监听数,就。。。。return掉this
class EventEmitter {
    constructor(){
        this.events = {}; // new Map()

        //可以设置最大监听数
        this.maxListeners = maxListeners || Infinity;
    }
     
    emit(event, ...args) {
        const cbs = this.events[event];

        if(!cbs){
            console.log("没有这个事件");
            return this;
        }

        cbs.forEach(cb => cb.apply(this, args));
        return this;
    }

    on(event, cb) {
        if (!this.events[event]) {
            this.events[event] = [];
        }

        //TODO:拦截最大监听
        if (this.maxListeners !== Infinity && this.events[event].length >= this.maxListeners){
            console.warn(`当前事件${event}超过最大监听数`);
            return this;
        }

        this.events[event].push(cb);
        return this;
    }

    once(event, cb) {
        const func = (...args) => {
            this.off(event, func);
            cb.apply(this, args);
        }
        this.on(event, func);

        return this;
    }

    off(event, cb) {
        if (!cb) {
            this.events[event] = null; // 移除监听
        } else {
            this.events[event] = this.events[event].filter(item => item!==cb);
        }
        return this; // 如果要链式调用 比如 event.on('add', add).emit()
    }
}

const add = (a, b) => console.log(a + b);
const log = (...args) => console.log(...args);
const event = new EventEmitter();

event.on('add', add);
event.on('log', log);
event.emit('add', 1, 2); // 3
event.emit('log', "hi~"); // hi~
event.off('add');
event.emit('add', 1, 2); // Error add event is not registered
event.once('once', add);
event.emit('once', 1, 2); // 3
event.emit('once', 1, 2); 
event.emit('once', 1, 2); 

五、01背包

在这里插入图片描述

01背包问题

给你一个可装载重量为W的背包和N个物品,每个物品有重量和价值两个属性,其中第i个物品的重量为wt[i],价值为val[i],现在让你用这个背包装物品,最多能装的价值是多少?

举个简单的例子,输入如下:

N = 3(3个物品) , W= 4(背包用量4)
wt = [2, 1, 3]
val = [4, 2, 3]
算法返回6,选择前两件物品装进背包,总重量3小于W,可以获得最大价值6

动态规划解题思路

一、状态和选择

可选物品
背包的容量限制

状态有2个
1、可选择的物品
2、背包的容量

所以选择有两个
1、装进背包
2、不装进背包

for 状态1 in 状态1的所有取值
    for 状态2 in 状态2的所有取值
        dp[状态1][状态2] = 选择(选择1,选择2)

二、明确dp数组的定义
状态有两个,二维dp数组

dp[i][w] = 对于前i个物品 当前背包的容量为w 这种情况下可以装的最大价值是dp[i][w]

dp[0][w] = 0
dp[i][0] = 0

dp[3][5] = 6,对于所有物品,如果只对前三个进行选择,当背包容量为5的时候,最多能装下的价值是6

let dp[N+1][W+1]
dp[0][xxxx] = 0;
dp[xxx][0] = 0;
for 状态1 in 状态1的所有取值
    for 状态2 in 状态2的所有取值
        dp[状态1][状态2] = 选择(把物品i装进背包, 不装进背包)

return dp[N][W]

三、状态转移方程怎么写?
dp[i][w] 对于前i个物品,当前背包的容量为w,这种情况下可以装下的最大价值是dp[i][w]

1、如果没有把第i个物品装进背包
dp[i][w] = dp[i-1][w]

2、如果把第i个物品装进背包了

第i个物品的价值是val[i-1]
第i个物品的重量是wt[i-1]

dp[i][w] = dp[i-1][w - wt[i-1]] + val[i-1];

let dp[N+1][W+1]
dp[0][xxxx] = 0;
dp[xxx][0] = 0;
for i in [i...N]
    for w in [1...W]
        dp[i][w] = Math.max(dp[i-1][w],dp[i-1][w-wt[i-1] + val[i-1]);

return dp[N][W]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值