网站首页 > 技术文章 正文
1. Internet互联
万物互联已经实实在在的组建在我们生活之中。我们作为网络中的一个节点,不管是否从事IT行业,了解HTTP协议对我们使用浏览器或其他APP都有一定的帮助,这些不限于
- 我们是如何能浏览网页的?
- 我们是如何能使用APP交水电煤的?
- 我们是如何使用电脑或APP买火车票的?
- 我们是如何通过电子网站购物的?
- 我们是如何登录一个系统就可以一直使用这个账号的?
- …
HTTP协议是应用层广泛使用的一个协议,了解它我们需要从协议的定义、协议的用法、跨服务调用(专业称为跨域请求)、数据缓存、长连接和它的版本演进为大家拨云开雾的一探究竟。
2. HTTP协议定义
HTTP 是HyperText Transfer Protocol的缩写,中文翻译就是超文本传输协议。顾名思义:
- 超文本表示数据类型不在局限于文本。在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等;
- 传输表示能双向沟通;
- 协议表示一个约定的规范。
即HTTP是一个应用层协议,无状态,由请求和响应构成,是一个标准的客户端服务器模型。在计算机世界里一个专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
3. 一个具体的HTTP请求分析
举个栗子,当我们打开头条的Web页面,通过F12打开控制台,我们挑选一个请求来分析下HTTP的结构。
这是一个 http 请求以及响应,这里的 General 可以理解成为 http请求和响应的起始行的一个公共部分,说明:
- Genaral headers: 同时适用于请求和响应消息,但与最终消息传输的数据无关的消息头。
- Request Headers: 包含更多有关要获取的资源或客户端本身信息的消息头。
- Response Headers:包含有关响应的补充信息,如其位置或服务器本身(名称和版本等)的消息头。
- Entity Headers[其他]:包含有关实体主体的更多信息,比如主体长(Content-Length)度或其MIME类型。
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成;
3.1 General Headers
通用头即可以包含在HTTP请求中,也可以包含在HTTP响应中。通用头的作用是描述HTTP协议本身。比如描述HTTP是否持久连接的Connection头,HTTP发送日期的Date头,描述HTTP所在TCP连接时间的Keep-Alive头,用于缓存控制的Cache-Control头等
- Request URL:请求地址
- Request Method:请求方法
- Status Code:请求状态
- Remote Address:Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP
- Referrer-Policy:控制请求头中referrer的内容
HTTP的请求方法
方法 | 描述 | |
1 | GET | (查询)发送请求来获得服务器上的资源,请求体中不会包含请求数据,请求数据放在协议头中。 |
2 | POST | (新增)向服务器提交资源(例如提交表单或上传文件)。数据被包含在请求体中提交给服务器。 |
3 | PUT | (修改)向服务器提交资源,并使用提交的新资源,替换掉服务器对应的旧资源.。 |
4 | DELETE | (删除)请求服务器删除指定的资源。 |
5 | HEAD | HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。 |
6 | OPTIONS | 获取http服务器支持的http请求方法,允许客户端查看服务器的性能,比如ajax跨域时的预检等。 |
7 | CONNECT | 建立一个到由目标资源标识的服务器的隧道。 |
8 | TRACE | 沿着到目标资源的路径执行一个消息环回测试,主要用于测试或诊断。 |
9 | PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新。 |
3.1.1 GET 和 POST 比较?
根据 RFC 规范
- GET 的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。
- POST 的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中, body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。
3.1.2 GET 和 POST 的安全和幂等性
- 安全:指请求方法不会「破坏」服务器上的资源。
- 幂等:多次执行相同的操作,结果都是「相同」的。
如果从 RFC 规范定义的语义来看:
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
3.2 Rquest Headers
实体头是那些描述HTTP信息的头。既可以出现在HTTP POST方法的请求中,也可以出现在HTTP响应中。比如Content-Type和Content-length等描述实体的类型和大小的头。其它还有用于描述实体的Content-Language、Content-MD5、Content-Encoding以及控制实体缓存的Expires、Last-Modifies头等。
请求头是那些由客户端发往服务端以便帮助服务端更好的满足客户端请求的头。请求头只能出现在HTTP请求中。比如告诉服务器只接收某种响应内容的Accept头,发送Cookies的Cookie头,显示请求主机域的HOST头,用于缓存的If-Match、If-Match-Since、If-None-Match头,用于只取HTTP响应信息中部分信息的Range头,用于附属HTML相关请求引用的Referer头等。
参数说明:浏览器侧的能力
- Accept:可以接受的媒体类型,例如: Accept: text/html 代表浏览器可以接受服务器回发的类型为 text/html
- Accept-Encoding:申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),注意:这不是指字符编码
- Accept-Language:申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等,例如: Accept-Language: en-us
- Connection:keep-alive 当一个网页打开完成后,客户端和服务器之间保持一个长连接。
- Referer:当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理。
- User-Agent:客户端使用的操作系统和浏览器的名称和版本。
- Cache-Control:Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。
- Cookie:Cookie是用来存储一些用户信息以便让服务器辨别用户身份的(大多数需要登录的网站上面会比较常见),比如cookie会存储一些用户的用户名和密码,当用户登录后就会在客户端产生一个cookie来存储相关信息,这样浏览器通过读取cookie的信息去服务器上验证并通过后会判定你是合法用户,从而允许查看相应网页。当然cookie里面的数据不仅仅是上述范围,还有很多信息可以存储在cookie里面,比如sessionid等。
- If-Modified-Since:把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中T。
- If-None-Match:If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request 中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag.
3.3 Response Headers
HTTP响应头是那些描述HTTP响应本身的头,这里面并不包含描述HTTP响应中第三部分也就是HTTP信息的头(这部分由实体头负责)。比如说定时刷新的Refresh头,当遇到503错误时自动重试的Retry-After头,显示服务器信息的Server头,设置COOKIE的Set-Cookie头,告诉客户端可以部分请求的Accept-Ranges头等。
参数说明:
- Access-Control-Allow-Credentials:跨域Ajax请求时是否带Cookie的设置;表示是否允许发送cookies。默认情况下,Cookies不包括在CORS请求中;设为true表示cookies可以包含在请求中一起发给服务器,如果不需要发送cookies给服务器,需删除字段。需要注意的是:除了设置Access-Control-Allow-Credential:true外,在ajax请求中也必须打开withCredentials
- Access-Control-Allow-Methods:必要字段 ,表示服务器支持的所有跨域请求方法,只要浏览器使用的请求方法包含在内即可通过
- Access-Control-Allow-Origin:必要字段 ,该站点可以被哪些网站进行跨域资源共享
- Access-Control-Expose-Headers:必要字段 ,表明服务器支持的所有头信息字段,也是为了避免多次预检请求
- Access-Control-Allow-Max-Age :可选字段, 单位是s,用来指定本次预检的有效期,即在给定时间内允许该条缓存回应,不会发出一条预检请求。
- Cache-Control:控制缓存的行为 浅谈http中的Cache-Control
- Connection:“Connection:Keep-Alive”或 “Connection:close”,这里具体的含义是有关http 请求的是否保持长连接,即链接是否复用,每次请求是复用已建立好的请求,还是重新建立一个新的请求。
- Content-Length:在Http 1.0及之前版本中,content-length字段可有可无;在http1.1及之后版本。如果是keep alive,则content-length和chunk必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无。
- Content-Encoding:Accept-Encoding 和Content-Encoding是HTTP中用来对采用哪种编码格式传输正文进行协定的一对头部字段。
- (Content-Encoding 中的 gzip 和 deflate:
- gzip,一种由文件压缩程序「Gzip,GUN zip」产生的编码格式,描述于 RFC 1952。这种编码格式是一种具有 32 位 CRC 的 Lempel-Ziv 编码(LZ77);
- deflate,由定义于 RFC 1950 的「ZLIB」编码格式与 RFC 1951 中描述的「DEFLATE」压缩机制组合而成的产物;)
- Content-Type:代表发送端发送的实体数据的数据类型(post请求肯定要发送数据包;因此对数据包的Type有专门的限定:Content-Type只能是application/x-www-form-urlencoded,application/json,multipart/form-data或 text/plain中的一种。)
- Keep-Alive:在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。
HTTP响应消息由状态行、响应头部、空行 和 响应体 4个部分组成,如下图所示:
3.2 HTTP 常见的状态码
场景的Status Code说明:
1XX
- 100(continue) 表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
2XX
- 200(OK) 表示从客户端发来的请求在服务器端被正常处理了。
- 204(No Content) 该状态码代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。
- 206(Partial Content) 该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求。响应报文中包含由 Content-Range 指定范围的实体内容。
3XX
- 301(Moved Permanently) 永久性重定向。该状态码表示请求的资源已被分配了新的 URI,以后应使用资源现在所指的 URI。
- 302(Found) 临时性重定向。比如在没有登录情况下访问网站"个人中心",会重定向到登录页,但是你登录后,访问个人中心时,它又不会重定向到其他地方了。
- 303(See Other) 和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
- 304(Not Modified) 如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
4XX
- 400(Bad Request) 该状态码表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。
- 401 Unauthorized 该状态码表示发送的请求需要有认证信息。返回含有 401 的响应必须包含一个适用于被请求资源的 WWW-Authenticate 首部用以询问用户信息。当浏览器初次接收到 401 响应,会弹出认证用的对话窗口。第二次接收到,则不弹出,直接表示认证失败。
- 403(Forbidden) 对请求资源的访问被服务器拒绝了,一般是未获得文件系统的访问授权,问权限出现某些问题。
- 404(Not Found) 浏览器地址错误。服务器找不到对应资源。
5XX
- 500(Internal Server Error) 服务器在执行时报错。
- 503(Service Unavailable) 服务器暂时处于超负载或正在进行停机维护,无响应。一般需要重启服务器即可。
4. HTTP 缓存技术
对于一些具有重复性的 HTTP 请求,或者静态数据文件,比如css/js/html等,可以把这对「请求-响应」的数据都缓存在本地,下次就直接读取本地的数据,进而提高访问性能。
HTTP 缓存的2种实现方式:强制缓存和协商缓存。
强制缓存
强制缓存:不判断直接使用浏览器的本地缓存。强缓存是通过下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
- Cache-Control:相对时间;
- Expires:绝对时间;
如果 HTTP 响应头部同时有上面2个字段的话,Cache-Control的优先级高于 Expires 。Cache-Control 配置比较多,建议使用 Cache-Control 来实现强缓存进行精细化控制。具体流程如下:
- 客户端第一次请求服务资源,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,并设置了过期时间大小;
- 客户端再次请求请求服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会更新 Response 头部的 Cache-Control。
协商缓存
当请求的响应码是 304时,服务器告诉浏览器可以使用本地缓存的资源,通常这种方式被称为协商缓存。
协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。
协商缓存通过配置头部有2种实现:
第一种:请求头部 If-Modified-Since 字段与响应头部中的 Last-Modified 字段
- Last-Modified:标示这个响应资源的最后修改时间;
- If-Modified-Since:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新,说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧,说明资源无新修改,响应 HTTP 304 走缓存。
第二种:请求头部 If-None-Match 字段与响应头部中的 ETag 字段
- 响应头部中 Etag:唯一标识响应资源;
- 请求头部中的 If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求资源时,会将请求头If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果没有变化返回 304,如果变化了返回 200。
第一种方式是基于时间实现的,第二种方式是基于一个唯一标识实现的。对比2种方式,第二种可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。
如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有 Etag 和 Last-Modified 字段,那么客户端再下一次请求的时候,如果带上了 ETag 和 Last-Modified 字段信息给服务端,这时 Etag 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 没有变化,然后再看 Last-Modified,否则不做其他检查。
**为什么 ETag 的优先级更高?**这是因为 ETag 主要能解决 Last-Modified 存在的问题:
- 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
- 可能有些文件是在秒级以内修改的,If-Modified-Since 能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次;
- 有些服务器不能精确获取文件的最后修改时间。
协商缓存这两个字段都需要配合强制缓存中 Cache-control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
强制缓存和协商缓存的工作流程如下:
使用 ETag 字段实现的协商缓存的过程:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识,它根据当前请求的资源生成;
- 当浏览器再次请求访问服务器中的该资源时,会先检查强制缓存是否过期:如果没有过期,则直接使用本地缓存;如果过期了,则会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
- 服务器再次收到请求后,会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较:如果相等,则返回 304;如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
- 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。
5. HTTP 特性
HTTP 当前常见到版本有 HTTP/1.1,HTTP/2.0,HTTP/3.0,特性不兼容。
5. 1 HTTP/1.1 的优点
HTTP /1.1的优点:
- 简单:HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。
- 灵活和易于扩展:HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。
- 应用广泛和跨平台:互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用遍地开花,同时天然具有跨平台的优越性。
HTTP/1.1 的缺点:
HTTP 协议里有优缺点一体的双刃剑,分别是「无状态、明文传输」,同时还有一大缺点「不安全」。
HTTP/1.1 的性能:
HTTP 协议是基于 TCP/IP,使用了「请求 - 应答」的通信模式。
长连接
HTTP/1.0 性能比较差,原因是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,增加了通信开销。
为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信方式。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。
持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。如果某个 HTTP 长连接超过一定时间没有任何数据交互,服务端就会主动断开这个连接。
管道网络传输
HTTP/1.1 采用了长连接的方式,则可以选择管道(pipeline)传输。即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。因为管道传输是串行的,所以会有一个问题。如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。
所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持,所以后面所有文章讨论HTTP/1.1 都是建立在没有使用管道化的前提。大家知道有这个功能,但是没有被使用就行了。
HTTP/1.1 的性能一般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能。
5.2 HTTP/1.1、HTTP/2、HTTP/3 演变
HTTP/1.1 相比 HTTP/1.0 性能上的改进:
- 长连接改善了 HTTP/1.0 短连接造成的性能开销。
- 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
但 HTTP/1.1 还是有性能瓶颈:
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
- 发送冗长的首部;
- 服务器是按请求的顺序响应的,容易队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
5.3 HTTP/2 优化
HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。
那 HTTP/2 相比 HTTP/1.1 性能上的改进:
- 头部压缩
- 二进制格式
- 并发传输
- 服务器主动推送资源
HTTP/2 缺点
HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很好,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。
HTTP/3 做了哪些优化?
前面我们知道了 HTTP/1.1 和 HTTP/2 都有队头阻塞的问题:
- HTTP/1.1 中的管道( pipeline)虽然优化了请求的队头阻塞,但是没有解决响应的队头阻塞,这属于 HTTP 层队头阻塞。
- HTTP/2 虽然通过多个请求复用一个 TCP 连接解决了 HTTP 的队头阻塞 ,基于流水线的传输模式,一旦发生丢包,就会阻塞住所有的 HTTP 请求,这属于 TCP 层队头阻塞。
HTTP/2 队头阻塞的问题是因为 TCP,所因此HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
UDP 并不是一个可靠传输,发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。不可靠的 UDP 进过改造后的QUIC 协议 可以实现类似 TCP 的可靠性传输。
QUIC 有以下 3 个特点。
- 无队头阻塞
- 连接建立更平滑
- 连接迁移更稳定
HTTP/3 现在普及的进度非常的缓慢,不知道未来 UDP 是否能够逆袭 TCP。
参考资料:
- 阮一峰.HTTP 协议入门.阮一峰的网络日志.
- 小林coding图解http
- 上一篇: 计算机网络面试题 计算机网络面试题牛客网
- 下一篇: 浏览器不同刷新方式的差异 浏览器刷新怎么弄
猜你喜欢
- 2024-11-13 「直击面试」- 搞定计算机网络,这些问题还没有我答不出来的
- 2024-11-13 HTTP面试题 高校毕业生就业难 面试题
- 2024-11-13 HTTP的起源与发展 http的具体内容是什么
- 2024-11-13 前端优化的步骤 前端优化的步骤包括
- 2024-11-13 Python最常见的170道面试题全解析答案(四)
- 2024-11-13 前端项目中 浏览器缓存的更新不及时问题及解决方法
- 2024-11-13 你还没弄懂浏览器的缓存机制吗? 介绍一下浏览器缓存
- 2024-11-13 如何使用缓存提高系统性能? 提高缓存速度
- 2024-11-13 网络相关面试题 网络相关面试问题
- 2024-11-13 为什么你做的H5开屏那么慢?H5首屏秒开方案探讨
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-