在SPA中,所有必要的HTML,JavaScript和CSS代码都可以由浏览器在一次页面加载中检索到,[1]或通常根据用户的操作动态加载并根据需要将适当的资源添加到页面中。尽管可以使用位置哈希或HTML5 History API来提供应用程序中单独逻辑页面的感知和可导航性,但是该页面不会在过程中的任何时刻重新加载,也不会将控制权转移到另一个页面。[2]
单页应用程序一词的起源尚不清楚,尽管该概念至少早在2003年就已经讨论过。[3]威尔士卡迪夫大学的编程学生Stuart Morris用slashdotslash.com编写了Self-Contained网站。相同的目标和功能在2002年4月,[4]以及同年晚些时候,Lucas Birdeau,Kevin Hakman,Michael Peachey和Clifford Yeh在美国专利8,136,109中描述了单页应用程序的实现。[5]
可以在Web浏览器中使用JavaScript来显示用户界面(UI),运行应用程序逻辑并与Web服务器通信。成熟的开放源代码库可支持SPA的构建,从而减少了开发人员必须编写的JavaScript代码。
有多种可用的技术使浏览器即使在应用程序需要服务器通信时也可以保留单个页面。
Web浏览器JavaScript框架和库(例如AngularJS,Ember.js,ExtJS,Knockout.js,Meteor.js,React和Vue.js)都采用了SPA原理。
当前使用的最突出的技术是Ajax。[1]主要使用JavaScript中的XMLHttpRequest / ActiveX Object(不建议使用)对象,其他Ajax方法包括使用IFRAME或脚本HTML元素。诸如jQuery之类的流行库使来自不同制造商的浏览器之间的Ajax行为规范化,从而进一步普及了Ajax技术。
WebSockets是HTML5规范的一部分,是双向实时客户端-服务器通信技术。在性能[10]和简单性方面,它们的使用优于Ajax 。
服务器发送事件(SSE)是一种技术,服务器可以通过该技术启动向浏览器客户端的数据传输。建立初始连接后,事件流将保持打开状态,直到被客户端关闭。SSE通过传统的HTTP发送,并且具有WebSocket设计缺乏的各种功能,例如自动重新连接,事件ID和发送任意事件的功能。[11]
尽管此方法已经过时,但也可以使用浏览器插件技术(例如Silverlight,Flash或Java applet)来实现对服务器的异步调用。
对服务器的请求通常会导致原始数据(例如XML或JSON)或新的HTML返回。在服务器返回HTML的情况下,客户端上的JavaScript更新DOM(文档对象模型)的部分区域。[12]当返回原始数据时,通常使用客户端JavaScript XML /(XSL)流程(对于JSON,则为模板)将原始数据转换为HTML,然后将其用于更新部分区域的DOM。
SPA将逻辑从服务器转移到客户端,Web服务器的角色演变成纯数据API或Web服务。在某些方面,这种架构上的转变被称为“瘦服务器架构”,以强调复杂性已从服务器转移到客户端,并提出这样的论点最终降低了系统的整体复杂性。
服务器将必要的状态保存在页面的客户端状态的内存中。这样,当任何请求到达服务器时(通常是用户操作),服务器会发送适当的HTML和/或JavaScript并进行具体更改,以使客户端进入新的所需状态(通常添加/删除/更新部分内容)客户DOM)。同时,服务器中的状态也会更新。大多数逻辑在服务器上执行,并且HTML通常也在服务器上呈现。在某些方面,服务器模拟Web浏览器,接收事件并在服务器状态下执行增量更改,这些更改会自动传播到客户端。
这种方法需要更多的服务器内存和服务器处理能力,但是优点是简化了开发模型,因为a)应用程序通常在服务器中完全编码,并且b)服务器中的数据和UI状态在相同的内存空间中共享而没有需要自定义的客户端/服务器通信桥。
这是有状态服务器方法的一种变体。客户机页面通常通过Ajax请求将表示其当前状态的数据发送到服务器。使用此数据,服务器能够重建需要修改的页面部分的客户端状态,并可以生成必要的数据或代码(例如JSON或JavaScript),然后将其返回给客户端以带来将其设置为新状态,通常会根据激发请求的客户端操作来修改页面DOM树。
此方法要求将更多数据发送到服务器,并且每个请求可能需要更多计算资源才能部分或完全重建服务器中的客户端页面状态。同时,此方法更容易扩展,因为服务器中没有保留每个客户端页面的数据,因此,可以将Ajax请求分派到不同的服务器节点,而无需会话数据共享或服务器关联。
某些SPA可以使用文件URI方案从本地文件执行。这使用户能够从服务器下载SPA并从本地存储设备运行文件,而无需依赖服务器连接。如果此类SPA要存储和更新数据,则必须使用基于浏览器的Web存储。这些应用程序受益于HTML5的高级功能。[13]
由于SPA是从最初设计用于浏览器的无状态页面重绘模型的演变而来的,因此出现了一些新的挑战。可能的解决方案(复杂性,全面性和作者控制各不相同)包括:[14]
由于某些流行的Web搜索引擎的搜寻器缺少JavaScript执行,[19] SEO(搜索引擎优化)历来为希望采用SPA模型的面向公众的网站提出了一个问题。[20]
在2009年至2015年之间,Google网站站长中心提出并推荐了一种“ AJAX抓取方案” [21] [22],该方法在有状态AJAX页面的片段标识符中使用了初始的感叹号#!
。SPA网站必须实施特殊行为,以允许搜索引擎的搜寻器提取相关元数据。对于不支持此URL哈希方案的搜索引擎,SPA的哈希URL保持不可见。在W3C上,包括Jeni Tennison在内的许多作家都将这些“哈希爆炸” URI视为有问题的,因为它们使无法在其浏览器中激活JavaScript的用户无法访问页面。它们还会破坏HTTP引用标头,因为浏览器不允许在Referer标头中发送片段标识符。[23]在2015年,Google弃用了其哈希爆炸式AJAX抓取建议。[24]
或者,应用程序可以在服务器上呈现第一页面加载,并在客户端上呈现后续页面更新。传统上这很困难,因为渲染代码可能需要在服务器和客户端上以不同的语言或框架编写。使用无逻辑模板,从一种语言到另一种语言进行交叉编译,或者在服务器和客户端上使用相同的语言可能有助于增加可以共享的代码量。
由于SEO兼容性在SPA中并非无关紧要,因此值得注意的是,SPA在搜索引擎索引不是必需的还是理想的情况下通常不使用。用例包括暴露隐藏在身份验证系统后面的私有数据的应用程序。在这些应用程序是消费产品的情况下,通常将经典的“页面重绘”模型用于应用程序着陆页和营销网站,该模型为应用程序提供了足够的元数据,以使其在搜索引擎查询中显示为热门。博客,支持论坛和其他传统的页面重绘工件通常位于SPA周围,可以为搜索引擎提供相关的术语。
以服务器为中心的Web框架(例如基于Java的ItsNat)使用的另一种方法是使用相同的语言和模板技术在服务器上呈现任何超文本。通过这种方法,服务器可以精确地知道客户端上的DOM状态,服务器中会生成所需的任何大页面或小页面更新,并由Ajax进行传输,这是将客户端页面带到执行DOM方法的新状态的确切JavaScript代码。 。开发人员可以决定Web Spider必须对SEO进行爬网的页面状态,并能够在加载时生成所需的状态,从而生成纯HTML而不是JavaScript。在ItsNat框架中,这是自动的,因为ItsNat将客户端DOM树作为Java W3C DOM树保留在服务器中。服务器中此DOM树的呈现会在加载时生成纯HTML,并为Ajax请求生成JavaScript DOM操作。这种双重性对SEO非常重要,因为开发人员可以使用相同的Java代码和纯基于HTML的模板来构建服务器中所需的DOM状态。在页面加载时,ItsNat会生成常规HTML,从而使该DOM状态与SEO兼容。从1.3版开始,[25] itsNat提供了一种新的无状态模式,并且客户端DOM不保留在服务器上,因为在使用无状态模式客户端时,基于状态发送的所需数据处理任何Ajax请求时,DOM状态在服务器上部分或完全重建了。客户端通知服务器当前的DOM状态;无状态模式也可能与SEO兼容,因为SEO兼容性发生在初始页面的加载时,不受有状态或无状态模式的影响。
有几种解决方法可以使它看起来像可爬网的网站。两者都涉及创建单独的HTML页面,以反映SPA的内容。服务器可以创建基于HTML的网站版本并将其交付给爬网程序,也可以使用无头浏览器(例如PhantomJS)运行JavaScript应用程序并输出结果HTML。
这两种方法都需要大量的工作,并且最终会给大型复杂站点带来维护上的麻烦。还有潜在的SEO陷阱。如果认为服务器生成的HTML与SPA内容相差太大,则该网站将受到处罚。运行PhantomJS来输出HTML可能会减慢页面的响应速度,而搜索引擎(尤其是Google)为此降低了排名。[26]
增加服务器和客户端之间可以共享的代码量的一种方法是使用诸如Mustache或Handlebars之类的无逻辑模板语言。可以从不同的宿主语言(例如服务器上的Ruby和客户端上的JavaScript)呈现此类模板。但是,仅共享模板通常需要重复业务逻辑用于选择正确的模板并使用数据填充它们。仅更新页面的一小部分(例如,大模板中输入的文本的值)时,从模板渲染可能会对性能产生负面影响。替换整个模板也可能会干扰用户的选择或光标位置,而仅更新更改的值可能不会。为了避免这些问题,应用程序可以使用UI数据绑定或精细的DOM操作来仅更新页面的适当部分,而不必重新呈现整个模板。
根据定义,SPA为“单个页面”,该模型使用“前进”或“后退”按钮破坏了浏览器的页面历史导航设计。当用户按下“后退”按钮,期望SPA中的上一个屏幕状态时,这会带来可用性障碍,但是,应用程序的单页将被卸载,并显示浏览器历史记录中的前一个页面。
SPA的传统解决方案是根据当前屏幕状态更改浏览器URL的哈希片段标识符。这可以通过JavaScript来实现,并导致在浏览器中建立URL历史记录事件。只要SPA能够从URL哈希中包含的信息中恢复相同的屏幕状态,就可以保留预期的后退按钮行为。
为了进一步解决此问题,HTML5规范引入了pushState和replaceState,以编程方式访问实际的URL和浏览器历史记录。
诸如Google Analytics(分析)之类的分析工具在很大程度上依赖于由新页面加载引发的整个新页面加载。SPA不能以这种方式工作。
加载第一页后,所有后续的页面和内容更改均由应用程序内部处理,该应用程序应简单地调用一个函数来更新分析包。未能调用该函数,浏览器永远不会触发新页面加载,浏览器历史记录也不会添加任何内容,并且分析包也不知道谁在做什么。
可以使用HTML5历史记录API将页面加载事件添加到SPA。这将有助于集成分析。困难在于管理此问题并确保正确跟踪所有内容–这涉及检查丢失的报告和重复输入。一些框架提供了针对大多数主要分析提供商的开源分析集成。开发人员可以将它们集成到应用程序中,并确保一切正常工作,但是无需从头开始做所有事情。[26]
单页应用程序的首页加载速度比基于服务器的应用程序慢。这是因为在将所需的视图在浏览器中呈现为HTML之前,第一个负载必须关闭框架和应用程序代码。基于服务器的应用程序仅需将所需的HTML推送到浏览器,从而减少延迟和下载时间。
有一些方法可以加快SPA的初始负载,例如在需要时使用繁重的方法来缓存和延迟加载模块。但是,这不可能摆脱这样一个事实,即它需要下载框架,至少一些应用程序代码,并且很可能会在浏览器中显示某些内容之前先为数据提供API。[26]这是“现在付款,或以后付款”的折衷方案。性能和等待时间的问题仍然是开发人员必须做出的决定。
SPA在初始页面加载中已完全加载,然后用按需从服务器加载的新页面片段替换或更新页面区域。为了避免过多下载未使用的功能,SPA通常会在需要时逐步下载更多功能,包括页面的小片段或完整的屏幕模块。
这样,在SPA中的“状态”与传统网站中的“页面”之间存在类比。因为同一页面中的“状态导航”类似于页面导航,所以从理论上讲,任何基于页面的网站都可以转换为单页面,而在同一页面中仅替换已更改的部分。
原文:https://www.cnblogs.com/it-Ren/p/13446317.html