【笔记】imooc-让你的页面速度飞起来 web前端性能优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aSuncat/article/details/89087527

第01章 课程简介

1-01 课程简介

一、前端性能优化点
1、网络层面
2、构建层面
3、浏览器渲染层面
4、服务端层面
二、涉及的功能
1、资源的合并与压缩
2、图片编解码原理和类型选择
3、浏览器选择机制
4、懒加载预加载
5、浏览器存储
6、缓存机制
7、PWA
8、Vue-SSR
三、前端性能优化原理
1、作用及原理
2、如何与真实业务场景结合
3、理论结合实践
4、量化分析
四、静态资源压缩合并,减少http请求。
(1)减少http请求数量
(2)减少请求资源大小
在线网站、fis3两种实现压缩与合并的方法

第02章 资源合并与压缩

2-01 http请求潜在的性能优化点

一、
1、cs架构
2、bs架构:webserver、cdn
二、浏览器的一个请求从发送到返回都经历了什么
在这里插入图片描述
三、加载静态资源,cdn和主站域名不同,这样可以防止访问主站携带cookie,避免http request header中携带cookie

2-02 html压缩

一、资源压缩
1、html压缩
2、css压缩
3、js的压缩和混乱
4、文件合并
5、开启gzip
二、如何进行html压缩
1、使用在线网站进行压缩
2、nodejs提供了html-minifier工具
3、后端模板引擎(cjs/ ejs/ jade)渲染压缩
二、css压缩
1、无效代码删除
2、css语义合并
三、如何进行css压缩
1、使用在线网站进行压缩
2、使用html-minifier对html中的css进行压缩
3、使用clean-css对css进行压缩
四、js压缩
1、无效字符的删除
2、剔除注释
3、代码语义的缩减和优化
4、代码保护
五、如何进行js压缩
1、使用在线网站进行压缩
2、使用html-minifier对html中的js进行压缩
3、使用uglifyjs2对js进行压缩
六、keep-alive,不合并请求的缺点:
1、文件与文件之间有插入的上行请求,增加了n-1个网络延迟
2、受丢包问题影响更严重
3、经过代理服务器时可能会被断开
七、文件合并存在的问题
1、首屏渲染问题
2、缓存失效问题
八、文件合并的建议
1、公共库合并
2、不同页面的合并
3、见机行事,随机应变。
九、如何进行文件合并
1、使用在线网站进行文件合并
2、使用nodejs实现文件合并
十、浏览器静态资源请求并发数是有限的,这也需要我们进行文件的合并

2-06 fis3构建工具自动压缩合并流程

一、fis3
1、百度内部使用的构建工具,结合前端构建、服务器端构建的整个工程化解决方案。

2-07 fis3构建工具自动压缩合并-实操

一、fis3支持match语法
fis3 release -cd output ,fis3打包并将结果打包到output文件夹下

第03章 图片相关的优化

3-01 一张jpg图片的解析过程

一、jpg图片解析过程
在这里插入图片描述
二、不同格式图片常用的业务场景
1、jpg有损压缩,压缩率高,不支持透明
2、png支持透明,浏览器兼容好
3、webq压缩程度更好,在ios webview有兼容性问题
4、svg矢量图,代码内嵌,相对较小,图片样式相对简单的场景
三、雪碧图(csssprits),将多个小图整合成一张大图
1、优点:减少http请求
2、缺点:图片太大,加载时间长,如果加载异常,影响整个页面的图片显示。
3、优化:将雪碧图进行拆分
(1)基于业务将业务相关的图整合到一张雪碧图
(2)对雪碧图进行拆分
四、淘宝图片是webp格式,如果不取_.webp,也可以使用降级的jpg
五、svg不需要重新发请求,因为是内嵌在html中的
六、网站
1、常用压缩图片网站:https://tinypng.com
2、图片格式转换网站:https://zhitu.isux.us
3、雪碧图网站:http://www.spritecow.com
七、inline-image,根据业务需要,<4kb或者<8kb的时候,推荐用inline。这种情况,相对于网络损耗,是更优的选择。

第04章 css与js的装载与执行

4-01 页面加载渲染的过程

一、html, 字符流->字节流->token
二、设置3到4个cdn域名
三、浏览器词法分析,token获取,是从上到下的,所以html是从上到下解析的
四、控制某个域名下的资源数量,从而避免达到并发的上限。
五、css阻塞
1、css head中阻塞页面的渲染
2、css阻塞js的执行
3、css不阻塞外部脚本的加载
六、js阻塞
1、直接引入的js阻塞页面的渲染
2、js不阻塞资源的加载
3、js顺序执行,阻塞后续js逻辑的执行

第05章 懒加载与预加载

5-01 懒加载原理

一、懒加载
监听scroll事件,图片进入可视区域,去请求资源,为图片赋上src
1、图片的top<屏幕的height时,就进入了可视区域

var viewHeight = document.documentElement.clientHeight; // 可视区域的高度
function lazyload() {
	var eles = document.querySelectorAll('img[data-original][lazyload]');
	[].forEach.call(eles, function(item, index) { // 等同于Array.prototype.forEach.call(), []是用来访问数组的原型链。
		var rect;
		if (item.dataset.original === '') {
			return;
		}
		rect = item.getBoundingClientRect();
		if (rect.bottom >= 0 && rect.top < viewHeight) {
			!function() {
				var img = new Image();
				img.src = item.dataset.original;
				img.onload = function() {
					item.src = img.src;	
				}
				item.removeAttribute('data-original');
				item.removeAttribute('lazyload');
			}();
		}
	})
}
lazyload();
document.addEventListener('scroll', lazyload);

二、预加载的3种方式
1、<img src="" style="display:none">
2、js中设置
使用Image对象

var image = new Image();
image.src = "http://test.png";

3、XMLHttpRequest

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = callback;
xhr.onprogress = progressCallback;
xhr.open('GET', 'http://test.png', true);
xhr.send();
function callback() {
	if (xhr.readyState == 4 && xhr.status == 200) {
		var responseText = xhr.responseText;
	} else {
		console.log('request was unsuccessful:' + xhr.status);
	}
}
function progressCallback(e) {
	e = e || event;
	if (e.lengthComputable) {
		console.log('Received' + e.loaded + ' of ' + e.total + 'bytes');
	}
}

(1)优点:①能监听数据传输情况
(2)缺点:①跨域
三、可以使用preload.js

var queue = new createjs.LoadQueue(false); // true:异步, false:使用html标签方式
queue.on('complete', handleComplete, false);
queue.loadManifest([
	{id: 'myImage', src: 'http://pic26.com/test1.png'}
	{id: 'myImage2', src: 'http://pic26.com/test2.png'}
])
function handleComplete() {
	var image = queue.getResult('myImage');
	document.body.appendChild(image);
}

第06章 重绘与回流

6-01 css性能让javascript变慢

一、不同线程
javascript解析
ui 渲染
二、ui线程和javascript线程是互斥的,执行ui线程的时候,javascript线程是冻结的;执行javascript线程的时候,ui线程是冻结的。

6-02 什么是重绘与回流

一、回流(重排):页面布局和几何属性发生改变的时候,就会触发回流。如尺寸、布局、layout的变化
二、重绘:当render tree的一些元素需要更新属性,而这些属性只是改变元素的外观、风格,而不改变布局,比如background-color,color
三、回流会引起重绘,重绘不一定会引起回流

6-03 避免重绘回流的两种方法

一、触发页面重绘回流的属性,页面重新布局
1、盒子模型相关:
width、height、padding、border、margin、display、border-width、min-height
2、定位属性及浮动:
top、right、bottom、left、position、float、clear
3、节点内部文字结构,行内属性
text-align、overflow、line-height、vertical-align、font-weight、white-space、font-size
二、只触发重绘
color、border-style、border-radius、visibility、background、box-shadow、outline
三、避免重绘回流的方法
1、属性替代:避免使用触发重绘、回流的css属性
2、新建图层:将频繁重绘回流的dom元素单独作为一个独立图层,那么这个dom元素的重绘和回流的影响只会在这个图层中
(1)chrome创建图层方法
①3d或透视变换css属性:persepective,

transform:translateZ(0);
transform: translate3d(0,0,0);

will-change:transform;
②使用加速视频解码的节点
③拥有3d(webGL)上下文或加速的2d上下文的
④混合插件(如flash)
⑤对自己的opacity做css动画或使用动画webkit变换的元素
⑥拥有加速css过滤器的元素
⑦元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
⑧元素有一个z-index较低且包含一个复合层的兄弟(换句话说就是该元素在复合层上面渲染)
(2)图层缺点:图层合成过程非常消耗运算量
四、新建dom的过程
1、获取dom后分割为多个图层
2、对每个图层的节点计算样式结果(recalculate style-样式重计算)
3、为每个节点生成图形和位置(layout-回流和重布局)
4、将每个节点绘制填充到图层位图中(paint setup和paint-重绘)
5、图层作为纹理上传至gpu
6、符合多个图层到页面上生成最终屏幕图像(coposite layers-图层重组)

6-04

一、调试
1、performance:性能监测
layer:展示图层的情况
more tools-rendering
二、重绘回流优化点
1、translate替换top,top会触发回流但是translate不会
2、opacity替换visibility,visibility会触发重绘但不会触发回流
3、不要一条一条修改dom样式,预先定义好class,然后修改dom的class
4、可以将dom离线操作,如先display:none(会有一次reflow),但是接下来的操作都不会重绘,等离线操作结束后,再显示。
5、不要把dom节点的属性值放在循环里,当成循环的变量
offsetWidth/ offsetHeight,强制刷新缓冲机制
6、不要使用table,即使最后一行改变,整个table也会回流
7、动画实现速度的选择
8、动画新建图层
9、使用gpu加速,位置变换时,用translate,persepective

transform:translateZ(0);
transform: translate3d(0,0,0);

cpu走主线到gpu,会有传输损耗

第07章 浏览器缓存

7-01

一、知识点
1、localstorage、cookie、sessionstorage、indexdb
2、pwa、service worder
二、cookie
在这里插入图片描述
1、cookie的初衷:因为http请求无状态,所以需要cookie去维持客户端状态。
2、cookie的生成方式:
(1)http response header中的set-cookie
(2)js中可以通过document.cookie可以读写cookie
3、使用
(1)用于浏览器端和服务器端的交互
(2)客户端自身数据的存储
4、cookie存储数据能力被localStorage替代
5、httponly,当前cookie不支持js读写
6、cookie中在相关域名下面-cdn的流量损耗
解决方法:cdn的域名和主站的域名要分开
二、indexDB
1、indexDB是一种低级API,用于客户端存储大量结构化数据。该api使用索引来实现对该数据的高性能搜索。虽然web storage对于存储少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法很有用。indexDB提供了一个解决方案。
为应用创建离线版本。

7-06

一、PWA
PWA(progressive web apps)是一种web app新模型,并不是具体指某一种前沿的技术或者某一个单一的知识点,是一个渐进式的web app,是通过一系列新的web特性,配合优秀的ui交互设计,逐步的增强web app的用户体验。
1、方向
(1)可靠:在没有网络的环境中也能提供基本的页面访问,而不会出现“未连接到互联网”的页面。
(2)快速:针对网页渲染及网络数据访问有较好优化。
(3)融入(engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。
2、检测pwa、检测性能:lighthouse(https://lavas.baidu.com/doc-assets/lavas/vue/more/downloads/lighthouse_2.1.0_0.zip);(aSuncat:链接进去是404?)
二、service worker
service worker是一个脚本,浏览器独立于当前网页,将其在后台运行,为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性包括推送消息,北京后台同步,geofencing(地理围栏定位),但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。
1、应用点
(1)使用拦截和处理网络请求的能力,去实现一个网络应用。
(2)使用service worker在后台运行同时能和页面通信的能力,去实现大规模后台数据的处理。
2、service worker生命周期
在这里插入图片描述

7-07

一、service worker
1、chrome://serviceworker-internals/
chrome://inspect/#service-workers
2、application里找serveiceworker注册的文件名,然后network里搜索serviceworker注册文件
3、serviceworker有个api叫cache,浏览器cache storage里有这些缓存资源
4、network的size能看到数据是否是从serviceworker的缓存中读取的
5、mobile.twitter.com能离线加载应用

7-08

1、cookie
document.cookie 获取cookie
document.cookie = 'userName=may' 写入cookie
document.cookie = 'gender=male'

7-09

1、indexDB,大规模数据处理

  function openDB(name, callback) {
    // 建立打开indexDB
    var request = window.indexedDB.open(name);
    request.onerror = function(e) {
      console.log('open indexdb error');
    }
    request.onsuccess = function(e) {
      myDB.db = e.target.result;
      callback && callback();
      // console.log(myDB.db);
      // console.log(myDB.db.close()); // 关闭
      // indexedDB.deleteDatabase(); 删除
    }
    // from no database to first version, first version to second version...
    request.onupgradeneeded = function() {
      var store = request.result.createObjectStore('books', {
        keyPath: 'isbn'
      });
      // 建索引
      var titleIndex = store.createIndex('by_title', 'title', {
        unique: true
      });
      var authorIndex = store.createIndex('by_author', 'author');
      store.put({
        title: 'Quarry memories',
        author: 'fred',
        isbn: 12345
      });
      store.put({
        title: 'water buffaloes',
        author: 'fred',
        isbn: 23456
      })
      store.put({
        title: 'bednack',
        author: 'barney',
        isbn: 34567
      })
    }
  }
  var myDB = {
    name: 'testDB',
    version: '1',
    db: null
  }
  function addData(db, storeName) {
    // object store,transaction事务,是为了操作object store
    var transaction = db.transaction('books', 'readwrite');
    var store = transaction.objectStore('books');
    // 获取当前indexdb中的数据
    var request = store.get(23456);
    request.onsuccess = function(e) {
      console.log(e.target.result);
    }
    // 添加信息到indexDB中
    store.add({
      title: 'flowers',
      author: 'quan',
      isbn: 222
    })
    // 删除记录
//    store.delete(222);

    store.get(222).onsuccess = function(e) {
      var books = e.target.result;
      console.log(books);
      books.author = 'james';
      var request = store.put(books); // put更新
      request.onsuccess = function(e) {
        console.log('update success');
      }
    }

  }
  openDB(myDB.name, function() {
    // 关闭indexdb
//    myDB.db.close();
    // 删除indexdb
//    window.indexedDB.deleteDatabase();
  });
  setTimeout(function() {
    addData(myDB.db);
  }, 2000)

2、indexDB没有表的概念,有object store的概念

7-12

一、每个service worker都有启动它的配置文件
app.js

if (navigator.serviceWorker) {
	var sendBtn = document.getElementById('send-msg-button');
	var msgInput = document.getElementById('msg-input');
	var msgBox = document.getElementById('msg-box');
	
	sendBtn.addEventListener('click', function() {
		// 主页面发送信息到serviceworker
		navigator.serviceWorker.controller.postMessage(msgInput.value);
	})
	navigator.serviceWorker.addEventListener('message', function() {
		msgBox.innerHTML = msgBox.innerHTML + ('<li>' + event.data.message + '</li>')
	})
	
	navigator.serviceWorker.register('./service-worder.js', {scoped: './'})
		.then(function(reg) {
			console.log(reg);
		})
		.catch(function(e) {
			console.log(e);
		})
} else {
	alert('service worker is not supported');
}

service-worker.js

self.addEventListner('install', function(e) {
	e.waitUntil( // waitUntil接收一个promise对象
		caches.open('app-vl') // caches:cacheStorage的对象
			.then(function(cache) {
				console.log('open cache');
				return cache.addAll([
					'./app.js',
					'./main.css',
					'./serviceWorker.html'
				])
			})
	)
})
self.addEventListener('fetch', function(event) {
    event.respondWith(
      caches.match(event.request).then(function(res) {
        if (res) {
          return res;
        }else {
          // 通过fetch方法向网络发起请求
          fetch(url).then(function (res) {
            if (res) {
              // 对于新请求的资源存储到我们的cachestorage
              caches
            } else {
              // 用户提示
            }
          });
        }
      })
    )
  })

二、service workers只能在https下才能生成,本地调试能用localhost:80,不能用ip:80
三、serviceworker的api
1、主页面给serviceworker发消息

self.addEventListner('message', function(event) {
	var promise = self.clients.matchAll().then(function(client) {
		var senderID = event.source ? event.source.id : 'unkown';
		clientList.forEach(function(client) {
			if (client.id === senderID) {
				return;
			} else {
				client.postMessage({
					client: senderID,
					message: event.data
				})
			}
		})
	})
	event.waitUtil(promise);
})

2、serviceworker给主页面发消息

第08章 缓存优化

8-01 缓存1

一、缓存
1、cache-control所控制的缓存策略
2、last-modified和etag以及整个服务端浏览器端的缓存流程
二、cache-control
1、max-age:从请求资源到这段时间之内,浏览器请求资源,不会向浏览器发起请求
(1)cache-control: max-age=315360000
状态码也是200:status code: 200 (from memory cache)
(2)max-age(http1.1)比expires(http1.0)的优先级高
2、s-maxage
(1)cache-control: s-maxage=21536000
状态码304
(2)s-maxage比max-age优先级高
(3)只适用于public缓存设备,不适用于private缓存设备
3、private: 私人缓存,如用户浏览器缓存
4、public: cdn缓存设备
5、no-cache
(1)cache-control: private, max-age=0, no-cache
向服务器端发起请求,根据服务端返回的信息决定缓存策略
6、no-store
(1)完全不使用任何缓存策略

8-03 last-modified、if-modified-since

一、expires
1、缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
2、告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
3、max-age, expires
强的浏览器端缓存,不需要向服务器端发起请求。
二、last-modified/ if-modified-since
1、基于客户端和服务端协商的缓存机制
2、last-modified —— response header
last-modified:Fri,18 Aug 2017 07:21:46 GMT
状态码304
先走max-age,过期了再取last-modified
3、if-modified-since —— request header
if-modified-since:Fri, 18 Aug 2017 07:31:46 GMT
4、last-modified/ if-modified-since需要与cache-control共同使用
4、last-modified有什么缺点
(1)某些服务端不能获取精确的修改时间
(2)文件修改时间改了,但文件内容却没有变

8-04 etag/ if-none-match

一、etag/ if-none-match
1、文件内容的hash值
2、etag——response header
3、if-none-match——request header
4、需要与cache-control共同使用
5、etag/ if-none-match优先级比last-modified/ if-modified-since更高
6、优点:比if-modified-since更精确
7、状态码:304
二、分级缓存策略
通过apche、nginx的配置,自动进行的缓存策略
在这里插入图片描述
三、缓存策略是服务端定的。
四、node开启的服务:
(1)chrome浏览器刷新直接加了一个cache-control:max-age=0;
(2)防止浏览器加max-age=0的方式:
①index.html,外链方式加入图片。②外链方式:右键,检查,地址栏输入地址
mime.js

exports.types = {
  'jpg': 'image/jpeg',
  'json': 'application/json'
}

config.js

exports.Expires = {
  fileMatch: /^(jpg|png|gif|js|css)$/ig,
  maxAge: 60 * 60 * 24 * 365
}

app.js

var PORT = 8000;
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var mime = require('mime').types;
var config = require('./config');

var server = http.createServer(function(request, response) {
  var pathname = url.parse(request.url).pathname;
  var realPath = 'assets/img' + pathname;
  var ext = path.extname(realPath);
  ext = ext ? ext.slice(1): 'unknown';
  var contentType = mime[ext] || 'text/plain';
  if (ext.match(config.Expires.fileMatch)) {
    var expires = new Date();
    expires.setTime(expires.getTime() + config.Expires.maxAge * 1);
    response.setHeader('Expires', expires.toUTCString());
    response.setHeader('Cache-Control', 'max-age=' + config.Expires.maxAge);
  }

  fs.stat(realPath, function(err, stat) { // stat:读取文件的状态,可获取最后修改日期
    if (stat) {
      var lastModified = stat.mtime.toUTCString();
      response.setHeader('Last-Modified', lastModified);
    }
    if (request.headers['if-modified-since'] && lastModified == request.headers['if-modified-since']) {
      response.writeHead(304, 'Not Modified');
      response.end();
    } else {
      fs.exists(realPath, function(exists) {
        if (!exists) {
          response.writeHead(404, {
            'Content-Type': 'text/plain'
          });
          response.write('this request url' + pathname + 'was not found');
        } else {
          fs.readFile(realPath, 'binary', function(err, file) { // binary:二进制的
            if (err) {
              response.writeHead(500, {
                'Content-Type': contentType
              })
            } else {
              response.write(file, 'binary');
              response.end();
            }
          })
        }
      })
    }
  });
});

server.listen(PORT);
console.log('runnding at port:' + PORT);
8-05 案例解析

一、缓存读取
1、max-age,浏览器读取缓存,200,from memory cache。
不会带if-modified-since
2、s-maxage,向cdn请求,这个请求是真正发出去了,读取的是cdn缓存,而不是走浏览器缓存。304
到cdn服务器后,cdn根据last-modified,if-modified-since判断当前文件是否过期,if-modified-since传到cdn,cdn会比对if-modified-since和文件最后修改日期是否相同,这个文件的相关内容从s-maxage来决定当前读取的文件的内容。如果s-maxage已经过期,就会去原服务器拿最新信息。
就算if-modified-since失效了,还是会返回304.
二、缓存机制
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

8-06 流程图

在这里插入图片描述
aSuncat:推荐文章(深入理解浏览器的缓存机制):https://blog.csdn.net/howgod/article/details/88176395

第09章 ssr

9-01

一、服务端是nodejs,客户端复杂运算可以放到服务端,减少前端运算成本。
二、流程
1、vue开发,页面加载流程
(1)加载vue.js
(2)执行vue.js代码
(3)生成html页面
三、没有前端框架时
1、用jsp/ php在服务器端进行数据的填充,发送给客户端就是已经填充好数据的html
2、使用jquery异步加载数据
3、使用react和vue前端框架
四、多层次的优化方案
1、构建层模板编译
2、数据无关的prerender的方式:如果用户进来看到的页面都是一样的(如营销活动),可以在构建层直接将vue.js执行掉,生成相应的html
3、服务端渲染:可能涉及到内存泄漏,服务端运算能力,服务器压力。
服务端本身是在集群上的
四、ssr: server side rendering

9-03

一、豆瓣资源:https://github.com/monkeyWangs/doubanMovie-SSR
二、渲染

<!--vue-ssr-outlet-->

猜你喜欢

转载自blog.csdn.net/aSuncat/article/details/89087527