一一八、项目难点总结

1. webpack打包原理

webpack详细原理 配置
webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖的文件,使用loaders来处理它们,最后打包成一个或多个浏览器可识别的js文件

module.exports={
    //入口文件的配置项
    entry:{},
    //出口文件的配置项
    output:{},
    //模块:例如解读CSS,图片如何转换,压缩
    module:{
    loader是用来对模块的源代码进行转换,而插件目的在于解决 loader 无法实现的其他事
基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换(或者直接在loader中引入现成的编译工具,例如sass-loader中就引入了node-sass将SCSS代码转换为CSS代码,再交由css-loader处理),然后交由下一环节进行处理,所有载入的模块最终都会经过moduleFactory处理,转成javascript可以识别和运行的代码,从而完成模块的集成。
	},
    //插件,用于生产模版和各项功能
    plugins:[],
    //配置webpack开发服务功能
    devServer:{}
}

//wepback 5 
官方描述:
	使用持久化缓存提高构建性能;
	使用更好的算法和默认值改进长期缓存(long-term caching);
	清理内部结构而不引入任何破坏性的变化;
	引入一些breaking changes,以便尽可能长的使用v5版本。
通俗版描述:
	减小打包后的文件体积
	按需加载支持文件名模式
	使用long-term caching解决生产环境下moduleIds & chunkIds变化的问题
	使用cache: {type: "filesystem"}配置实现持久化缓存,提高构建速度
	优化minSize&maxSize的配置方式
	Node.js polyfills 自动加载功能被移除

2. web worker

  • window.navigator.hardwareConcurrency计算cpu
  • 单例模式缓存每次生成的sheet实例
  • web worker 多线程计算
//主线程 main.js
var worker = new Worker("worker.js");
worker.onmessage = function(event){
    // 主线程收到子线程的消息
};
// 主线程向子线程发送消息
worker.postMessage({
    type: "start",
    value: 12345
});

//web worker.js
onmessage = function(event){
   // 收到
};
postMessage({
    type: "debug",
    message: "Starting processing..."
});
import SimpleWebWorker from 'simple-web-worker';
export default class WorkerQueue {
  constructor() {
    try {
      this.hardwareConcurrency = window.navigator.hardwareConcurrency;
    } catch (error) {
      console.log(
        'Set 4 Concurrency,because can`t get your hardwareConcurrency.'
      );
      this.concurrency = 4;
    }
    this.concurrency = 4;
    this._worker = SimpleWebWorker;
    this.workerCont = 0;
    this.queue = [];
  }
  push(fn, callback, ...args) {
    this.queue.push({ fn, callback, args });
    this.run();
  }
  run() {
    while (this.queue.length && this.concurrency > this.workerCont) {
      this.workerCont++;
      const { fn, callback, args } = this.queue.shift();
      this._worker
        .run(fn, args)
        .then((res) => {
          callback(res);
          this.workerCont--;
          this.run();
        })
        .catch((e) => {
          //   this.workerCont--;
          //   this.run();
          throw e;
        });
    }
  }
}

3. 性能优化

// var timing = window.performance.timing
// timing.domLoading  //浏览器开始解析 HTML 文档第一批收到的字节
// timing.domInteractive  // 浏览器完成解析并且所有 HTML 和 DOM 构建完毕
// timing.domContentLoadedEventStart //DOM 解析完成后,网页内资源加载开始的时间
// timing.domContentLoadedEventEnd // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
// timing.domComplete //网页上所有资源(图片等)下载完成,且准备就绪的时间
let timing = performance.timing,
     start = timing.navigationStart,
     dnsTime = 0,
     tcpTime = 0,
     firstPaintTime = 0,
     domRenderTime = 0,
     loadTime = 0;
//DNS解析时间
dnsTime = timing.domainLookupEnd - timing.domainLookupStart;
//TCP建立时间
tcpTime = timing.connectEnd - timing.connectStart;
//首屏时间
firstPaintTime = timing.responseStart - start;
//dom渲染完成时间
domRenderTime = timing.domContentLoadedEventEnd - start;
//页面onload时间
loadTime = timing.loadEventEnd - start;

  • 代码
1. 路由懒加载
2. 组件按需加载
3. 防抖节流
4. 重排重绘
5. 缓存
6. 预加载、预渲染
7. 图片优化
8. 长列表滚动到可视区域动态加载
9. SPA 页面采用keep-alive缓存组件
10. 骨架屏
11. 避免重定向
  • 浏览器+后端
12. http2
13. 减少http请求数量
14. gzip
15. cdn 
16. 缓存
17. ssr后端渲染, 减少前端渲染时间,就是一次性输出html内容,不用频繁调用ajax
  • wepack
18. speed-measure-webpack-plugin //速度分析
19. webpack-bundle-analyzer //体积分析
20. thread-loader(webpack4官方所推荐)和HappyPack //多进程/多实例构建 
21. uglifyjs-webpack-plugin //多进程并行压缩代码 
22. DllPlugin 和 DllReferencePlugin //预编译资源模块
23. babel-loader开启缓存 使用cache-loader 使用hard-source-webpack-plugin//利用缓存提升二次构建速度
24. babel-polyfill //动态 Polyfill 服务 
25. optimization.splitChunks代码分割
26. sourceMap优化
27. 使用cdn加载第三方模块

4. pdf 预览并加上水印

  • pdf js 预览
    PDFJS.getDocument({
      url: filePath,
      cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.2.228/cmaps/",
      cMapPacked: true
    }, null, null, this.progressCallback.bind(this)).then((pdf) => {
      this.setState({ pdf, containerWidth });
    });
  • 水印
  • 背景图实现全屏水印
  • canvas 生成水印 url 赋值给 css background 属性
    pointer-events 如果值为none,元素永远不会成为鼠标事件的 target。
    通过这个属性呢我们就可以实现我们之前的穿透水印遮罩的需求了。该属性也可用来提高滚动时的帧频。因为,当滚动时,鼠标悬停在某些元素上,则触发其上的hover效果,然而这些影响通常不被用户注意,并多半导致滚动出现问题。对 body 元素应用pointer-events:none,禁用了包括 hover 在内的鼠标事件,从而提高滚动性能。
/**
 * 先设置crossOrigin 后设置src 否则报错 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
 * @param config
 * @constructor
 * @author mjt
 */
export default function ImgWatermark(config = {}) {
  const img = new Image();
  img.crossOrigin = 'Anonymous';
  img.src = config.watermark_img_url;
  img.onload = function () {
    let canvas = document.createElement('canvas');
    let dropWidth = Math.floor(img.width / 3)
    let dropHeight = Math.floor(img.height / 4)
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = `${config.watermark_fontsize} Microsoft Yahei`;
    ctx.fillStyle = config.watermark_color;
    ctx.rotate(-(config.watermark_angle) * Math.PI / 180);
    for (let i = 0; i < dropHeight; i++) {
      for (let j = 0; j < dropWidth; j++) {
        ctx.fillText(config.watermark_txt, (dropWidth + config.watermark_x_space) * i, (dropHeight + config.watermark_y_space) * j);
      }
    }
    const base64Url = canvas.toDataURL();
    setTimeout(() => {
      const imgDom = document.querySelector(config.watermark_parent_node)
      imgDom && (imgDom.src = base64Url)
    }, 0)
  }
}
  • SVG 方式 (动态设置text 然后设置backgroundImage)

    支持 gif 图水印,不存在跨域问题,使用 repeat 属性,无插入 dom 过程,无性能问题;

5. 页面卡顿 fiber拆分计算任务,匀到多帧

React是怎么做的?
React 在页面更新时,会自顶向下计算 virtual dom 上的不同处。如果计算任务耗时过长,渲染线程在 16 ms 中无法执行任务,页面会出现掉帧/卡死现象。
React Fiber 解决这个问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务。每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续

按照 fiber 的设计思想,拆分计算任务,来解决页面卡死的问题

    const repeat = (str, count) => {
        let maxLoopCount = 999999;
        async function runTask(count, cb) {
            let taskStr = '';
            let taskResult = new Promise((rs) => {
            	// window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
                window.requestAnimationFrame(() => {
                    for (let i = 0; i < count && i < maxLoopCount; i++) {
                        taskStr += str;
                    }
                    // 拆分任务 每次执行的任务不大于maxLoopCount
                    if (count > maxLoopCount) {
                        runTask(count - maxLoopCount, (restValue) => {
                            cb && cb(taskStr + restValue);
                            rs(taskStr + restValue);
                        });
                    } else {
                        cb && cb(taskStr);
                        rs(taskStr);
                    }
                });
            });
            return taskResult;
        }
        return runTask(count);
    };
    repeat('1', 999999999).then((result) => console.log(result));

6. PWA+直出+预加载(H5秒开)

计算首屏加载时间

<script type="text/javascript">
    // 不兼容performance.timing 的浏览器,如IE8
    window.pageStartTime = Date.now();
</script>
 window.onload = function(){
	window.firstPaint = Date.now();
}
可使用 Performance API 时
白屏时间 = firstPaint - performance.timing.navigationStart;

不可使用 Performance API 时
白屏时间 = firstPaint - pageStartTime;

不管是离线包技术,还是 webview 代理请求,都是对前端侵入非常大的,pwa 作为 web 标准,能够通过纯 web 的方案去加速和优化加载性能。

首先,pwa 的能够通过 cacheStorage 缓存普通的图片、js、css 资源。另一方面,在传统的 http cache 中,我们一般不会缓存 html,这是因为页面一旦设置了过长的 max-age,在浏览器缓存过期时间内,用户看到的永远将是旧的。

如果使用了 pwa 的 html 页面,能否直接缓存呢?由于 pwa 可精细化控制缓存,答案是可以的。

对于直出 html,我们可以配合 pwa,将从后台直出的文件,缓存到 cacheStorage,在下一次请求时,优先从本地缓存中获取,同时发起网络请求更新本地 html 文件。

但是在 hybrid 的 h5 应用,第一次启动的加载资源仍然费时,我们可以通过 app 端上支持预加载一个 javascript 脚本,拉取需要 PWA 缓存的页面,可以提前完成缓存。

对于非直出的页面,我们仍然无法避免浏览器渲染 html 时间的问题,应该如何减少这里的时间呢?

这里明确两个点,第一次永远只能靠提前加载,所以上面的借助端上预加载脚本仍然生效;第二点非直出页面,每个页面需要有独一无二的标记,比如 hash。浏览器获取到数据,并且渲染好的 html,能够通过 outerHTML 方法,将 html 页面缓存到 cacheStorage 中,第二次访问优先从本地获取,同时发起 html 请求,通过对比其中唯一标识的差异,决定是否需要更新。
在这里插入图片描述
pwa 一系列方案替代离线包策略,带来的好处是,属于 web 标准,适用于普通能够支持 service-worker 的 H5 页面。在允许兼容问题允许的情况下,建议主加。
总结:

  • 在整个链路中减少中间环节。比如将串行改并行,包括小程序内部执行机制。
  • 尽可能的预加载、预执行。比如从数据预取,到页面预取渲染等。

7. react-reactivity

  1. 通过@vue/reactivity定义响应式state
  2. 在父组件通过Provider将store传递下去
  3. 子组件通过useStore传入一个schedule(store)方法
  4. react-reactivity先通过useReducer在当前组件中注册一个强制更新的函数
  5. 通过useContext读取用户从Provider中传入的store
  6. 再通过Vue的effect去帮我们执行selector(store),并且指定scheduler为forceUpdate,这样就完成了依赖收集
  7. 响应式数据更新后,触发effect调用forceUpdate,完成页面渲染

8. 微前端

“取巧”实现一个微前端沙箱
qiankun源码解析
微前端原理+实践篇
微前端通信篇
沙箱
在这里插入图片描述

genSandbox 内部的沙箱主要是通过是否支持 window.Proxy 分为 LegacySandbox 和 SnapshotSandbox 两种

  1. LegacySandbox (LegacySandbox 的沙箱隔离机制利用快照模式实现)
    • LegacySandbox 的沙箱隔离是通过激活沙箱时还原子应用状态,卸载时还原主应用状态(子应用挂载前的全局状态)实现的
    • 在激活沙箱时,沙箱会通过 currentUpdatedPropsValueMap 查询到子应用的独立状态池(沙箱可能会激活多次,这里是沙箱曾经激活期间被修改的全局变量),然后还原子应用状态。
    • 在关闭沙箱时,通过 addedPropsMapInSandbox 删除在沙箱运行期间新增的全局变量,通过 modifiedPropsOriginalValueMapInSandbox 还原沙箱运行期间被修改的全局变量,从而还原到子应用挂载前的状态。
  2. ProxySandbox(ProxySandbox 是一种新的沙箱模式,目前用于多实例模式的状态隔离。在稳定后以后可能会成为 单实例沙箱)
    • 当调用 set 向子应用 proxy/window 对象设置属性时,所有的属性设置和更新都会命中 updateValueMap,存储在 updateValueMap 集合中(第 38 行),从而避免对 window 对象产生影响(旧版本则是通过 diff 算法还原 window 对象状态快照,子应用之间的状态是隔离的,而父子应用之间 window 对象会有污染)。
    • 当调用 get 从子应用 proxy/window 对象取值时,会优先从子应用的沙箱状态池 updateValueMap 中取值,如果没有命中才从主应用的 window 对象中取值(第 49 行)。对于非构造函数的取值将会对 this 指针绑定到 window 对象后,再返回函数。
    • 如此一来,ProxySandbox 沙箱应用之间的隔离就完成了,所有子应用对 proxy/window 对象值的存取都受到了控制。设置值只会作用在沙箱内部的 updateValueMap 集合上,取值也是优先取子应用独立状态池(updateValueMap)中的值,没有找到的话,再从 proxy/window 对象中取值。
      相比较而言,ProxySandbox 是最完备的沙箱模式,完全隔离了对 window 对象的操作,也解决了快照模式中子应用运行期间仍然会对 window 造成污染的问题。
  3. SnapshotSandbox(在不支持 window.Proxy 属性时,将会使用 SnapshotSandbox 沙箱)
    • SnapshotSandbox 的沙箱环境主要是通过激活时记录 window 状态快照,在关闭时通过快照还原 window 对象来实现的。
    • 在沙箱激活时,会先给当前 window 对象打一个快照,记录沙箱激活前的状态(第 38~40 行)。打完快照后,函数内部将 window 状态通过 modifyPropsMap 记录还原到上次的沙箱运行环境,也就是还原沙箱激活期间(历史记录)修改过的 window 属性。
    • 在沙箱关闭时,调用 inactive 函数,在沙箱关闭前通过遍历比较每一个属性,将被改变的 window 对象属性值(第 54 行)记录在 modifyPropsMap 集合中。在记录了 modifyPropsMap 后,将 window 对象通过快照 windowSnapshot 还原到被沙箱激活前的状态(第 55 行),相当于是将子应用运行期间对 window 造成的污染全部清除。
    • SnapshotSandbox 沙箱就是利用快照实现了对 window 对象状态隔离的管理。相比较 ProxySandbox 而言,在子应用激活期间,SnapshotSandbox 将会对 window 对象造成污染,属于一个对不支持 Proxy 属性的浏览器的向下兼容方案。

9. 架构

在这里插入图片描述

  • 前端性能优化(依托性能监控平台,异常报错平台)
  • 前端工具平台
  • 图标库平台
  • css样式平台
  • 组件库(原生、vue/react)
  • 组件调试平台
  • 组件文档平台
  • 前端业务开发工具包(根据公司实际业务)
  • 前端脚手架自动化页面配置平台优化(依托性能监控平台,异常报错平台)
  • 代码管理平台(gitlab、代码审核…)
  • 测试、生产发布平台
  • 前端团队建设(一部分主管职责)
  • 招聘常
  • 规业务技术选型,新业务技术选型和探索

10、小程序自适应

百度智能小程序,是 H5 和 Native 技术结合的产物,
包括 API、组件、框架、动态库组件、服务端API 等多种类别的1000多项能力,具有多端、多宿主运行的特点。

车载小程序自适应

11、一语直达vsl

                  // 监听body
                    const targetNode = document.body;

                    // 观察body下的任意节点发生变化
                    const config = {
                        attributes: true,
                        childList: true,
                        subtree: true
                    };
                    // 当观察到变动时执行的回调函数
                    const callback = debounce(mutationsList => {
                        if (!this.lifeCycle.disposed && document.visibilityState === 'visible') {
                        	//注册检测到的语音指令
                            this.registerVSLComponent();
                        }
                    }, 1000);
                    // 创建一个观察器实例并传入回调函数
                    const observer = new MutationObserver(callback);
                    observer.observe(targetNode, config);

12、全埋点

1.可视化埋点

通过可视化交互的手段,代替代码埋点。将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个可视化系统,可以在业务代码中自定义的增加埋点事件等等,最后输出的代码耦合了业务代码和埋点代码。例如:GrowingIO、
可视化埋点实际上是用一个系统来实现手动插入代码埋点的过程。

优点

  • 埋点只需业务同学接入,无需开发支持
  • 解决了代码埋点的工作量大和更新代价大问题

缺点

  • 无法做到自定义获取数据,可视化埋点覆盖的功能有限
  • 开发周期长,难度大
  1. 无埋点

前端的任意一个事件都被绑定一个标识,所有的事件都记录下来。上传记录文件,配合文件解析,解析出来我们想要的数据传递给server端。
比如从页面的js代码中,找出dom上被绑定的事件,然后进行全埋点。

优点

  • 由于采集的是全量数据,所以产品迭代过程中是不需要关注埋点逻辑的,也不会出现漏埋、误埋等现象
  • 减少了沟通成本

缺点

  • 无埋点采集全量数据,给数据传输和服务器增加压力
  • 无法灵活的定制各个事件所需要上传的数据
  1. 配置化埋点

结合无埋点+代码埋点,通过配置文件或者特定标示指定需要埋点的元素或者方法,当定位到用户点击的元素或者执行的方法在配置文件中则上传埋点。

优点

  • 可灵活的定制各个事件所需要上传的数据
  • 减轻服务器压力
  • 维护成本低

缺点

  • 需求开发支持配置
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值