racy 2019-06-27
原文请查阅这里,略有改动,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。
本系列持续更新中,Github 地址请查阅这里。
这是 JavaScript 工作原理的第十二章。
正如在之前关于渲染引擎的文章中所讲的那样,我们相信好的和伟大的 JavaScript 开发者之间的差别在于后者不仅仅只是理解了语言的具体细节还了解其内部构造和运行环境。
49 年前,ARPAnet 诞生了。它是早期的报文分组交换网络及第一个实现 TCP/IP 协议套件的网络。该网络连通了加利福亚大堂和斯坦福研究所。20 年后,Tim Berners-Lee 分发了一个后来为人所熟知的万维网的 『Mesh』草案。在 49 年的时间里,网络走过了一段漫长的旅程,从仅仅只是是两台电脑间交换数据报文到至少 7500 万台服务器,38 亿人使用互联网以及 13 亿个网站。
本文将试着分析现代浏览器使用哪些技术来自动提升应用性能(有些你甚至不了解),然后着重介绍浏览器网络层。最后,提供一些让浏览器提升网络应用程序性能的技巧。
现代浏览器专门为快速,高效和安全数据传输的网络应用/网站而设计开发的。拥有数以百计的组件运行于各个不同的层级,从进程管理和安全沙箱到 GPU 管线,音频和视频及其它更多等等,网络浏览器更类似于一个操作系统而不仅仅只是一个软件。
浏览器的整体性能是由一些大型的组件所决定的,这些组件包括:解析,布局,样式计算,JavaScript 和 WebAssembly 执行,渲染,当然还有网络堆栈。
一般情况下,工程师们会把网络堆栈看成是一个性能瓶颈。经常会发生这样的情况因为从网络抓取所有的资源会堵塞渲染剩下的步骤。为了更加高效的网络层,它需要不仅仅只是扮演套接字管理员的角色。在我们看来获取资源是一个非常简单的机制,但是实际上它集成自身的优化准则,接口和服务的一整套平台。
网页开发者不需要担心单独的 TCP 或者 UDP 数据包,请求格式化,缓存以及其它正在发生的一切。浏览器会处理这些复杂的玩意,这样就可以专注开发自己的程序。但是,知道其内部的原理可以帮助开发者开发出更加高效和安全的程序。
本质上,当用户开始和浏览器发生交互所产生的情况如下:
W3C Navigation Timing specification 提供了浏览器接口及浏览器中每个请求背后的可视化计时和性能数据。让我们浏览下这些组件,因为每个组件在获取最佳用户体验方面扮演了重要的角色。
整个网络请求过程是相当复杂的并且有许多的层次结构,每一层都有可能成为性能瓶颈。这就是为什么浏览器使用各种技术努力提升其性能,以便把整个网络通信的性能损耗降至最低。
看些新技术吧:
JavaScript 和 WebAssembly 禁止开发者操作单独的网络套接字的生命周期,这样是相当的明智的。这样不仅仅可以让你头发少掉点而且可以让浏览器自动优化大量的性能,这些性能包括套接字重用,请求优化和延迟绑定,协议协商,强制连接限制及其它的优化措施。
实际上,现代浏览器更一步地将请求管理周期从套接字管理中剥离了出来。用套接字池来组织套接字,以源来分组套接字,每个套接字池强制限制其连接数和安全约束。排队,优先化等待的请求,然后和套接字池中的单个套接字绑定。如果不是服务器主动关闭这些连接,多个请求可以自动重用相同的套接字。
由于创建一个新的 TCP 连接会带来额外的性能开销,重用连接会为其自带来极大的性能提升。默认情况下,当发起请求的时候,浏览器使用所谓的 『keepalive』机制以节省创建到服务器的新连接所耗费的时间。创建一个新的 TCP 连接的平均时间为:
这样的架构衍生出了一些其它的优化方法。请求可以依据优先级来以不同的顺序执行。浏览器可以优化所有套接字间的带宽分配或者它可以创建套接字以等待预期的请求。
如上所述,这些都是浏览器所控制而不用开发者进行干预。但这并不意味着我们无所事事了。选择正确的数据传输所用的网络通信模式,类型和频率,正确的协议类型以及正确的服务器堆栈隧道/优化对于提升整个程序的性能有着至关重要的作用。
一些浏览器甚至更进一步。例如,当你使用 Chrome 的时候,当用户使用的时候它会进行自我学习从而变得更加快速。它基于访问过的网页和典型的浏览器模式来进行学习,这样就可以预期可能的用户行为且在用户进行任意操作之前进行操作。最简单的例子即当用户悬停在某个链接上的时候预渲染页面。如果你想学习更多关于 Chrome 优化技术的文章,可以查看 High-Performance Browser Networking 这本书的 https://www.igvita.com/posa/h... 章节。
允许浏览器操作单独的套接字有另一个非常重要的目的即:浏览器就可以针对不被信任的程序资源强制实施一套一致的安全和政策约束措施。例如,浏览器禁止通过 API 直接访问原始网络套接字,因为这样会导致任意可疑的程序随意连接任意主机。浏览器也强制连接数限制以保护服务器免受由于客户端访问而耗尽其资源。
浏览器格式化所有流出的请求以强制格式正确和一致的协议语义来保护服务器。同样地,浏览器会自动解码响应内容以保护用户免受可疑服务器的攻击。
Transport Layer Security (TLS) 是一个为计算机网络提供通信安全的加密协议。它广泛应用于大量应用程序,其中之一即浏览网页。网站可以使用 TLS 来保证服务器和网页浏览器之间的所有通信安全。
整个 TLS 握手过程包含以下几个步骤:
每当发生任何验证失败的时候,用户会收到警告。比如服务器使用自签名的证书。
当两个页面的协议,端口(如果有指定)以及主机名都是一样的则称为同源。
以下为一些可能包含跨域的资源示例:
<script src=”…”></script>
里面的 JavaScript 代码。语法错误的错误信息仅适用于同源脚本。<link rel=”stylesheet” href=”…”>
的 CSS。由于 CSS 的松散语法规则,跨域 CSS 要求正确的 Content-Type 头。各个浏览器的限制不同。<img>
图片<video>
和 <audio>
媒体文件<object>
, <embed>
和 <applet>
插件以上的列表还远远不够;该列表旨在强调工作中的『最小特权』原则。浏览器只为应用程序代码暴露出其所必需的接口和资源:应用提供数据和 URL 地址,然后浏览器格式化请求及处理每条连接的整个生命周期。
需要注意的是并没有一个简单的 『同源策略』概念。
相反,有一系列相关的机制来强制限制浏览器的 DOM 访问,cookie 和 会话状态管理,网络连接和其它组件。
最好和最快的请求即不创建请求。在分派一个请求前,浏览器自动检查其资源缓存,进行必要的验证检查然后当指匹配指定的条件时返回一份本地资源拷贝。如果缓存中没有可用的本地资源,则发起网络请求然后把响应内容自动放置于缓存中以备之后的访问(如果这是被允许的)。
管理一个高效和优化的资源缓存是非常困难的。谢天谢地,浏览器为我们处理了整个复杂的玩意,而我们所需要做的即保证服务器返回恰当的缓存指令;想了解更多可以看 客户端资源缓存 文章。你为网页上的所有资源添加 Cache-Control,ETag,和 Last-Modified 的响应头信息。
最后,一个经常被忽略但至关重要的浏览器功能即其提供了验证,会话和 cookie 管理。浏览器为每个源维护单独的『cookie jars』,通过提供必要的程序和服务器接口来读写新的 cookie,会话和认证数据,以及自动挂载和处理适当的 HTTP 头来为我们自动处理整个过程。
一个简单但明了的用来展示浏览器的延迟会话状态管理的方便性的例子即:多个选项卡或者浏览器窗口可以共享一个认证会话,反之亦然;一个选项卡中的登出操作可以使所有其它打开窗口的会话失效。
了解了网络服务之后,最终要讲到应用程序接口和协议。众所周知,更底层的结构提供了一组广泛的重要服务:套接字和连接管理,请求和响应处理,各种安全策略,缓存及其它更多的强制措施。每当初始化一个 HTTP 请求或者 XMLHttpRequest,一个持久的服务推事件或者 WebSocket 会话抑或打开一个 WebRTC 连接,我们就是在和部分或者所有这些底层服务进行交互。
没有单一的最佳协议或者接口。每个复杂的程序都会基于不同的要求混合使用不同的传输协议:和浏览器缓存的交互,协议开销,消息延迟,可靠性,数据传输类型以及其它。一些协议拥有低数据传输延迟的特性(比如服务器推事件,WebSocket),但是可能不符合其它重要的场合,比如利用浏览器缓存或者支持任意情况下的二进制数据传输的能力。
本系列持续更新中,Github 地址请查阅这里。
写过小程序的应该知道,微信的request不封装基本上不能用,写的显的太冗长,而且是回调式的,回调地狱什么的就不说了,可读性差。下面是我的封装代码,顺便支持一下promise。加了登录锁后的代码如下: