从上一篇《前端动画实现与原理分析》,我们从 Performance 进行动画的性能分析,并根据 Performance 分析来优化动画。但,前端不仅仅是实现流畅的动画。ToB 项目会经常与数据的保存、渲染打交道。例如开发中,为了提高用户体验,遇到了一些场景,其实就是在利用某些手段,来进行性能优化。
那不免引发思考,我们从缓存与数据存储来思考,该如何优化?
是什么?
Http 缓存其实就是浏览器保存通过 HTTP 获取的所有资源,
是浏览器将网络资源存储在本地的一种行为。
请求的资源在哪里?
Expires相反,时间是相对于请求的时间? Cache-control
last-Modified : 表示本地文件最后修改的日期,浏览器会在请求头中带上 if-Modified-since(也是上次返回的 Last-Modified 的值),服务器会将这个值与资源修改的时间进行匹配,如果时间不一致,服务器会返回新的资源,并且将 Last-modified 值更新,并作为响应头返回给浏览器。如果时间一致,表示资源没有更新,服务器会返回 304 状态,浏览器拿到响应状态码后从本地缓存中读取资源。
http 1.1 中, 服务器通过 Etag 来设置响应头缓存标示。Etag 是由服务器来生成的。
第一次请求时,服务器会将资源和 ETag 一并返回浏览器,浏览器将两者缓存到本地缓存中。
第二次请求时,浏览器会将 ETag 的值放到 If-None-Match 请求头去访问服务器,服务器接收请求后,会将服务器中的文件标识与浏览器发来的标识进行比对,如果不同, 服务器会返回更新的资源和新的 Etag,如果相同,服务器会返回 304 状态码,浏览器读取缓存。
? 思考为什么有了 Last-Modified 这一对儿,还需要 Etag 来标识是否过期进行命中协商缓存
⚠️ 如果 Etag 与 Last-Modified 同时存在,服务器会先检查 ETag,然后在检查 Last-Modified, 最终确定是返回 304 或 200。
测试环境: 利用 Koa,搭建一个 node 服务,用来测试如何命中强缓存还是协商缓存
当 index.js 没有配置任何关于缓存的配置时, 无论怎么刷新 chrome,都没有缓存机制的;
app.use(async (ctx) => {
// ctx.body = 'hello Koa'
const url = ctx.request.url;
if(url === '/'){
// 访问跟路径返回 index.html
ctx.set('Content-type', 'text/html');
ctx.body = await parseStatic('./index.html')
}else {
ctx.set('Content-Type', parseMime(url))
ctx.set('Expires', new Date(Date.now() + 10000).toUTCString()) // 实验1
ctx.set('Cache-Control','max-age=20') // 实验2
ctx.body = await parseStatic(path.relative('/', url))
}
})
app.listen(3000, () => {
console.log('starting at port 3000')
})
/**
* 命中协商缓存
* 设置 Last-Modified, If-Modified-Since
*/
ctx.set('cache-control', 'no-cache'); // 关闭强缓存
const isModifiedSince = ctx.request.header['if-modified-since'];
const fileStat = await getFileStat(filePath);
if(isModifiedSince === fileStat.mtime.toGMTString()){
ctx.status = 304
}else {
ctx.set('Last-Modified', fileStat.mtime.toGMTString())
}
ctx.body = await parseStatic(path.relative('/', url))
/**
* 命中协商缓存
* 设置 Etag, If-None-Match
*/
ctx.set('cache-control', 'no-cache');
const ifNoneMatch = ctx.request.headers['if-none-match'];
const fileBuffer = await parseStatic(filePath);
const hash = crypto.createHash('md5');
hash.update(fileBuffer);
const etag = `"${hash.digest('hex')}"`
if (ifNoneMatch === etag) {
ctx.status = 304
} else {
ctx.set('Etag', etag)
ctx.body = fileBuffer
}
}
class Cookie {
getCookie: (name) => {
const reg = new RegExp('(^| )'+name+'=([^;]*)')
let match = document.cookie.match(reg); // [全量,空格,value,‘;’]
if(match) {return decodeURI(match[2])}
}
setCookie:(key,value,days,domain) => {
// username=John Smith; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/
let d = new Date();
d.setTime(d.getTime()+(days*24*60*60*1000));
let expires = "; expires="+d.toGMTString();
let domain = domain ? '; domain='+domain : '';
document.cookie = name + '=' + value + expires + domain + '; path=/'
}
deleteCookie: (name: string, domain?: string, path?: string)=> {
// 删除cookie,只需要将时间设置为过期时间,而无需删除cookie的value
const d = new Date(0);
domain = domain ? `; domain=${domain}` : '';
path = path || '/';
document.cookie =
name + '=; expires=' + d.toUTCString() + domain + '; path=' + path;
},
}
浏览器能以一种比使用 Cookie 更直观的方式存储键值对
为每一个给定的源(given origin)维持一个独立的存储区域,浏览器关闭,然后重新打开后数据仍然存在。
为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)。
基本操作与实际数据库操作基本一致。
最终的数据去向,一般只是做临时存储和大型网站的业务运行存储缓存的作用,页面刷新后该库就不存在了。而其本身与关系数据库的概念比较相似。
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。现有的浏览器数据储存方案,都不适合储存大量数据;
IndexedDB 是浏览器提供的本地数据库, 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
在提及 Service Worker 之前,需要对 web Worker 有一些了解;
webWorker : Web Worker 是浏览器内置的线程所以可以被用来执行非阻塞事件循环的 JavaScript 代码。 js 是单线程,一次只能完成一件事,如果出现一个复杂的任务,线程就会被阻塞,严重影响用户体验, Web Worker 的作用就是允许主线程创建 worker 线程,与主线程同时进行。worker 线程只需负责复杂的计算,然后把结果返回给主线程就可以了。简单的理解就是,worker 线程执行复杂计算并且页面(主线程)ui 很流畅,不会被阻塞。
Service Worker 是浏览器和网络之间的虚拟代理。其解决了如何正确缓存往后网站资源并使其在离线时可用的问题。
Service Worker 运行在一个与页面 js 主线程独立的线程上,并且无权访问 DOM 结构。他的 API 是非阻塞的,并且可以在不同的上下文之间发送和接收消息。
他不仅仅提供离线功能,还可以做包括处理通知、在单独的线程上执行繁重的计算等事务。Service Workers 非常强大,因为他们可以控制网络请求,修改网络请求,返回缓存的自定义响应或者合成响应。
? PWA,全称 Progressive web apps,即渐进式 Web 应用。PWA 技术主要作用为构建跨平台的 Web 应用程序,并使其具有与原生应用程序相同的用户体验。
? PWA 的核心: 最根本、最基本的,就是 Service Worker 以及在其内部使用的 Cache API。只要通过 Service Worker 与 Cache API,实现了对网站页面的缓存、对页面请求的拦截、对页面缓存的操纵 。
为什么使用 PWA:
传统的 Web 存在的问题:
传统 Native APP 存在的问题:
PWA 的存在,就是为了解决以上问题所带来的麻烦:
优势:
{
"short_name": "User Mgmt",
"name": "User Management",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".", // 调整网站的起始链接
"display": "standalone", // 设定网站提示模式 : standalone 表示隐藏浏览器的UI
"theme_color": "#000000", // 设定网站每个页面的主题颜色
"background_color": "#ffffff" // 設定背景顏色
}
/* eslint-disable no-restricted-globals */
// 确定哪些资源需要缓存
const CACHE_VERSION = 0;
const CACHE_NAME = 'cache_v' + CACHE_VERSION;
const CACHE_URL = [
'/',
'index.html',
'favicon.ico',
'serviceWorker.js',
'static/js/bundle.js',
'manifest.json',
'users'
]
const preCache = () => {
return caches
.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(CACHE_URL)
})
}
const clearCache = () => {
return caches.keys().then(keys => {
keys.forEach(key => {
if (key !== CACHE_NAME) {
caches.delete(key)
}
})
})
}
// 进行缓存
self.addEventListener('install', (event) => {
event.waitUntil(
preCache()
)
})
// 删除旧的缓存
self.addEventListener('activated', (event) => {
event.waitUntil(
clearCache()
)
})
console.log('hello, service wold');
self.addEventListener('fetch', (event) => {
console.log('request:', event.request.url)
event.respondWith(
fetch(event.request).catch(() => { // 优先网络请求,如果失败,则从缓存中拿资源
return caches.match(event.request)
})
)
})
当离线的时候依然拿到缓存的资源,并且正常展示,可以看出资源被 serviceWorker 缓存。
借助开发者工具:
chrome devtools : chrome://inspect/#service-workers ,可以展示当前设备上激活和存储的 service worker
参考优秀网站:
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun