Web应用开发中的几个问题
Introduction
由于Ajax技术在Gmail中的成功应用和高性能的V8引擎的推出使得编写Web应用变得流行 起来,使用前端技术也可以编写具有复杂交互的应用。相对于native应用,Web应用具 有如下优点:
- 跨平台,开发和维护成本低;
- 升级和发布方便,没有版本的概念,随时随地发布,用户没有感知,不需要安装;
- 响应式设计(Responsive Design)使得Web应用可以跨平台,同一份代码自适应各种 屏幕大小
- 即使最终不采用Web应用方案,也很适合开发原型
当然,Web应用也不是没有缺点。由于不同平台和厂商的浏览器并不完全一样,跨平台 也有一些兼容成本。另外,Web应用的性能不如native应用,交互有时候不是很流畅, 再加上HTML5的API上的限制,使得有些功能采用Web应用不太合适。由于这些原因,结 合两者优点的混合方案变得流行起来(比如微信、手机QQ和手机QQ浏览器中会嵌入一 些Web页面)。
根据笔者的开发经验,下面总结一些Web应用开发过程中的要面临的几个问题。
模块化编程
模块化编程是编写大规模应用必不可少的一个特性,与其它主流的编程语言相比 Javascript没有对模块提供直接的支持,更不用说维护模块之间的依赖关系,这使得维 护Javascript代码变得异常困难,在<script>标签中包含代码的顺序需要人工维护。
要支持模块化编程必须解决两个问题:
- 支持编写模块并为模块命名,防止名字冲突和全局变量的使用;
- 支持显示指定模块之间的依赖关系,并在程序执行时自动加载依赖的模块。
Douglas Crockford在”Javascript: The Good Parts”一书中提出的Module Pattern利 用Javascript的闭包技术来模拟模块的概念,防止名字冲突和全局变量的使用。这解 决了第一个问题。
1
2
3
4
5
6
7
8
9
|
var moduleName = function () { // Define private variables and functions var private = ... // Return public interface. return { foo: ... }; }(); |
为了解决第二个问题CommonJS组织定义了 AMD规范方便 开发者显示指定模块之间的依赖关系,并在需要时加载依赖的模块。 RequireJS是AMD规范的一个比较流行的实现。
首先我们在a.js中定义模块A.
1
2
3
4
5
6
|
define(function () { return { color: "black", size: 10 }; }); |
然后定义模块B依赖模块A.
1
2
3
|
define(["a"], function (A) { // ... }); |
当模块B执行时RequireJS保证模块A已被加载。具体细节可参考RequireJS官方文 档。
脚本加载
最简单的脚本加载方式是放在<head>加载。
1
2
3
4
|
<head> <script src="base.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> </head> |
其缺点是:
- 加载和解析是顺序是同步执行的,先下载base.js然后解析和执行,然后再下载 app.js;
- 加载脚本时还会阻塞对<script>之后的DOM元素的渲染。
为了缓解这些问题,现在的普遍做法是将<script>放在<body>的底部。
1
2
3
|
<script src="base.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> </body> |
但并不是所有的脚本都可以放在<body>的底部,比如有些逻辑要在页面渲染时执行, 不过大多数脚本没有这样的要求。
将脚本放在<body>底部仍然没有解决顺序下载的问题,一些浏览器厂商也意识到了 这个问题并开始支持异步下载。HTML5也提供了标准的解决方案:
1
2
|
<script src="base.js" type="text/javascript" async></script> <script src="app.js" type="text/javascript" async></script> |
标上async属性的脚本表明你没有在里面使用document.write之类的代码。浏览器 将异步下载和执行这些脚本,并且不会组织DOM树的渲染。但是这会导致另一个问题: 由于是异步执行,app.js可能在base.js之前执行,如果它们之间有依赖关系这将 导致错误。
讲到这里从开发者角度来看我们其实需要的是这些特性:
- 异步下载,不要阻塞DOM的渲染;
- 按照模块的依赖关系解析和执行脚本。
所以脚本的加载其实需要与模块化编程问题结合起来解决。RequireJS不仅记录了模 块之间的依赖关系,并且提供了根据依赖关系的按需加载和执行(详情请参考 RequireJS官方文档)。
关于脚本加载的更多方案请看 这里.
静态资源文件的部署
这里的静态资源文件是指CSS、Javascript和CSS需要的一些图片文件。它们的部署需 要考虑两个问题:
- 下载速度
- 版本管理
静态资源文件的一个特点变化不频繁,且与用户身份无关(即与Cookie无关),因此 很适合缓存。另一方面,一旦静态资源文件变化时,浏览器必须从Web服务器下载最新 的版本。当发布新版本的Web应用时,并不是所有用户马上就用上新版本,老版本和新 版本将会共存,这就涉及到版本匹配问题。老版本的应用需要下载老版本的CSS和 Javascript,新版本的应用需要下载新版本的静态资源。
- 为了防止版本不一致,每当发布新版本的应用时静态资源文件都需要改名,让旧的 HTML引用旧的静态文件,新的HTML引用新的静态文件。一个常见办法就是在文件名 中加时间戳;
- 为了防止悬挂引用,资源文件应该比HTML先发布。
上述方案可以解决版本问题,这样每个静态文件的缓存时间可以设置得任意大,防止 重复下载,同时在新版本发布时浏览器将及时更新。
为解决下载速度问题,可以考虑以下几个方案:
- 合并静态文件以免文件数量过多,过多的文件需要更多的连接来下载,浏览器通常 对同一个域名的连接数量有限制;
- 压缩静态文件;为了可读性,CSS和Javascript通常有很多空行、缩进和注释,这 些在发布时都可以去掉;
- 静态文件通常与Cookie没有关系,所以为了减小传输大小和增加缓存命中率(缓存 的key需要考虑Cookie),静态文件最好托管在没有Cookie的域名上;
最后也是最重要的,要使上述过程自动化。
MVC编程模型
Web应用采用的是事件驱动编程模型,与native应用是一样的,区别仅在于基础设施提 供的API不一样。UI编程通常采用MVC设计模式,以流行的 Backbone.js为例包括如下部分:
- Model
- 数据的唯一来源
- 负责获取和存储数据
- 可提供缓存机制
- 数据变化时通过事件通知其它对象
- View
- 负责渲染
- 监听UI事件和Model事件并重绘UI
- 渲染结果取决于两类数据:Model和UI交互状态
- UI的交互状态通常存在View对象中,有时候为了方便也存在DOM树节点中
- 为了降低渲染成本,尽量减少需要渲染的区域,每次当数据变化时只渲染受影响 的区域
- Router
- 负责监听URL的变化,并通知相应的View对象渲染页面
为了有效地使用MVC,有几个问题需要注意。
Model应与View完全隔离
Model仅提供数据的访问,不应该依赖View,因此Model不应该知道View的存在。所以 Model不能持有对任何View对象的引用。Model的数据发生变化时只能通过事件通知 View.
View在初始化时采用委派方式监听UI事件
这里有两个关键点:
- 在初始化时监听事件var View = Backbone.View.extend({ initialize: function () { this.$el.on(‘click’, ‘#id’, function () { // … }); } });
除了一些特殊情况外(请看下文),所有UI事件都应该在View初始化时初始化,防止同 一个事件被绑定多次。即使有些事件是动态监听的(有时候需要监听,有时候有不需要 监听,比如有些按钮有时候是有效的,有时候又无效),也需要在初始化时监听,然后 在事件回调函数里判断是否需要处理。这样逻辑更简单,更容易维护。
- 采用委派方式监听UI事件
关于委派方式监听请参考jQuery文档.
上面已强调要在初始化时监听事件,但是初始化时需要监听的DOM节点可能还不存在, 所以没法直接绑定事件,只能采用委派方式。不过采用委派方式要求事件可以冒泡。
对于那些没法冒泡的事件(比如<img>的load事件)只能在保证其存在的情况下直 接绑定,而不一定要在初始化时绑定。
复杂的View组织成树形层次结构
函数太大了需要拆分成几个子函数。同样,View的逻辑如果过于复杂也应根据页面结 构拆成几个子View:
- 父View通过引用访问子View,但是子View不应该持有父View的引用;
- 子View只负责自己区域的渲染,其它区域由父View负责渲染;
- 父View通过函数调用访问子View的功能,子View通过事件与父View通信;
- 子View之间不能直接通信。
其它技巧可查看 Backbone技巧与模式.
离线应用缓存
为使Web应用体验更加流畅,可考虑使用HTML5离线应用缓存,不过有以下几点需要注 意:
- 不要将离线应用缓存与HTTP缓存机制搞混淆,前者是HTML5引入的新特性,与HTTP缓 存机制是相互独立并存的;
- Cache manifest文件不应被HTTP缓存太久(通过HTTP头Cache-Control控制缓存 时间),否则发布新版后浏览器不会及时监测到变化并下载新文件;
- 在Cache manifest文件的NETWORK节放一个*,否则没有列在这个文件的资源不 会被请求;
- 不适合缓存的请求最好都放在NETWORK节;
- 如果之前使用过离线应用缓存现在不想再使用了,从<html>删除manifest属性, 并发送404响应给manifest文件请求。仅仅删除manifest属性是没有效的。
线上错误报告
Javascript是一个动态语言,许多检查都是在运行时执行的,所以大多数错误只有执 行到的时候才能检查到,只能在发布前通过大量测试来发现。即使这样仍可能有少数 没有执行到的路径有错误,这只能通过线上错误报告来发现了。
1
2
3
4
5
6
7
|
window.onerror = function (errorMsg, fileLoc, linenumber) { var s = 'url: ' + document.URL + '\nfile: ' + fileLoc + '\nline number: ' + linenumber + '\nmessage: ' + errorMsg; Log.error(s); // 发给服务器统计监控 console.log(s); }; |
通常线上的Javascript都是经过了合并和压缩的,上报的文件名和行号基本上没法对 应到源代码,对查错帮助不是很大。不过最新版的Chrome支持在onerror的回调函数 中获取出错时的栈轨迹:window.event.error.stack.
Web应用开发中的几个问题的更多相关文章
- web前端开发中常用的尺寸和位置
我们在日常web前端开发过程中,会经常用到各种尺寸和位置.通常是js做动画的时候.轮播图,滚屏动画,粒子,碰撞检测,拖拽,滚动加载等等.这里我将常用的尺寸和位置的获取进行总结,不包括canvas,SV ...
- WEB前端开发中的图片压缩
web前端开发中,图片的重要性不言而喻,而由于一些图片的大小加上现在国内的网速不给力等种种原因,我们非常有必要对网站使用的图片进行压缩,压缩图片必然会带来图片质量的损失,我们要尽可能的在质量降低很小的 ...
- Java Web应用开发中的一些概念
最近在学习Java Web,发现Java Web的概念很多,而且各个概念之间的关系也挺复杂,本篇博客把这些关系总结于此,主要参考的博客附在文章末尾. 概念 服务器 服务器,硬件角度上说就是一台高性能的 ...
- Web前端开发中的MCRV模式(转)
作者: izujian 来源: baiduux 摘要:针对前端开发中基于ajax的复杂页面开发所面临的代码规模大,难以组织和维护,代码复用性.扩展性和适应性差等问题,本文尝试以MVC思想为 基础,结 ...
- Web前端开发中的小错误
Web前端开发中的小错误 错误1:表单的label标签跟表单字段没有关联 利用“for”属性允许用户单击label也可以选中表单中的内容.这可以扩大复选框和单选框的点击区域,非常实用. 错误2:log ...
- px em rem在WEB前端开发中的区别
我们都知道基于像素的字体大小所用的单位是px,但是随着响应式设计的不断火热,基于相对字体大小的单位em变开始流行起来.当然,rem也在Web前端开发人员讨论如何更好设置字体大小的讨论话题之列.是不是需 ...
- web前端开发中的浏览器兼容性总结
1.居中问题 div里的内容,IE默认为居中,而FF默认为左对齐,可以尝试增加代码margin: 0 auto; 2.高度问题 两上下排列或嵌套的div,上面的div设置高度(height),如果di ...
- WEB前端开发中的SEO注意点
近几年来,SEO在国内得到了蓬勃的发展,其中很多的SEO技术越来越体现在web前端的一些细节上.要做好SEO,WEB前端这一块也要做必不可少的优化. 这就要求我们WEB前端工程师在开发页面的时候,要写 ...
- web移动开发中如何实现图标点击态的蒙层效果
webapp开发中经常需要加入点击二态,即用户点击(tap)页面某个部分时该部分的样式进行相应的变化来相应用户的点击操作,这样能够带来更好的用户体验,今天我们要讨论的是如何给图标加上点击的二态效果. ...
随机推荐
- Hadoop到底能做什么?怎么用hadoop?
hadoop是什么?(1)Hadoop是一个开源的框架,可编写和运行分布式应用处理大规模数据,是专为离线和大规模数据分析而设计的,并不适合那种对几个记录随机读写的在线事务处理模式.Hadoop=HDF ...
- 由RS-232串口到PROFIBUS-DP总线的转换接口设计
转自:http://gongkong.ofweek.com/2013-08/ART-310007-11001-28716256_2.html 1.PROFIBUS-DP网络协议 PROFIBUS的网络 ...
- cobbler技术详解(是PXE二次详解)
Cobbler是PXE的二次封装,使用Python语言开发, 可以用来快速建立 Linux 网络安装环境,它已将 Linux 网络安装的技术门槛,从大专以上文化水平,成功降低到初中以下,连补鞋匠都能学 ...
- Envoy如何打败Linkerd成为L7负载平衡器的最佳选择?
本文转自:http://www.servicemesh.cn/?/article/41 作者:MIKE WHITE 翻译:姚炳雄 原文:Using Envoy to Load Balance gRPC ...
- SQL中字符串截取、连接、替换等函数的用法
一.SQL中SUBSTRING函数的用法1.功能:返回字符.二进制.文本或图像表达式的一部分2.语法:SUBSTRING ( expression, start, length )3.QL 中的 su ...
- JVM内存管理机制
Java与C++之间有一堆由内存动态分配与垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来. —— <深入理解Java虚拟机:JVM高级特性与最佳实践> Java虚拟机在 ...
- 【Java并发编程】之一:可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- PGM学习之二 PGM模型的分类与简介
废话:和上一次的文章确实隔了太久,希望趁暑期打酱油的时间,将之前学习的东西深入理解一下,同时尝试用Python写相关的机器学习代码. 一 PGM模型的分类 通过上一篇文章的介绍,相信大家对PGM的定义 ...
- BZOJ5092 分割序列(贪心)
设si为该序列的异或前缀和,则显然相当于求Σmax{sj+sj^si} (i=1~n,j=0~i).从高位到低位考虑,如果该位si为1,无论sj怎么填都是一样的:如果该位si为0,则sj该位应尽量为1 ...
- Min Cost Climbing Stairs - LeetCode
目录 题目链接 注意点 解法 小结 题目链接 Min Cost Climbing Stairs - LeetCode 注意点 注意边界条件 解法 解法一:这道题也是一道dp题.dp[i]表示爬到第i层 ...