HTTP缓存简介

前端如何优化页面加载速度是很重要的一个工作,提高资源加载速度便是解决方法之一,如何利用好HTTP缓存,便是重中之中,这边文章将讲述HTTP的缓存解决方案

前言

利用浏览器缓存是web优化页面加载速度的一个重要方法,特别是现在前端富应用化越来越严重的情况下,如何高效的利用HTTP缓存是至关重要的,在思考如何构造完善的缓存系统时,了解HTTP缓存的基础是至关重要的

HTTP缓存方式,主要依靠请求头信息来配置,通过是否在下次请求时需要请求服务器可以分为强制缓存临时缓存

强制缓存

当资源配置了强制缓存的规则后,如果资源发现本地存在有效缓存,那么将会直接应用该缓存

缓存有效时

缓存无效时

Exprise

Expires的值为服务端返回的该资源的到期时间,即下一次请求时,如果请求时间小于服务端返回的到期时间,将直接使用缓存数据。

但是Expires是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。

同时到期时间是由服务端生成的,但是客户端时间可能跟服务端时间存在误差,这就会导致错误的缓存命中。
在HTTP 1.1 的版本,使用了Cache-Control替代。

Pragma

Pragma是HTTP 1.0中控制缓存的方式,在HTTP 1.1中是无效的,可以通过设置为no-cache来禁止缓存

Cache-Control

Cache-Control是HTTP1.1最重要的缓存规则,它控制整个缓存机制的运行,响应的控制字段主要包括max-ageprivatepublicno-cache以及no-store

字段 作用
max-age 在字段设置的时间(秒)后,可以资源可以认定为废弃,在这之前都是可使用的
private 表示为私有数据,不能被中间服务器(第三方、CND)缓存,是默认的
public 表示为公有数据,可以被中间服务器(第三方、CND)缓存
no-cache 表示不能使用强制缓存,需要执行请求进行比对缓存
no-store 表示不能使用任何缓存,客户端不需要存储该缓存

例如max-age=10,表示缓存时间为10秒,在10秒内请求该接口都可以直接从缓存数据库取数据(强制缓存),max-age=10,no-cache,表示缓存时间为10秒的请求需要请进行比对

比对缓存

比对缓存,也就是需要对缓存进行比对来判断是否可以使用该缓存。

在客户端第一次请求数据时服务端除了数据还会将该数据的标识一同返回给客户端,客户端将标识和数据缓存在本地缓存数据库中,当再次请求数据时,客户端将备份的缓存标识附带在请求当中发送给服务端,服务端会根据标识来判断该请求的结果是否发生了变化,如果没有则响应304,否则正常响应并返回新的数据和标识

第一次请求

后续请求(缓存未变动)

后续请求(缓存改变)

Last-Modified/If-Modified-Since

这组标识对通过修改时间来判断,看名字就知道Last-Modified指的最后的修改时间,If-Modified-Since是询问从哪个时间(获取缓存的最后修改时间)开始是否有修改

Etag/If-None-Match

这组标识通过计算响应数据的摘要,来判断数据是否发送改变,Etag标识的资源的摘要,没有固定的计算要求,客户端只需要将缓存标识原样返回就行了。If-None-Match主要在请求是附带Etag的标识主要用于GET请求,其他特殊请求一般使用If-Match

其中Etag有强弱之分,例如强缓存为"aabbcc",弱缓存为W/"aabbcc",强弱的控制也是由服务端控制,对于不同强弱可能会采用不同的算法,一般来说强缓存每次文件修改都会要求更新缓存,而弱缓存不会。这个主要解决Last-Modified/If-Modified-Since在某些系统上不能精确到s级以下的问题,而弱缓存则主要防止在一秒多次更新缓存(但数据实时性要求不是很高)

总结

以上便是HTTP缓存相关的信息,这里列举了主要的HTTP请求头、响应头与缓存有关的字段,知道这些字段大致就可以应对大部分的缓存场景了,当然还有一些额外的字段,可以在下方的参考资料,查看规范了解更加详细的内容。

参考资料

RFC1945 Expires规范

RFC7232

RFC7232 Last-Modified规范

RFC7232 If-Modified-Since规范

RFC7232 Etag规范

RFC7232 If-None-Match规范

RFC7234

RFC7234 Cache-Control规范

木上有水-彻底弄懂HTTP缓存机制及原理

END

2018-05-31 完成

2017-08-02 立项

功能实现之浏览器JS获取计算字符宽高

在开发中遇到了需要获取一段文字的长度的技术性需求,这篇文章将会讲述我是如何解决这样的问题的

浏览器环境下

解决方法一: SPAN标签

我最开始的解决方法是创建一个span元素,然后innerHTML设置文本在设置好样式后插入到body中,读取宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getCharSize(char, style = {}){
let {
fontSize = "12px",
fontFamily = "SimSun"
} = style

let span = document.createElement("span")
span.style.font = `${fontSize} ${fontSize}`
span.style.lineHeight = fontSize
span.innerHTML = str
document.body.appendChild(span)
let rect = span.getBoundingClientRect()
let width = rect.width
let height = rect.height
document.body.removeChild(span)
return {
width,
height
}
}

问题

  1. 不同浏览器的差异

    不同浏览器获取的高度宽高有一些差别

    chrome:

    firefox:

    不知道为何firefox的高总是比chrome高2px,但这个我们可以通过直接获取传入的fontSize作为高,这样就可以统一了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function getCharSize(char, style = {}){
    let {
    fontSize = 14,
    fontFamily = "SimSun"
    } = style
    /*其他操作*/
    return {
    width,
    height: fontSize
    }
    }
  2. 浏览器对字体大小的限制

    chrome默认最小字体为12px,基本是人尽皆知的

    这里可以使用scale的方式实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function getCharSize(char, style = {}){
    let {
    fontSize = 14,
    fontFamily = "SimSun"
    } = style

    /*其他操作*/
    let scale = fontSize / 20
    span.style.fontSize = `${20}px`
    span.style.transform = `scale(${scale})`
    span.style.display = "inline-block" //让scale生效
    /*其他操作*/
    return {
    width,
    height: fontSize
    }
    }

优点

兼容几乎所有浏览器

缺点

  1. 会受一些潜在的全局样式影响

解决方法二:Canvas measureText函数

除了使用span来获取浏览器表现的大小这样直接的方式以外,还有可以通过使用canvasAPI的CanvasRenderingContext2D.measureText()方式来快速获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ctx = document.createElement('canvas').getContext('2d')
function getCharSizeByCanvas(char, style = {}){
let {
fontSize = 14,
fontFamily = "SimSun"
} = style

ctx.font = `${fontSize}px ${fontFamily}`
let text = ctx.measureText(char)
let result = {
height: fontSize,
width: text.width
}

return result
}

问题

  1. chrome浏览器存在BUG,如果canvas不在DOM树上设置字体大小小于12px时,字体大小会强制设置为12px

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    //setfont before append
    const canvas1 = document.createElement('canvas')
    canvas1.width = 100
    canvas1.height = 100
    const ctx1 = canvas1.getContext('2d')
    ctx1.font = "8px Arial"
    ctx1.fillText(ctx1.font, 0, 50)
    document.body.appendChild(canvas1)

    //set font after append
    const canvas2 = document.createElement('canvas')
    canvas2.width = 100
    canvas2.height = 100
    const ctx2 = canvas2.getContext('2d')
    document.body.appendChild(canvas2)
    ctx2.font = "8px Arial"
    ctx2.fillText(ctx2.font, 0, 50)

代码整理

下面给出优化后的代码

方法一:span

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let span = document.createElement("span")
span.style.positon = "ablsolute"

function getCharSize(char, style = {}){
let {
fontSize = 14,
fontFamily = "SimSun"
} = style

let scale = fontSize / 20
span.style.fontSize = "20px"
span.style.fontFamily = fontFamily
span.style.lineHeight = "0"
span.style.transform = `scale(${scale})`
span.style.display = "inline-block"
span.innerHTML = char
document.body.appendChild(span)
let rect = span.getBoundingClientRect()
let width = rect.width
document.body.removeChild(span)
return {
width,
height: fontSize
}
}

方法二:canvas计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let canvas = document.createElement('canvas')
canvas.style.positon = "ablsolute"
let ctx = canvas.getContext('2d')

function getCharSizeByCanvas(char, style = {}){
let {
fontSize = 14,
fontFamily = "Arial"
} = style
document.body.appendChild(canvas)
ctx.font = `${fontSize}px ${fontFamily}`
document.body.removeChild(canvas)
let text = ctx.measureText(char) // TextMetrics object
ctx.fillText(char, 50, 50)
let result = {
height: fontSize,
width: text.width
}
return result
}

性能比较

数据为进行10000次单字符计算

  1. 都需要插入DOM的情况下(方法二兼容chrome,且字体都为12px以下)

    方法二效率比方法一快: 150%左右

  2. 方法二字体都为12px以上

    方法二效率比方法一快: 1500%左右

从效率上来讲,canvas效率是极高的,同时canvas还有还在制定的标准,可以提供更加详细的文本信息,chrome只需要去chrome://flags/开启Experimental Extension APIs(新版本)或Experimental Extension APIs(老版本)就可以提前使用该功能

参考资料

CanvasRenderingContext2D.measureText()

TextMetrics

END

2019-01-15 更新:谷歌浏览器开启flag的实验特性有改变(Experimental Extension APIs)

2018-03-31 完成

2017-12-13 立项

让你的浏览器更加灵性--Page Visibility

以前访问别人的网站时,发现当页面tab切开的时候,网站的title会变化,当时没太注意这个的实现方法,后来发现了Page Visibility这个浏览器API,这篇文章简单讲下这个API的用法

Page Visibility API

通过Page Visibility API可以获取到当前网页的可见状态

这个API主要作为Document的扩展接口,所以需要通过document对象来获取数据

属性

Page Visibility API包含两个属性visibilityStatehidden

visibilityState

这个属性为string类型,包含4个值,表示的当前文档的可见状态

描述
visible 页面内容是可见的,实际就是浏览器非最小化窗口的激活的选项卡
hidden 页面不可见,实际就是浏览器最小化了或者操作系统锁屏,或者标签被切出去了
prerender 页面正在预渲染
unloaded 页面正在被关闭

hidden

这个属性为boolean类型,用于描述当前文档是否被隐藏,true表示当前文档被隐藏,false表示当前当前文档可见

这个属性其实就是在visibilityStatevisible时为true,其他时候为false

事件

当文档的可见状态改变时,可以通过监听visibilitychange事件来知晓

1
2
3
4
5
function visibilityChange(){
console.log(document.visibilityState)
}

document.addEventListener("visibilitychange", visibilityChange)

如图可以看到切换出去和切换回来后,输出了hiddenvisible

下面来个简单的标题改变的应用

应用

标题变幻

1
2
3
4
5
6
7
8
9
function visibilityChange(){
if(document.hidden){
document.title = "哎!别走啊"
} else {
document.title = "我就知道你会回来的~~"
}
}

document.addEventListener("visibilitychange", visibilityChange)

结尾

这个简单的API就这样讲的差不多了,除了可以改变标题让你的文档更加灵性,这个API还主要用在控制视频播放,Banner滚动,页面完全关闭前保存数据等等地方

参考资料

W3C规范

MDN_Page Visibility API

caniuse Page Visibility API ?

END

2018-03-20 完成

2018-03-19 立项

功能实现之时间格式化函数

好久没写函数封装的东西了,最近忽然发现还没有试过写时间格式化函数,这里给出我写的简单的时间格式化函数,如果想要功能强大的时间格式化工具,可以去看看moment哦,文章内有传送门

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function formatDate(date, formatString){
if (typeof date === 'string' || typeof date === 'number'){
date = new Date(date)
}
if (!(date instanceof Date)){
throw new TypeError("时间格式不规范")
}

if (typeof formatString !== 'string'){
throw new TypeError("格式化字符串类型错误")
}

const RegEs = [
{reg: "YYYY", data: date.getFullYear()}, //年份
{reg: "YY", data: date.getFullYear()}, //年份
{reg: "MM", data: date.getMonth() + 1}, //月份
{reg: "DD", data: date.getDate()}, //日期
{reg: "D", data: date.getDay()}, //星期
{reg: "HH", data: date.getHours()}, //小时24小时制
{reg: "hh", data: date.getHours() % 12}, //小时12小时制
{reg: "mm", data: date.getMinutes()}, //分钟
{reg: "ss", data: date.getSeconds()}, //秒
]
RegEs.forEach(RegE => {
let regExp = new RegExp(`${RegE.reg}`, 'g')
while (1){
let result = regExp.exec(formatString)
if (!result) break

formatString = formatString.replace(result[0], (new Array(result[0].length).fill('0').join('') + RegE.data).substr(-result[0].length))
}
})

return formatString
}

用法

参数说明

参数 类型 是否必传 说明
date String,Number,Date 时间
formatString String 时间格式化字符串

格式化支持

表达式 内容
YYYY 千年份
YY 百年份
MM 月份
DD 当月日期
HH 小时24小时制
hh 小时12小时制
mm 分钟
ss

超简单解析

就是用正则表达式啦,然后存在包含关系的就长的优先进行匹配就行了

END

2018-02-24 完成

2018-02-24 立项

2017回顾 与 2018目标

2017虽然已经过去2个月了,狗年也到了,还是回顾下经历挺多的2017年吧


重要的事 – 春招 & 实习

要说2017年有哪些重要的事情,主要便是上半年的春招,年中在qunar的实习和下半年回实验室的实习经历了

春招

辛辛苦苦(假装)学习2年多了,大三下学期的也正是开始找以后工作的时候了,作为计算机行业,春招是一个很好的机会,提前加入大公司,了解大公司的流程,构成等

性情懒惰的我,就看了看题,也没咋复习,就跑去水啊水,结果当然是悲惨,基本都GG了,但运气好因为qunar笔试题确实是太简单,便过了,便被叫去面试了,便在我的一脸懵逼的忽悠下,成功通过了面试,成为了一名qunar实习生

整个春招过程,我都感觉自己是浑浑噩噩的,可能是自己也没有一个确定的目标吧,我自己都不知道我以后想要干啥,最后全靠运气好,才加入了qunar网,与我一同战斗的同学们都加入了很不错的公司,正是他们努力的准备了这次秋招。

这次经历,感觉有的时候真的需要有个努力的目标才能真正触摸到自己想要的

实习

在qunar的实习经历说好也可以说不好,作为第一次在社会中实践,体现自己价值的经历,我只能给自己的行为打60分,可能是技术栈不契合的缘故导致在组里的表现不是很好,存在很多BUG,还好在组中的人都很不错,鑫哥和allen都很照顾我,不然很GG,在这里真心祝福qunar超级巴士越来越好

在qunar实习1个半月后,正是已经熟悉内部流程,要大展伸手的时候,突然离职回学校实验室,是真心感觉对不起,因为我自己确实感觉不太适合那个产品,技术栈吧,在这次经历中,我也越来越明确自己可能并不是很喜欢业务逻辑,虽然我能担当上写业务逻辑的责任,但是我可能更加喜欢写内部平台系统吧,因为那里更多机会去尝试新技术

在qunar之后,我再次回到了实验室,虽然回到实验室后感觉到了无奈,说好的后台已经OK,哎~~~~~~~,接下来就是半年漫长的易企签新版开发,期间也去实践了一些小功能,这半年的摸索,对易企签的未来有了更加明确的目标,就如上面我的春招经历一般,没有目标的话,将永远无法完全发挥你的实力,即使你可以成为最强,你没有目标永远都会被那些努力向着一个目标前进的人所超越

也许正是在最后两个月的时间,我们的目标越来越清晰,开发速度才开始正式启动

真的、感觉、我们这个拖了一年的项目就用了2个多月在开发:(

保研

今年也尝试了保研,靠着不知道怎么就上去了的学分,莫名奇妙的保上了本校的研究生(主要是懒得去找别的学校的研究生了,怕遇到坑爹老师),希望今年顺利毕业,读个研究生,再给自己3年时间寻找下自己真正想做的事吧

个人反省

除了上面的重要的事情,平时的生活中我也感觉的出来,以及通过相关人格测试,能感觉出,我确实过于内向,同时也很容易烦躁,烦躁来源于自己的嫉妒之心,来源于自己对自己的缺点的无奈,小时候的环境导致我自己总是很看中周围人对自己的看法,导致自己畏首畏尾不敢真正的表达自己,缺少对自己表达的方法,无法正确的表达自己,每次都无法准确的表达自己的意图,这样的情况在将来进入社会后必然是一种拖累,所以我想慢慢调整自己的状态,锻炼自己的表达能力,减少自己莫名其妙的次数

2018目标

2017年年初,我在我的博客写下了2017年的目标

  • 一周一篇技术博客
  • 一周一道算法题(算法乃程序的灵魂)(后来被我删了)
  • 看完犀牛书和红皮书

好吧,到现在来看一个都没有完成,在暑假出去实习后就戛然而止了,我的github全绿计划也化为一旦,每周一篇博客可能很难,因为我总希望自己能写出对别人确实有帮助的文档,所以我决定,将博文的目标改为

  • 每月一篇高质量技术博客 或两篇中质量技术博客 或四篇低质量技术博客

而算法,虽然我确实没有认真研究过,依然还有很多算法听都没听说过,所以一周一篇还是有必要的,但为了避免时间上的冲突,在遇到复杂算法题的时候需要放缓脚步(毕竟不是在学ACM)

  • 每两周一道中上难度算法题 或 两道低难度算法题

至于看完犀牛书和红皮书,我觉得我自己已经不需要刻意去看这些JS宝典了,我需要给自己制定一些身心上的目标

  • 每周至少运动7小时
  • 尝试放开别人的看法,表达自己

就这样我的2018目标:

  • 每月一篇高质量技术博客 或两篇中质量技术博客 或四篇低质量技术博客
  • 每两周一道中上难度算法题 或 两道低难度算法题
  • 每周至少运动7小时(其实就是每天早起去跑步,做运动)
  • 尝试放开别人的看法,表达自己

希望今年的目标能完成75%吧

从2月22号开始吧,那是我回学校后的第一天

END

2018-02-18 完成

2018-02-17 立项

input type=file 打开缓慢

有时会需要使用Input选择文件,但是选择框在一些情况下会很久才能打开,这篇文章将会收集我遇到这类问题,并记录解决方法

情况一: accept 滥用 *

有的时候会通过accept字段来筛选可选文档,提高用户体验,但是只是初略的使用image/*这样的字段,在某些浏览器(比如chrome,)会导致选择框打开缓慢的问题,所以尽量将文件类型限制描述完全

image/*

image/png, image/jpg

1
2
3
<input type="file" accept="image/*">image/*</input>

<input type="file" accept="image/png, image/jpg">image/png, image/jpg</input>

END

2017-10-24 完成文章,添加了一个情况

2017-09-14 立项

JS创建文件并请求下载到本地

平时开发中,下载资源都是通过后台提供URL的方式下载一些内容,然而当页面作为一个应用有时需要提供下载页面生成的数据的功能,这篇文章将简单介绍JS如何实现本地生成文件并下载的功能

下载功能的实现

想要通过JS直接触发数据的下载目前是没有方法的,最好的办法便是使用<a>标签来实现下载

download属性

<a>标签的download属性会指示浏览器下载URL而不是导航到URL,比如<a href="http://www.baidu.com" download="baidu"></a>将会提示下载访问http://www.baidu.com得到的数据

所以我们只需要构建一个<a>标签设置属性,并触发它就行了

1
2
3
4
let a = document.createElement('a')
a.download = "..."
a.href = "..."
a.click()

那么如何下载JS运行中生成的数据呢?

<a>标签的href属性上不只可以填写相关的锚点、跳转链接,还可以填写blob: URLsdata: URLs的内容,来运行用户下载JS生成的数据

Blob

Blob对象表示不可变的类似文件对象的原始数据。File的接口正式基于Blob实现的,通过构建Blob然后输出Blob对象的DataURL就可以下载JS中的内容了

new Blob(array[, options])

Blob的构建函数第一个参数必传,是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的数组,或者其他类似对象的混合体,它将会被放进BlobDOMStrings会被编码为UTF-8

第二个参数为Blob对象的配置对象,有主要的一个属性

  1. type,默认为"",它代表了将会被放入到blob中的数组内容的MIME类型,在下载文件时,如果文件名忽略了后缀,那么下载将会按照该属性的MIME类型给予默认后缀
1
2
let data = "this is a test"
let blob = new Blob([data])

这样我们就构建了一个Blob对象,接下来只需要获取BlobDataURL就可以了

1
let dataURL = URL.createObjectURL(blob)

然后结合上面<a>标签下载数据的方式,我们就可以下载啦

整体流程

1
2
3
4
5
6
7
let data = "this is a test"
let blob = new Blob([data])
let dataURL = URL.createObjectURL(blob)
let a = document.createElement('a')
a.download = "xxx"
a.href = dataURL
a.click()

小贴士

  1. 通过Blob的type控制自动补全后缀

    1
    URL.createObjectURL(blob,{type: "image/png"})

参考资料

MDN_a

MDN_blob

END

2017-10-21 完成

2017-10-20 立项

触发和监听自定义事件

浏览器自带有大量的事件提供给开发者使用,但是在一些特殊情况下,浏览器的自带事件是无法满足需求的,所以浏览器允许触发和监听开发者自定义的事件,这篇文章将讲述如何新建事件对象,并触发,以及监听

创建事件对象

我在之前的文章中简单的介绍了浏览器的事件流,以及对事件的监听,但浏览器同样支持创建开发者自己的事件

1
2
3

new Event("event_name")

通过上面这行代码便可以创建一个我们自己命名的事件,创建事件除了Event对象外,还可以使用大量其他的Event对象

目前为止现在有这么多可供使用的Event子类(来自MDN)

触发事件

当事件构建好了,就可以尝试触发事件了

事件的触发主要通过调用需要触发事件的元素的dispatchEvent方法

1
2
3

Element.dispatchEvent(e)

简单的DEMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<div id="div3">
<button id="button" onclick="click">click</button>
</div>
<script>
document.getElementById("button").addEventListener("diy_event",function(e){
console.log("button");
},true)

document.getElementById("button").addEventListener("click",function(){
let e = new Event("diy_event")
this.dispatchEvent(e)
})
</script>

这个简单的DEMO,点击按钮可以在控制台看到出现了button输出

构建事件对象的参数

然而如果别的节点尝试在冒泡阶段监听事件,会发现并没有收到消息,因为事件的创建还有额外的选项!

1
2
3

event = new Event(eventName, eventOptions);

创建事件的第一个参数是事件的名称,第二个参数是事件对象的参数对象

bubbles

bubbles参数是描述这个事件是否进行冒泡,是Boolean类型,默认为false

所以当这样配置时,就可以在冒泡阶段监听到啦~~

1
2
3
4
5

new Event('diy_event', {
bubbles: true
});

cancelable

cancelable参数是描述这个事件是否可取消默认事件,是Boolean类型,默认为false

composed

composed参数是描述这个事件是否可被阴影根(shadow-root)以外的监听器监听,是Boolean类型,默认为false


结语

这篇文章主要介绍了Event这个最基础的事件构建以及如何触发事件,其他不同的事件构建函数都有不同的区别,详情还是参考规范最好~~

参考资料

DOM事件流简介

MDN_Event

END

2017-09-27 完成

2017-09-21 立项

功能实现之input file 限制文件格式

文章简单讲述 Input 标签在 type=file 的情况下通过 accept 限制选择文件的类型和后缀的方法,以及提供一个简单的类型校验函数

利用 accept 属性限制

accept 属性可以控制选择文件时的默认筛选条件,比如下面的代码

1
2
3

<input type="file" accept=".pdf">

点击这下面的选择文件,你会看到这样的页面

.pdf

这时可选择的文件就都是后缀为 .pdf 的了

除了通过 .*** 的形式设置外还可以通过设置 MIME 类型

1
2
3

<input type="file" accept="application/*">

application/*

这样就可以选择所有 application 类型的文件了,当然也可以指定特定的类型

指定多个类型

多个类型可以通过 , 号分割,比如

1
2
3

<input type="file" accept=".pdf,.docx">

.pdf,.docx

依然要限制

然鹅,上面的图你会看到可以切换全部文件类型,然后用户就开始乱选了(一般测试才会这样去- -),这时候依然需要对其进行检测,下面提供我根据和 accept 相同的值来检测加载的文件是否符合需求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

function checkFileType(file, accept){
let acceptArr = accept.split(',').map(a => a.trim())
let name = file.name
let type = file.type


if(acceptArr.length === 0)
return true

return acceptArr.some(function(accept){
if(accept === '*'){//全部
return true
}

if(/^\..+$/.test(accept)){ //文件后缀名
let reg = new RegExp(`.+\\${accept}$`)
return reg.test(name)
}

if(/^.+\/\*/.test(accept)){ //文件MIME部分
accept = accept.replace('*','')
let reg = new RegExp(`^${accept}.+`)
return reg.test(type)
}

if(accept === name || accept === type){
return true
}

return false
})
}

之于为嘛有后缀和MIME两种方式,可以查看参考资料

参考资料

MDN_MIME

END

2017-09-06 完成

2017-09-06 立项