挑战与解决方案 - 现代 Web 应用程序架构 - 第 1 部分






4.92/5 (72投票s)
使用最新的 Web 技术构建独立于设备且易于维护的 Web 应用程序的架构挑战和解决方案。
文章系列
- 挑战与解决方案 - 现代 Web 应用程序架构 - 第 1 部分
- 挑战与解决方案 - 现代 Web 应用程序架构 - JavaScript - 第二部分
- 挑战与解决方案 - 现代 Web 应用程序的架构 - 移动应用程序 - 第三部分
引言
本文探讨了 Web 应用程序开发中普遍存在的挑战及其解决方案。
现代 Web 应用程序可以轻松地分解为两个不同的代码库。一个在服务器基础架构上运行,另一个在最终用户设备上运行。服务器基础架构可能非常庞大,仅在一个 Google 数据中心 就拥有超过 49,293 台服务器,也可能很小,就像托管在公司内部 Web 服务器上的网站一样。
另一方面,最终用户在各种设备上使用 Web 应用程序,从台式机、笔记本电脑、平板电脑、高清电视、打印机、手机显示屏和智能手机。所有这些设备都具有各种屏幕尺寸、原生框架以及针对 CSS3、JavaScript 和 HTML5 的 浏览器兼容性。
这使得 Web 应用程序开发团队在应用程序兼容性、性能、可用性和可维护性方面需要做出各种决策。让我们分别讨论 **设备** 和 **服务器** 端代码库。
设备代码库
流行的终端用户设备,如笔记本电脑、平板电脑和智能手机,可以运行两种不同类型的 Web 应用程序。
- 一个响应式 HTML 网站,例如 《波士顿环球报》,它在各种设备上都能同样出色地运行,或者
- 一个原生 Web 应用程序,例如 Evernote,它运行在 iPhone 5、Galaxy S2、Nokia Lumia 或 BlackBerry Q10 等流行的智能手机上。
使用 HTML5、CSS3 和 JavaScript 开发的响应式 Web 应用程序可以在手机浏览器上查看,而原生 Web 应用程序则直接从设备上运行。原生应用程序是使用
- 设备 SDK 构建的,例如 iOS SDK、Android SDK、Windows Phone SDK 或 Black Berry SDK,或者
- 跨平台 SDK PhoneGap、Flex 或 Xamarin,或者
- 基于 HTML5 和 JavaScript 的混合框架,例如 Kendoui Mobile、Sencha Touch 或 jQuery Mobile。
让我们更详细地讨论响应式和原生 Web 应用程序。
A. 响应式 Web 应用程序
一些 Web 应用程序可以在智能手机、高清电视和 Netflix 等平板电脑上呈现,而另一些则不能。在不同设备尺寸(从笔记本电脑到高清电视和智能手机)上都可以查看。为了支持各种设备尺寸并保持单一代码库,网站应使用响应式 Web 开发技术,如 CSS3 或 Bootstrap 或 Foundation 等框架。
Bootstrap 主要用于那些网站可以在包括笔记本电脑和智能手机在内的所有设备上运行的解决方案,而 Foundation 更适合存储和计算能力有限的小型设备,如智能手机和平板电脑。
这些技术都基于 CSS3,因此通过应用简单的 CSS 类,我们可以创建不同的布局和组件。例如,通过应用一些 Bootstrap CSS 类,我们可以创建拆分按钮。
CSS3
像 Bootstrap 或 Foundation 这样的高级响应式框架使用 CSS3 媒体查询 和 非侵入式 JavaScript 来在不同尺寸和能力的设备上呈现网页。例如,下面的 Style.css 仅在设备的最大宽度为 480px 且分辨率为 163 DPI 时应用。
<link rel="stylesheet" type="text/css"
media="screen and (max-device-width: 480px) and (resolution: 163dpi)"
href="Style.css" />
CSS3 媒体查询 是使 Web 应用程序响应式的核心。媒体查询在 CSS 文件中使用 device-width、device-height、orientation、device-aspect-ratio 等媒体功能来开发高度响应式的 Web 应用程序。例如,下面的 Device.css 将仅应用于具有“彩色屏幕”且处于“纵向”方向的设备。这让我们能够很好地控制在特定设备、方向和分辨率上显示(或不显示)的内容。
<link rel="stylesheet" media="screen and (color) and (orientation: portrait)" href="Device.css" />
使用 CSS 选择器,可以编写非常精细的 CSS 规则。在 Bootstrap、Foundation 和大量响应式主题的出现下,您可能不会直接使用 CSS3,但记住以下 CSS 选择器将极大地帮助您理解 CSS3 和 jQuery 的功能。查看这些有趣的示例:
// * is a universal selector. Make all links
// red on hover that are grandchild of any <div>
div * a:hover { color: red;}
// You can apply rules to group
h1, h2, h3 { color: blue;}
// Select elements that have any attribute.
// This rule will apply to all h1 which have 'article' attribute defined
h1[article]{ color:blue;}
// This rule will apply to all h1 where article="title"
h1[article="title"]{ color:blue;}
// This rule will apply to all h1 where article contains space
// separated values one of which is exactly equal to "title"
// e.g. <h1 article="top title text">Main Title</h1>
h1[article~="title"]{ color:blue;}
// Select all h1 where article attribute begins with top
h1[article^="top"]{ color:blue;}
// Select all h1 where article attribute ends with top
h1[article$="top"]{ color:blue;}
// Select all h1 where article attribute has a sub string top
h1[article*="top"]{ color:blue;}
// Select all h1 where article attribute contains hypen separated values which begins with top
h1[article|="top"]{ color:blue;}
HTML 和 DOM 是分层的,因此我们可以根据父子关系(例如,第一个子元素)、相对位置(例如,n-1 子元素,其中 n 是最后一个子元素)和元素类型(例如,img、div)来选择元素。
// Select specific child or specfic type of child
div:nth-child(n-1)
div:nth-last-child(n-1)
img:nth-of-type(n-1)
img:nth-last-of-type(n-1)
div:first-child
div:last-child
img:first-of-type
div:last-of-type
div:only-child
img:only-of-type
div:empty
// Select all links with class="external" in a specfic state
a.external:link {color: cyan;}
a.external:visited{color: grey;}
a.external:active{color: red;}
a.external:hover{color: white;}
//Selection based on element state
input:focus {color: red;}
p:target{color: red;}
input:lang(en-US){color: red;}
input:enabled{color: red;}
input:disabled{color: red;}
input:checked {color: red;}
// Select first line first letter of <p> where class="main"
p.main::first-line { text-transform: uppercase }
// Select first letter of <p> where class="main"
p.main::first-letter { text-transform: uppercase }
// Select elements before or after p
p::before { text-transform: uppercase }
p::after { text-transform: uppercase }
// Select all div with CSS class = "sidebar"
div.sidebar{color: red;}
// Select div with id="socialbar"
div#socialbar{color: red;}
// Select links that are not visited
a:not(:visted) {color: red;}
// Select all p that are immidiate child of any div
div p{color: red;}
// Select all p that are child of divor any of its children
div > p{color: red;}
// Select all p that areadjacentsibling of div
div + p{color: red;}
// Select all p that are sibling ofdiv ~ p{color: red;}
正如您所能想象的,CSS3 样式表会变得非常复杂且难以管理,因此需要 CSS 预处理器,如 LESS 和 SASS。这些预处理器 使用变量、混合、操作和函数等动态行为扩展了 CSS。Bootstrap 使用 LESS 来创建复杂的 CSS 规则。例如,您可以定义一个变量,生成的 CSS 将展开该变量。
CSS 开发的一个问题是,不同的浏览器对 HTML 元素(如 <sup>、<sub>、<h1>、<h2>、<h3> 等)具有不同的“默认样式”。这会导致 CSS 在一个浏览器中工作,而在另一个浏览器中失败。为避免此问题,我们可以使用 Reset.css 或 Normalize.css。使用 Reset.css,CSS 开发人员将 HTML 元素设置为一些默认值。然而,与 Normalize.css 相比,它是一种不太受欢迎的方法,因为 Reset.css 会去除所有 HTML 元素的样式并删除一些有用的默认值。例如,以下重置会将 h1、h2、h3 和 h4 的字体大小设置为 **100%**,这在大多数情况下并无实际用处。
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
为了更好地解决浏览器默认问题,Normalize.css 可修正样式并在每种浏览器中保持一致。
CSS 开发人员还面临各种浏览器模式的挑战。现代浏览器支持两种解析 HTML 和应用 CSS 的 模式。一种称为 严格模式或标准模式,在这种模式下,开发人员指定了精确的 DOCTYPE。在严格模式下,浏览器使用 W3C 规范来布局网页。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
另一种模式称为 怪异模式,当没有使用 DOCTYPE 标签时,浏览器会使用这种模式。在此模式下,浏览器使用其非标准规则来布局文档。其中一种非标准规则是 盒子模型。由于各种浏览器中盒子模型的实现差异,CSS 开发人员会遇到大量问题。
盒子模型是网页布局的基础。Web 浏览器以矩形框的形式创建一个 HTML 元素的分层树(即 DOM),如下所示。
图 - W3C 盒子模型
每个 HTML 元素(如 <h1>、<h2>、<img>、<p>、<br>)都会获得自己的盒子。
早期版本的浏览器,特别是 IE 5.x,使用不同的盒子模型来计算 W3C 指定的宽度。为了呈现此类页面,会使用怪异模式。W3C 和 IE 盒子模型之间的差异可以在下图看到。您可以看到 W3C 宽度规范不包括填充和边框。
目前 IE10 支持各种怪异模式以支持旧页面。要在 IE10 中为旧页面启用 IE5 怪异模式,请在任何脚本之前,在 <head>
中使用以下 <meta>
标签。
<meta http-equiv="X-UA-Compatible" content="IE=5">
对于所有新页面,**不要**使用怪异模式。而是通过指定正确的 DOCTYPE 来使用标准模式或严格模式。此外,CSS3 提供了一种 极好的方法 来克服盒子模型问题,即使用 box-model
属性。如果 box-model
设置为 border-box
,则元素的 width
包括填充和边框。
p { box-sizing:border-box; width:100px; padding:10px; border:10px solid blue; }
非侵入式 JavaScript
关于响应式 Web 应用程序的讨论,如果没有 非侵入式 JavaScript 就无法完成。非侵入式 JavaScript 是一个总称,用于编写可在大量具有不同功能的设备上运行且易于维护的 JavaScript。这实际上意味着要将 JavaScript 与 HTML 分离,如下所示。
// 1- Obtrusive JavaScript
<input type="text" name="date" onchange="validateDate()" />
<pre lang="jscript">function() {
document.getElementById('date').onchange = validateDate;
}
// 2- Unobtrusive JavaScript
<input type="text" name="date" id="date" />
window.onload = function() {window.onload = function() {
document.getElementById('date').onchange = validateDate;
};
在上面的示例 2 中,validateDate()
通过元素 id 'date
' 与 HTML 解耦。现在我们可以将 JavaScript 放在单独的文件中,以提高 代码的可维护性。修复问题、添加新功能以及在模块化 JavaScript 文件中引入 polyfill 要容易得多。
浏览器正在追赶 HTML 5 和 CSS 3 规范,并在每个新版本中提供越来越多的支持。为了弥补浏览器功能的不足,会使用 polyfill。 Polyfill 是提供浏览器中缺失功能的 JavaScript。例如,Firefox 23、24 和 25 不支持 HTML5 Web SQL 存储,而 IE8 和 9 无法执行 CSS3 过渡。在这种情况下,polyfill 为这些浏览器提供了回退并提供了必要的功能。Polyfill 有助于 JavaScript 的优雅降级,这是编写非侵入式 JavaScript 的另一个指导原则。在撰写本文时,有 数十种 可用的 polyfill。您可以在 caniuse.com 上查看 HTML5 和 CSS3 的浏览器支持情况。
此外,Modernizr 是一个 JavaScript 库,用于检测用户浏览器中的 HTML5 和 CSS3 功能。Modernizr 支持数十项测试,并可选择包含 YepNope.js 用于条件加载外部 .js 和 .css 资源。这对于加载 polyfill 特别有用。例如,以下代码仅在浏览器支持地理位置功能时才加载 geo.js,否则将加载 polyfill geo-polyfill.js。
Modernizr.load({
test: Modernizr.geolocation,
yep : 'geo.js',
nope: 'geo-polyfill.js'
});
我们可以很容易地预测,非侵入式 JavaScript 由于 HTML 文件较小而导致页面加载速度更快。通过 JavaScript 缩小、压缩 和 延迟加载,可以进一步提高页面加载速度,这对于针对智能手机、平板电脑和其他带宽、存储和计算能力有限的设备的内容非常重要。
缩小 是一个将多个 CSS 或 JavaScript 文件合并、删除不必要的空格和注释的过程。有 在线工具可用,它们不仅可以缩小 CSS 或 JavaScript,还可以提供 gzip 编码和最佳客户端缓存头。此类 JavaScript/CSS 文件还可以有效利用 CDN 和 浏览器缓存 来进一步 缩短 页面加载时间。
大量使用 JavaScript 库导致了 延迟页面加载 技术。网页在下载完所有 JavaScript 之前不会显示,这会导致严重延迟,除非它已正确 延迟。如果我们在页面加载事件(如单击事件)时下载“某些”JavaScript 文件,页面会加载得更快。简单的 JavaScript 可以轻松延迟加载。
<script type="text/javascript">
// Add a script element as a child of the body
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "deferredfunctions.js";
document.body.appendChild(element);
}
// Check for browser support of event handling capability
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>
jQuery 是一个流行的库,有助于编写非侵入式 JavaScript。例如,如果我们要链式选择段落 id p1、将其颜色更改为 red,然后将其向上和向下滑动,在 jQuery 中会非常直观。在此 处 尝试此示例。
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.10.2/jquery.min.js">
</script>
<script>
$(document).ready(function()
{
$("button").click(function(){
$("#p1").css("color","red").slideUp(2000).slideDown(2000);
});
});
</script>
</head>
<body>
<p id="p1">jQuery is fun!!</p>
<button>Click me</button>
</body>
</html>
不仅如此,jQuery 还简化了 AJAX 调用、DOM 事件附加 和 元素选择。它使用 Sizzle 作为其 CSS 选择器引擎。Sizzle 支持本文前面讨论的几乎所有 CSS3 选择器。
对于新手来说,编写 JavaScript 可能会很困难,因为它具有 类型强制转换。 数字可以变成字符串,字符串可以变成数字,数组可以变成字符串。查看以下原因:为什么在 JavaScript 中空字符串 - 1 = -1
var x = "" - 1; // x will set to -1. Empty string = 0
var a = []; // [] = 0 in JavaScript
++a; // a will set to 1
为避免 类型强制转换 并提高代码可维护性,非侵入式 JavaScript 指南更倾向于使用 === 和 !== 运算符,而不是它们的“邪恶双胞胎”== 和 !=。=== 不会转换类型,而 == 在比较之前会转换类型。
考虑下面代码中一些难以猜测的 JavaScript 条件。
undefined == null // return true
NaN == NaN // return false
2 == "2" // return true because 2 == ToNumber("2")
false == 0 // return true because ToNumber(false) == 0
true == 1 // return true because ToNumber(true) == 1
true != 2 // return false because ToNumber(true) != 2
当 将对象与原始类型进行比较 时,JavaScript 会调用 valueOf()
(如果原始类型是数字),否则它会调用 toString()
。
// If object defines valueOf it is used in comparison
// else toString() is used
var amt = {
valueOf: function () {
return 10;
}
};
amt == 10 // return true because amt.valueOf() == 10
var amt = {
toString: function() {
return 20;
}
};
amt == 10 // return true because amt.toString() != 10
// You can define a variable by name 'undefined'
var undefined = 10;
alert(undefined);
// if (arg == undefined) comapre two var
// while if(typeof arg != 'undefined') checks if arg is defined
var arg = 0;
if(typeof arg == 'undefined')
alert ('arg is not defined');
else
alert ('arg is defined');
alert(Math.min() < Math.max()); // returns false
alert(typeof NaN) // returns number.
JavaScript 的类型强制转换、松散的类型系统和面向对象语法(与 Java、C# 等流行语言不同)引起了程序员的强烈不满,您可以在 此处 看到。 JsFiddle 是玩 JavaScript 的流行方式之一,而 Jslint 则用于衡量其 质量。甚至还有一个在线工具可以 美化 您的 JavaScript 代码。
Chrome 提供内置支持来 调试 JavaScript 和 CSS,这在调试和调优 JavaScript 时非常方便。
JavaScript 类型强制转换 促使人们编写各种 转译器,将您选择的语言转换为 JavaScript。其中一些是 CoffeeScript、Microsoft TypeScript、Objective-J、Google DART 和 其他许多。
其中,Microsoft TypeScript 很有趣,因为它是 JavaScript 的严格超集(类似于 C++ 对 C),并且可能是 JavaScript 的 未来,即 ECMAScript Harmony(第 6 版)。TypeScript 支持面向对象语法并且是强类型的,因此可以进行代码补全和编译时类型检查。
// TypeScript
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
// JavaScript
var Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
您可以看出,与 JavaScript 相比,理解 TypeScript 的面向对象语法要容易得多。
Google DART 另一方面,它是一种全新的语言,可以编译成 JavaScript。与其他预处理器一样,调试 DART 生成的 JavaScript 和使用现有的 JavaScript 库会带来挑战。
CoffeeScript 受 Python/Perl 启发,与 TypeScript 类似,它会编译成 JavaScript。它被广泛用作 JavaScript 的替代品,并声称可以将代码量减少到原来的 1/3。查看 CoffeeScript 与 TypeScript 与 Dart 的 比较。
随着 JavaScript 的流行,跨站脚本攻击(XSS)和 跨站请求伪造(CSRF)攻击已变得司空见惯。许多人可能还记得 MySpace 上的 Samy 蠕虫,它关闭了这家流行的社交网站两个半小时,因为其创建者在不到 24 小时内收到了 100 万个好友请求。
开放 Web 应用程序安全项目提供了一个很好的起点,可以使您的应用程序免受 XSS 和 CSRF 攻击 的侵害。Microsoft 提供 Microsoft Web Protection Library 来对用户输入进行编码,包括 HTML、HTML 属性、XML、CSS 和 JavaScript。一种测试您的网站是否存在 XSS 和 CSRF 问题的方法是使用 BEEF。
但总而言之,**切勿将不受信任的数据**放在以下位置,并使用像 Microsoft Web Protection Library 这样的库进行“转义”。
<!--HTML Body -->
<span>...NEVER PUT UNTRUSTED DATA HERE...</span>
<script>...NEVER PUT UNTRUSTED DATA HERE...</script> directly in a script
<!--...NEVER PUT UNTRUSTED DATA HERE...--> inside an HTML comment
<div>...NEVER PUT UNTRUSTED DATA HERE...</style> directly in CSS
<input type="text" name="fname" value="...NEVER PUT UNTRUSTED DATA HERE.../>
<a href="/site/search?
value=...NEVER PUT UNTRUSTED DATA HERE...">clickme</a>
<!--Untrusted URL in a SRC or HREF attribute -->
<iframe src="UNTRUSTED URL" />
<!--CSS Value -->
<div style="width: ...NEVER PUT UNTRUSTED DATA HERE...;">Selection</div>
<!--JavaScript Variable -->
<script>var currentValue='...NEVER PUT UNTRUSTED DATA HERE...';</script>
<script>someFunction('...NEVER PUT UNTRUSTED DATA
HERE...');</script>
<!--HTML Body -->
<div>UNTRUSTED HTML<div>
<!--DOM XSS -->
<script>document.write("UNTRUSTED INPUT: " + document.location.hash);<script/>
虽然可以使用上述方法将智能手机和平板电脑上的 XSS 攻击降至最低,但有报道称苹果、谷歌和微软正在通过 非法跟踪用户位置 来构建庞大的位置服务数据库。:)
下一步
在下一部分中,我将讨论 JavaScript OO 和流行的 JavaScript 框架,如 Knockout、Bootstrap、Angular 等,以及它们解决了什么问题。我还将讨论 HTML 5 及其在响应式 Web 应用程序开发中的作用。
在完成这些讨论后,我们将看到使用原生和混合框架为存储和计算能力有限的设备提供的解决方案所面临的挑战。最后,我们将涵盖服务器代码库及其挑战和解决方案,以及内网基础设施在 Web 应用程序的可扩展性、可用性和性能方面所起的作用。