vue相关
react相关
日记杂七乱八
性能优化--1.缓存
性能优化--2.网络篇
性能优化--3.渲染篇
性能优化--4.应用篇
html css js img 最简单的压缩合并 YUI Compressor tinyPNG https://zh.recompressor.com/
雅虎军规
服务器端的:etag gzip
第三方库代码:资源压缩 强缓etag jq vue
渐进式图片加载 首先加载低质量或模糊的图像,然后在页面继续加载时,使用 Guy Podjarny 提出的 LQIP (Low Quality Image Placeholders) technique(低质量图像占位符)技术替换它们的清晰版本
业务代码:大容量的解决方案 -> 异步
web本地存储 -localStorage 5M内存,but超过2.5M某些机型会卡顿
移动端测试网速方案(根据网速判断是加载二倍图还是三杯图)
let img = new Image(); const size = 1 img.src = '' // 假如是个1k的图片 let start = Date.now() img.onLoad = function() { let end = Date.now() let speed = size / (end - start) / 1000 网速 if( ) }
cdn 1.找到离你最近
缓存静态资源
不用带那么多cookie
并发数量有限制 不放一个cdn
dns预解析 dns-prefetch
DNS 请求需要的带宽非常小,但是延迟却有点高,这点在手机网络上特别明显。预读取 DNS 能让延迟明显减少一些,例如用户点击链接时。在某些情况下,延迟能减少一秒钟。
在某些浏览器中这个预读取的行为将会与页面实际内容并行发生(而不是串行)。正因如此,某些高延迟的域名的解析过程才不会卡住资源的加载。
这样可以极大的加速(尤其是移动网络环境下)页面的加载。在某些图片较多的页面中,在发起图片加载请求之前预先把域名解析好将会有至少 5% 的图片加载速度提升 <meta http-equiv="x-dns-prefetch-control" content="on"> <link href='j1.58cdn.com' rel='dns-prefetch'>
<meta http-equiv="x-dns-prefetch-control" content="on"> <link href='j1.58cdn.com' rel='dns-prefetch'>
preconnect预链接 浏览器要建立一个连接,一般需要经过DNS查找,TCP三次握手和TLS协商(如果是https的话),这些过程都是需要相当的耗时的,所以preconnect,就是一项使浏览器能够预先建立一个连接,等真正需要加载资源的时候就能够直接请求了。
<link rel="preconnect" href="//example.com"> <link rel="preconnect" href="//cdn.example.com" crossorigin>
/prefetch/preload/
测网速 判断 然后 2倍图3倍图 去掉无用功能 只留核心组件
- **uglifyjs-webpack-plugin** or **new webpack.optimize.UglifyJsPlugin** - **webpack-parallel-uglify-plugin** 多进程压缩文件,类似happyPack ``` const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); new ParallelUglifyPlugin({ // 传递给 UglifyJS 的参数 uglifyJS: { output: { // 最紧凑的输出 beautify: false, // 删除所有的注释 comments: false, }, compress: { // 在UglifyJs删除没有用到的代码时不输出警告 warnings: false, // 删除所有的 `console` 语句,可以兼容ie浏览器 drop_console: true, // 内嵌定义了但是只用到一次的变量 collapse_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值 reduce_vars: true, } }, }), ```
``` const ExtractTextPlugin = require('extract-text-webpack-plugin'); rules: [ { test: /\.css/, // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ // 通过 minimize 选项压缩 CSS 代码 use: ['css-loader?minimize'] }), }, ] ```
- 配置**loader 缓存** 以及**include exclude** ``` rules: [ { // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能 test: /\.js$/, // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启 use: ['babel-loader?cacheDirectory'], // 只对项目根目录下的 src 目录中的文件采用 babel-loader include: path.resolve(__dirname, 'src'), }, ] ``` - 配置 **resolve.modules**(resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。) ``` module.exports = { resolve: { // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 modules: [path.resolve(__dirname, 'node_modules')] }, }; ``` - 配置**resolve.mainFields** 字段 (慎用 除非你知道你用的所有第三方模块的入口文件描述) ``` 第三方包可能有根据不同的运行环境有多个入口文件 为了减少搜索步骤,在你明确第三方模块的入口文件描述字段时,你可以把它设置的尽量少。 resolve: { // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤 mainFields: ['main'], }, ``` - 配置 **resove.alias** ``` resolve: { // 使用 alias 把导入 react 的语句换成直接使用单独完整的 react.min.js 文件, // 减少耗时的递归解析操作 alias: { 'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'), }
}, ``` - 配置 resolve.extensions (配置后缀默认 ['.js','.json']) 尽可能较少默认后缀 频繁使用的放前面 等
``` externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'axios': 'axios' } ```
业界比较成熟的做法: - 针对HTML 不开启缓存,放到服务器上而不是CDN,同时关闭自己服务器上的缓存,服务器会提供HTML和接口数据 - 针对静态JS CSS IMAGE等,开启CDN和缓存,同时文件内容带上hash - 同时HTML进入资源地址也要改成CDN的地址 带上hash 之前的相对路径都改成了指向CDN服务的结对路径 - 同一时刻针对同一个域名的资源并行请求是有限制的话,所以会把js css img 分别放到3个cdn下 js.cdn.com css.cdn.com img.cdn.com - 当然多域名 又会增加域名解析时间。 可以通过 **CDN预解析** 较少解析带来的延迟 webpack 接入cdn ``` const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const {WebPlugin} = require('web-webpack-plugin'); module.exports = {
... output: { // 给输出的 JavaScript 文件名称加上 Hash 值 filename: '[name][chunkhash:8].js', path: path.resolve(__dirname, './dist'), // 指定存放 JavaScript 文件的 CDN 目录 URL publicPath: '//js.cdn.com/id/', }, module: { rules: [ { // 增加对 CSS 文件的支持 test: /.css/, // 提取出 Chunk 中的 CSS 代码到单独的文件中 use: ExtractTextPlugin.extract({ // 压缩 CSS 代码 use: ['css-loader?minimize'], // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL publicPath : '//img.cdn.com/id/' }), }, { // 增加对 PNG 文件的支持 test: /.png/, // 给输出的 PNG 文件名称加上 Hash 值 use: ['file-loader?name=[name][hash:8].[ext]'], }, ... ] }, plugins: [ new WebPlugin({ template: './template.html', filename: 'index.html', // 指定存放 CSS 文件的 CDN 目录 URL stylePublicPath: '//css.cdn.com/id/', }), new ExtractTextPlugin({ // 给输出的 CSS 文件名称加上 Hash 值 filename: [name]_[contenthash:8].css, }), //keep module.id stable when vendor modules does not change new webpack.HashedModuleIdsPlugin(), ], };
[name]_[contenthash:8].css
``` 我一般是通过loader来配置图片的cdn ``` 区分环境 生产环境下加载cdn图片,并且启用图片压缩,开发环境下加载本地图片 const ISPROD = process.env.NODE_ENV === 'production' const IMGURL = '[name].[ext]' const IMAGELOADER = ISPROD ? `image-webpack-loader?{mozjpeg: {progressive: true,quality: 65},pngquant:{quality: "55-80", speed: 4}}` : null const IMG_PUBLICPATH = '//img.58cdn.com.cn/crop/baseteam/yunying/pc/gzpp/img/' const IMG_QUERY = ISPROD ? `&name=${IMGURL}&outputPath=/img&publicPath=${IMG_PUBLICPATH}` : `&name=${DEVIMGURL}` { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [ `url-loader?limit=10000${IMG_QUERY}`, IMAGELOADER, ] }, ```
- 区别 CommonsChunkPlugin:webpack每次打包实际还是会处理这些第三方库,但是打包之后能和第三方库和我们业务代码分开,DLLPlugin 可提前把第三方库完全分开,每次只打包自己业务代码 - 区别 externals:
一般策略是 1. 第三方基本库放到一个js中(为了缓存) 2. 剔除第三方基本库公共部分放一个js 3. 每个页面自己的js
- 第三方采用的commonJS是无效的 因为 ES6 模块化语法是静态的(导入导出语句中的路径必须是静态的字符串,而且不能放入其它代码块中),这让 Webpack 可以简单的分析出哪些 export 的被 import 过了。 如果你采用 ES5 中的模块化,例如 module.export={...}、require(x+y)、if(x){require('./util')},Webpack 无法分析出哪些代码可以剔除。 - 一般第三方库会有两个入口文件 ``` 如redux package.json { "main": "lib/index.js", // 指明采用 CommonJS 模块化的代码入口 "jsnext:main": "es/index.js" // 指明采用 ES6 模块化的代码入口 (社区约定) } 1. webpack约定优先入口文件 resolve: { mainFields: ['jsnext:main', 'browser', 'main'] }, 2. 修改.babelrc 让其保留 ES6 模块化语句 { "presets": [ [ "env", { "modules": false //关闭babel模块转化功能 } ] ] } 3. webpack只是指出那些函数没用上,还要通过UglifyJS 去剔除掉无用的代码 ```
好处 - 代码体积更小,因为函数申明语句会产生大量代码; - 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。 - 前提: **tree shake**一样源码必须采用 ES6 模块化语句和 - [scope hoisting原理](#scopehositing) - 分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。
module.exports = { resolve: { mainFields: ['jsnext:main', 'browser', 'main'] }, plugins: [ new webpack.optimize.ModuleConcatenationPlugin(), ], };
- 在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的 JavaScript 代码。 实际上 Prepack 就是一个部分求值器,编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值。 - 通过在编译阶段预先执行了源码得到执行结果 prepack-webpack-plugin - [prepack运行原理](#prepack) - 通过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码; - Prepack 实现了一个 JavaScript 解释器,用于执行源码。借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程中的结果返回到输出中。 - prepack目前技术不成熟 慎用
- 构建时优化,任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。 - [hanppyPack原理]():把 Loader 对文件的转换操作部分任务分解到多个进程去并行处理,从而减少了总的构建时间。把 Loader 处理的文件都先交给了 happypack/loader 去处理,收集到了这些文件的处理权后 HappyPack 就好统一分配了。 1. 每通过 new HappyPack() 实例化一个 HappyPack 其实就是告诉 HappyPack 核心调度器如何通过一系列 Loader 去转换一类文件,并且可以指定如何给这类转换操作分配子进程。 2. 核心调度器的逻辑代码在主进程中,也就是运行着 Webpack 的进程中,核心调度器会把一个个任务分配给当前空闲的子进程,子进程处理完毕后把结果发送给核心调度器,它们之间的数据交换是通过进程间通信 API 实现的。 3. 核心调度器收到来自子进程处理完毕的结果后会通知 Webpack 该文件处理完毕。 ``` const HappyPack = require('happypack'); // 构造出共享进程池,进程池中包含5个子进程 const happyThreadPool = HappyPack.ThreadPool({ size: 5 }); module.exports = { module: { rules: [ { test: /\.js$/, // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例 use: ['happypack/loader?id=babel'], exclude: path.resolve(__dirname, 'node_modules'), }, { // 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例 test: /\.css$/, use: ExtractTextPlugin.extract({ use: ['happypack/loader?id=css'], }), }, ] }, plugins: [ new HappyPack({ // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件 id: 'babel', // 如何处理 .js 文件,用法和 Loader 配置中一样 loaders: ['babel-loader?cacheDirectory'], // 使用共享进程池中的子进程去处理任务 threadPool: happyThreadPool, }), new HappyPack({ id: 'css', // 如何处理 .css 文件,用法和 Loader 配置中一样 loaders: ['css-loader'], // 使用共享进程池中的子进程去处理任务 threadPool: happyThreadPool, }), ], }; ```
- [webopack4已支持](https://webpack.docschina.org/guides/code-splitting/#prefetching-preloading-modules) 但是4.6之后才支持 ``` import(/* webpackPreload: true */ 'ChartingLibrary'); import(/* webpackPrefetch: true */ 'LoginModal'); ``` - 或借用插件 1. [webpack插件resource-hints-webpack-plugin](https://github.com/jantimon/resource-hints-webpack-plugin) similarly no async chunk support 2. [preload-webpack-plugin](https://github.com/vuejs/preload-webpack-plugin) 3. [[译]Preload,Prefetch 和它们在 Chrome 之中的优先级](https://juejin.im/post/58e8acf10ce46300585a7a42?utm_source=gold-miner&utm_medium=readme&utm_campaign=github)
``` import(/* webpackChunkName: "show" */ './show') ``` **webpack支持import语法的支持**,当执行到import时 - 以 ./show.js 为入口新生成一个 Chunk; - 当代码执行到 import 所在语句时才会去加载由 Chunk 对应生成的文件。 - import 返回一个 Promise,当文件加载成功时可以在 Promise 的 then 方法中获取到 show.js 导出的内容。 - /* webpackChunkName: "show" */ 为动态生成的文件取名字, OR 默认[id].js,当然为了正确输出文件名字 需要配置出口 - babel-plugin-syntax-dynamic-import ``` module.exports = { output: { // 为动态加载的 Chunk 配置输出文件的名称 chunkFilename: '[name].js', } }; ``` - vue按需加载: 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。 ``` vue按需加载比较简单 const index = () => import(/* webpackChunkName: "index" */ "@/components/index.vue"); ``` - react按需加载Router ``` import React, {PureComponent, createElement} from 'react'; import {render} from 'react-dom'; import {HashRouter, Route, Link} from 'react-router-dom'; import PageHome from './pages/home'; /** * 异步加载组件 * @param load 组件加载函数,load 函数会返回一个 Promise,在文件加载完成时 resolve * @returns {AsyncComponent} 返回一个高阶组件用于封装需要异步加载的组件 */ function getAsyncComponent(load) { return class AsyncComponent extends PureComponent { componentDidMount() { // 在高阶组件 DidMount 时才去执行网络加载步骤 load().then(({default: component}) => { // 代码加载成功,获取到了代码导出的值,调用 setState 通知高阶组件重新渲染子组件 this.setState({ component, }) }); } render() { const {component} = this.state || {}; // component 是 React.Component 类型,需要通过 React.createElement 生产一个组件实例 return component ? createElement(component) : null; } } } // 根组件 function App() { return ( <HashRouter> <div> <nav> <Link to='/'>Home</Link> | <Link to='/about'>About</Link> | <Link to='/login'>Login</Link> </nav> <hr/> <Route exact path='/' component={PageHome}/> <Route path='/about' component={getAsyncComponent( // 异步加载函数,异步地加载 PageAbout 组件 () => import(/* webpackChunkName: 'page-about' */'./pages/about') )} /> <Route path='/login' component={getAsyncComponent( // 异步加载函数,异步地加载 PageAbout 组件 () => import(/* webpackChunkName: 'page-login' */'./pages/login') )} /> </div> </HashRouter> ```
- webpack-bundle-analyzer - webpack --profile --json 记录构件耗时并输出json文件 - [Webpack Analyse](http://webpack.github.io/analyse/)官方可视化工具
new webpack.HotModuleReplacementPlugin(), //当启动时带上 `--hot` 参数会自动调用 new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
- wokbox 是用于向web应用程序添加离线支持的JavaScript库。 - https://segmentfault.com/a/1190000019281388?utm_source=tag-newest
1. 获取dom 分割成多个层 2. 对每个图层的节点计算样式结果 (Recalculate style) 3. 为每个节点生成图形和位置 layout 4. 将每个节点绘制并填充到图形的位置中 paint 5. 图层作为纹理上传到GPU 6. 符合条件的多图层生成屏幕的图像 Composite layer 合成层 7.浏览器渲染过程 layout -> paint -> composite layer (触发的阶段越高,代价越高) 独立的层 -->重排重绘 不会影响父层
1. 根元素 position transform 半透明 canvas video overflow 滤镜...
- 虽然生成独立的层,但是还是cpu一直在计算重绘重排,再传到GPU 进行合成 - 开启GPU加速后 就省去CPU重排重绘的过程 GPU(图形处理器)擅长图形处理 (gpu.js) - css3d video webgl transfrom css滤镜 flash z-index大于某个相邻节点的layer元素...
- 术语:Texture 即CPU传到GPU的一个Bitmap - GPU 能对 快速对Texture 进行 偏移 旋转,缩放,修改透明度等 - 节约了 1. CPU进行layout paint 的时间 2. CPU上传位图的时间
- 相同:都有总线与外界联系,有自己的缓存体系,以及数字与逻辑运算单元,两者都是为了完成计算任务而设计的 - 不同:CPU 主要处理操作系统和应用程序 GPU主要处理跟显示相关的数据处理, GPU的活一般CPU都能干,但是效率低
- 浏览器根据css属性生成layer - 将layer作为Texture上传GPU - 当改变layer的transfrom opacity 等属性,渲染会跳过 layout paint 直接通知GPU做出改变
- border-raduis box-shadow color ...
- 跳过优化(浏览器对重排有一定的优化,合并多次重排)读取 offsetTop scrollTop width clientWidth getComputedStyle() getBoundingClientRect() - 删除位置 大小 盒子 - 读写分离 **fastdom.js** ``` var h = document.getElementById('h').clientHeight //写操作放到下一帧 (假如下一针有大量的操作,requestAnimationFrame会自动调整帧数从60FPS降到30FPS,不会导致丢帧) requestAnimationFrame(function(){ xxx.style.height = h*2 }) ``` - display:none -> block 重绘重排 visibility:visible -> hidden
##. 直出 - 假如多页的话,每次跳转都会重新加载你以来的vue react - 假如SPA单页面:访问html文件 -> 初始化路由 -> 找到对应跟路由 -> 找到对应组件 ->发请求 发给后端再渲染 导致白屏 - SSR + SPA 用户访问到网站直接吐html - 多页转单页 pushstate技术 a/b ->c/d 检测到路由变化向后台发请求 - 如果header是ajax ("X-Requested-With":"XMLHttpRequest")就是单页,不是render
wx window.__moon_map = {"pages/iframe_communicate.js":"//res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/pages/iframe_communicate42f400.js","new_video/player.html.js":"//res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/new_video/player.html433882.js",} 58.com 下localstorage clv {j1.58cdn.com.cn/componentsLoader/dist/PopCollection.js: "20180427170320" j1.58cdn.com.cn/componentsLoader/dist/PopupBind.js: "20181221193411" j1.58cdn.com.cn/componentsLoader/dist/PopupLogin.js: "20181221193411" j1.58cdn.com.cn/git/passport-popupwarn/BindBefore.js: "20181201104527" j1.58cdn.com.cn/git/passport-popupwarn/MobileSure.js: "20181201104527" j1.58cdn.com.cn/git/passport-popupwarn/Warn.js: "20181201104527" j1.58cdn.com.cn/git/private-phone-call-pc/dist/js/bundle_pc_generate.js: "0" updateTime: 1555060912448//现在的时间戳 }
TIP
请打开chrome工具对比 preference timeingline
对比 preference timeingline <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <style> .container { position: relative; } #ball,#ball2 { position: absolute; border-radius: 50%; box-shadow: 0 0 5px 0 red; width: 100px; height: 100px; } #ball2 { box-shadow: 0 0 5px 0 gray; } .run { animation: running-circle 3s infinite; } .run2 { animation: running-circle2 3s infinite; } @keyframes running-circle { 0% { top: 0; left: 0; } 25% { top: 0px; left: 200px; } 50% { top: 200px; left: 200px; } 75% { top: 200px; left: 0px; } 100% { top: 0px; left: 0px; } } @keyframes running-circle2 { 0% { transform: translate(0,0) } 25% { transform: translate(0,200px) } 50% { transform: translate(200px,200px) } 75% { transform: translate(200px,0) } 100% { transform: translate(0,0) } } </style> <body> <div class="container"> <div id="ball"></div> <div id="ball2"></div> </div> <script> let ball = document.getElementById('ball') ball.classList.add('run') let ball2 = document.getElementById('ball2') ball2.classList.add('run2') </script> </body> </html>
ball分析
ball2分析
前端性能优化清单 前端性能优化清单 从Chrome源码看浏览器如何加载资源