当面试官问出这个题后,大部分人听到都是内心窃喜:早就背下这篇八股文。
但是稍等,下面几个问题你能答出来吗:
HTTP协议中参数的传输是key=value
这种键值对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。
如?name1=value1&name2=value2
,这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值。
针对name1=value1&name2=value2
我们来说一下客户端到服务端的概念上解析过程:
上述字符串在计算机中用ASCII吗表示为:
text
6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532。
6E616D6531:name1
3D:=
76616C756531:value1
26:&
6E616D6532:name2
3D:=
76616C756532:value2
服务端在接收到该数据后就可以遍历该字节流,首先一个字节一个字节的解析,当解析到3D这字节后,服务端就知道前面解析得字节表示一个key,再向后解析,如果遇到26,说明从刚才解析的3D到26子节之间的是上一个key的value,以此类推就可以解析出客户端传过来的参数。
现在有这样一个问题,如果我的参数值中就包含=或&这种特殊字符的时候该怎么办?
比如说name1=value1
,其中value1
的值是va&lu=e1
字符串,那么实际在传输过程中就会变成这样name1=va&lu=e1
。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了歧义。
如何解决上述问题带来的歧义呢?解决的办法就是对参数进行URL编码
URL编码只是简单的在特殊字符的各个字节前加上%,例如,我们对上述会产生奇异的字符进行URL编码后结果:name1=va%26lu%3D
,这样服务端会把紧跟在“%”后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。
RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~
4个特殊字符以及所有保留字符。RFC3986文档对Url的编解码问题做出了详细的建议,指出了哪些字符需要被编码才不会引起Url语义的转变,以及对为什么这些字符需要编码做出了相应的解释。
RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]
不安全字符:还有一些字符,当他们直接放在Url中的时候,可能会引起解析程序的歧义。这些字符被视为不安全字符,原因有很多。
需要注意的是:对于Url中的合法字符,编码和不编码是等价的,但是对于上面提到的这些字符,如果不经过编码,那么它们有可能会造成Url语义的不同。因此对于Url而言,只有普通英文字符和数字,特殊字符%$-_.+!*'()还有保留字符
,才能出现在未经编码的Url之中。其他字符均需要经过编码之后才能出现在Url中。
其实不止中文需要编码,因为最初搞这些技术的,或者说技术最初技术牛逼的大多都是那些说英文的,所以除了英文和那些与英文一起常常出现的特殊符号被url认可,其他的什么韩语、日语还有一些其他国家与英文不同根源的字符都需要编码成url认可的,也可以说编码成老外(讲英文的老外)看的懂的东西才是url认可的,但是实际应用中一些老外看的懂的也是需要编码的,一些url认可的字符仍然需要编码,防止服务器解析的时候出现错误
JavaScript
中提供了3对函数用来对Url编码以得到合法的Url,它们分别是escape / unescape
, encodeURI / decodeURI
和encodeURIComponent / decodeURIComponent
。由于解码和编码的过程是可逆的,因此这里只解释编码的过程。
这三个编码的函数都是用于将不安全不合法的Url字符转换为合法的Url字符表示
*/@+-._0-9a-zA-Z
!#$&'()*+,/:;=?@-._~0-9a-zA-Z
!'()*-._~0-9a-zA-Z
escape
函数是从Javascript 1.0
的时候就存在了,其他两个函数是在Javascript 1.5
才引入的。但是由于Javascript 1.5
已经非常普及了,所以实际上使用encodeURI和encodeURIComponent
并不会有什么兼容性问题。ASCII
字符的编码方式相同,均是使用百分号+两位十六进制字符来表示。Unicode
字符,escape
的编码方式是%uxxxx
,其中的xxxx是用来表示unicode字符的4位十六进制字符。这种方式已经被ECMAScript v3 已从标准中删除了 unescape() 函数,并反对使用它,因此应该用 decodeURI() 和 decodeURIComponent()
取而代之。encodeURI和encodeURIComponent
则使用UTF-8对非ASCII字符进行编码,然后再进行百分号编码。这是RFC推荐的。因此建议尽可能的使用这两个函数替代escape进行编码。encodeURI
被用作对一个完整的URI进行编码。encodeURIComponent
被用作对**URI的一个组件进行编码。**从上面提到的安全字符范围表格来看,我们会发现,encodeURIComponent编码的字符范围要比encodeURI的大。或者子组件
,如:
号用于分隔scheme和主机,?
号用于分隔主机和路径。encodeURI
操纵的对象是一个完整的的URI,这些字符在URI中本来就有特殊用途,因此这些保留字符不会被encodeURI编码,否则意义就变了都属于强缓存。
Memory Cache 也就是内存中的缓存
优点:读取速度快
缺点:一旦我们关闭 Tab 页面,内存中的缓存也就被释放了
如何触发:当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存
Disk Cache 也就是存储在硬盘中的缓存
优点:缓存再硬盘中,容量大
缺点:读取速度慢
如何触发:根据浏览器请求头确定
preload
是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源prefetch
是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源preload
,对于可能在将来的页面中使用的资源使用 prefetch
详细描述
script标签存在两个属性,defer和async
,因此script标签的使用分为三种情况:
defer或async
属性,浏览器会立即加载并执行相应的脚本。也就是说在渲染script
标签之后的文档之前,不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载;
async
属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行;
defer
属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是并行进行的(异步),js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded
事件触发执行之前。图可以直观的看出三者之间的区别:
蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。
从图中我们可以明确一下几点:
defer
更符合大多数场景对应用脚本加载和执行的要求;defer
属性的脚本,那么它们是按照加载顺序执行脚本的;而对于async
,它的加载和执行是紧紧挨着的,无论声明顺序如何,只要加载完成就立刻执行,它对于应用脚本用处不大,因为它完全不考虑依赖。【从浏览器地址栏输入 url 到请求返回的过程】—— 7. 断开TCP连接:四次挥手
HTTPS加密的过程你了解吗?
↩︎