Skip to content

《基于MVC的JavaScript Web富应用开发》精读笔记

写在前面

  • 书籍介绍:这本书专注于讲述如何构建“优雅又不失高水准”的JavaScript应用,包括软件架构、模板引擎、框架和库、同服务器的消息通信等内容。
  • 我的简评:比较老的一本书,引用的一些框架和库现在已经不流行了,可以大致翻看一下。内容主要讲如何抽象js前端开发,让代码变得更清晰,具体采用的方法在js里面实现class、MVC、module等抽象,对提高编程思维帮助还是挺大的。
  • !!福利:文末有pdf书籍、笔记思维导图、随书代码打包下载地址哦

第1章 MVC和类

之初

  • 一个原因是早期的JavaScript实现非常糟糕,有很多bug;另一个原因是因为其名字带有“Java”前缀,让人以为它和Java有关系
  • 真正的原因在于大多数开发者接触使用JavaScript的方式

增加结构

  • 构建大型的JavaScript应用的秘诀是不要构建大型JavaScript应用。你应当把你的应用解耦成一系列相互平等且独立的部分
  • 本书提倡使用MVC模式,这是一种久经考验的搭建应用的方式,可以确保应用的可维护性和可扩展性

什么是MVC

  • MVC是一种设计模式,它将应用划分为3个部分:数据(模型)、展现层(视图)和用户交互层(控制器
  • 一个事件的发生是这样的过程:1.用户和应用产生交互;2.控制器的事件处理器被触发;3.控制器从模型中请求数据,并将其交给视图;4.视图将数据呈现给用户;

模型

  • 模型用来存放应用的所有数据对象
  • 模型不必知晓视图和控制器的细节,模型只需包含数据及直接和这些数据相关的逻辑。任何事件处理代码、视图模板,以及那些和模型无关的逻辑都应当隔离在模型之外

视图

  • 视图层呈现给用户的,用户与之产生交互。在JavaScript应用中,视图大都是由HTML、CSS和JavaScript模板组成的。除了模板中简单的条件语句之外,视图不应当包含任何其他逻辑

控制器

  • 控制器是模型和视图之间的纽带。控制器从视图获得事件和输入,对它们进行处理(很可能包含模型),并相应的更新视图

向模块化进军 创建类

  • 有必要强调一下:JavaScript是基于原型的编程语言,并没有包含内置类的实现。但通过JavaScript可以轻易的模拟出经典的类
  • JavaScript中并没有真正的类,但JavaScript中有构造函数和new运算符。构造函数用来给实例对象初始化属性和值。任何JavaScript函数都可以用做构造函数,构造函数必须使用new运算符作为前缀来创建新的实例
  • new 运算符改变了函数的执行上下文,同时改变了return语句的行为
  • 当使用new关键字来调用构造函数时,执行上下文从全局对象(window)变成一个空的上下文,这个上下文代表了新生成的实例

基于原型的类继承

  • JavaScript是基于原型的编程语言,原型用来区别类和实例
  • 当你读取一个对象的属性时,JavaScript首先会在本地对象查找这个属性,如果没有找到,JavaScript开始在对象的原型中查找,若还未找到还会继续查找原型的原型,直到查找到Object.prototype。如果找到这个属性,则返回这个值,否则返回undefined

函数调用

  • 函数内上下文,如this的取值,取决于调用它的位置和方法
  • 其他两种方法可以调用函数:apply()和call()。两者的区别在于传入函数的参数的形式
  • 其他编程语言不允许手动更换上下文也没什么不好。JavaScript中允许更换上下文是为了共享状态,尤其是在事件回调中
  • 在新版本的JavaScript-ES5种同样加入了bind()函数用以控制调用的作用域。bind()是基于函数进行调用的。用来确保函数是在指定的this值所在的上下文中调用的

添加私有方法

  • 很多开发者都习惯在私有属性之前冠以下划线前缀_。尽管本质上这并不是私有属性,但至少能一眼看出来他们就是私有属性,因此它是私有API的组成部分
  • JavaScript的确支持不可变属性,然而在主流浏览器中并未实现,我们还没办法直接利用这个特性
  • 相反,我们可以利用JavaScript匿名函数创建私有作用域,这些私有作用域只能在内部访问

第2章 事件和监听

写在前面

  • 在JavaScript诞生之初“事件”的实现并不标准
  • 尽管后来W3C对此作了标准化,但IE仍然坚持使用与W3C不兼容的事件模型,直到发布的IE9才遵循标准化
  • jQuery和Prototype的类库很好的处理了兼容性问题,对外提供了统一的API来实现事件

监听事件

  • 绑定事件监听的函数叫做addEventListener(),它有3个参数:type、listener及useCapture

事件顺序

  • Netscape4支持事件捕捉(capturing),从顶层的父节点开始触发事件,从外到内传播
  • 微软则支持事件冒泡(bubbling),从最内层的节点开始触发事件,逐级冒泡直到顶层节点,从内向外传播
  • W3C对此做了让步,将对这两种事件模型的支持都加入标准规范之中。根据W3C模型,事件首先被目标元素所捕捉,然后向上冒泡

取消事件

  • 一些类库如jQuery还支持stopImmediatePropagation()函数,用来阻止后续所有的事件触发-哪怕这些事件是注册在同一个节点元素上的也不例外
  • 可以通过调用event对象的preventDefault()函数来阻止默认行为,同样也可以通过在回调中返回false来实现同样的效果

事件库

  • jQuery的API提供了bind()函数用来跨浏览器绑定事件监听。在一个jQuery实例上调用此函数,传入事件名称和回调函数
  • 并不是所有的浏览器都支持DOMContentLoaded,因此jQuery将它融入ready()函数,这个函数是兼容各个浏览器的

委托事件

  • 从事件冒泡时开始就发生了事件委托,我们可以直接给父元素绑定事件监听,用来检测在其子元素内发生的事件

自定义事件

  • 除了浏览器内置的事件之外,我们也可以触发和绑定自定义事件
  • jQuery中可以使用trigger()函数来触发自定义事件

DOM无关事件

  • 事件本质上是和DOM无关的,因此可以很容易的开发一个事件驱动的库
  • 发布/订阅(Pub/Sub)是一种消息模式,它有两个参与者:发布者和订阅者。发布者向某个信道(channel)发布一条消息,订阅者绑定这个信道,当有消息发布至信道时就会接受到一个通知
  • 发布者和订阅者的解耦可以让你的应用易于扩展,而不必引入额外的交叉依赖和耦合,从而提高了应用的可维护性,添加额外功能也非常容易

第3章 模型和数据

  • 传统方式是通过页面请求从数据库获取数据,用户和页面中的结果进行直接交互
  • 在复杂的JavaScript应用中做数据管理是非常困难的。前端并没有请求/响应模型,也没有办法访问服务器端的变量,甚者,远程取回的数据只是临时的保存在客户端

MVC和命名空间

  • 在JavaScript中,我们通过给对象添加属性来管理一个命名空间,这个命名空间可以是函数,也可以是变量

构建对象关系映射(ORM)

  • 对象关系映射(Object-relational mapper,简称ORM)是在除了JavaScript以外的编程语言中常见的一种数据结构
  • 在JavaScript应用中,对象关系映射也是一种非常有用的技术,它可以用做数据管理及用做模型
  • 原型继承:Object.create()传入一个对象,返回一个继承了这个对象的新对象,可以很容易的模拟出这个函数
  • 添加ORM属性:jQuery.extend()只是代替for循环手动复制属性的一种快捷方式
  • 持久化记录:我们需要一种保持记录持久化的方法,即将引用保存至新创建的实例中以便任何时候都能访问它

增加ID支持

  • 技术的角度讲,出于API的原因,Javascript无法友好正式的生成128位的GUID,它只能生成伪随机数
  • 生成真正随机的GUID是一个众所周知的难题,操作系统使用MAC地址、鼠标位置、BIOS的校验和、测量电信号的噪声或者检测放射性衰变来计算GUID,甚至用上了熔岩灯

装载数据

  • 数据的预加载时一个重要的手段,这能让应用的体验更流畅、速度更快,用户的等待时间也尽可能压缩到很短
  • 数据可以直接嵌套显示在初始页面中,或者通过Ajax或JSONP的方式使用单独的HTTP请求加载数据
  • 直接在初始页面中嵌套数据会增加页面体积,而并行加载会更快一些。Ajax和JSON同样允许你将HTML页面缓存住,而不是每次渲染都会动态发起请求
  • JSONP:原理是通过创建一个script标签,所辖的外部文件包含一段JSON数据,数据是由服务器所返回的,作为参数包装在一个函数调用中。script标签获取脚本文件并不受跨域的限制,所有浏览器都支持这种技术

本地存储数据

  • 在过去本地数据存储一直都是瓶颈。惟一可用的方法就是使用cookies和类似Adobe Flash的插件
  • HTML5加入了对本地存储的支持,而且主流浏览器都已经实现了本地存储。和cookies不同,这些数据妥妥的存放在客户端,且不会发给服务器。而且可存储的数据量非常巨大,不同的浏览器额存储上限有所不同,但至少都能为每个域名提供5M的存储空间
  • 浏览器端所储存的数据是以域名分隔开的,某个域中的脚本存储的数据只能被这个域读取

给ORM添加本地存储

  • 页面加载时从本地存储中读取数据,页面关闭时将数据保存在本地存储中,这的确是个好主意

第4章 控制器和状态

写在前面

  • JavaScript应用往往被限制在单页面,这也意味着可以将状态保存在客户端的内存中
  • 应当避免将状态或数据保存在DOM中,因为根据滑坡理论,这会导致程序逻辑变得更加错综复杂且混乱不堪
  • 控制器是模块化的且非常独立
  • 理想状态下不应该定义任何全局变量,而应当定义完全解耦的功能组件

模块模式

  • 模块模式是用来封装逻辑并避免全局空间污染的好方法。使用匿名函数可以做到这一点,匿名函数也是JavaScript中被证明最优秀的特性之一

添加少量上下文

  • 使用局部上下文是一种架构模块很有用的方法,特别是当需要给事件注册回调函数时

状态机

  • 使用状态机可以轻松地管理很多控制器,根据需要显示和隐藏视图
  • 本质上讲状态机由两部分组成:状态和转换器。它只有一个活动状态,但也包括很多非活动状态。当活动状态之间相互切换时就会调用状态转换器

路由选择

  • 我们需要将应用的状态反映在URL中,建立状态和URL的某种对应关系,当应用的状态发生改变时,URL也随之改变,反之亦然

第5章 视图和模板

写在前面

  • 视图是应用的接口,它们为用户提供视觉呈现并与用户产生交互
  • 视图是无逻辑的HTML片段,由应用的控制器来管理,视图处理事件回调及内嵌数据
  • 最常用的方法就是用Ajax,Ajax返回一个JSON对象,然后由应用的模型载入它。你不必在服务器端对HTML做预渲染操作,而是将渲染界面的操作全放在客户端

动态渲染视图

  • 通过JavaScript程序创建视图有很多种方式,其中一种方式是使用document.createElement()创建DOM元素,设置它们的内容并将它们追加至页面中。当需要重绘视图时,只需将视图清空并重复前面的过程
  • 我更推荐将静态HTML包含在页面中,在必要的时候显示或隐藏。这会让控制器中所有和视图相关的代码量降到最少,你也可以根据需要更新元素的内容

模板

  • JavaScript模板的核心概念是,将包含模板变量的HTML片段和JavaScript对象做合并,把模板变量替换为对象中的属性值
  • JavaScript中的模板类库的实现原理和其他语言没什么两样,比如PHP的Smarty,Ruby的ERB,以及Python的字符串格式化

绑定

  • 本质上讲,绑定将视图元素和JavaScript对象(通常是模型)挂接在一起。当JavaScript对象发生改变时,视图会根据新修改后的对象做实时更新
  • 绑定,意味着当记录发生改变时你的控制器不必处理视图的更新,因为这些更新都是在后台自动完成的

第6章 依赖管理

  • JavaScript中缺失了很多计算机高级编程语言所应有的功能特性,其中一个重要的特性就是依赖管理和模块系统
  • 依赖管理系统除了能解决实际的编程复杂度和可维护性的问题,还能解决性能方面的问题

CommonJS

  • Node.js提供了require()函数,用来加载外部资源文件,Node.js自有的模块系统也使用这种方式来管理

模块加载器

  • 目前最主要、应用最广泛的两个模块实现时Transport C和Transport D
  • Yabble
  • RequireJS

模块的按需加载

  • Sprockets非常聪明,它会保证只加载库文件一次,自动忽略之后的加载需求。相比于CommonJS模块,Sprockets支持缓存和压缩
  • Sprockets(包括所有的模块包装器)的中心思想是,所有的JavaScript文件都需要预处理,不管是在服务器端用程序作处理,还是使用命令行工作作处理
  • LABjs

无交互行为内容的闪烁(FUBC)

  • 用户可能会看到页面闪了一下,出现一部分没有交互行为的内容快速闪过(FUBC),比如在JavaScript执行之前会有一部分无样式的页面原始内容闪烁一下

第7章 使用文件

  • 以往,文件的访问和操作都是基于桌面应用程序,在Web中操作文件必须借助第三方插件技术,比如Adobe Flash
  • 在现代浏览器中,用户可以直接将文件拖拽进页面中,粘贴格式化数据,或是实时查看上传文件的进度

浏览器支持

  • 特性检测:window.File && window.FileReader && window.FileList

获取文件信息

  • HTML5的文件操作有一定的安全限制,主要的限制是只有被用户选中的文件才能被访问

文件输入

  • 对于开发者来说,长期以来很让人头疼的是多文件上传。过去开发者只能堆积很多文件输入框或者依赖插件(如Flash)来实现多文件上传。HTML5提供了multiple属性来支持多文件

拖拽

  • 早在1999年,微软的IE5就“设计”并实现了最原始的拖拽,自那时起后续的IE版本都支持拖拽。HTML5规范刚刚增加了拖拽的内容,现在Safari、Firefox和Chrome也都模仿IE的实现提供了拖拽支持

复制和粘贴

  • 除了将拖拽功能和桌面进行整合,有些浏览器还支持复制和粘贴
  • 复制粘贴使用了clipbordData,拖拽使用了dataTransfer

读文件

  • 当你获得File的引用时,可以用它来实例化一个FileReader对象,将文件内容读入内存
  • FileReader中包含四个方法来读取文件数据:readAsBinaryString、readAsDataURL、readAsText、readAsArrayBuffer
  • 二进制大文件和文件切割

自定义浏览器按钮

  • 我们往往希望点击一个带自定义样式的“浏览”或“附件”按钮来即刻打开浏览文件对话框
  • 文件输入框并不提供打开浏览文件对话框的函数,而且Firefox的实现更诡异,当点击文件输入框时甚至无法触发自定义的click事件
  • hack技术,当将鼠标移动到按钮上方时,在相同的位置放置一个透明的文件输入框,尺寸和按钮一样。透明的文件输入框可以获取任何点击事件,并打开一个浏览文件的对话框

上传文件

  • FormData实例用一种非常简单的接口表示表单的内容。可以直接通过抓取一个表单来创建FormData,或者在实例化对象时传入已经存在的form元素

jQuery拖拽上传

  • 实现一个可拖拽上传文件的功能。需要几个库:jquery.js用来做底层库,jquery.ui.js用来构建进度条,jquery.drop.js用来提供抽象的拖拽API,以及jquery.upload.js用来作Ajax上传

第8章 实时Web

实时Web的发展历史

  • 传统的Web是基于HTTP的请求/响应模型的:客户端请求一个新页面,服务器将内容发送到客户端,客户端再请求另外一个页面时又要重新发送请求
  • 如果服务器有更多数据需要推送到客户端,在页面加载完成后是无法直接将数据从服务器发送给客户端的
  • 最简单(暴力)的方案是用轮询:每隔一段时间都会向服务器请求新数据,这让用户感觉应用是实时的
  • 后面随着Comet技术的提出,又出现了很多更高级的解决方案。这些技术方案包括永久帧(forever frame)、XHR流(xhr-multipart)、htmlfile以及长轮询
  • 长轮询是指,客户端发起一个到服务器的XHR连接,这个连接永不关闭,对客户端来说连接始终是挂起状态。当服务器有新数据时,就会及时地将响应发送给客户端,接着再将连接关闭。然后重复整个过程,通过这种方式就实现了“服务器推”(server push)
  • 现在HTML5规范为我们准备了一个替代方案。鉴于大部分浏览器还未实现HTML5的WebSocket,现行最好的办法仍然是使用Comet

WebSocket

  • WebSocket是HTML5规范的一部分,提供基于TCP的双向的、全双工的socket连接。这意味着服务器可以将数据推送给客户端,而不需要开发者求助于长轮询或插件来实现,这是一个很大的进度
  • 为了更好更成功的使用WebSocket,这里给出一些步骤
  • 幸运的是,在很多语言中都实现了对WebSocket的支持,比如Ruby、Python和Java
  • Node.jsSocket.IO

实时架构

  • 实时架构是基于事件驱动的(event-driven)。事件往往是由用户交互触发的:用户修改了数据记录,事件就会传播给系统,直到数据推送给已经建立连接的客户端并更新数据
  • 如何向特定用户发送通知?最佳方法是使用发布/订阅模式:客户端订阅某个特定的信道,服务器向这个信道发布消息。每个用户订阅惟一的信道。信道包含一个ID,可能是用户在数据库中存放的ID。然后,服务器只需向这个惟一的信道发布消息即可,这样就可以做到将通知发送给特定的用户

感知速度

  • 速度是UI设计最重要也是最易忽略的问题,速度对用户体验(UX)的影响非常大,并直接影响网站的收益
  • Web应用中最耗时的部分就是新数据的加载。最明智的做法是在用户请求数据之前预测用户的行为并预加载数据,这一点非常重要

第13章 JavaScriptMVC类库

  • JavaScriptMVC(JMVC)是一个基于jQuery的开源JavaScript框架。它基本上就是一个完整的前端开发框架,将一些实用的测试工具、依赖关系管理、文档和许多非常有用的jQuery插件都打包在一起了
  • JavaScriptMVC的$.Class(基于JavaScript的类系统)、$.Model(传统的模型层)、$.View(客户端模板系统)、$.Controller(jQuery的widget工厂)

模型

  • JavaScriptMVC的模型和它的插件提供了很多组织模型数据的工具,如校验、关联、列表等。但核心功能集中在服务封装、类型转换和事件上

在视图中使用客户端模板

  • $.View是一个模板接口,使用模板来降低复杂度,提供如下支持

写在后面