Introduction

  由于Ajax技术在Gmail中的成功应用和高性能的V8引擎的推出使得编写Web应用变得流行 起来,使用前端技术也可以编写具有复杂交互的应用。相对于native应用,Web应用具 有如下优点:

  • 跨平台,开发和维护成本低;
  • 升级和发布方便,没有版本的概念,随时随地发布,用户没有感知,不需要安装;
  • 响应式设计(Responsive Design)使得Web应用可以跨平台,同一份代码自适应各种 屏幕大小
  • 即使最终不采用Web应用方案,也很适合开发原型

  当然,Web应用也不是没有缺点。由于不同平台和厂商的浏览器并不完全一样,跨平台 也有一些兼容成本。另外,Web应用的性能不如native应用,交互有时候不是很流畅, 再加上HTML5的API上的限制,使得有些功能采用Web应用不太合适。由于这些原因,结 合两者优点的混合方案变得流行起来(比如微信、手机QQ和手机QQ浏览器中会嵌入一 些Web页面)。

  根据笔者的开发经验,下面总结一些Web应用开发过程中的要面临的几个问题。

 模块化编程

  模块化编程是编写大规模应用必不可少的一个特性,与其它主流的编程语言相比 Javascript没有对模块提供直接的支持,更不用说维护模块之间的依赖关系,这使得维 护Javascript代码变得异常困难,在<script>标签中包含代码的顺序需要人工维护。

  要支持模块化编程必须解决两个问题:

  1. 支持编写模块并为模块命名,防止名字冲突和全局变量的使用;
  2. 支持显示指定模块之间的依赖关系,并在程序执行时自动加载依赖的模块。

  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>

  其缺点是:

  1. 加载和解析是顺序是同步执行的,先下载base.js然后解析和执行,然后再下载 app.js;
  2. 加载脚本时还会阻塞对<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之前执行,如果它们之间有依赖关系这将 导致错误。

  讲到这里从开发者角度来看我们其实需要的是这些特性:

  1. 异步下载,不要阻塞DOM的渲染;
  2. 按照模块的依赖关系解析和执行脚本。

  所以脚本的加载其实需要与模块化编程问题结合起来解决。RequireJS不仅记录了模 块之间的依赖关系,并且提供了根据依赖关系的按需加载和执行(详情请参考 RequireJS官方文档)。

  关于脚本加载的更多方案请看 这里.

 静态资源文件的部署

  这里的静态资源文件是指CSS、Javascript和CSS需要的一些图片文件。它们的部署需 要考虑两个问题:

  1. 下载速度
  2. 版本管理

  静态资源文件的一个特点变化不频繁,且与用户身份无关(即与Cookie无关),因此 很适合缓存。另一方面,一旦静态资源文件变化时,浏览器必须从Web服务器下载最新 的版本。当发布新版本的Web应用时,并不是所有用户马上就用上新版本,老版本和新 版本将会共存,这就涉及到版本匹配问题。老版本的应用需要下载老版本的CSS和 Javascript,新版本的应用需要下载新版本的静态资源。

  1. 为了防止版本不一致,每当发布新版本的应用时静态资源文件都需要改名,让旧的 HTML引用旧的静态文件,新的HTML引用新的静态文件。一个常见办法就是在文件名 中加时间戳;
  2. 为了防止悬挂引用,资源文件应该比HTML先发布。

  上述方案可以解决版本问题,这样每个静态文件的缓存时间可以设置得任意大,防止 重复下载,同时在新版本发布时浏览器将及时更新。

  为解决下载速度问题,可以考虑以下几个方案:

  1. 合并静态文件以免文件数量过多,过多的文件需要更多的连接来下载,浏览器通常 对同一个域名的连接数量有限制;
  2. 压缩静态文件;为了可读性,CSS和Javascript通常有很多空行、缩进和注释,这 些在发布时都可以去掉;
  3. 静态文件通常与Cookie没有关系,所以为了减小传输大小和增加缓存命中率(缓存 的key需要考虑Cookie),静态文件最好托管在没有Cookie的域名上;

  最后也是最重要的,要使上述过程自动化。

 MVC编程模型

  Web应用采用的是事件驱动编程模型,与native应用是一样的,区别仅在于基础设施提 供的API不一样。UI编程通常采用MVC设计模式,以流行的 Backbone.js为例包括如下部分:

  1. Model

    • 数据的唯一来源
    • 负责获取和存储数据
    • 可提供缓存机制
    • 数据变化时通过事件通知其它对象
  2. View
    • 负责渲染
    • 监听UI事件和Model事件并重绘UI
    • 渲染结果取决于两类数据:Model和UI交互状态
    • UI的交互状态通常存在View对象中,有时候为了方便也存在DOM树节点中
    • 为了降低渲染成本,尽量减少需要渲染的区域,每次当数据变化时只渲染受影响 的区域
  3. Router
    • 负责监听URL的变化,并通知相应的View对象渲染页面

  为了有效地使用MVC,有几个问题需要注意。

  Model应与View完全隔离

  Model仅提供数据的访问,不应该依赖View,因此Model不应该知道View的存在。所以 Model不能持有对任何View对象的引用。Model的数据发生变化时只能通过事件通知 View.

  View在初始化时采用委派方式监听UI事件

  这里有两个关键点:

  1. 在初始化时监听事件var View = Backbone.View.extend({ initialize: function () { this.$el.on(‘click’, ‘#id’, function () { // … }); } });

  除了一些特殊情况外(请看下文),所有UI事件都应该在View初始化时初始化,防止同 一个事件被绑定多次。即使有些事件是动态监听的(有时候需要监听,有时候有不需要 监听,比如有些按钮有时候是有效的,有时候又无效),也需要在初始化时监听,然后 在事件回调函数里判断是否需要处理。这样逻辑更简单,更容易维护。

  1. 采用委派方式监听UI事件

  关于委派方式监听请参考jQuery文档.

  上面已强调要在初始化时监听事件,但是初始化时需要监听的DOM节点可能还不存在, 所以没法直接绑定事件,只能采用委派方式。不过采用委派方式要求事件可以冒泡。

  对于那些没法冒泡的事件(比如<img>的load事件)只能在保证其存在的情况下直 接绑定,而不一定要在初始化时绑定。

  复杂的View组织成树形层次结构

  函数太大了需要拆分成几个子函数。同样,View的逻辑如果过于复杂也应根据页面结 构拆成几个子View:

  1. 父View通过引用访问子View,但是子View不应该持有父View的引用;
  2. 子View只负责自己区域的渲染,其它区域由父View负责渲染;
  3. 父View通过函数调用访问子View的功能,子View通过事件与父View通信;
  4. 子View之间不能直接通信。

  其它技巧可查看 Backbone技巧与模式.

 离线应用缓存

  为使Web应用体验更加流畅,可考虑使用HTML5离线应用缓存,不过有以下几点需要注 意:

  1. 不要将离线应用缓存与HTTP缓存机制搞混淆,前者是HTML5引入的新特性,与HTTP缓 存机制是相互独立并存的;
  2. Cache manifest文件不应被HTTP缓存太久(通过HTTP头Cache-Control控制缓存 时间),否则发布新版后浏览器不会及时监测到变化并下载新文件;
  3. 在Cache manifest文件的NETWORK节放一个*,否则没有列在这个文件的资源不 会被请求;
  4. 不适合缓存的请求最好都放在NETWORK节;
  5. 如果之前使用过离线应用缓存现在不想再使用了,从<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应用开发中的几个问题相关推荐

  1. 表单html遇到的问题及处理,Web前端开发中常见问题及解决方案

    Web前端开发中常见问题及解决方案 时间:2017-04-24     来源:web前端开发小赢家 作为一名web前端开发工程师,我们在工作时免不了会遇到各种各样的问题.因为web前端开发相对于Jav ...

  2. web前端开发中需要掌握的技术:

    web前端开发中需要掌握的技术: 1.学习HTML,这是最简单,最基本的是要掌握div,formtable.Ulli.P.跨度.字体这些标签,这些都是最常用的,尤其是DIV和表格,DIV,表也可以用于 ...

  3. J2EE平台WEB组件开发中如何使用定制标签

    J2EE平台WEB组件开发中如何使用定制标签       J2EE PLATFORM WEB组件开发涉及SERVLET 和JSP技术,SERVLET和JSP各有其优缺点.JVAVABEAN和定制标签对 ...

  4. Web前端开发中的表单练习

    在web前端开发中,练习table的使用是非常重要的,通过表单的练习可以很好的帮助我们掌握表单的使用方式. 效果如图所示: 代码如下: css采用了外部样式 HTML部分: <!DOCTYPE ...

  5. 列举在Web前端开发中经常会设置的特殊样式!

    在实际开发中,移动Web页面的设计风格更接近App(手机应用),而不是传统的网页.为了有更好的用户体验,我们可以给移动Web页面设置一些特殊样式.下面为列举在移动Web开发中经常会设置的特殊样式,具体 ...

  6. Web网站开发中,Cookie是什么?

    你好,是我琉忆. 今天我们讲一讲什么是Cookie,怎么理解Cookie. 在我们Web开发中,例如要想长久的存储一个用户的信息,到底需要使用什么样的一个方式进行储存?我们一起来看看. 1.Cooki ...

  7. 干净架构在 Web 服务开发中的实践

    干净架构(The Clean Architecture)是 Bob 大叔在 2012 年的一篇博文 The Clean Architecture 中,提出的一种适用于复杂业务系统的软件架构方式.干净架 ...

  8. 将chart放入panel中出现滚动条_聊天场景在web前端开发中的体验与优化

    在日常工作中,如下图的聊天场景是不是很熟悉,没错就是我们再熟悉不过的 QQ 和微信,一个正常的聊天界面大致上是长这个样子的: 这种聊天窗口的消息流有两个明显的特点: 最新的消息和滚动条初始位置需要在列 ...

  9. 透过NpetShop 看Web项目开发中的分工合作

    以前做项目基本上是从数据库设计到web页面的制作都是一个人来做,甚至做着做着会觉得好乱的感觉. 后来是几个人一起做,开发的模式是一个人负责一部分项目的一部分,这里的分工是纵向的,即一个人要完成这一个部 ...

最新文章

  1. vue中一个组件导入另一个组件
  2. java第三方接口对接_调用多个第三方接口哪一种方案更好?
  3. redis 获取所有key_Redis笔记
  4. mysql事务中怎么更改空值_MySQL事务
  5. 腾讯云服务器配置ftp~
  6. js简单实现div宽度匀速增加/减小
  7. html编写代码制作网站教程,html代码编写教程
  8. grandMA2onPC控制UE4灯光
  9. 传感器检测技术之转换电路——电桥
  10. yii2自动更新时间
  11. C语言实现物品竞拍管理系统
  12. Spss-系统聚类手算实操
  13. python界面显示图片更换背景_用python制作一个简陋的证件照换底色的桌面控制台应用...
  14. 电脑录屏软件哪个好用,分享4款不限时长的录屏软件
  15. [附源码]Java计算机毕业设计SSM动物园动物饲养管理
  16. 程序的灵活性与可扩展性
  17. 部署开源LWM2M服务器 leshan
  18. Java应用程序安全框架
  19. 计算机主机能上网玩游戏吗,为什么现在人人都有电脑,还要去网吧玩游戏?
  20. 用openAI写个js的排序算法(快速排序算法)

热门文章

  1. CSS3属性之圆角效果——border-radius属性
  2. swift变量和函数
  3. 基于ARM 构架(带MMU)的copy_from_user与copy_to_user详细分析
  4. Struts2上传文件的大小设置
  5. 客户端控件调用服务器的参数
  6. MPU6050代码解析
  7. c# 调用c库dll ,char*转string的解决办法
  8. C#中控件Control的Paint事件和OnPaint虚函数的区别
  9. C# 简单的XML读取修改写入
  10. python参数顺序 元组 字典_python学习之元组列表字典操作