JavaScript-全栈教程-全-

JavaScript 全栈教程(全)

原文:Full Stack JavaScript

协议:CC BY-NC-SA 4.0

一、基础

我认为每个人都应该学习如何给电脑编程,因为它教会你如何思考。我认为计算机科学是一门文科,是每个人都应该学会的。—史蒂夫·乔布斯

在本章中,我们将讨论以下主题:

  • HTML、CSS 和 JavaScript 语法概述
  • 敏捷方法简介
  • 云计算、Node.js 和 MongoDB 的优势
  • HTTP 请求/响应和 RESTful API 概念的描述

如果你是一个有经验的 web 开发人员,我不推荐,但是可以跳过这一章。在继续之前,温习一下基本概念是很重要的。为什么呢?也许你听说过并熟悉一些术语,但想知道它们实际上是什么意思。另一个很好的原因是,本章将以一种非常初学者友好的方式介绍 RESTful API。REST 在几乎所有现代 web 架构中都有使用,我们会在书中大量使用它。还有最后一个原因:在鸡尾酒会上,或者在你的同事和老板面前,你会显得很聪明。

前端定义

前端是浏览器应用的术语。在某些对话中,这可能意味着服务器首先面对请求。然而,对于这本书,我们假设所有的前端仅限于浏览器和移动应用及其代码。

前端开发,或前端 web 开发,意味着各种技术的使用。它们中的每一个单独来说都不太复杂,但是它们的数量之多让初学者感到胆怯。例如,有级联样式表(CSS)、超文本标记语言(HTML)、可扩展标记语言(XML)、JavaScript、JavaScript 对象标记法(JSON)、统一资源标识符(URI)、超文本传输协议(HTTP)和许多其他缩写。

除了底层技术,还有许多框架、工具和库;比如 jQuery,Backbone.js,Angular.js,Grunt 等等。请不要混淆前端框架和后端框架:后者运行在服务器上,而前者运行在浏览器上。

前端 web 开发由以下组件组成:

HTML or templates that compile to HTML   Stylesheets to make HTML pretty   JavaScript to add interactivity or some business logic to the browser app   Some hosting (AWS, Apache, Heroku, etc.)   Build scripts to prepare code, manage dependencies, and do pretty much anything that’s needed   Logic to connect to the server (typically via XHR requests and RESTful API)

现在你知道前端开发人员这个头衔意味着什么了。掌握这个大杂烩的最大回报是通过构建漂亮有用的应用来表达你的创造力。

在我们开始构建之前,让我们鸟瞰一下 web 请求周期。

Web 请求周期

这对于刚接触 web 开发的人来说很重要。整个万维网或互联网是关于客户端和服务器之间的通信。这种通信通过发送请求和接收响应来实现。通常浏览器(最流行的 web 客户端)向服务器发送请求。在后台,服务器向其他服务器发送自己的请求。这些请求类似于浏览器请求。请求和响应的语言是 HTTP(S)。让我们更详细地研究浏览器请求。

web 请求由以下步骤组成:

A user types a URL or follows a link in his or her browser (also called the client).   The browser makes an HTTP request to the server.   The server processes the request, and if there are any parameters in a query string or body of the request, it takes them into account.   The server updates, gets, and transforms data in the database.   The server responds with an HTTP response containing data in HTML, JSON, or other formats.   The browser receives the HTTP response.   The browser renders an HTTP response to the user in HTML or any other format (e.g., JPEG, XML, JSON).

移动应用的行为方式与普通网站相同,只是没有浏览器,而是有一个本地应用。移动应用(原生或 HTML5)只是另一个客户端。手机和网络之间的其他微小差异包括运营商带宽导致的数据传输限制、更小的屏幕和更有效地使用本地存储。很可能你,我的读者,是一个渴望在移动中使用你的网页印章的网页开发者。有了 JavaScript 和 HTML5,这是可能的,因此有必要更深入地讨论 web 开发。

移动开发

手机会超越网络和桌面平台吗?也许吧。目前,移动开发领域还非常不成熟,还是一个新领域。如果你是先锋,那很好,但我们大多数人都不是。与 web 相比,这在工具和库方面是一个更大的差距。差距正在缩小。使用 HTML5,您可以编写一次代码并在移动设备上重用代码。还有其他方法。

这些是移动开发的方法,每种都有自己的优点和缺点:

Native: Native iOS, Android, Blackberry apps built with Objective-C and Java.   Abstracted native: Native apps built with JavaScript in Appcelerator ( http://www.appcelerator.com ), Xamarin, ( https://xamarin.com ), Smartface ( http://www.smartface.io ) React Native or similar tools, and then compiled into native Objective-C or Java.   Responsive: Mobile web sites tailored for smaller screens with responsive design, CSS frameworks like Twitter Bootstrap ( http://twitter.github.io/bootstrap/ ) or Foundation ( http://foundation.zurb.com/ ), regular CSS, or different templates. You might use some JavaScript frameworks for the development like Backbone.js, Angular.js, Ember.js, or React.js.   Hybrid: HTML5 apps that consist of HTML, CSS, and JavaScript, and are usually built with frameworks like Sencha Touch ( http://www.sencha.com/products/touch ), Trigger.io ( https://trigger.io ), JO ( http://joapp.com ), React Native ( https://facebook.github.io/react-native ), or Ionic ( http://ionicframework.com ) and then wrapped into a native app with PhoneGap ( http://phonegap.com ). As in the third approach, you probably will want to use a JavaScript framework for the development, such as Backbone.js, Angular.js, Ember.js, or React.js.

我个人最喜欢的是第二和第四种方法。第二种方法不需要不同的代码库。只需向 CSS 库添加一个链接,就可以构建一个最小可行产品(MVP)。第四种方法更强大,提供了更具可伸缩性(从开发的角度来看)的 ui。这更适合复杂的应用。跨平台移动和 web 之间的代码重用很容易,因为大多数时候你是用 JavaScript 编写的。

超文本标记语言

HTML 本身不是一种编程语言。它是一组标记标签,描述内容并以结构化和格式化的方式呈现。HTML 标签由尖括号(<>)内的标签名称组成。在大多数情况下,标签包围着内容,结束标签在标签名称前有一个正斜杠。

在本例中,每一行都是一个 HTML 元素:

<h2>``Overview of HTML

<div>``HTML is a ...

<link``rel="stylesheet" type="text/css" href="style.css"

HTML 文档本身是<html>标签的一个元素,所有其他元素都是该<html>标签的子元素:

<!DOCTYPE html>

<html``lang="en"

<head>

<link``rel="stylesheet" type="text/css" href="style.css"

</head>

<body>

<h2>``Overview of HTML

<p>``HTML is a ...

</body>

</html>

HTML 有不同的风格和版本,比如 DHTML、XHTML 1.0、XHTML 1.1、XHTML 2、HTML 4 和 HTML 5。这篇文章很好地解释了不同之处:误解标记:XHTML 2/HTML 5 漫画( http://coding.smashingmagazine.com/2009/07/29/misunderstanding-markup-xhtml-2-comic-strip/ )。

任何 HTML 元素都可以有属性。其中最重要的是class, id, style, data-name, onclick,还有其他事件属性比如onmouseover, onkeyup等等。

class

属性定义了一个类,用于 CSS 或域对象模型(DOM)操作中的样式化;例如:

<p``class="normal"``>``...

id

属性定义了一个 ID,其目的类似于元素类,但是它必须是唯一的;例如:

<div id="footer">...</div>

style

属性定义内联 CSS 来样式化一个元素;例如:

<font style="font-size:20px">...</font>

title

属性指定了大多数浏览器通常在工具提示中显示的附加信息;例如:

<a title="Up-vote the answer">...</a>

data-name

属性允许元数据存储在 DOM 中;例如:

<tr data-token="fa10a70c-21ca-4e73-aaf5-d889c7263a0e">...</tr>

onclick

当点击事件发生时,onclick属性调用内联 JavaScript 代码;例如:

<input``type="button" onclick="validateForm();"``>``...

onmouseover

除了鼠标悬停事件,onmouseover属性类似于onclick;例如:

<a``onmouseover="javascript: this.setAttribute(’css’,’color:red’)"``>``...

内联 JavaScript 代码的其他 HTML 元素属性如下:

  • onfocus:当浏览器聚焦于一个元素时
  • onblur:当浏览器焦点离开一个元素时
  • onkeydown:当用户按下键盘按键时
  • ondblclick:当用户双击鼠标时
  • onmousedown:当用户按下鼠标按钮时
  • onmouseup:当用户释放鼠标按钮时
  • onmouseout:当用户将鼠标移出元素区时
  • oncontextmenu:当用户打开上下文菜单时

事件兼容性表( http://www.quirksmode.org/dom/events/index.html )中提供了此类事件的完整列表和浏览器兼容性表。

我们将在 Twitter Bootstrap framework 中广泛使用类,但是使用内联 CSS 和 JavaScript 代码通常不是一个好主意,所以我们将尽量避免。然而,知道 JavaScript 事件的名称是有好处的,因为它们在 jQuery、Backbone.js 以及普通 JavaScript 中随处可见。要将属性列表转换成 JS 事件列表,只需去掉前缀on;例如,onclick属性表示click事件。

更多信息可在示例中找到:捕捉鼠标点击( https://developer.mozilla.org/en-US/docs/JavaScript/Getting_Started#Example:_Catching_a_mouse_click )、维基百科( http://en.wikipedia.org/wiki/HTML )和 MDN ( https://developer.mozilla.org/en-US/docs/Web/HTML )。

级联样式表

CSS 提供了一种格式化和显示内容的方法。一个 HTML 文档可以通过一个<link>标记包含一个外部样式表,如前面的例子所示,或者它可以在一个<style>标记中直接包含 CSS 代码:

<style>

body {

padding-top:``60px``;

}

</style>

每个 HTML 元素可以有id属性、class属性,或者两者都有:

<div``id="main" class="large"

Lorem ipsum dolor sit amet,

Duis sit amet neque eu.

</div>

在 CSS 中,我们通过元素的idclass、标签名来访问元素,在某些边缘情况下,通过父子关系或元素属性值来访问元素。

这会将所有段落的颜色(<p>标签)设置为灰色(#999999):

p {

color:``#999999

}

这会用mainid属性设置<div>元素的填充:

div#main {

padding-bottom:``2em

padding-top:``3em

}

这将类large的所有元素的字体大小设置为 14 像素:

.large {

font-size:``14pt

}

这隐藏了<div>,它是<body>元素的直接子元素:

body > div {

display:``none

}

对于name属性为email的输入,这将宽度设置为 150 像素:

input[name="email"] {

width:``150px

}

更多信息可查阅维基百科( http://en.wikipedia.org/wiki/Cascading_Style_Sheets )和 MDN ( https://developer.mozilla.org/en-US/docs/Web/CSS )。

CSS3 是对 CSS 的升级,包括一些新的处理方式,如圆角、边框和渐变,这些在常规 CSS 中只有借助 PNG/GIF 图像和使用其他技巧才能实现。

更多信息请参考 CSS3.info ( http://css3.info )、w3school ( http://www.w3schools.com/css3/default.asp ),以及 CSS3 与 CSS 关于扣球的对比文章( http://coding.smashingmagazine.com/2011/04/21/css3-vs-css-a-speed-benchmark )。

Java Script 语言

JavaScript (JS)于 1995 年在网景公司以 LiveScript 的名字出现。它与 Java 的关系就像仓鼠与火腿的关系一样,所以请不要混淆两者。

如今,JavaScript 被用于客户端和服务器端 web,以及桌面应用开发、无人机、物联网(IoT)和其他事物。这是本书的主要焦点,因为使用 JavaScript 你可以跨所有层进行开发。你不需要任何其他语言!

让我们从 HTML 中的 JavaScript 开始。将 JS 代码放入<script>标签是在 HTML 文档中使用 JavaScript 最简单的方法:

<script``type="text/javascript" language="javascript"

alert("Hello world!")

//simple alert dialog window

</script>

请注意,混合 HTML 和 JS 代码并不是一个好主意,所以为了将它们分开,我们可以将代码移动到一个外部文件,并通过在script标签上设置 source 属性src="filename.js"来包含它,例如,对于app.js资源:

<script``src="js/app.js" type="text/javascript" language="javascript"

</script>

请注意,结束标签</script>是强制的,即使是像我们包含外部源文件的空元素。由于 JavaScript 的压倒性优势,Typelanguage属性多年来在现代浏览器中成为可选属性。

运行 JavaScript 的其他方式包括:

  • 内联方法已经介绍过了
  • WebKit 浏览器开发工具和 FireBug 控制台
  • 交互式Node.js外壳

JavaScript 语言的优势之一是它是松散类型的。与 C 和 Java 等语言中的强类型( http://en.wikipedia.org/wiki/Strong_typing )相反,这种松散或弱类型使 JavaScript 成为更好的原型开发编程语言。下面是 JavaScript 对象或类的一些主要类型(本质上没有类;对象继承自对象)。

数字原语

数字原语是数值;例如:

var num = 1

数字对象

这是数( https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number )对象及其方法;例如:

var numObj = new Number(’123’) //Number object

var num = numObj.valueOf()     //number primitive

var numStr = numObj.toString() //string representation

字符串原语

字符串原语是单引号或双引号内的字符序列;例如:

var str = ’some string’

var newStr = "abcde".substr(1,2)

为方便起见,JavaScript 自动用String对象方法包装字符串原语,但又不太一样( https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String#Distinction_between_string_primitives_and_String_objects )。

字符串对象

String对象有很多有用的方法,像length, match等等;例如:

var strObj = new String("abcde") //String object

var str = strObj.valueOf()       //string primitive

strObj.match(/ab/)

str.match(/ab/) //both call will work

RegExp 对象

正则表达式是用于查找匹配、替换和测试字符串的字符模式。

var pattern = /[A-Z]+/

’ab’.match(pattern) // null

’AB’.match(pattern) // ["AB"]

match()方法返回一个匹配数组(["AB"])。如果你需要的只是一个布尔型的true/false,那么只需使用pattern.test(str)。例如:

var str = ’A’

var pattern = /[A-Z]+/

pattern.test(str) // true

特殊类型

有疑问的时候(调试的时候)随时可以调用typeof obj。以下是 JS 中使用的一些特殊类型:

  • NaN:不是数字
  • null:空无一物,zip
  • undefined:未声明的变量
  • function:功能

数据

JSON 库允许我们解析和序列化 JavaScript 对象;例如:

var obj = JSON.parse(’{a: 1, b: "hi"}’)

var stringObj = JSON.stringify({a: 1, b: ’hi’})

数组对象

数组( https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array )是基于零索引的列表。例如,要创建数组:

var arr = new Array()

var arr = [’apple’, ’orange’, ’kiwi’]

Array对象有很多好的方法,比如indexOfslicejoin。确保你熟悉它们,因为如果使用正确,它们会节省很多时间。

数据对象

var obj = {name: ’Gala’, url: ’img/gala100x100.jpg’, price: 129}

或者

var obj = new Object()

稍后我们将提供更多关于继承模式的内容。

布尔原语和对象

就像StringNumberBoolean ( https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Boolean )可以是原语,也可以是对象。

var bool1 = true

var bool2 = false

var boolObj = new Boolean(false)

日期对象

Date ( https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date )对象允许我们处理日期和时间;例如:

var timestamp = Date.now() // 1368407802561

var d = new Date()         // Sun May 12 2013 18:17:11 GMT-0700 (PDT)

数学对象

这些用于数学常数和函数()https://developer。mozilla。org/en-US/docs/JavaScript/Reference/Global _ Objects/Math);例如:

var x = Math.floor(3.4890)

var ran = Math.round(Math.random()*100)

浏览器对象

浏览器对象让我们能够访问浏览器及其属性,如 URLs 例如:

window.location.href = ’http://rapidprototypingwithjs.com

console.log(’test’)

DOM 对象

DOM 对象或 DOM(developer . Mozilla . org/en/docs/Web/API/Node)Node 是页面上呈现的 DOM 元素的浏览器接口。它们有宽度、高度、位置等属性,当然还有内部内容,可以是另一个元素或文本。要获得一个 DOM Node,可以使用它的 ID;例如:

var transactionsContainer = document.createElement(’div’)

transactionsContainer.setAttribute(’id’, ’main’)

var content = document.createTextNode(’Transactions’)

transactionsContainer.appendChild(content)

document.body.appendChild(transactionsContainer)

var main = document.getElementById(’main’)

console.log(main, main.offsetWidth, main.offsetHeight)

全球

除了像StringArrayNumberMath这样有很多有用方法的类之外,您还可以调用以下称为全局的方法,这意味着您可以从代码中的任何地方调用它们:

约定

JavaScript 使用了许多样式约定。其中一个是 camelCase,你可以把多个单词打成一个单词,从第二个单词开始把每个单词的第一个字母大写。

分号是可选的。以下划线开头的名称是私有方法或属性,但不是因为它们受语言保护。我们使用_只是为了提醒开发人员不要使用它们,因为它们可能会在未来发生变化。

JavaScript 只支持最大 53 位的数字。如果你需要处理比大数更大的数,请查阅大数库。

JavaScript 和 DOM 对象的完整参考可从Mozilla Developer Network(https://developer . Mozilla . org/en-US/docs/JavaScript/Reference)和w3school(www . w3schools . com/jsref/default . ASP)获得。

对于 JS 资源,比如 ECMA 规范,请查看位于JavaScript Language Resources ( developer 的列表。mozilla。org/en-US/docs/JavaScript/Language _ Resources。在撰写本文时,最新的 JavaScript 规范是 ECMA-262 5.1 版。ECMA-国际。org/publications/files/ECMA-ST/Ecma-262。pdf )和 HTML(www . ECMA-international . org/ECMA-262/5 . 1/)。

JS 的另一个重要区别是它是一种函数式的原型语言。函数声明的典型语法如下所示:

function Sum(a,b) {

var sum = a + b

return sum

}

console.log(Sum(1, 2))

JavaScript 中的函数是一等公民(en . Wikipedia . org/wiki/First-class _ function)由于语言的函数编程(en . Wikipedia . org/wiki/Functional _ programming)性质。因此,函数可以用作其他变量或对象;例如,函数可以作为参数传递给其他函数:

var f = function (str1){

return function(str2){

return str1 + ’ ’ + str2

}

}

var a = f(’hello’)

var b = f(’goodbye’)

console.log((a(’Catty’))

console.log((b(’Doggy’))

知道有几种方法可以在 JS 中实例化一个对象是有好处的:

要进一步了解继承模式,请查看 JavaScript 中的继承模式(bolin fest . com/JavaScript/Inheritance . PHP)和继承再访( developer)。mozilla。org/en-US/docs/JavaScript/Guide/Inheritance _ Revisited

有关浏览器运行 JavaScript 的更多信息,请访问 Mozilla 开发者网络( developer。mozilla。org/en-US/docs/JavaScript/Reference)、维基百科(en . Wikipedia . org/wiki/JavaScript)、w3schools(www . w3schools . com/js/default . ASP)。

敏捷方法

敏捷软件开发方法的发展是由于传统方法如瀑布在高度不可预测的情况下不够好;也就是在解未知的时候( www。startuplessonsle 学到了。结合-敏捷-开发-与。html 。敏捷方法包括 Scrum/sprint、测试驱动开发、持续部署、成对编程和其他实用技术,其中许多都是从极限编程借鉴来的。

混乱

关于管理,敏捷方法使用 Scrum 方法。关于 Scrum 的更多信息可以从以下来源获得:

Scrum 方法是一系列短周期,每个周期被称为一次冲刺。一次冲刺通常持续一到两周。一个典型的 sprint 以一个 sprint 规划会议开始和结束,在这个会议上,新的任务被分配给团队成员。无法将新任务添加到正在进行的 sprint 中;它们只能在 sprint 会议上添加。

scrum 方法论的一个重要部分是每天的 Scrum 会议,因此得名。每次 scrum 是一个 5 到 15 分钟长的会议,通常在走廊上进行。在 scrum 会议中,每个团队成员都要回答三个问题:

What have you done since yesterday?   What are you going to do today?   Do you need anything from other team members?

灵活性使得敏捷成为瀑布方法的一个改进,特别是在高度不确定的情况下(例如,在创业中)。

Scrum 方法的优势在于,它在很难提前计划的情况下是有效的,在反馈循环被用作主要决策权威的情况下也是有效的。

测试驱动开发

测试驱动开发(TDD)由以下步骤组成:

Write failing automated test cases for new features, tasks, or enhancement by using assertions that are either true or false.   Write code to successfully pass the test cases.   Refactor code if needed, and add functionality while keeping the test cases passed.   Repeat until all tasks are complete.

测试可以分为功能测试和单元测试。后者是指系统测试单个单元、方法和功能,并模拟出依赖关系,而前者(也称为集成测试)是指系统测试一部分功能,包括依赖关系。

TDD 有几个优点:

  • 更少的错误和缺陷
  • 更高效的代码库
  • 确信代码能够工作,并且不会破坏旧的功能

持续部署和集成

持续部署(CD)是一套快速向客户交付新特性、缺陷修复和增强的技术。CD 包括自动化测试和自动化部署。使用 CD,减少了人工开销,并最大限度地缩短了反馈循环时间。基本上,开发人员越快从客户那里获得反馈,产品就能越快转向,从而在竞争中获得更多优势。许多初创公司在一天内部署多次,相比之下,大公司和大公司通常需要 6 到 12 个月的发布周期。

CD 方法的优点包括减少反馈循环时间和人工开销。

CD 和持续集成的区别在文章Continuous Delivery vs. Continuous Deployment vs. Continuous Integration - Wait huh? ( blog。安培拉。com/assembly blog/tabid/12618/bid/92411/连续交付-连续部署-连续集成-等等。aspx

一些最流行的持续集成解决方案包括:

结对编程

结对编程是两个开发人员在一个环境中一起工作的一种技术。其中一个开发人员是司机,另一个是观察员。驱动编写代码,观察者通过观看和提出建议来辅助。然后他们交换角色。司机有一个更注重当前任务的战术角色。相比之下,观察者具有更具战略性的角色,监督“更大的画面”并发现错误和改进算法的方法。

以下是成对编程的优点:

  • 结对产生更短更有效的代码库,并引入更少的错误和缺陷。
  • 作为一个额外的奖励,知识在程序员们一起工作时传递。然而,开发人员之间的冲突是可能的,而且并不罕见。

后端定义

后端是服务器的另一个名称。是浏览器之后的一切。包括 PHP、Python、Java、Ruby,当然还有 Node.js 这样的服务器平台,以及数据库等技术。

幸运的是,借助现代后端即服务解决方案,您可以完全绕过后端开发。只需包含一个<script>标签,您就可以获得一个实时数据库,能够将一些逻辑放入其中,如访问级别控制(ALC)、验证等等。我说的是 Firebase.com 和 Parse.com。

在那些仍然需要自己的定制服务器代码的情况下,Node.js 是首选的武器!

Node.js

Node.js 是一种开源的、事件驱动的异步 I/O 技术,用于构建可伸缩的、高效的 web 服务器。Node.js 由 Google 的 V8 JavaScript 引擎( en。维基百科。org/wiki/V8 _(JavaScript _ engine))。它由云公司 Joyent 维护。com ),而是转移到技术指导委员会治理。

Node.js 的用途和用法类似于 Python 的 Twisted(Twisted matrix . com/TRAC/)和 Ruby 的 event machine(Ruby event machine . com/)。Node 的 JavaScript 实现是继尝试使用 Ruby 和 C++编程语言之后的第三个。

Node.js 本身并不是像 Ruby on Rails 那样的框架;它更像是 PHP 和 Apache 的组合。我将提供一个顶级 Node.js 框架的列表第六章。

以下是使用 Node.js 的优点:

  • 开发人员很可能熟悉 JavaScript,因为它是 web 和移动开发的事实标准
  • 使用一种语言进行前端和后端开发可以加快编码过程。开发人员的大脑不必在不同的语法之间切换,也就是所谓的上下文切换。方法和类的学习进行得更快。
  • 有了 Node.js,你可以快速制作原型,并尽早进入市场进行客户开发和客户获取。这是相对于其他使用不太灵活的技术(例如 PHP 和 MySQL)的公司的一个重要的竞争优势。
  • Node.js 通过利用 web 套接字来支持实时应用。

更多信息可以去维基百科(en . Wikipedia . org/wiki/Nodejs)、Nodejs.org(Nodejs . org/about/)和读写文章( readwrite)。com/2011/01/25/wait-whats-nodejs-good-for-aga)和 O ' Reilly(radar . oreilly . com/2011/07/what-is-node . html)。

关于 Node.js 目前的状态(截至本文撰写时),请参考 Node.js 官方博客(nodejs . org/en/blog/)。

NoSQL 和蒙古 b

来自 huMONGOus 的 MongoDB 是一个高性能、无关系的海量数据数据库。当传统的关系数据库管理系统(RDBMSs)无法应对海量数据的挑战时,NoSQL 概念应运而生。

以下是使用 MongoDB 的优势:

  • 可伸缩性:由于分布式的特性,多个服务器和数据中心可以有冗余数据。
  • 高性能:MongoDB 对于存储和检索数据非常有效,部分原因是数据库中的元素和集合之间没有关系。
  • 灵活性:键值存储是原型开发的理想选择,因为它不需要开发人员了解模式,也不需要固定的数据模型或复杂的迁移。

云计算

云计算由以下组件组成:

  • 基础设施即服务(IaaS),包括 Rackspace 和 Amazon Web Services
  • 平台即服务(PaaS),包括 Heroku 和 Windows Azure
  • 后端即服务(BaaS),最新、最酷的产品,包括 Parse.com 和 Firebase
  • 软件即服务(SaaS),包括谷歌应用和 Salesforce.com

云应用平台具有以下优势:

  • 可扩展性;例如,它们可以在几分钟内产生新的实例
  • 易于部署;例如,要推送至 Heroku,您只需使用$ git push
  • 按需付费计划,用户根据需求添加或删除内存和磁盘空间
  • 用于简化数据库、应用服务器、软件包等的安装和配置的附加组件
  • 安全和支持

PaaS 和 BaaS 是原型开发、构建最小可行产品(MVP)和早期创业公司的理想选择。

以下是最受欢迎的 PaaS 解决方案列表:

HTTP 请求和响应

每个 HTTP 请求和响应都由以下组件组成:

  • Header:关于编码、正文长度、来源、内容类型等信息
  • 主体:传递给服务器或发送回客户端的内容,通常是参数或数据

此外,HTTP 请求包含以下元素:

  • 方法:有几种方法,最常见的有GETPOSTPUTDELETE
  • URL:主机、端口、路径。例如, https://graph.facebook.com/498424660219540
  • 查询字符串:URL 中问号后面的所有内容(例如,?q=rpjs&page=20)

约定接口规范

RESTful(表述性状态转移)API 因分布式系统中的需求而变得流行,其中每个事务都需要包括关于客户端状态的足够信息。从某种意义上来说,这个标准是无状态的,因为服务器上没有存储关于客户机状态的信息,这使得不同的系统为每个请求提供服务成为可能。

以下是 RESTful API 的一些独特特征:

  • 由于不同的组件可以独立部署到不同的服务器上,因此它具有更好的可扩展性支持。
  • 因为更简单的动词和名词结构,它取代了简单对象访问协议(SOAP)。
  • 它使用的 HTTP 方法有GETPOSTDELETEPUTOPTIONS等等。

表 1-1 是一个用于消息收集的简单创建、读取、更新和删除(CRUD) RESTful API 的例子。

表 1-1。

An Example of a CRUD RESTful API

| 方法 | 统一资源定位器 | 意义 | | --- | --- | --- | | 得到 | /messages.json | 以 JSON 格式返回消息列表 | | 放 | /messages.json | 更新/替换所有消息并返回 JSON 中的状态/错误 | | 邮政 | /messages.json | 创建新消息并以 JSON 格式返回其 ID | | 得到 | /messages/{id}。数据 | 以 JSON 格式返回 ID 为{id}的消息 | | 放 | /messages/{id}。数据 | 更新/替换 ID 为{id}的消息,如果{id}消息不存在,请创建它 | | 删除 | /messages/{id}。数据 | 删除 id 为{id}的消息,以 JSON 格式返回状态/错误 |

休息不是一个协议;它是一种比 SOAP 更灵活的架构,SOAP 是一种协议。因此,如果我们想要支持这些格式,REST API URLs 可能看起来像/messages/list.html/messages/list.xml

PUTDELETE是幂等方法( en。维基百科。org/wiki/Hypertext _ Transfer _ Protocol # Idempotent _ methods _ and _ web _ applications),也就是说如果服务器收到两个或两个以上类似的请求,最终结果是一样的。GET是无效的,而POST不是幂等的,可能会影响状态并产生副作用。

关于 REST API 的进一步阅读可以在维基百科上找到。维基百科。org/wiki/presentation _ state _ transfer)以及 REST 文章简介(www . infoq . com/articles/REST-Introduction)。

摘要

第一章到此结束。在这一章中,我们已经讨论了一些网络开发的核心概念。它们将成为本书其余部分的坚实基础。我相信有些概念你很熟悉:

  • 超文本标记语言
  • 半铸钢ˌ钢性铸铁(Cast Semi-Steel)
  • JavaScript 类型和对象
  • 敏捷
  • Node.js
  • NoSQL
  • HTTP 请求
  • 约定接口规范

尽管如此,重温一下还是有好处的,因为它们数量众多,范围广阔。如果不理解理论如何应用并有益于实际代码,理论就没有那么有用或有趣。因此,我们将快速进入技术设置,让您快速进入编码项目。

二、设置

我最有效率的一天是扔掉了 1000 行代码。——肯·汤普森

在本章中,我们将讨论以下主题:

  • 对工具集的建议
  • 逐步安装本地组件
  • 使用云服务的准备

正确的设置对生产发展至关重要。当你开始长途旅行时,你需要准备好一切,对吗?该工具集将使您富有成效,其他安装都是依赖项,如 Node.js 或 MongoDB。它们分别启用服务器端代码和持久性。除此之外,在云部分,我们将介绍部署和开发服务的设置。它们将使您能够将代码置于版本控制之下,并以可伸缩的方式进行部署。

本地设置

本地设置是我们在项目工作时在开发机器上使用的。它包括从文件夹、浏览器、编辑器、HTTP 服务器到数据库的任何东西。图 2-1 显示了初始开发环境设置的示例。

显影夹

如果您的 web 开发项目没有特定的开发文件夹,您可以在 Documents 文件夹中创建一个开发文件夹(路径将是Documents/Development)。为了处理代码示例,在 web 开发项目文件夹中创建一个fullstack-javascript文件夹;例如,如果您在Development文件夹中创建一个fullstack-javascript文件夹,路径将是Documents/Development/fullstack-javascript。您可以在 Mac OS X 上使用 Finder,或者在 OS X/Linux 系统上使用以下终端命令:

$ cd ∼/Documents

$ mkdir Development

$ cd Development

$ mkdir fullstack-javascript

A978-1-4842-1751-1_2_Fig1_HTML.jpg

图 2-1。

Initial development environment setup Tip

要从终端打开当前目录中的 Mac OS Finder 应用,只需键入并运行$ open .命令。在 Windows 上,终端是命令提示符。

要获取文件和文件夹列表,请使用以下 UNIX/Linux 命令:

$ ls

或者显示隐藏的文件和文件夹,如.git,使用这个:

$ ls -lah

$ ls的另一个替代物是$ ls -alt-lah-alt选项的区别在于,后者按时间顺序排序,而前者按字母顺序排序。

您可以使用 Tab 键自动完成文件和文件夹的名称。

稍后,您可以将示例复制到fullstack-javascript文件夹中,并在该文件夹中创建应用。

Tip

另一个有用的东西是在 Mac OS X 上的 Finder 中有新的“终端在文件夹中”选项。要启用它,请打开您的“系统偏好设置”(您可以使用 Command + Space,也就是 Spotlight)。找到键盘并点击它。打开键盘快捷键,然后点按“服务”。选中“在文件夹中新建端子”和“在文件夹中新建端子”复选框。关闭窗口(可选)。

浏览器

我们建议您选择下载最新版本的 WebKit ( http://en.wikipedia.org/wiki/WebKit )或 Gecko at [http://en.wikipedia.org/wiki/Gecko_(layout_engine](http://en.wikipedia.org/wiki/Gecko_(layout_engine) )浏览器:

鉴于 Chrome 和 Safari 已经内置了开发工具,你将需要 Firefox 的 Firebug 插件(图 2-2 )。

A978-1-4842-1751-1_2_Fig2_HTML.jpg

图 2-2。

Chrome Developer Tools in action

Firebug 和开发人员工具允许开发人员做许多事情,包括:

A978-1-4842-1751-1_2_Fig3_HTML.jpg

图 2-3。

Google tutorials for mastering web developer tools

  • 调试 JavaScript
  • 操纵 HTML 和 DOM 元素
  • 动态修改 CSS
  • 监控 HTTP 请求和响应
  • 运行概要文件并检查堆转储
  • 查看加载的资源,如图像、CSS 和 JS 文件

有一些很棒的 Chrome 开发者工具(DevTools)教程,比如下面的和图 2-3 和 2-4 中显示的:

A978-1-4842-1751-1_2_Fig4_HTML.jpg

图 2-4。

Mastering Chrome DevTools

ide 和文本编辑器

JavaScript 最大的好处之一就是你不需要编译代码。因为 JS 存在并运行在浏览器中,所以您可以在浏览器中进行调试!这是一种解释型语言,不是编译型语言。因此,我们强烈推荐一个轻量级的文本编辑器,而不是一个成熟的集成开发环境( http://en.wikipedia.org/wiki/Integrated_development_environment )或 IDE,但如果您已经熟悉并熟悉您选择的 IDE,如 Eclipse、(http://www.eclipse.org/)NetBeans(http://netbeans.org/)或 Aptana ( http://aptana.com/ ),请继续使用它。

以下是 web 开发中最流行的文本编辑器和 ide 列表:

A978-1-4842-1751-1_2_Fig6_HTML.jpg

图 2-6。

WebStorm IDE home page

A978-1-4842-1751-1_2_Fig5_HTML.jpg

图 2-5。

Sublime Text code editor home page

版本控制系统

版本控制系统( http://en.wikipedia.org/wiki/Revision_control )即使在只有一个开发人员的情况下也是必不可少的。此外,许多云服务(例如 Heroku)需要 Git 进行部署。我们也强烈建议习惯 Git 和 Git 终端命令,而不是使用 Git 可视化客户端和带有 GUI 的应用:GitX ( http://gitx.frim.nl/ )、Gitbox ( http://www.gitboxapp.com/ )或 GitHub for Mac ( http://mac.github.com/

Subversion 是一个非分布式版本控制系统。本文比较了 Git 和 Subversion(git.wiki.kernel.org/index.php/GitSvnComparison)。

以下是在您的机器上安装和设置 Git 的步骤:

Download the latest version for your OS at http://git-scm.com/downloads (Figure 2-7).

A978-1-4842-1751-1_2_Fig7_HTML.jpg

图 2-7。

Downloading latest release of Git   Install Git from the downloaded *.dmg package; that is, run the *.pkg file and follow the wizard.   Find the terminal app by using Command + Space, a.k.a. Spotlight (Figure 2-8), on OS X. For Windows you could use PuTTY ( http://www.chiark.greenend.org.uk/∼sgtatham/putty/ ) or Cygwin ( http://www.cygwin.com/ ).

A978-1-4842-1751-1_2_Fig8_HTML.jpg

图 2-8。

Using Spotlight to find and run an application   In your terminal, type these commands, substituting "John Doe" and johndoe@example.com with your name and e-mail: $ git config --global user.name "John Doe" $ git config --global user.email johndoe@example.com   To check the installation, run command: $ git version   You should see something like this in your terminal window (your version might vary; in our case it’s 1.8.3.2, as shown in Figure 2-9): git version 1.8.3.2

A978-1-4842-1751-1_2_Fig9_HTML.jpg

图 2-9。

Configuring and testing Git installation

生成 SSH 密钥并将它们上传到 SaaS/PaaS 网站将在后面介绍。

本地 HTTP 服务器

虽然您可以在没有本地 HTTP 服务器的情况下进行大部分前端开发,但是在使用 HTTP 请求/AJAX 调用加载文件时需要使用本地 HTTP 服务器。此外,一般来说,使用本地 HTTP 服务器是一个很好的做法。这样,您的开发环境就尽可能接近生产环境。

我建议您使用基于 Node 的工具作为静态 web 服务器。它们缺少图形用户界面,但是简单快捷。你可以用 npm(自带 Node.js 说明在本章后面):

如果您更喜欢 GUI 而不是命令行界面(CLI ),您可能会考虑对 Apache web 服务器进行以下修改。MAMP,MAMP 堆栈和 XAMPP 有直观的图形用户界面,允许您更改配置和主机文件设置。

Mac 主页的 MAMP 如图 2-10 所示。

A978-1-4842-1751-1_2_Fig10_HTML.jpg

图 2-10。

MAMP for Mac home page

数据库:MongoDB

以下步骤更适合基于 Mac OS X/Linux 的系统,但稍加修改后,它们也可用于 Windows 系统(即$PATH变量,步骤 3)。这里我们描述官方包中的 MongoDB 安装,因为我们发现这种方法更健壮,导致的冲突更少。不过,也有很多其他方法可以在 Mac 上安装它( http://docs.mongodb.org/manual/tutorial/install-mongodb-on-os-x/ ),例如使用 Brew,也可以在其他系统上安装( http://docs.mongodb.org/manual/installation/ )。

MongoDB can be downloaded at ( http://www.mongodb.org/downloads ). For the latest Apple laptops, like MacBook Air, select OS X 64-bit version. The owners of older Macs should browse the link at ( http://dl.mongodb.org/dl/osx/i386 ).   Tip

要确定您的处理器的架构类型,请在命令行中键入$ uname -p

Unpack the package into your web development folder (∼/Documents/Development or any other). If you want, you could install MongoDB into the /usr/local/mongodb folder.   Optional: If you would like to access MongoDB commands from anywhere on your system, you need to add your mongodb path to the $PATH variable. For Mac OS X the open system paths file with: sudo vi /etc/paths or, if you prefer TextMate: mate /etc/paths And add this line to the /etc/paths file: /usr/local/mongodb/bin   Create a data folder; by default, MongoDB uses /data/db. Please note that this might be different in new versions of MongoDB. To create it, type and execute the following commands in the terminal (Figure 2-11): $ sudo mkdir -p /data/db $ sudo chown id -u /data/db

A978-1-4842-1751-1_2_Fig11_HTML.jpg

图 2-11。

Initial setup for MongoDB: Create the data directory If you prefer to use a path other than /data/db you could specify it using the --dbpath option to mongod (the main MongoDB service).   Go to the folder where you unpacked MongoDB. That location should have a bin folder in it. From there, type the following command in your terminal: $ ./bin/mongod   If you see something like the following (and as in Figure 2-12) it means that the MongoDB database server is running: MongoDB starting: pid =7218 port=27017... By default, it’s listening at http://localhost:27017. If you go to your browser and type http://localhost:28017 you should be able to see the version number, logs, and other useful information. In this case the MondoDB server is using two different ports (27017 and 28017): One is primary (native) for the communications with apps and the other is a web-based GUI for monitoring and statistics. In our Node.js code we’ll be using only 27017. Don’t forget to restart the Terminal window after adding a new path to the $PATH variable.

A978-1-4842-1751-1_2_Fig12_HTML.jpg

图 2-12。

Starting up the MongoDB server Now, to take it even further, we can test to determine if we have access to the MongoDB console/shell, which will act as a client to this server. This means that we’ll have to keep the terminal window with the server open and running.   Open another terminal window at the same folder and execute: $ ./bin/mongo You should be able to see something like "MongoDB shell version 2.0.6 ..."   Then type and execute: > db.test.save( { a: 1 } ) > db.test.find() If you see that your record is being saved, then everything went well (Figure 2-13).

A978-1-4842-1751-1_2_Fig13_HTML.jpg

图 2-13。

Running MongoDB client and storing sample data Commands find and save do exactly what you might think they do.

详细说明也可登陆 MongoDB.org: Install MongoDB on OS X ( http://docs.mongodb.org/manual/tutorial/install-mongodb-on-os-x )。对于 Windows 用户,在Installing MongoDB ( http://www.tuanleaded.com/blog/2011/10/installing-mongodb )有一篇很好的介绍文章。

Note

MAMP 和 XAMPP 的应用附带了 MySQL——一个开源的传统 SQL 数据库——和 phpMyAdmin——一个 MySQL 数据库的 web 接口。

在 Mac OS X(和大多数 UNIX 系统)上,使用 Control + C 来关闭进程。如果使用 Control + Z,它将使进程进入睡眠状态(或分离终端窗口);在这种情况下,您可能会锁定数据文件,并且必须使用 kill 命令或活动监视器,并手动删除数据文件夹中的锁定文件。在普通的 Mac 终端命令+。是 Control + C 的替代。

其他组件

这些都是必需的技术。在进入下一章之前,请确保您有这些文件。

Node.js: We need it for build tools and back-end apps.   Browser JS libraries: We need them for front-end apps.   LESS app: We need it to compile LESS into CSS (Mac OS X only).

Node.js 安装

Node.js 在 http://nodejs.org/#download 可用(图 2-14 )。安装很简单:下载归档文件并运行*.pkg包安装程序。要检查 Node.js 的安装,您可以键入并执行:

$ node -v

我在这本书里用的是 v5.1.0,并且用 v5.1.0 测试了所有的例子。如果你用的是另一个版本,风险自负。我不能保证这些例子会运行。

假设您有 5.1.0 版本,它应该会显示如下内容:

v5.1.0

如果您想在 Node.js 的多个版本之间切换,有一些解决方案可以满足您的需求:

Node.js 包已经包含了 Node 包管理器( https://npmjs.org ) (NPM)。我们将广泛使用 NPM 来安装 Node.js 模块。

A978-1-4842-1751-1_2_Fig14_HTML.jpg

图 2-14。

Node.js home page

浏览器 JavaScript 库

前端 JavaScript 库从各自的网站下载和解包。这些文件通常放在开发文件夹中(例如:/Documents/Development)以备将来使用。通常,在缩小的生产版本(在第四章的 AMD 和 Require.js 部分有更多的介绍)和广泛丰富的注释开发版本之间有一个选择。

另一种方法是热链接来自 cdn 的这些脚本,例如 Google 托管库( https://developers.google.com/speed/libraries/devguide )、CDNJS ( http://cdnjs.com/ )、微软 Ajax 内容交付网络( http://www.asp.net/ajaxlibrary/cdn.ashx )等等。通过这样做,应用对一些用户来说会更快,但如果没有互联网,就根本无法在本地运行。

更少的应用

LESS 应用是一个 Mac OS X 应用,用于将 LESS 动态编译为 CSS。在 incident57.com/less 有售(图 2-15 )。

A978-1-4842-1751-1_2_Fig15_HTML.jpg

图 2-15。

LESS App for Mac home page

云设置

下面几节讨论的云设置将允许您将代码置于版本控制之下,并以可伸缩的方式进行部署。

SSH 密钥

SSH 密钥提供了一个安全的连接,不需要每次都输入用户名和密码。对于 GitHub 库,后一种方法用于 HTTPS URL;比如https://github.com/azat-co/fullstack-javascript.git;前者使用 SSH URLs 比如git@github.com:azat-co/fullstack-javascript.git

要在 Mac OS X/UNIX 机器上为 GitHub 生成 SSH 密钥,请执行以下操作:

Check for existing SSH keys: $ cd ∼/.ssh $ ls -lah   If you see some files like id_rsa (please refer to Figure 2-16 for an example), you could delete them or back them up into a separate folder by using the following commands: $ mkdir key_backup $ cp id_rsa* key_backup $ rm id_rsa*   Now we can generate a new SSH key pair using the ssh-keygen command, assuming we are in the ∼/.ssh folder: $ ssh-keygen -t rsa -C "your_email@youremail.com"   Answer the questions; it is better to keep the default name of id_rsa. Then copy the content of the id_rsa.pub file to your clipboard (Figure 2-16): $ pbcopy < ∼/.ssh/id_rsa.pub

A978-1-4842-1751-1_2_Fig16_HTML.jpg

图 2-16。

Generating RSA key for SSH and copying public key to clipboard   Alternatively, open id_rsa.pub file in the default editor: $ open id_rsa.pub   Or in TextMate: $ mate id_rsa.pub

开源代码库

After you have copied the public key, go to github.com , log in, go to your account settings, select SSH Key, and add the new SSH key. Assign a name, such as the name of your computer, and paste the value of your public key.   To check if you have an SSH connection to GitHub, type and execute the following command in your terminal: $ ssh -T git@github.com If you see something like this: Hi your-GitHub-username! You’ve successfully authenticated, but GitHub does not provide shell access. then everything is set up.   The first time you connect to GitHub, you can receive an Authenticity of Host ... Can’t Be Established warning. Please don’t be confused with such a message—just proceed by answering Yes as shown in Figure 2-17.

A978-1-4842-1751-1_2_Fig17_HTML.jpg

图 2-17。

Testing SSH connection to GitHub for the very first time If for some reason you have a different message, please repeat Steps 3 and 4 from the previous section on SSH keys or reupload the content of your *.pub file to GitHub.   Warning

保密你的文件,不要与任何人分享!

GitHub 提供了更多的说明:生成 SSH 密钥(help.github.com/articles/generating-ssh-keys)。

Windows 用户可能会发现[PuTTY]中的 SSH 密钥生成器功能非常有用。

微软云操作系统

以下是设置 Windows Azure 帐户的步骤:

You’ll need to sign up for Windows Azure Web Site and Virtual Machine previews. Currently they have a 90-day free trial available at https://azure.microsoft.com/en-us/ .   Enable Git Deployment and create a user name and password, then upload the SSH public key to Windows Azure.   Install the Node.js SDK, which is available at https://azure.microsoft.com/en-us/develop/nodejs/ .   To check your installation type: $ azure -v You should be able to see something like this: Windows Azure: Microsoft’s Cloud Platform... Tool Version 0.6.0   Log in to Windows Azure Portal at https://windows.azure.com/ (Figure 2-18).

A978-1-4842-1751-1_2_Fig18_HTML.jpg

图 2-18。

Registering on Windows Azure   Select New, then select Web Site, and Quick Create. Type the name that will serve as the URL for your web site, and click OK.   Go to this newly created web site’s Dashboard and select Set Up Git Publishing. Come up with a user name and password. This combination can be used to deploy to any web site in your subscription, meaning that you do not need to set credentials for every web site you create. Click OK.   On the follow-up screen, it should show you the Git URL to push to, something like this: https://azatazure@azat.scm.azurewebsites.net/azat.git You will also see instructions on how to proceed with deployment. We’ll cover them later.   Advanced user option: Follow this tutorial to create a virtual machine and install MongoDB on it: Install MongoDB on a virtual machine running CentOS Linux in Windows Azure ( https://www.windowsazure.com/en-us/manage/linux/common-tasks/mongodb-on-a-linux-vm/ ).

赫罗库

Heroku 是一个多语言的敏捷应用部署平台(参见 http://www.heroku.com/ )。Heroku 的工作方式类似于 Windows Azure,你可以使用 Git 来部署应用。MongoDB 不需要安装虚拟机,因为 Heroku 有一个 MongoHQ 插件(addons.heroku.com/mongohq)。

要设置 Heroku,请按照下列步骤操作:

Sign up at ( http://heroku.com ). Currently they have a free account; to use it, select all options as minimum (0) and database as shared.   Download Heroku Toolbelt at (​toolbelt.​heroku.​com). Toolbelt is a package of tools; that is, libraries that consist of Heroku, Git, and Foreman ( github.com/ddollar/foreman ). For users of older Macs, get this client ( github.com/heroku/heroku ) directly. If you utilize another OS, browse Heroku Client GitHub ( github.com/heroku/heroku ).   After the installation is done, you should have access to the heroku command. To check it and log in to Heroku, type: $ heroku login It will ask you for Heroku credentials (user name and password), and if you’ve already created the SSH key, it will automatically upload it to the Heroku web site (Figure 2-19).

A978-1-4842-1751-1_2_Fig19_HTML.jpg

图 2-19。

The response to the successful $ heroku login command   If everything went well, to create a Heroku application inside of your specific project folder, you should be able to run this command: $ heroku create More detailed step-by-step instructions are available at Heroku: Quickstart ( devcenter.heroku.com/articles/quickstart ) and Heroku: Node.js ( devcenter.heroku.com/articles/nodejs ).

摘要

在这一章中,我们已经介绍了版本控制系统的技术设置、云客户端以及安装的各种工具和库。我们将在书中使用这些库和工具,因此安装并准备好它们是很重要的。此外,本章还提供了一些外部资源的链接,可以让你更好地理解和学习 web 开发工具。其中最有用的资源之一是 DevTools。

你一定很想开始真正的编码。等待结束了。在下一章中,我们将看到第一个 fullstack JavaScript 代码。

三、jQuery 和 Parse.com

构建一个软件设计有两种方式:一种是让它简单到没有明显的缺陷,另一种是让它复杂到没有明显的缺陷。第一种方法要困难得多。——东尼·霍尔

本章涵盖以下主题:

  • JSON、AJAX 和 CORS 的定义
  • 主要 jQuery 函数概述
  • Twitter 引导脚手架
  • 主要零部件较少
  • OpenWeatherMap API 示例上 JSONP 调用的图示
  • Parse.com 概述
  • 关于如何使用 jQuery 和 Parse.com 构建留言板前端应用的说明
  • 关于部署到 Windows Azure 和 Heroku 的分步说明
  • 消息的更新和删除

本章是前端 web 开发的基本介绍。它涵盖了对 Twitter Bootstrap 等应用前端开发很重要的东西。这些令人惊叹的库让开发人员可以很快拥有一个漂亮的用户界面。

它涵盖了术语并解释了 JSON、AJAX 和 CORS。然后,我们探索天气应用的例子。

我们使用 Parse.com 作为我们的后端来简化事情,使开发更快,同时仍然保持现实。本章的基础是用 Parse.com 和 jQuery 构建的持久性留言板应用。

定义

在此之前,让我们先澄清一些术语。它们非常重要,足以让我们停下来熟悉它们。如果这些对你来说很熟悉,你可能想跳过。

JavaScript 对象符号

下面是来自 www 的 JavaScript 对象表示法(JSON)的定义。json。组织

JavaScript Object Notation(JSON)是一种轻量级的数据交换格式。对人类来说,读和写很容易。机器很容易解析生成。它基于 JavaScript 编程语言的子集,标准 ECMA-262 第三版-1999 年 12 月 ( www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf )。

JSON 是一种完全独立于语言的文本格式,但是它使用了 C 语言系列的程序员所熟悉的约定,包括 C、C++、C#、Java、JavaScript、Perl、Python 和许多其他语言。这些特性使 JSON 成为理想的数据交换语言。

JSON 已经成为在 web 和移动应用以及第三方服务的不同组件之间传输数据的标准。JSON 也广泛用于应用内部,作为配置、地区、翻译文件或任何其他数据的格式。

典型的 JSON 对象如下所示:

{

"a": "value of a",

"b": "value of b"

}

我们有一个带有键/值对的对象。键在冒号(:)的左边,值在冒号的右边。在计算机科学术语中,JSON 相当于哈希表、键列表或关联数组(取决于特定的语言)。JSON 和 JS object literal notation(原生 JS 对象)之间唯一的大区别是,前者更严格,要求键标识符和字符串值使用双引号(")。假设我们有一个字符串格式的有效 JSON 对象,这两种类型都可以用JSON.stringify()序列化为字符串表示,用JSON.parse()反序列化。

然而,对象的每个成员都可以是数组、原语或另一个对象;例如:

{

"posts": [{

"title": "Get your mind in shape!",

"votes": 9,

"comments": ["nice!", "good link"]

}, {

"title": "Yet another post",

"votes": 0,

"comments": []

}

],

"totalPost": 2,

"getData":``function ()

return new Data().getDate();

}

}

在这个例子中,我们有一个带有posts属性的对象。属性的值是一个对象数组,每个对象都有titlevotescomments键。属性保存一个数字原语,而属性是一个字符串数组。我们也可以用函数作为值。在这种情况下,键被称为方法;也就是getData

JSON 比 XML 或其他数据格式灵活和紧凑得多,如本文所述:JSON:XML 的无脂肪替代品 ( www.json.org/xml.html )。为了方便起见,MongoDB 使用了一种类似 JSON 的格式,称为二进制 JSON ( bsonspec。org ) (BSON),稍后在 BSON 的第七章中进一步讨论。

创建交互式、快速动态网页应用的网页开发技术

在客户端(浏览器)使用异步 JavaScript 和 XML (AJAX)通过利用 JavaScript 语言中的XMLHttpRequest对象来发送和接收来自服务器的数据。尽管有这个名字,但并不要求使用 XML,通常使用 JSON。这就是为什么开发者几乎再也不说 AJAX 了。请记住,HTTP 请求可以同步发出,但是这样做并不是一个好的做法。同步请求最典型的例子是包含<script>标签。

跨域呼叫

出于安全原因,当客户端代码和服务器端代码位于不同的域时,XMLHTTPRequest 对象的初始实现不允许跨域调用。有一些方法可以解决这个问题。

其中之一就是使用 JSONP(en . Wikipedia . org/wiki/JSONP),带 padding/前缀的 JSON。这基本上是通过 DOM 生成的<script>标签进行的动态操作。脚本标签不属于相同的域限制。JSONP 请求在请求查询字符串中包含回调函数的名称。例如,jQuery.ajax()函数自动生成一个惟一的函数名,并将其附加到请求中(为了便于阅读,将一个字符串分成多行):

https://graph.facebook.com/search

?type=post

&limit=20

&q=Gatsby

&callback=jQuery16207184716751798987_1368412972614&_=1368412984735

第二种方法是使用跨源资源共享(CORS(www . w3 . org/TR/CORS)),这是一个更好的解决方案,但是它需要控制服务器端来修改响应头。我们在留言板示例应用的最终版本中使用这种技术。以下是 CORS 服务器响应标头的示例:

Access-Control-Allow-Origin: *

更多关于 CORS 的信息可以在资源中通过启用 CORS ( http://enable-cors.org/resources.html )和使用 CORS 通过 HTML5 岩石教程( http://www.html5rocks.com/en/tutorials/cors/ )获得。你可以在 test-cors.org 测试 CORS 的请求。

jQuery 函数

在培训期间,我们将使用 jQuery ( http://jquery.com/ )进行 DOM 操作、HTTP 请求和 JSONP 调用。jQuery 成为事实上的标准是因为它的$对象或函数,它提供了一种简单而有效的方法,通过 ID、类、标记名、属性值、结构或它们的任意组合来访问页面上的任何 HTML DOM 元素。语法非常类似于 CSS,我们用#表示 id,用.表示类选择。例如:

$(’#main’).hide()

$(’p.large’).attr(’style’,’color:red’)

$(’#main’).show().html(’<div>new div</div>’)

下面是最常用的 jQuery API 函数列表:

大多数 jQuery 函数不仅作用于调用它们的单个元素,如果选择的结果有多个项目,还作用于一组匹配的元素。这是一个导致错误的常见陷阱,通常发生在 jQuery 选择器太宽的时候。

此外,jQuery 有许多可用的插件和库,它们提供了丰富的用户界面或其他功能。例如:

Twitter 引导

引导您完成实施并演示项目的补充视频: http://bit.ly/1RKx9uY

Twitter Bootstrap(get Bootstrap。com )是 CSS/LESS 规则和 JavaScript 插件的集合,用于创建良好的用户界面和用户体验,而无需在圆角按钮、交叉兼容性、响应性等细节上花费大量时间。这个集合或框架非常适合你的想法的快速原型制作。然而,由于其可定制的能力,Twitter Bootstrap 也是严肃项目的良好基础。源代码是用 LESS(lessscss 写的。org ),但是普通的 CSS 也可以下载使用。

这里有一个简单的例子,使用 Twitter Bootstrap 搭建版本 4.0.0-alpha。项目的结构应该是这样的:

/01-bootstrap

-index.html

/css

-bootstrap.css

-bootstrap.min.css

... (other files if needed)

/js

-bootstrap.js

-bootstrap.min.js

-npm.js

首先让我们用适当的标签创建index.html文件:

<!DOCTYPE html>

<html``lang="en"

<head>

</head>

<body>

</body>

</html>

包含 Twitter 引导库作为一个缩小的 CSS 文件:

<!DOCTYPE html>

<html``lang="en"

<head>

<link

type="text/css"

rel="stylesheet"

href="css/bootstrap.min.css" />

</head>

<body>

</body>

</html>

应用具有container-fluidrow-fluid等级的脚手架:

<body >

<div``class="container-fluid"

<div``class="row-fluid"

</div>``<!--``row-fluid

</div>``<!--``container-fluid

</body>

Twitter Bootstrap 使用 12 列网格。单个小区的大小可以由类别spanN指定,例如span1span2span12。还有offsetN类,比如offset1offset2,...offset12,向右移动单元格。完整的参考资料可在 http://twitter.github.com/bootstrap/scaffolding.html 获得。

我们将对主要内容块使用span12hero-unit类:

<div``class="row-fluid"

<div``class="span12"

<div``id="content"

<div``class="row-fluid"

<div``class="span12"

<div``class="hero-unit"

<h1>

Welcome to Super

Simple Backbone

Starter Kit

</h1>

<p>

This is your home page.

To edit it just modify

the``<i>``index.html``</i>

</p>

<p>

<a

class="btn btn-primary btn-large"

href="http://twitter.github.com/bootstrap

target="_blank" >

Learn more

</a>

</p>

</div>``<!--``hero-unit

</div>``<!--``span12

</div>``<!--``row-fluid

</div>``<!--``content

</div>``<!--``span12

</div>``<!--``row-fluid

这是来自 1-bootstrapindex.html的完整源代码:

<!DOCTYPE html>

<html``lang="en"

<head>

<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"

</head>

<body >

<div``class="container-fluid"

<div``class="row-fluid"

<div``class="span12"

<div``id="content"

<div``class="row-fluid"

<div``class="span12"

<div``class="hero-unit"

<h1>``Welcome to Super Simple Backbone Starter Kit

<p>``This is your home page. To edit it just modify``<i>``index.html``</i>``file!

<p><a``class="btn btn-primary btn-large" href="http://twitter.github.com/bootstrap" target="_blank"``>??</a></p>

</div>``<!--``hero-unit

</div>``<!--``span12

</div>``<!--``row-fluid

</div>``<!--``content

</div>``<!--``span12

</div>``<!--``row-fluid

</div>``<!--``container-fluid

</body>

</html>

这个例子可以从 GitHub 公共github.com/azat-co/fullstack-javascript 下载和拉取,在 01-bootstrap 文件夹 ( https://github.com/azat-co/fullstack-javascript/tree/master/01-bootstrap )下。如果你更喜欢看截屏,我在 YouTube 上录了一个( http://bit.ly/1RKx9uY )。

本视频和其他视频将带您完成书中概述的相同步骤。所以,如果你正在阅读这本书的印刷版本,不用担心。书中的信息足够了。

这里有一些其他有用的工具——CSS 框架和 CSS 预处理程序——值得一试:

要使用 Twitter Bootstrap 源文件,您需要使用 LESS 或 SASS (另一个类似 LESS 的 CSS 框架)。

较少的

LESS 是一种动态样式表语言。有时候,在这种情况下,少即是多,多即是少。浏览器无法解释更少的语法,因此必须以三种方式之一将更少的源代码编译成 CSS:

In the browser by the LESS JavaScript library   On the server side by language or framework; for example, for Node.js there is the LESS module ( https://www.npmjs.com/package/less )   Locally on your machine by command line (installed with npm by running $ npm install -g less), WinLess ( http://winless.org/ ), LESS App ( http://incident57.com/codekit/index.html ), SimpLESS ( http://wearekiss.com/simpless ), or a similar app

浏览器选项适用于开发环境,但不太适合生产环境。

以下是一些在线编译工具:

LESS 具有变量、混合和操作符,使得开发人员可以更快地重用 CSS 规则。

较少变量

变量减少了冗余,并允许开发人员通过将它们放在一个规范的地方来快速更改值,我们知道在设计(和样式)中我们经常需要非常频繁地更改值。

我们有时会用一些更少的代码,用符号@标记变量,比如在@color中:

@color: #4D926F;

#header {

color:``@color

}

h2 {

color:``@color

}

这段代码将被编译成 CSS 中的等效代码:

#header {

color:``#4D926F

}

h2 {

color:``#4D926F

}

好处是,只需要在一个地方更新颜色值,而在 CSS 中只需要两个地方。这是最好的抽象。

更少的混音

这是关于混音功能的。混合的语法与创建类选择器的语法相同。例如,这是一个.border混音:

.border {

border-top:``dotted 1px black

border-bottom:``solid 2px black

}

#menu a {

color:``#111

.border;

}

.post a {

color:``red

.border;

}

转换成这个 CSS,其中的.border被替换为实际的样式,而不是名称:

.border {

border-top:``dotted 1px black

border-bottom:``solid 2px black

}

#menu a {

color:``#111

border-top:``dotted 1px black

border-bottom:``solid 2px black

}

.post a {

color:``red

border-top:``dotted 1px black

border-bottom:``solid 2px black

}

更有用的是给混音传递一个参数。这使得开发人员能够创建更加通用的代码。例如,.rounded-corners是一个可以根据参数radius的值改变大小的混音:

.rounded-corners (@radius: 5px) {

border-radius:``@radius

-webkit-border-radius:``@radius

-moz-border-radius:``@radius

}

#header {

.rounded-corners;

}

#footer {

.rounded-corners(10px);

}

该代码将编译成 CSS 格式:

#header {

border-radius:``5px

-webkit-border-radius:``5px

-moz-border-radius:``5px

}

#footer {

border-radius:``10px

-webkit-border-radius:``10px

-moz-border-radius:``10px

}

无论您使用不带参数的 mix-in 还是带多个参数的 mix-in,它们在创建抽象和实现更好的代码重用方面都非常出色。

较少操作

少支持运营。通过运算,我们可以对数字、颜色或变量执行数学函数。这对于大小、颜色和其他与数字相关的样式非常有用。

下面是一个在 LESS 中执行乘法和加法的运算符示例:

@the-border: 1px;

@base-color: #111;

@red:        #842210;

#header {

color:``@base-color * 3

border-left:``@the-border

border-right:``@the-border * 2

}

#footer {

color:``@base-color + #003300

border-color:``desaturate(@red, 10%)

}

该代码在 CSS 中编译,编译器用变量和操作替换表达式的结果:

#header {

color:``#333333

border-left:``1px

border-right:``2px

}

#footer {

color:``#114411

border-color:``#7d2717

}

如您所见,LESS 显著提高了普通 CSS 的可重用性。在大型项目中,这可以节省时间,因为您可以创建更少的模块,并在多个应用中重用它们。

其他重要的少特征 ( http://lesscss.org/#docs )包括如下:

  • 模式匹配
  • 嵌套规则
  • 功能
  • 名称空间
  • 范围
  • 评论
  • 进口

使用第三方 API (OpenWeatherMap)和 jQuery 的示例

引导您完成实施并演示项目的补充视频: http://bit.ly/1RKxyxA

这个例子纯粹是为了演示的目的。它不是后面章节中讨论的主要留言板应用的一部分。目标只是说明 jQuery、JSONP 和 REST API 技术的组合。

注意,这个例子使用了 OpenWeatherMap API 2.5。API 要求对 REST 调用进行身份验证(一个应用 ID)。您可以在 openweathermap.org/appid 获得所需的钥匙。API 文档可在 openweathermap.org/api 获得。

这个天气应用的想法是向您显示城市名称的输入字段以及公制和英制的按钮。一旦你输入城市名称并点击其中一个按钮,应用将从 OpenWeatherMap 获取天气预报。

在这个例子中,我们将使用 jQuery 的$.ajax()函数。它具有以下语法:

var request = $.ajax({

url: url,

dataType: ’jsonp’,

data: {q: cityName, appid: appId, units: units},

jsonpCallback: ’fetchData’,

type: ’GET’

}).fail(function(error){

console.error(error)

alert(’Error sending request’)

})

在刚刚显示的ajax()函数的代码片段中,我们使用了以下参数:

  • url是 API 的一个端点。
  • dataType是我们期望从服务器得到的数据类型;例如,“json”、“xml”、“jsonp”(带前缀的 JSON—不支持 CORS 的服务器的格式)。
  • data是要发送到服务器的数据。
  • jsonpCallback是函数名,字符串格式,请求返回后调用;默认情况下,jQuery 会创建一个名称。
  • type是请求的 HTTP 方法;比如“得”、“贴”。

还有一个链接的方法.fail,它有一个逻辑,当请求有错误(即失败)时做什么。

有关ajax()功能的更多参数和示例,请访问 api.jquery.com/jQuery.ajax

为了将我们的函数分配给用户触发的事件,我们需要使用 jQuery 库中的click函数。语法非常简单:

$(’#btn’).click(function() {

...

}

$(’#btn’)是一个 jQuery 对象,用btnid指向 DOM 中的一个 HTML 元素。

为了确保我们想要访问和使用的所有元素都在 DOM 中,我们需要将所有 DOM 操作代码包含在以下 jQuery 函数中:

$(document).ready(function(){

...

}

这是动态生成的 HTML 元素的常见错误。它们在被创建并注入 DOM 之前是不可用的。

我们必须将按钮的事件处理程序放在$(document).ready()回调中。否则,代码可能会尝试将事件侦听器附加到不存在的 DOM 元素。$(document).ready()确保浏览器呈现所有的 DOM 元素。

$(document).ready(function(){

$(’.btn-metric’).click(function() {

prepareData(’metric’)

})

$(’.btn-imperial’).click(function() {

prepareData(’imperial’)

})

})

我们使用类而不是 ID,因为类更灵活(不能有多个同名的 ID)。下面是按钮的 HTML 代码:

<div``class="row"

<div``class="span6 offset1"

<input``type="button" class="btn-primary btn btn-metric" value="Get forecast in metric"

<div``class="span6 offset1"

<input``type="button" class="btn-danger btn btn-imperial" value="Get forecast in imperial"

</div>

<div``class="span3"

<p``id="info"

</div>

</div>

ID 为info的最后一个容器是我们放置预测的地方。

这个想法很简单:我们有按钮和事件侦听器,一旦用户单击按钮,它们就会做一些事情。前面提到的按钮调用prepareData()方法。这是它的定义:

var openWeatherAppId = ’GET-YOUR-KEY-AT-OPENWEATHERMAP’,

openWeatherUrl = ’http://api.openweathermap.org/data/2.5/forecast

var prepareData = function(units) {

var cityName = $(’#city-name’).val()

if (cityName && cityName != ’’){

cityName = cityName.trim()

getData(openWeatherUrl, cityName, openWeatherAppId, units)

}

else {

alert(’Please enter the city name’)

}

}

代码应该简单明了。我们从输入框中获取城市名称的值,检查它是否为空,并调用getDada(),这将向服务器发出 XHR 请求。您已经看到了一个$.ajax请求的例子。请注意,回调函数名为fetchData。这个函数将在浏览器从 OpenWeatherMap API 获得响应后被调用。不用说,我们必须按如下方式传递城市名称、应用 ID 和单位:

function getData (url, cityName, appId, units) {

var request = $.ajax({

url: url,

dataType: ’jsonp’,

data: {

q: cityName,

appid: appId,

units: units

},

jsonpCallback: ’fetchData’,

type: ’GET’

}).fail(function(error){

console.error(error)

alert(’Error sending request’)

})

}

JSONP 获取函数神奇地(多亏了 jQuery)通过注入脚本标记和将回调函数名附加到请求查询字符串来进行跨域调用。

此时,我们需要实现fetchData并用预测更新视图。console.log有助于查找响应的数据结构;也就是字段所在的位置。城市名称和国家将显示在预测上方,以确保找到的位置与我们在输入框中请求的位置相同。

function fetchData (forecast) {

console.log(forecast)

var html = ’’,

cityName = forecast.city.name,

country = forecast.city.country

现在,我们通过迭代预测并连接字符串来形成 HTML:

html += ’<h3> Weather Forecast for ’

+ cityName

+ ’, ’

+ country

+ ’</h3>’

forecast.list.forEach(function(forecastEntry, index, list){

html += ’<p>’

+ forecastEntry.dt_txt

+ ’: ’

+ forecastEntry.main.temp

+ ’</p>’

})

最后,我们为 ID 为log的 div 获取一个 jQuery 对象,并在 HTML 中注入城市名称和预测:

$(’#log’).html(html)

简单来说,有一个按钮元素触发了prepareData(),它调用了getData(),在它的回调中是fetchData()。如果您觉得这令人困惑,这里是index.html文件的完整代码:

<!DOCTYPE html>

<html``lang="en"

<head>

<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"

<script``src="js/jquery.js" type="text/javascript"

<meta``name="viewport" content="width=device-width, initial-scale=1.0"

<script>

var openWeatherAppId = ’GET-YOUR-KEY-AT-OPENWEATHERMAP’,

openWeatherUrl = ’http://api.openweathermap.org/data/2.5/forecast

var``prepareData =``function

var cityName = $(’#city-name’).val()

if (cityName && cityName != ’’){

cityName = cityName.trim()

getData(openWeatherUrl, cityName, openWeatherAppId, units)

}

else {

alert(’Please enter the city name’)

}

}

$(document).ready(function(){

$(’.btn-metric’).click(function() {

prepareData(’metric’)

})

$(’.btn-imperial’).click(function() {

prepareData(’imperial’)

})

})

function getData (url, cityName, appId, units) {

var request = $.ajax({

url: url,

dataType: ’jsonp’,

data: {

q: cityName,

appid: appId,

units: units

},

jsonpCallback: ’fetchData’,

type: ’GET’

}).fail(function(error){

console.error(error)

alert(’Error sending request’)

})

}

function fetchData (forecast) {

console.log(forecast)

var html = ’’,

cityName = forecast.city.name,

country = forecast.city.country

html += ’<h3> Weather Forecast for ’

+ cityName

+ ’, ’

+ country

+ ’</h3>’

forecast.list.forEach(function(forecastEntry, index, list){

html += ’<p>’

+ forecastEntry.dt_txt

+ ’: ’

+ forecastEntry.main.temp

+ ’</p>’

})

$(’#log’).html(html)

}

</script>

</head>

<body>

<div``class

<div``class

<div``class

<h2>Weather App</h2>

<p>Enter city name to get the weather forecast</p>

</div>

<div class="span6  offset1"><input class="span4" type="text" placeholder="Enter the city name" id="city-name" value=""/>

</div>

</div>

<div``class

<div``class``="span6 offset1"><input type="button"``class

<div class="span6 offset1"><input type="button" class="btn-danger btn btn-imperial" value="Get forecast in imperial"/>

</div>

<div class="span3">

<p id="info"></p>

</div>

</div>

<div``class

<div``class

<div id="log">Nothing to show yet</div>

</div>

</div>

<div class="row">

<hr/>

<p>Azat Mardan (<a href="``http://twitter.com/azat_co">@azat_co</a>)</p

</div>

</div>

</body>

</html>

试着启动它,看看它是否在本地 HTTP 服务器上工作(只需在浏览器中打开index.html)。如果没有 HTTP 服务器,它应该无法工作,因为它依赖于 JSONP 技术。你可以得到http-statichttp-server命令行工具,如第二章所述。

源代码可以在 0 2-weather 文件夹和 GitHub ( github)上找到。com/azat-co/full stack-JavaScript/tree/master/02-weather)。YouTube 上有一个截屏视频,它会带你完成实现并演示该应用。

此示例是用 OpenWeatherMap API v2.5 构建的,可能不适用于更高版本。此外,您还需要名为 app ID 的 API 键。您可以在 openweathermap.org/appid 获得所需的钥匙。如果你觉得必须要有一个工作实例,请将你的反馈提交到 GitHub 资源库,以获得本书的项目( https://github.com/azat-co/fullstack-javascript )。

jQuery 是一个很好的从 RESTful 服务器获取数据的库。有时我们不仅仅是从服务器上读取数据;我们也想写它。通过这种方式,信息得以保存,并可以在以后被访问。Parse.com 将允许你没有摩擦地保存你的数据。

Parse.com

引导您完成实施并演示项目的补充视频: http://bit.ly/1SU8imX

Parse.com(parse。com )是一个可以替代数据库和服务器的服务。它最初是作为支持移动应用开发的手段出现的。然而,有了 REST API 和 JavaScript SDK,Parse.com 可以在任何 web 和桌面应用中用于数据存储(以及更多),这使得它成为快速原型开发的理想选择。

去 Parse.com 注册一个免费账户。创建一个应用,复制应用 ID、REST API 键和 JavaScript 键。我们需要这些钥匙来打开我们在 Parse.com 的收藏。请注意数据浏览器选项卡,因为在那里您可以看到您的收藏和项目。

我们将创建一个简单的应用,使用 Parse.com JavaScript SDK 将值保存到集合中。我们的应用将由一个index.html文件和一个app.js文件组成。下面是我们项目文件夹的结构:

/03-parse-sdk

-index.html

-app.js

-jquery.js

/css

-boostrap. css

该示例位于 GitHub ( github)上的 03-parse-sdk 文件夹中。com/azat-co/full stack-JavaScript/tree/master/03-parse-SDK),但是鼓励您从头开始键入自己的代码。首先,创建index.html文件:

<html``lang="en"

<head>

从本地文件中包含缩小的 jQuery v2.1.4 库(可以下载并保存到文件夹中):

<script

type="text/javascript"

src=

"jquery.js" >

</script>

包括来自解析 CDN 位置的 Parse.com JavaScript SDK 库 v1.6.7:

<script

src="// www.parsecdn.com/js/parse-1.6.7.min.js "

</script>

包括我们的app.js文件和 Twitter Bootstrap v4.0.0-alpha:

<script``type="text/javascript" src="app.js"

<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"

</head>

<body>

<!--``We’ll do something here

</body>

</html>

HTML 页面的<body><textarea>元素组成。我们将用它来输入 JSON:

<body>

<div``class="container-fluid"

<div``class="row-fluid"

<div``class="span12"

<div``id="content"

<div``class="row-fluid"

<div``class="span12"

<div``class="hero-unit"

<h1>``Parse JavaScript SDK demo

<textarea``cols="60" rows="7"``>

"name": "John",

"text": "hi"

} </textarea>

<textarea>的缩进看起来不正常,因为这个元素保留了空白,当我们将该字符串处理成 JSON 时,我们不想要它。

在输入区域之后,有一个按钮将触发保存到 Parse.com:

<p><a``class="btn btn-primary btn-large btn-save"``>``Save object

<pre``class="log"

Go to``<a``href="https://parse.com/apps/" target="_blank"``>``Parse.com``</a>``to check the data.

</div>``<!--``hero-unit

</div>``<!--``span12

</div>``<!--``row-fluid

</div>``<!--``content

</div>``<!--``span12

</div>``<!--``row-fluid

</div>``<!--``container-fluid

</body>

</html>

创建app.js文件并使用$(document).ready函数来确保 DOM 已准备好进行操作:

$(document).ready(function() {

parseApplicationIdparseJavaScriptKey更改为 Parse.com 应用仪表板中的值(您需要注册该服务):

var parseApplicationId = ’GET-YOUR-KEYS-AT-PARSE.COM’

var parseJavaScriptKey = ’GET-YOUR-KEYS-AT-PARSE.COM’

因为我们已经包含了 Parse JavaScript SDK 库,所以我们现在可以访问全局对象Parse。我们用键初始化一个连接,并创建一个对Test集合的引用:

Parse.initialize(parseApplicationId, parseJavaScriptKey)

var Test = Parse.Object.extend(’Test’)

var test = new Test()

这个简单的代码将把一个带有键nametext的对象保存到 Parse.comTest集合中:

var Test = Parse.Object.extend(’Test’)

var test = new Test()

$(’.btn-save’).click(function(){

接下来的几条语句处理从<textarea>获取 JSON,并将其解析成普通的 JavaScript 对象。try/catch非常关键,因为 JSON 的结构非常严格。你不能有任何额外的符号。每次出现语法错误,都会破坏整个应用。因此,我们需要考虑错误的语法:

try {

var data = JSON.parse($(’textarea’).val())

} catch (e) {

alert(’Invalid JSON’)

}

if (!data) return false

方便的是,save()方法接受回调参数successerror,就像jQuery.ajax()函数一样。要得到确认,我们只需查看页面上的log容器(<pre class="log"></pre>):

success: function(object) {

console.log(’Parse.com object is saved: ’, object)

$(’.log’).html(JSON.stringify(object, null, 2))

// Alternatively you could use alert(’Parse.com object is saved’)

},

了解我们保存对象失败的原因很重要:

error: function(object) {

console.log(’Error! Parse.com object is not saved: ’, object)

}

})

})

})

这样你就不必点击 Github 链接(或从书中键入)来查找app.js文件的完整源代码,我在这里提供了它:

$(document).ready(function() {

var parseApplicationId = ’GET-YOUR-KEYS-AT-PARSE.COM’

var parseJavaScriptKey = ’GET-YOUR-KEYS-AT-PARSE.COM’

// Change parseApplicationId and parseJavaScriptKey to values from Parse.com application dashboard

Parse.initialize(parseApplicationId, parseJavaScriptKey)

var Test = Parse.Object.extend(’Test’)

var test = new Test()

$(’.btn-save’).click(function(){

try {

var data = JSON.parse($(’textarea’).val())

} catch (e) {

alert(’Invalid JSON’)

}

if (!data) return false

test.save(data, {

success: function(object) {

console.log(’Parse.com object is saved: ’, object)

$(’.log’).html(JSON.stringify(object, null, 2))

},

error: function(object) {

console.log(’Error! Parse.com object is not saved: ’, object)

}

})

})

})

对于这种方法,我们需要使用 Parse.com 仪表板中的 JavaScript SDK 键。对于 jQuery 示例,我们将使用来自同一 web 页面的 REST API 键。

要运行该应用,请在项目文件夹中启动本地 web 服务器,并在浏览器中导航到该地址(如http://localhost:8080)。如果你从 Parse.com 得到一个 401 未授权错误,那可能是因为你有错误的 API 密匙。

如果一切正常,您应该能够在 Parse.com 的数据浏览器中看到填充了值“John”和“hi”的Test。此外,您应该看到带有新创建的 ID 的正确消息。Parse.com 自动创建对象 id 和时间戳,这在我们的留言板应用中非常有用。

Parse.com 还为 Hello World 应用提供了详细的说明,这些说明可以在新项目( https://parse.com/apps/quickstart#js/blank )和现有项目( https://parse.com/apps/quickstart#js/existing )的快速入门指南部分中找到。

让我们继续讨论留言板应用。

Parse.com 总览留言板

引导您完成实施并演示项目的补充视频: http://bit.ly/1SU8pyS

留言板将由一个输入框、一个消息列表和一个发送按钮组成。我们需要显示现有消息的列表,并能够提交新消息。我们现在将使用 Parse.com 作为后端,稍后使用 MongoDB 切换到 Node.js。

你可以在 Parse.com 获得一个免费账户。JavaScript 指南在 https://parse.com/docs/js_guide 可用,JavaScript API 在 https://parse.com/docs/js/ 可用。

注册 Parse.com 后,转到仪表板并创建一个新的应用(如果您还没有这样做)。复制您新创建的应用的应用 ID 和 JavaScript 密钥以及 REST API 密钥。你以后会需要它们的。有几种方法可以使用 Parse.com:

  • REST API:我们将在 jQuery 示例中使用这种方法。
  • JavaScript SDK:我们刚刚在前面的测试示例中使用了这种方法,稍后我们将在 Backbone.js 示例中使用它。

REST API 是一种更通用的方法。Parse.com 提供了端点,我们可以用 jQuery 库中的$.ajax()方法请求这些端点。可用 URL 和方法的描述可以在 parse.com/docs/rest 找到。

带有 Parse.com 的留言板:REST API 和 jQuery 版本

完整的代码可以在04-board-parse-rest ( github 中找到。com/azat-co/full stack-JavaScript/tree/master/04-board-parse-rest)文件夹,但我们鼓励您先尝试编写自己的应用。

我们将使用 Parse.com 的 REST API 和 jQuery。Parse.com 支持不同的源域 AJAX 调用,所以我们不需要 JSONP。

当您决定在不同的域上部署您的后端应用(它将充当 Parse.com 的替代品)时,您需要在前端使用 JSONP,或者在后端使用定制的 CORS 头文件。这一主题将在本书的后面介绍。

现在,应用的结构应该如下所示:

index.html

css/bootstrap.min.css

css/style.css

js/app.js

img/spinner.gif

让我们为留言板应用创建一个可视化表示。我们只想按时间顺序显示带有用户名的消息列表。因此,一个表格就足够了,我们可以动态地创建<tr>元素,并在获得新消息时不断地插入它们。

用以下内容创建一个简单的 HTML 文件index.html:

  • 包含 JS 和 CSS 文件
  • 具有 Twitter 引导的响应结构
  • 信息表
  • 新邮件的表单

让我们从head和依赖项开始。我们将包括 CDN jQuery、local app.js、local minified Twitter Bootstrap 和自定义样式表style.css:

<!DOCTYPE html>

<html``lang="en"

<head>

<script``src="js/jquery.js" type="text/javascript" language="javascript"

<script``src="js/app.js" type="text/javascript" language="javascript"

<link``href="css/bootstrap.min.css" type="text/css" rel="stylesheet"

<link``href="css/style.css" type="text/css" rel="stylesheet"

<meta``name="viewport" content="width=device-width, initial-scale=1"

</head>

body 元素将具有由类container-fluidrow-fluid定义的典型 Twitter Boostrap scaffolding 元素:

<body>

<div``class="container-fluid"

<div``class="row-fluid"

<h1>``Message Board with Parse REST API

消息表是空的,因为我们将从 JS 代码中以编程方式填充它:

<table``class="table table-bordered table-striped"

<caption>``Messages

<thead>

<tr>

<th>

Username

</th>

<th>

Message

</th>

</tr>

</thead>

<tbody>

<tr>

<td``colspan="2"``><img``src="img/spinner.gif" width="20"

</tr>

</tbody>

</table>

</div>

另一行是我们的新消息表单,其中的发送按钮使用了 Twitter 引导类btnbtn-primary:

<div``class="row-fluid"

<form``id="new-user"

<input type="text" name="username"

placeholder="Username" />

<input type="text" name="message"

placeholder="Message" />

<a``id="send" class="btn btn-primary"``>``SEND

</form>

</div>

</div>

</body>

</html>

该表将包含我们的消息,该表单将为新消息提供输入。

现在我们要写三个主要函数:

getMessages(): The function to get the messages   updateView(): The function to render the list of messages   $(’#send’).click(...): The function that triggers sending a new message

为了简单起见,我们将所有的逻辑放在一个文件app.js中。当然,当你的项目越来越大时,根据功能来分离代码是一个好主意。

用您自己的值替换这些值,并注意使用 REST API 键(不是前面示例中的 JavaScript SDK 键):

var parseID=’YOUR_APP_ID’

var parseRestKey=’YOUR_REST_API_KEY’

先说document.ready。它将具有获取消息的逻辑,并定义发送按钮的点击事件:

$(document).ready(function(){

getMessages()

$(’#send’).click(function(){

让我们保存按钮对象:

var $sendButton = $(this)

我们应该显示一个微调图像(“正在加载...”)按钮上,因为请求可能需要一些时间,我们希望用户看到我们的应用正在工作,而不是无缘无故地冻结。

$sendButton.html(’<img src="img/spinner.gif" width="20"/>’)

var username = $(’input[name=username]’).val()

var message = $(’input[name=message]’).val()

当我们提交一个新消息(POST 请求)时,我们用jQuery.ajax函数进行 HTTP 调用。在 api.jquery.com/jQuery.ajax 可获得ajax功能的完整参数列表。最重要的是 URL、头和类型参数。

$.ajax({

url: ’https://api.parse.com/1/classes/MessageBoard

headers: {

’X-Parse-Application-Id’: parseAppID,

’X-Parse-REST-API-Key’: parseRestKey

},

contentType: ’application/json’,

数据的类型是 JSON:

dataType: ’json’,

processData: false,

data: JSON.stringify({

’username’: username,

’message’: message

}),

type: ’POST’,

success: function() {

console.log(’sent’)

假设我们的 POST 请求 Parse 保存了新消息(success),我们现在希望获得包含我们的消息的更新的消息列表,并使用文本替换微调图像,就像有人单击按钮之前一样:

getMessages()

$sendButton.html(’SEND’)

},

error: function() {

console.log(’error’)

$sendButton.html(’SEND’)

}

})

总的来说,单击 Send 按钮将向 Parse.com REST API 发送一个 POST 请求,然后在成功响应时,获得调用getMessages()函数的消息。

从我们的远程 REST API 服务器获取消息的getMessages()方法也使用了jQuery.ajax函数。该 URL 包含收藏集的名称(MessageBoard)和一个查询字符串参数,该参数将限制设置为 1,000:

function getMessages() {

$.ajax({

url: ’https://api.parse.com/1/classes/MessageBoard?limit=1000

我们需要在头中传递密钥:

headers: {

’X-Parse-Application-Id’: parseAppID,

’X-Parse-REST-API-Key’: parseRestKey

},

contentType: ’application/json’,

dataType: ’json’,

type: ’GET’,

如果请求成功完成(状态200/ok或类似),我们调用updateView函数:

success: function(data) {

console.log(’get’)

updateView(data)

},

error: function() {

console.log(’error’)

}

})

}

然后,在成功响应时,它将调用updateView()函数,该函数清除表tbody,并使用$.each jQuery 函数( api.jquery.com/jQuery.each )遍历响应的结果。

该函数呈现我们从服务器获得的消息列表:

function updateView(messages) {

我们使用 jQuery 选择器.table tbody创建一个引用该元素的对象。然后我们清理该元素的所有 innerHTML:

var table=$(’.table tbody’)

table.html(’’)

我们使用jQuery.each函数遍历每条消息:

$.each(messages.results, function (index, value) {

var trEl =

以下代码以编程方式创建 HTML 元素(以及这些元素的 jQuery 对象):

(’<tr><td>’

+ value.username

+ ’</td><td>’

+ value.message +

’</td></tr>’)

从某种意义上来说,trEl是一个字符串,包含留言板中每条消息或每一行的 HTML。下一行将表的tbody元素追加到我们的行中:

table.append(trEl)

})

console.log(messages)

}

下面是使用 jQuery 动态创建 HTML 元素(例如,div)的另一种方法:

$(’<div>’)

以下是完整的app.js供您参考:

var parseAppID=’your-parse-app-id’

var parseRestKey=’your-rest-api-key’

$(document).ready(function(){

getMessages()

$(’#send’).click(function(){

var $sendButton = $(this)

$sendButton.html(’<img src="img/spinner.gif" width="20"/>’)

var username = $(’input[name=username]’).val()

var message = $(’input[name=message]’).val()

$.ajax({

url: ’https://api.parse.com/1/classes/MessageBoard

headers: {

’X-Parse-Application-Id’: parseAppID,

’X-Parse-REST-API-Key’: parseRestKey

},

contentType: ’application/json’,

dataType: ’json’,

processData: false,

data: JSON.stringify({

’username’: username,

’message’: message

}),

type: ’POST’,

success: function() {

console.log(’sent’)

getMessages()

$sendButton.html(’SEND’)

},

error: function() {

console.log(’error’)

$sendButton.html(’SEND’)

}

})

})

})

function getMessages() {

$.ajax({

url: ’https://api.parse.com/1/classes/MessageBoard?limit=1000

headers: {

’X-Parse-Application-Id’: parseAppID,

’X-Parse-REST-API-Key’: parseRestKey

},

contentType: ’application/json’,

dataType: ’json’,

type: ’GET’,

success: function(data) {

console.log(’get’)

updateView(data)

},

error: function() {

console.log(’error’)

}

})

}

function updateView(messages) {

var table=$(’.table tbody’)

table.html(’’)

$.each(messages.results, function (index, value) {

var trEl=(’<tr><td>’

+ value.username

+ ’</td><td>’

+ value.message

+ ’</td></tr>’)

table.append(trEl)

})

console.log(messages)

}

尝试使用本地 HTTP 服务器运行代码。您应该看到消息(显然,第一次应该没有消息),并通过单击按钮能够发布新消息。

如果您需要做的只是在本地机器上开发应用,这没问题,但是将它部署到云上呢?要做到这一点,我们首先需要用 Git 应用版本控制。

推送至 GitHub

向您介绍项目部署的补充视频(Git 和 Heroku 部分从 9 分 57 秒开始): http://bit.ly/1SU8K4I

要创建 GitHub 存储库,请进入 github.com ,登录并创建一个新的存储库。会有一个 SSH 地址;复制它。在您的终端窗口中,导航到您想要推送到 GitHub 的项目文件夹。

Create a local Git and .git folder in the root of the project folder: $ git init   Add all of the files to the repository and start tracking them: $ git add .   Make the first commit: $ git commit -am "initial commit"   Add the GitHub remote destination: $ git remote add your-github-repo-ssh-url It might look something like this: $ git remote add origin git@github.com:azat-co/simple-message-board.git   Now everything should be set to push your local Git repository to the remote destination on GitHub with the following command: $ git push origin master   You should be able to see your files at github.com under your account and repository.

稍后,当您对文件进行更改时,没有必要重复所有这些步骤。只需执行:

$ git add .

$ git commit -am "some message"

$ git push origin master

如果没有要开始跟踪的新的未跟踪文件,请使用以下命令:

$ git commit -am "some message"

$ git push origin master

要包括单个文件的更改,请运行:

$ git commit filename -m "some message"

$ git push origin master

要从 Git 存储库中删除文件,请使用:

$ git rm filename

有关更多 Git 命令,请参见:

$ git --help

使用 Windows Azure 或 Heroku 部署应用就像将代码和文件推送到 GitHub 一样简单。最后三个步骤(4–6)将替换为不同的远程目标(URL)和不同的别名。

部署到 Windows Azure

使用此过程,您应该能够使用 Git 部署到 Windows Azure。

Go to the Windows Azure Portal at https://windows.azure.com/ 1, log in with your Live ID and create a web site if you haven’t done so already. Enable Set Up Git Publishing by providing a user name and password (they should be different from your Live ID credentials). Copy your URL somewhere.   Create a local Git repository in the project folder that you would like to publish or deploy: $ git init   Add all of the files to the repository and start tracking them: $ git add .   Make the first commit: $ git commit -am "initial commit"   Add Windows Azure as a remote Git repository destination: $ git remote add azure your-url-for-remote-repository In my case, this command looked like this: $ git remote add > azure https://azatazure@azat.scm.azurewebsites.net/azat.git   Push your local Git repository to the remote Windows Azure repository, which will deploy the files and application: $ git push azure master

与 GitHub 一样,当您稍后更新文件时,没有必要重复前面的几个步骤,因为我们已经应该在项目文件夹的根目录中有一个以.git文件夹形式的本地 Git 存储库。

部署到 Heroku

向您介绍项目部署的补充视频(Git 和 Heroku 部分从 9 分 57 秒开始): http://bit.ly/1SU8K4I

唯一的主要区别是 Heroku 使用 Cedar Stack,它不支持静态项目,包括普通的 HTML 应用,如我们的 Parse.com 测试应用或 Parse.com 版本的留言板应用。我们可以使用一个“假”的 PHP 项目来克服这个限制。在项目文件夹中创建一个与index.html处于同一级别的文件index.php,您希望将该文件发布或部署到 Heroku,其内容如下:

<?php``echo file_get_contents(’index.html’);

为了方便起见,index.php文件已经包含在04-board-parse-rest中。

有一种更简单的方法可以用 Cedar Stack 在 Heroku 上发布静态文件,这在 Heroku Cedar 上的 post Static 站点中有所描述。heroku-cedar 河畔 static-sites。html 。要让 Cedar Stack 处理静态文件,您只需在项目文件夹中键入并执行以下命令:

$``touch

$``echo``’php_flag engine off’``>

或者,您可以使用 Ruby Bamboo 堆栈。在这种情况下,我们需要以下结构:

-project folder

-config.ru

/public

-index.html

-/css

app.js

...

index.html中到 CSS 和其他资产的路径应该是相对的,也就是’css/style.css’config.ru文件应该包含以下代码:

use Rack::Static,

:urls => ["/stylesheets", "/images"],

:root => "public"

run lambda { |env|

[

200,

{

’Content-Type’  => ’text/html’,

’Cache-Control’ => ’public, max-age=86400’

},

File.open(’public/index.html’, File::RDONLY)

]

}

更多详情可以参考 devcenter.heroku.com/articles/static-sites-on-heroku

一旦您有了 Cedar Stack 或 Bamboo 的所有支持文件,请遵循以下步骤:

Create a local Git repository and .git folder if you haven’t done so already: $ git init   Add files: $ git add .   Commit files and changes: $ git commit -m "my first commit"   Create the Heroku Cedar Stack application and add the remote destination: $ heroku create If everything went well, it should tell you that the remote has been added and the app has been created, and give you the app name.   To look up the remote type and execute (optional): $ git remote show   Deploy the code to Heroku with: $ git push heroku master Terminal logs should tell you whether or not the deployment went smoothly.   To open the app in your default browser, type: $ heroku open or just go to the URL of your app, something like http://yourappname-NNNN.herokuapp.com .   To look at the Heroku logs for this app, type: $ heroku logs To update the app with the new code, repeat the following steps only: $ git add -A $ git commit -m "commit for deploy to heroku" $ git push -f heroku

每次使用命令$ heroku create创建一个新的 Heroku 应用时,您都会被分配一个新的应用 URL。

更新和删除消息

根据 REST API,对象的更新通过PUT方法执行,删除通过DELETE方法执行。只要我们提供一个想要对其执行操作的对象的 ID,这两个操作都可以用我们对GETPOST使用的同一个jQuery.ajax函数轻松执行。

摘要

这一章很难。希望您对 JSON、AJAX 和跨域调用有所了解。记住,当访问服务器时,你需要确保它们支持 CORS 或 JSONP。

我们已经介绍了一些最基本的特性,并使用 Parse 来持久化数据。我们还使用 Git 版本系统将我们的应用部署到云中。

四、Backbone.js 简介

代码不是资产。这是一种责任。写的越多,以后要维护的就越多。—未知

本章将演示:

  • 从头开始设置 Backbone.js 应用并安装依赖项
  • 使用 Backbone.js 集合
  • Backbone.js 事件绑定
  • Backbone.js 视图和带有下划线的子视图
  • 重构 Backbone.js 代码
  • AMD 和 Require.js 进行 Backbone.js 开发
  • Backbone.js 产品需要. js
  • 一个简单的 Backbone.js 初学者工具包

Backbone.js 已经存在了一段时间,所以它非常成熟,可以被信任用于严肃的前端开发项目。这个框架绝对是极简主义和非个人化的。您可以将 Backbone.js 与许多其他库和模块一起使用。我认为 Backbone.js 是构建一个定制框架的基础,这个框架将非常适合您的特定用例。

有些人对 Backbone.js 的非个性化和极简主义感到厌恶。他们更喜欢能为他们做更多事情的框架,并执行一种特定的做事方式(例如,Angular 最佳实践(github . com/John papa/Angular-style guide))。这对我来说完全没问题,您可以继续研究更复杂的前端框架。它们都非常适合 Node.js 堆栈和生态系统。就本书的目的而言,Backbone.js 是理想的,因为它为普通的非框架 jQuery 代码提供了一些急需的理性,同时它没有陡峭的学习曲线。你需要知道的只是一些类和方法,我们在这本书里会讲到。其他的都是 JavaScript,不是特定领域的语言。

从头开始设置 Backbone.js 应用

我们将使用 Backbone.js 和模式-视图-控制器(MVC)架构构建一个典型的 starter Hello World 应用。在开始时,这听起来可能有点过头了,但是随着我们的发展,我们会增加越来越多的复杂性,包括模型、子视图和集合。

Hello World 应用的完整源代码可以在05-backbone/hello-world下和 GitHub ( https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/hello-world )上找到。

Backbone.js 依赖项

引导您完成实施并演示项目的补充视频: http://bit.ly/1O7xRCY

下载以下库:

显然,当这本书出版时,这些版本不会是最新的。我建议坚持使用本书中的版本,因为这是我用来测试所有示例和项目的版本。使用不同的较新版本可能会导致一些意外的冲突。

创建一个index.html文件,并将这些框架包含在这个文件中,如下所示:

< !DOCTYPE>

<html>

<head>

<script``src="jquery.js"

<script``src="underscore.js"

<script``src="backbone.js"

<script>

// TODO write some awesome JS code!

</script>

</head>

<body>

</body>

</html>

我们也可以将<script>标签放在文件末尾的</body>标签之后。这将改变脚本和其余 HTML 的加载顺序,并影响大文件的性能。

让我们在<script>标签中定义一个简单的 Backbone.js 路由:

...

var router = Backbone.Router.extend({

})

...

现在,为了简单起见(亲亲——保持简单),我们将把所有的 JavaScript 代码放在index.html文件中。对于真正的开发或生产代码来说,这不是一个好主意,所以我们稍后将重构它。

接下来,在一个extend调用中设置一个特殊的routes属性:

var router = Backbone.Router.extend({

routes: {

}

})

Backbone.js routes属性需要采用以下格式:’path/:param’:’action’

这将导致filename#path/param URL 触发一个名为action的函数(在Router对象中定义)。现在,我们将添加一条home路线:

var router = Backbone.Router.extend({

routes: {

’’: ’home’

}

})

这很好,但是现在我们需要添加一个home函数:

var router = Backbone.Router.extend({

routes: {

’’: ’home’

},

home: function(){

// TODO render HTML

}

})

稍后我们将回到home函数,为视图的创建和渲染添加更多的逻辑。现在我们应该定义我们的homeView:

var homeView = Backbone.View.extend({

})

看起来很眼熟吧?Backbone.js 对其所有组件使用相似的语法:extend函数和一个 JSON 对象作为它的参数。

从现在开始有多种方法可以继续,但是最佳实践是使用eltemplate属性,它们在 Backbone.js 中是特殊的:

var homeView = Backbone.View.extend({

el: ’body’,

template: _.template(’Hello World’)

})

属性el只是一个包含 jQuery 选择器的字符串(您可以使用带有“.”的类名)和带' # '的 id 名称)。模板属性被赋予了一个Underscore.js函数template,其中只有一个纯文本“Hello World”。

为了呈现我们的homeView,我们使用了this.$el,它是一个编译过的 jQuery 对象,引用了el属性中的元素,并使用 jQuery .html()函数用this.template()值替换 HTML。以下是我们的 Backbone.js 视图的完整代码:

var homeView = Backbone.View.extend({

el: ’body’,

template: _.template(’Hello World’),

render: function(){

this.$el.html(this.template({}))

}

})

现在,如果我们回到router,我们可以将这两行添加到home函数中:

var router = Backbone.Router.extend({

routes: {

’’: ’home’

},

initialize: function(){

},

home: function(){

this.homeView = new homeView

this.homeView.render()

}

})

第一行创建了homeView对象,并将其分配给路由的homeView属性。第二行将调用homeView对象中的render()方法,触发‘Hello World’输出。

最后,为了启动一个主干应用,我们在一个文档就绪包装器中调用new Router来确保文件的 DOM 被完全加载:

var app

$(document).ready(function(){

app = new router

Backbone.history.start()

})

这一次,我不会列出index.html的完整源代码,因为它相当简单。

在浏览器中打开index.html看看是否工作;也就是说,“Hello World”消息应该出现在页面上。

使用 Backbone.js 集合

引导您完成实施并演示项目的补充视频: http://bit.ly/1O7xRCY

这个例子的完整源代码在05-backbone/collections下面。它建立在从头开始设置 Backbone.js 应用练习中的“Hello World”示例的基础上,可以从 GitHub ( https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/collections )下载。

我们应该添加一些数据来玩,充实我们的观点。为此,请将此代码添加到<script>标签之后,其他代码之前:

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

这是我们的苹果数据库,或者更准确地说,是我们的 REST API 端点替代品,它为我们提供了苹果的名称和图像 URL(数据模型)。

注意,通过将后端的 REST API 端点分配给 Backbone.js 集合、模型或两者中的url属性,并对它们调用fetch()方法,可以很容易地替换这个模拟数据集。

现在,为了让用户体验更好一点,我们可以在主干路由中向routes对象添加一个新路由:

...

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

...

这将允许用户去index.html#apples/SOMENAME并期望看到一些关于苹果的信息。该信息将由主干路由定义中的loadApple函数获取和呈现:

loadApple: function(appleName){

this.appleView.render(appleName)

}

你注意到一个appleName变量了吗?这与我们在route中使用的名称完全相同。这就是我们如何在 Backbone.js 中访问查询字符串参数(例如,?param=value&q=search)

现在我们需要重构一些代码来创建一个主干集合,用我们的appleData变量中的数据填充它,并将集合传递给homeViewappleView。非常方便的是,我们在路由构造器方法initialize中完成了这一切:

initialize: function(){

var apples = new Apples()

apples.reset(appleData)

this.homeView = new homeView({collection: apples})

this.appleView = new appleView({collection: apples})

},

此时,我们已经基本完成了对Router类的处理,它看起来应该是这样的:

var router = Backbone.Router.extend({

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize: function(){

var apples = new Apples()

apples.reset(appleData)

this.homeView = new homeView({collection: apples})

this.appleView = new appleView({collection: apples})

},

home: function(){

this.homeView.render()

},

loadApple: function(appleName){

this.appleView.render(appleName)

}

})

让我们稍微修改一下homeView来查看整个数据库:

var homeView = Backbone.View.extend({

el: ’body’,

template: _.template(’Apple data: <%= data %>’),

render: function(){

this.$el.html(this.template({data: JSON.stringify(this.collection.models)}))

}

// TODO subviews

})

现在,我们只是在浏览器中输出 JSON 对象的字符串表示。这一点也不用户友好,但是稍后我们将通过使用列表和子视图来改进它。

我们的 apple Backbone 系列非常简洁:

var Apples = Backbone.Collection.extend({

})

当我们从其 API 中使用fetch()reset()函数时,Backbone 会自动在集合中创建模型。我发现使用这些功能非常有用。

苹果视图不再复杂;它只有两个属性:templaterender。在模板中,我们希望显示带有特定值的figureimgfigcaption标签。下划线. js 模板引擎在这项任务中非常方便:

var appleView = Backbone.View.extend({

template: _.template(

’<figure>\

<img src="<%= attributes.url %>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

...

})

为了使包含 HTML 标记的 JavaScript 字符串更具可读性,我们可以使用反斜杠换行符转义符(\),或者关闭字符串并用加号(+)将它们连接起来。这是前面的appleView的一个例子,它是用后一种方法重构的:

var appleView = Backbone.View.extend({

template: _.template(

’<figure>’+

+’<img src="<%= attributes.url %>"/>’+

+’<figcaption><%= attributes.name %></figcaption>’+

+’</figure>’),

...

})

请注意“”符号;它们是 Undescore.js 在attributes对象的属性urlname中打印值的指令。

最后,我们将render函数添加到appleView类中。

render: function(appleName){

为了获得按名称过滤的苹果列表,在Collection类上有一个where方法。我们只需要数组中的第一项,因为 JavaScript 中的数组是从零开始的(它们从 0 开始,而不是从 1 开始),按名称获取 apple 模型的语法如下:

var appleModel = this.collection.where({name: appleName})[0]

一旦我们有了自己的模型,我们需要做的就是把模型传递给模板(也叫补水模板)。结果是我们注入到<body>中的一些 HTML:

var appleHtml = this.template(appleModel)

$(’body’).html(appleHtml)

}

因此,我们通过where()方法在集合中找到一个模型,并使用[]选择第一个元素。现在,render函数负责数据的加载和渲染。稍后我们将重构函数,将这两个功能分离到不同的方法中。

为了您的方便,这里是整个应用,它在05-backbone/collections/index.html和 GitHub ( https://github.com/azat-co/fullstack-javascript/blob/master/05-backbone/collections/index.html )文件夹中:

< !DOCTYPE>

<html>

<head>

<script``src="jquery.js"

<script``src="underscore.js"

<script``src="backbone.js"

<script>

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

var app

var router = Backbone.Router.extend({

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize:``function

var``apples =``new

apples.reset(appleData)

this``.homeView =``new

this``.appleView =``new

},

home:``function

this .homeView.render()

},

loadApple:``function

this .appleView.render(appleName)

}

})

var homeView = Backbone.View.extend({

el: ’body’,

template: _.template(’Apple data: <%= data %>’),

render:``function

this .$el.html( this .template({data: JSON.stringify( this

}

})

var Apples = Backbone.Collection.extend({

})

var appleView = Backbone.View.extend({

template: _.template(’<figure>\

<img src="<%= attributes.url%>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

render: function(appleName){

var appleModel = this.collection.where({name: appleName})[0]

var appleHtml = this.template(appleModel)

$(’body’).html(appleHtml)

}

})

$(document).ready(function(){

app = new router

Backbone.history.start()

})

</script>

</head>

<body>

<div></div>

</body>

</html>

在浏览器中打开collections/index.html文件。你应该看到我们数据库中的数据;也就是Apple data: [{"name":"fuji","url":"img/fuji.jpg"},{"name":"gala","url":"img/gala.jpg"}]

现在,让我们在浏览器中转到collections/index.html#apples/fujicollections/index.html#apples/gala。我们希望看到一个带标题的图像。这是一个项目的详细视图,在本例中是一个苹果。干得好!

Backbone.js 事件绑定

引导您完成实施并演示项目的补充视频: http://bit.ly/1k0ZnUB

在现实生活中,获取数据并不是瞬间发生的,所以让我们重构代码来模拟它。为了更好的用户体验(UX),我们还必须向用户显示一个加载图标(spinner 或 ajax-loader ),通知他们信息正在被加载。

在 Backbone 中有事件绑定是一件好事。如果没有它,我们将不得不传递一个呈现 HTML 的函数作为对数据加载函数的回调,以确保在我们有实际数据要显示之前不执行呈现函数。

因此,当用户进入详细视图(apples/:id)时,我们只调用加载数据的函数。然后,有了合适的事件监听器,当有新数据时(或者当数据改变时,我们的视图将自动地(这不是一个错误)更新自己;Backbone.js 支持多个甚至自定义事件)。

供您参考,如果您不想输入代码(这是我推荐的),它在05-backbone/binding和 GitHub ( https://github.com/azat-co/fullstack-javascript/blob/master/05-backbone/binding/index.html )中。

让我们更改路由中的代码:

...

loadApple: function(appleName){

this.appleView.loadApple(appleName)

}

...

其他一切都保持不变,直到我们到达appleView类。我们需要添加一个构造函数或一个initialize方法,这是 Backbone.js 框架中的一个特殊单词或属性。每次我们创建一个对象的实例,比如var someObj = new SomeObject(),它都会被调用。我们还可以向initialize函数传递额外的参数,就像我们对视图所做的那样(我们传递了一个带有键collection和值apples主干集合的对象)。在 backbonejs.org/#View-constructor 阅读更多关于 Backbone.js 构造函数的内容。

...

var appleView = Backbone.View.extend({

initialize: function(){

// TODO: create and setup model (aka an apple)

},

...

我们有我们的initialize函数;现在我们需要创建一个代表单个苹果的模型,并在模型上设置适当的事件监听器。我们将使用两种类型的事件,change和一个名为spinner的自定义事件。为此,我们将使用on()函数,它接受这些属性:on(event, actions, context)。你可以在 backbonejs.org/#Events-on 了解更多。

...

var appleView = Backbone.View.extend({

initialize: function(){

this.model = new (Backbone.Model.extend({}))

this.model.bind(’change’, this.render, this)

this.bind(’spinner’, this.showSpinner, this)

},

...

})

...

前面的代码基本上可以归结为两件简单的事情:

Call the render() function of the appleView object when the model has changed.   Call the showSpinner() method of the appleView object when event spinner has been fired.

到目前为止,一切顺利,对吧?但是 GIF 图标 spinner 又是怎么回事呢?让我们在appleView中创建新的属性:

...

templateSpinner: ’<img src="img/spinner.gif" width="30"/>’,

...

还记得路由里的loadApple调用吗?这就是我们如何实现appleView中的功能:

...

loadApple:function(appleName){

要显示 spinner GIF 图像,使用this.trigger让 Backbone 调用showSpinner:

this.trigger(’spinner’)

接下来,我们需要访问闭包内部的上下文。有时候我喜欢用一个有意义的名字来代替_this或者self,所以:

var view = this

接下来,您将对服务器进行 XHR 调用(例如,$.ajax())来获取数据。我们将模拟从远程服务器获取数据时的实时延迟:

setTimeout(function(){

view.model.set(view.collection.where({

name:appleName

})[0].attributes)

}, 1000)

},

...

attributes是一个 Backbone.js 模型属性,它为一个普通的 JavaScript 对象提供了模型属性。总的来说,第一行将触发spinner事件(我们仍然需要为其编写函数)。第二行只是用于范围问题(所以我们可以在闭包里使用appleView)。

setTimeout函数模拟真实远程服务器响应的时间延迟。在其中,我们通过使用一个model.set()函数和一个model.attributes属性(返回模型的属性)将一个选定模型的属性分配给视图的模型。

现在我们可以从render方法中移除一段额外的代码,并实现showSpinner函数:

render: function(appleName){

var appleHtml = this.template(this.model)

$(’body’).html(appleHtml)

},

showSpinner: function(){

$(’body’).html(this.templateSpinner)

}

...

仅此而已!在浏览器中打开index.html#apples/galaindex.html#apples/fuji,在等待苹果图像加载的同时欣赏加载动画。

下面是index.html文件的完整代码(也在05-backbone/binding/index.htmlhttps://github.com/azat-co/fullstack-javascript/blob/master/5-backbone/binding/index.html ):

< !DOCTYPE>

<html>

<head>

<script``src="jquery.js"

<script``src="underscore.js"

<script``src="backbone.js"

<script>

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

var app

var router = Backbone.Router.extend({

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize:``function

var``apples =``new

apples.reset(appleData)

this``.homeView =``new

this``.appleView =``new

},

home:``function

this .homeView.render()

},

loadApple:``function

this .appleView.loadApple(appleName)

}

})

var homeView = Backbone.View.extend({

el: ’body’,

template: _.template(’Apple data: <%= data %>’),

render:``function

this .$el.html( this .template({data: JSON.stringify( this

}

})

var Apples = Backbone.Collection.extend({

})

var appleView = Backbone.View.extend({

initialize:``function

this``.model =``new

this .model.on(’change’, this .render, this

this .on(’spinner’, this .showSpinner, this

},

template: _.template(’<figure>\

<img src="<%= attributes.url%>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

templateSpinner: ’<img src="img/spinner.gif" width="30"/>’,

loadApple:``function

this .trigger(’spinner’)

var``view =

setTimeout(``function

view.model.set(view.collection.where({name: appleName})[0].attributes)

}, 1000)

},

render:``function

var appleHtml = this .template( this

$(’body’).html(appleHtml)

},

showSpinner:``function

$(’body’).html(``this

}

})

$(document).ready(function(){

app =``new

Backbone.history.start()

})

</script>

</head>

<body>

<div></div>

</body>

</html>

Backbone.js 视图和带有下划线的子视图

引导您完成实施并演示项目的补充视频: http://bit.ly/1k0ZnUB 。而这个例子在 https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/subview 都有。

子视图是在另一个主干视图中创建和使用的主干视图。子视图概念是抽象(分离)UI 事件(例如,点击)和类似结构元素(例如,苹果)的模板的好方法。

子视图的用例可能包括表格中的一行、列表中的一项、一个段落或一个新行。

我们将重构我们的主页来显示一个漂亮的苹果列表。每个列表项都有一个苹果名称和一个带有onClick事件的购买链接。让我们首先用我们的标准主干extend()函数为单个苹果创建一个子视图:

...

var appleItemView = Backbone.View.extend({

tagName: ’li’,

template: _.template(’’

+’<a href="#apples/<%=name%>" target="_blank">’

+’<%=name%>’

+’</a> <a class="add-to-cart" href="#">buy</a>’),

events: {

’click .add-to-cart’: ’addToCart’

},

render: function() {

this.$el.html(this.template(this.model.attributes))

},

addToCart: function(){

this.model.collection.trigger(’addToCart’, this.model)

}

})

...

现在我们可以用tagNametemplateeventsrenderaddToCart属性和方法填充对象。

...

tagName: ’li’,

...

tagName自动允许 Backbone.js 创建一个带有指定标签名的 HTML 元素,在本例中是列表项的<li>。这将是单个苹果的表示,我们列表中的一行。

...

template: _.template(’’

+’<a href="#apples/<%=name%>" target="_blank">’

+’<%=name%>’

+’</a> <a class="add-to-cart" href="#">buy</a>’),

...

模板只是一个带有下划线. js 指令的字符串。它们被包裹在<%%>符号中。<%=简单来说就是打印一个值。同样的代码可以用反斜杠转义来编写:

...

template: _.template(’\

<a href="#apples/<%=name%>" target="_blank">\

<%=name%>\

</a> <a class="add-to-cart" href="#">buy</a>\

’),

...

每个<li>将有两个锚元素(<a>),链接到一个详细的苹果视图(#apples/:appleName,和一个购买按钮。现在我们要将一个事件监听器附加到购买按钮上:

...

events: {

’click .add-to-cart’: ’addToCart’

},

...

语法遵循以下规则:

event + jQuery element selector: function name

键和值(由冒号分隔的左右部分)都是字符串。例如:

’click .add-to-cart’: ’addToCart’

或者

’click #load-more’: ’loadMoreData’

为了呈现列表中的每一项,我们将对this.$el jQuery 对象使用 jQuery html()函数,该对象是基于我们的tagName属性的<li> HTML 元素:

...

render: function() {

this.$el.html(this.template(this.model.attributes))

},

...

addToCart将使用trigger()函数通知集合,该特定型号(apple)可供用户购买:

...

addToCart: function(){

this.model.collection.trigger(’addToCart’, this.model)

}

...

下面是appleItemView主干视图类的完整代码:

...

var appleItemView = Backbone.View.extend({

tagName: ’li’,

template: _.template(’’

+ ’<a href="#apples/<%=name%>" target="_blank">’

+ ’<%=name%>’

+ ’</a> <a class="add-to-cart" href="#">buy</a>’),

events: {

’click .add-to-cart’: ’addToCart’

},

render: function() {

this.$el.html(this.template(this.model.attributes))

},

addToCart: function(){

this.model.collection.trigger(’addToCart’, this.model)

}

})

...

很简单!但是主视图呢,它应该呈现我们所有的项目(苹果)并为 HTML 元素提供一个包装器<ul>容器?我们需要修改和增强我们的homeView

首先,我们可以添加 jQuery 可以理解的额外的string类型的属性作为homeView的选择器:

...

el: ’body’,

listEl: ’.apples-list’,

cartEl: ’.cart-box’,

...

我们可以在模板中使用前面的属性,或者只是在homeView中硬编码它们(我们稍后将重构我们的代码):

...

template: _.template(’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’),

...

创建homeView(new homeView())时会调用initialize函数。在这里,我们呈现我们的模板(使用我们最喜欢的html()函数),并将一个事件监听器附加到集合,集合是一组苹果模型:

...

initialize: function() {

this.$el.html(this.template)

this.collection.on(’addToCart’, this.showCart, this)

},

...

绑定事件的语法在上一节中介绍过。本质上是在调用homeViewshowCart()函数。在这个函数中,我们将appleName添加到购物车中(连同一个换行符,一个<br/>元素):

...

showCart: function(appleModel) {

$(this.cartEl).append(appleModel.attributes.name + ’<br/>’)

},

...

最后,这是我们期待已久的render()方法,在该方法中,我们遍历集合中的每个模型(每个苹果),为每个苹果创建一个appleItemView,为每个苹果创建一个<li>元素,并将该元素附加到 DOM 中带有类apples-listview.listEl<ul>元素:

...

render: function(){

view = this

// So we can use view inside of closure

this.collection.each(function(apple){

var appleSubView = new appleItemView({model:apple})

// Creates subview with model apple

appleSubView.render()

// Compiles template and single apple data

$(view.listEl).append(appleSubView.$el)

// Append jQuery object from single

// Apple to apples-list DOM element

})

}

...

让我们确保在homeView主干视图中没有遗漏任何东西。以下是没有内联注释的完整代码:

...

var homeView = Backbone.View.extend({

el: ’body’,

listEl: ’.apples-list’,

cartEl: ’.cart-box’,

template: _.template(’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’),

initialize: function() {

this.$el.html(this.template)

this.collection.on(’addToCart’, this.showCart, this)

},

showCart: function(appleModel) {

$(this.cartEl).append(appleModel.attributes.name + ’<br/>’)

},

render: function(){

view = this

this.collection.each(function(apple){

var appleSubView = new appleItemView({model: apple})

appleSubView.render()

$(view.listEl).append(appleSubView.$el)

})

}

})

...

您应该能够点击购买按钮,并在购物车中放入您选择的苹果。查看单个苹果不再需要在浏览器的 URL 地址栏中键入其名称。我们可以单击该名称来打开一个新窗口,其中包含详细视图。

通过使用子视图,我们为所有项目(苹果)重用了模板,并为每个项目附加了一个特定的事件(见图 4-1 )。这些事件足够智能,可以将关于模型的信息传递给其他对象:视图和集合。

A978-1-4842-1751-1_4_Fig1_HTML.jpg

图 4-1。

The list of apples rendered by subviews

为了以防万一,下面是子视图示例的完整代码,也可以在 https://github.com/azat-co/fullstack-javascript/blob/master/05-backbone/subview/index.html 获得:

<!DOCTYPE>

<html>

<head>

<script src="jquery.js"></script>

<script src="underscore.js"></script>

<script src="backbone.js"></script>

<script>

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

var app

var router = Backbone.Router.extend({

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize: function(){

var apples = new Apples()

apples.reset(appleData)

this.homeView = new homeView({collection: apples})

this.appleView = new appleView({collection: apples})

},

home: function(){

this.homeView.render()

},

loadApple: function(appleName){

this.appleView.loadApple(appleName)

}

})

var appleItemView = Backbone.View.extend({

tagName: ’li’,

template: _.template(’\

<a href="#apples/<%=name%>" target="_blank">\

<%=name%>\

</a> <a class="add-to-cart" href="#">buy</a>\

’),

events: {

’click .add-to-cart’: ’addToCart’

},

render: function() {

this.$el.html(this.template(this.model.attributes))

},

addToCart: function(){

this.model.collection.trigger(’addToCart’, this.model)

}

})

var homeView = Backbone.View.extend({

el: ’body’,

listEl: ’.apples-list’,

cartEl: ’.cart-box’,

template: _.template(’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’),

initialize: function() {

this.$el.html(this.template)

this.collection.on(’addToCart’, this.showCart, this)

},

showCart: function(appleModel) {

$(this.cartEl).append(appleModel.attributes.name + ’<br/>’)

},

render: function(){

view = this

this.collection.each(function(apple){

var appleSubView = new appleItemView({model: apple})

appleSubView.render()

$(view.listEl).append(appleSubView.$el)

})

}

})

var Apples = Backbone.Collection.extend({

})

var appleView = Backbone.View.extend({

initialize: function(){

this.model = new (Backbone.Model.extend({}))

this.model.on(’change’, this.render, this)

this.on(’spinner’, this.showSpinner, this)

},

template: _.template(’<figure>\

<img src="<%= attributes.url%>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

templateSpinner: ’<img src="img/spinner.gif" width="30"/>’,

loadApple:function(appleName){

this.trigger(’spinner’)

var view = this

setTimeout(function(){

view.model.set(view.collection.where({name: appleName})[0].attributes)

}, 1000)

},

render: function(appleName){

var appleHtml = this.template(this.model)

$(’body’).html(appleHtml)

},

showSpinner: function(){

$(’body’).html(this.templateSpinner)

}

})

$(document).ready(function(){

app = new router

Backbone.history.start()

})

</script>

</head>

<body>

<div></div>

</body>

</html>

到单个项目的链接,例如collections/index.html#apples/fuji,也应该通过在浏览器地址栏中键入它而独立工作。

重构 Backbone.js 代码

引导您完成实施并演示项目的补充视频: http://bit.ly/1k0ZnUB

此时,您可能想知道使用框架并在一个文件中拥有多个具有不同功能的类、对象和元素有什么好处。这样做是为了坚持让事情简单的想法。

你的应用越大,无组织的代码库就越痛苦。让我们将应用分解成多个文件,每个文件都是以下类型之一:

  • 视角
  • 模板
  • 路由
  • 募捐
  • 模型

让我们编写这些脚本,将标签包含到我们的index.html头或主体中,如前所述:

<script``src="apple-item.view.js"

<script``src="apple-home.view.js"

<script``src="apple.view.js"

<script``src="apples.js"

<script``src="apple-app.js"

名称不必遵循破折号和点号的约定,只要能容易地说出每个文件应该做什么。

现在,让我们将对象和类复制到相应的文件中。

我们的主index.html文件应该看起来非常简洁:

< !DOCTYPE>

<html>

<head>

<script``src="jquery.js"

<script``src="underscore.js"

<script``src="backbone.js"

<script``src="apple-item.view.js"

<script``src="apple-home.view.js"

<script``src="apple.view.js"

<script``src="apples.js"

<script``src="apple-app.js"

</head>

<body>

<div></div>

</body>

</html>

其他文件只有与其文件名对应的代码。

apple-item.view.js的内容将拥有appleView对象:

var appleView = Backbone.View.extend({

initialize: function(){

this.model = new (Backbone.Model.extend({}))

this.model.on(’change’, this.render, this)

this.on(’spinner’, this.showSpinner, this)

},

template: _.template(’<figure>\

<img src="<%= attributes.url %>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

templateSpinner: ’<img src="img/spinner.gif" width="30"/>’,

loadApple:function(appleName){

this.trigger(’spinner’)

var view = this

// We’ll need to access that inside of a closure

setTimeout(function(){

// Simulates real time lag when fetching

// data from the remote server

view.model.set(view.collection.where({

name: appleName

})[0].attributes)

}, 1000)

},

render: function(appleName){

var appleHtml = this.template(this.model)

$(’body’).html(appleHtml)

},

showSpinner: function(){

$(’body’).html(this.templateSpinner)

}

})

apple-home.view.js文件具有homeView对象:

var homeView = Backbone.View.extend({

el: ’body’,

listEl: ’.apples-list’,

cartEl: ’.cart-box’,

template: _.template(’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’),

initialize: function() {

this.$el.html(this.template)

this.collection.on(’addToCart’, this.showCart, this)

},

showCart: function(appleModel) {

$(this.cartEl).append(appleModel.attributes.name + ’<br/>’)

},

render: function(){

view = this // So we can use view inside of closure

this.collection.each(function(apple){

var appleSubView = new appleItemView({model:apple})

// Create subview with model apple

appleSubView.render()

// Compiles template and single apple data

$(view.listEl).append(appleSubView.$el)

// Append jQuery object from

// single apple to apples-list DOM element

})

}

})

apple.view.js文件包含主苹果列表:

var appleView = Backbone.View.extend({

initialize: function(){

this.model = new (Backbone.Model.extend({}))

this.model.on(’change’, this.render, this)

this.on(’spinner’,this.showSpinner, this)

},

template: _.template(’<figure>\

<img src="<%= attributes.url %>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’),

templateSpinner: ’<img src="img/spinner.gif" width="30"/>’,

loadApple:function(appleName){

this.trigger(’spinner’)

var view = this

// We’ll need to access that inside of a closure

setTimeout(function(){

// Simulates real time lag when

// fetching data from the remote server

view.model.set(view.collection.where({

name:appleName

})[0].attributes)

}, 1000)

},

render: function(appleName){

var appleHtml = this.template(this.model)

$(’body’).html(appleHtml)

},

showSpinner: function(){

$(’body’).html(this.templateSpinner)

}

})

apples.js是一个空集合:

var Apples = Backbone.Collection.extend({

})

apple-app.js是包含数据、路由和启动命令的主应用文件:

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

var app

var router = Backbone.Router.extend({

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize: function(){

var apples = new Apples()

apples.reset(appleData)

this.homeView = new homeView({collection: apples})

this.appleView = new appleView({collection: apples})

},

home: function(){

this.homeView.render()

},

loadApple: function(appleName){

this.appleView.loadApple(appleName)

}

})

$(document).ready(function(){

app = new router

Backbone.history.start()

})

现在让我们尝试打开应用。它应该与前面的子视图示例完全一样。

这是一个好得多的代码组织,但它仍然远非完美,因为我们仍然在 JavaScript 代码中直接使用 HTML 模板。问题是设计人员和开发人员不能处理同一个文件,对表示的任何更改都需要更改主要代码库。

我们可以在我们的index.html文件中再添加几个 JS 文件:

<script``src="apple-item.tpl.js"

<script``src="apple-home.tpl.js"

<script``src="apple-spinner.tpl.js"

<script``src="apple.tpl.js"

通常,一个主干视图有一个模板,但是在我们的appleView——一个单独窗口中的苹果的详细视图——中,我们还有一个旋转器,一个“加载”GIF 动画。

文件的内容只是被赋予一些字符串值的全局变量。稍后,当我们调用下划线. js 辅助方法_.template()时,我们可以在视图中使用这些变量。

下面是apple-item.tpl.js文件:

var appleItemTpl = ’\

<a href="#apples/<%=name%>" target="_blank">\

<%=name%>\

</a> <a class="add-to-cart" href="#">buy</a>\

这是apple-home.tpl.js文件:

var appleHomeTpl = ’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’

下面是apple-spinner.tpl.js文件:

var appleSpinnerTpl = ’<img src="img/spinner.gif" width="30"/>’

这是apple.tpl.js文件:

var appleTpl = ’<figure>\

<img src="<%= attributes.url %>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’

现在尝试启动应用。完整代码在 https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/refactor

正如你在前面的例子中看到的,我们使用了全局范围的变量(没有关键字window)。

在全局名称空间中引入大量变量时要小心(window关键字)。可能会有冲突和其他不可预测的后果。例如,如果您编写了一个开源库,而其他开发人员开始直接使用这些方法和属性,而不是使用接口,那么当您最终决定删除或反对这些全局泄漏时,会发生什么呢?为了防止这种情况,正确编写的库和应用使用了 JavaScript 闭包 ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures )。

下面是一个使用闭包和全局变量模块定义的例子:

;(function() {

var apple= function() {

...// Do something useful like return apple object

}

window.Apple = apple

}())

如果我们需要访问 app 对象(这会创建对该对象的依赖):

;(function() {

var app = this.app

// Equivalent of window.appliation

// in case we need a dependency (app)

this.apple = function() {

...

// Return apple object/class

// Use app variable

}

// Equivalent of window.apple = function(){...}

}())

如您所见,我们已经创建了函数并立即调用了它,同时还将所有内容括在括号()中。

AMD 和 Require.js 进行 Backbone.js 开发

引导您完成实施并演示项目的补充视频: http://bit.ly/1k0ZnUB

AMD 允许我们将开发代码组织成模块,管理依赖关系,并异步加载它们。这篇文章很好地解释了为什么 AMD 是个好东西:为什么是 AMD?

启动您的本地 HTTP 服务器,例如 MAMP(www . mamp . info/en)或 node-static ( https://github.com/cloudhead/node-static )。

让我们通过使用 Require.js 库来增强我们的代码。

我们的index.html会缩水更多:

< !DOCTYPE>

<html>

<head>

<script``src="jquery.js"

<script``src="underscore.js"

<script``src="backbone.js"

<script``src="require.js"

<script``src="apple-app.js"

</head>

<body>

<div></div>

</body>

</html>

我们在应用中只包含了库和一个 JavaScript 文件。该文件具有以下结构:

require([...],function(...){...})

以一种更具解释性的方式:

require([

’name-of-the-module’,

...

’name-of-the-other-module’

],function(referenceToModule, ..., referenceToOtherModule){

...// Some useful code

referenceToModule.someMethod()

})

基本上,我们告诉浏览器从文件名数组中加载文件——require()函数的第一个参数——然后将这些文件中的模块作为变量传递给匿名回调函数(第二个参数)。在 main 函数(匿名回调)内部,我们可以通过引用那些变量来使用我们的模块。因此,我们的apple-app.js倏然变成:

require([

’apple-item.tpl’, // Can use shim plug-in

’apple-home.tpl’,

’apple-spinner.tpl’,

’apple.tpl’,

’apple-item.view’,

’apple-home.view’,

’apple.view’,

’apples’

],function(

appleItemTpl,

appleHomeTpl,

appleSpinnerTpl,

appleTpl,

appelItemView,

homeView,

appleView,

Apples

){

var appleData = [

{

name: ’fuji’,

url: ’img/fuji.jpg’

},

{

name: ’gala’,

url: ’img/gala.jpg’

}

]

var app

var router = Backbone.Router.extend({

// Check if need to be required

routes: {

’’: ’home’,

’apples/:appleName’: ’loadApple’

},

initialize: function(){

var apples = new Apples()

apples.reset(appleData)

this.homeView = new homeView({collection: apples})

this.appleView = new appleView({collection: apples})

},

home: function(){

this.homeView.render()

},

loadApple: function(appleName){

this.appleView.loadApple(appleName)

}

})

$(document).ready(function(){

app = new router

Backbone.history.start()

})

})

我们将所有代码放在函数中,该函数是require()的第二个参数,通过文件名提到模块,并通过相应的参数使用依赖关系。现在我们应该定义模块本身。这就是我们如何使用define()方法实现的:

define([...],function(...){...})

其含义类似于require()函数:依赖项是数组中作为第一个参数传递的文件名(和路径)的字符串。第二个参数是 main 函数,它接受其他库作为参数(数组中参数和模块的顺序很重要):

define([’name-of-the-module’],function(nameOfModule){

var b = nameOfModule.render()

return b

})

注意,不需要在文件名后添加.js。Require.js 会自动完成。Shim 插件用于导入 HTML 模板等文本文件。

让我们从模板开始,将它们转换成 Require.js 模块。

下面是新的apple-item.tpl.js文件:

define(function() {

return ’\

<a href="#apples/<%=name%>" target="_blank">\

<%=name%>\

</a> <a class="add-to-cart" href="#">buy</a>\

})

这是apple-home.tpl文件:

define(function(){

return ’Apple data: \

<ul class="apples-list">\

</ul>\

<div class="cart-box"></div>’

})

下面是apple-spinner.tpl.js文件:

define(function(){

return ’<img src="img/spinner.gif" width="30"/>’

})

这是apple.tpl.js文件:

define(function(){

return ’<figure>\

<img src="<%= attributes.url %>"/>\

<figcaption><%= attributes.name %></figcaption>\

</figure>’

})

下面是apple-item.view.js文件:

define(function() {

return ’\

<a href="#apples/<%=name%>" target="_blank">\

<%=name%>\

</a> <a class="add-to-cart" href="#">buy</a>\

})

apple-home.view.js文件中,我们需要声明对apple-home.tplapple-item.view.js文件的依赖:

define([’apple-home.tpl’, ’apple-item.view’], function(

appleHomeTpl,

appleItemView){

return  Backbone.View.extend({

el: ’body’,

listEl: ’.apples-list’,

cartEl: ’.cart-box’,

template: _.template(appleHomeTpl),

initialize: function() {

this.$el.html(this.template)

this.collection.on(’addToCart’, this.showCart, this)

},

showCart: function(appleModel) {

$(this.cartEl).append(appleModel.attributes.name + ’<br/>’)

},

render: function(){

view = this // So we can use view inside of closure

this.collection.each(function(apple){

var appleSubView = new appleItemView({model:apple})

// Create subview with model apple

appleSubView.render()

// Compiles template and single apple data

$(view.listEl).append(appleSubView.$el)

// Append jQuery object from

// a single apple to apples-list DOM element

})

}

})

})

apple.view.js文件依赖于两个模板:

define([

’apple.tpl’,

’apple-spinner.tpl’

], function(appleTpl,appleSpinnerTpl){

return  Backbone.View.extend({

initialize: function(){

this.model = new (Backbone.Model.extend({}))

this.model.on(’change’, this.render, this)

this.on(’spinner’,this.showSpinner, this)

},

template: _.template(appleTpl),

templateSpinner: appleSpinnerTpl,

loadApple:function(appleName){

this.trigger(’spinner’)

var view = this

// We’ll need to access that inside of a closure

setTimeout(function(){

// Simulates real time lag when

// fetching data from the remote server

view.model.set(view.collection.where({

name:appleName

})[0].attributes)

}, 1000)

},

render: function(appleName){

var appleHtml = this.template(this.model)

$(’body’).html(appleHtml)

},

showSpinner: function(){

$(’body’).html(this.templateSpinner)

}

})

})

这是apples.js文件:

define(function(){

return Backbone.Collection.extend({})

})

我希望你现在能看到这个模式。我们所有的代码都根据逻辑(例如,视图类、集合类、模板)被分割成单独的文件。主文件用require()函数加载所有的依赖项。如果我们需要非主文件中的某个模块,那么我们可以在define()方法中请求它。通常,在模块中我们想要返回一个对象;例如,在模板中我们返回字符串,在视图中我们返回主干视图类和对象。

尝试启动位于 https://github.com/azat-co/fullstack-javascript/blob/master/05-backbone/amd/ 的示例。目测应该不会有什么变化。如果您在开发工具中打开“网络”选项卡,您可以看到文件加载方式的不同。

图 4-2 ( https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/refactor/index.html )所示的旧文件以串行方式加载我们的 JavaScript 脚本,而图 4-3 ( https://github.com/azat-co/fullstack-javascript/blob/master/05-backbone/amd/index.html )所示的新文件以并行方式加载它们。

A978-1-4842-1751-1_4_Fig3_HTML.jpg

图 4-3。

The new 05-backbone/amd/index.html file

A978-1-4842-1751-1_4_Fig2_HTML.jpg

图 4-2。

The old 05-backbone/refactor/index.html file

Require.js 有很多配置选项,这些选项是通过 HTML 页面顶层的requirejs.config()调用定义的。更多信息请访问 requirejs.org/docs/api.html#config

让我们在示例中添加一个半身像参数。bust 参数将被附加到每个文件的 URL,防止浏览器缓存文件。这对开发来说是完美的,对生产来说是可怕的。

将此内容添加到其他内容之前的apple-app.js文件中:

requirejs.config({

urlArgs: ’bust=’ +  (new Date()).getTime()

})

require(

...

注意在图 [4-4 中,每个文件请求现在的状态是 200 而不是 304(未修改)。

A978-1-4842-1751-1_4_Fig4_HTML.jpg

图 4-4。

Network tab with bust parameter added

Backbone.js 产品需要. js

我们将使用 Node 包管理器(NPM)来安装requirejs库(这不是打字错误;名称中没有句号)。在项目文件夹中,在终端中运行以下命令:

$ npm init

那就跑

$ npm install requirejs

或添加-g进行全球安装:

$ npm install -g requirejs

创建一个名为app.build.js的文件:

({

appDir: "./js",

baseUrl: "./",

dir: "build",

modules: [

{

name: "apple-app"

}

]

})

将脚本文件移动到js文件夹(appDir属性)。生成的文件将被放在build文件夹中(dir参数)。有关构建文件的更多信息,请查看 https://github.com/jrburke/r.js/blob/master/build/example.build.js 提供的带有注释的广泛示例。

现在,构建一个巨大的 JavaScript 文件的一切都应该准备好了,这个文件将包含我们所有的依赖项和模块:

$ r.js -o app.build.js

或者

$ node_modules/requirejs/bin/r.js -o app.build.js

您应该会得到一个 r.js 已处理文件的列表,如图 4-5 所示。

A978-1-4842-1751-1_4_Fig5_HTML.jpg

图 4-5。

A list of the r.js processed files

从浏览器窗口的构建文件夹中打开index.html,检查网络选项卡是否显示任何改进,现在只需加载一个请求或文件(图 4-6 )。

A978-1-4842-1751-1_4_Fig6_HTML.jpg

图 4-6。

Performance improvement with one request or file to load

如需了解更多信息,请访问 requirejs.org/docs/optimization.html 查看 r.js 官方文档。

示例代码可从 https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/r 获得

还有 https://github.com/azat-co/fullstack-javascript/tree/master/05-backbone/r/build

对于 JS 文件的丑化(减小文件大小),我们可以使用丑化 2 模块。要使用 NPM 安装它,请使用:

$ npm install uglify-js

然后用optimize: "uglify2"属性更新app.build.js文件:

({

appDir: "./js",

baseUrl: "./",

dir: "build",

optimize: "uglify2",

modules: [

{

name: "apple-app"

}

]

})

使用以下命令运行 r.js:

$ node_modules/requirejs/bin/r.js -o app.build.js

您应该得到这样的结果:

define("apple-item.tpl",[],function(){return’ <a href="#apples/<%=name%>" target="_blank"> <%=name%> </a> <a class="add-to-cart" href="#">buy</a>’}),define("apple-home.tpl",[],function(){return’Apple data:<ulclass="apples-list"></ul><div class="cart-box"></div>’}),define("apple-spinner.tpl",[],function(){return’<img src="img/spinner.gif" width="30"/>’}),define("apple.tpl",[],function(){return’<figure><img src="<%= attributes.url %>"/><figcaption><%= attributes.name %></figcaption></figure>’}),define("apple-item.view",["apple-item.tpl"],function(e){return Backbone.View.extend({tagName:"li",template:_.template(e),events:{"click .add-to-cart":"addToCart"},render:function(){this.$el.html(this.template(this.model.attributes))},addToCart:function(){this.model.collection.trigger("addToCart",this.model)}})}),define("apple-home.view",["apple-home.tpl","apple-item.view"],function(e,t){return Backbone.View.extend({el:"body",listEl:".apples-list",cartEl:".cart-box",template:_.template(e),initialize:function(){this.$el.html(this.template),this.collection.on("addToCart",this.showCart,this)},showCart:function(e){$(this.cartEl).append(e.attributes.name+"<br/>")},render:function(){view=this,this.collection.each(function(e){var i=new t({model:e});i.render(),$(view.listEl).append(i.$el)})}})}),define("apple.view",["apple.tpl","apple-spinner.tpl"],function(e,t){return Backbone.View.extend({initialize:function(){this.model=new(Backbone.Model.extend({})),this.model.on("change",this.render,this),this.on("spinner",this.showSpinner,this)},template:_.template(e),templateSpinner:t,loadApple:function(e){this.trigger("spinner");var t=this;setTimeout(function(){t.model.set(t.collection.where({name:e})[0].attributes)},1e3)},render:function(){var e=this.template(this.model);$("body").html(e)},showSpinner:function(){$("body").html(this.templateSpinner)}})}),define("apples",[],function(){return Backbone.Collection.extend({})}),requirejs.config({urlArgs:"bust="+(new Date).getTime()}),require(["apple-item.tpl","apple-home.tpl","apple-spinner.tpl","apple.tpl","apple-item.view","apple-home.view","apple.view","apples"],function(e,t,i,n,a,l,p,o){var r,s=[{name:"fuji",url:"img/fuji.jpg"},{name:"gala",url:"img/gala.jpg"}],c=Backbone.Router.extend({routes:{"":"home","apples/:appleName":"loadApple"},initialize:function(){var e=new o;e.reset(s),this.homeView=new l({collection:e}),this.appleView=new p({collection:e})},home:function(){this.homeView.render()},loadApple:function(e){this.appleView.loadApple(e)}});$(document).ready(function(){r=new c,Backbone.history.start()})}),define("apple-app",function(){});

该文件故意没有格式化以显示 uglify 2(github . com/mishoo/uglifyjs 2)是如何工作的。如果没有换行符,代码就在一行上。还要注意变量和对象的名字被缩短了。

超级简单的 Backbone.js 初学者工具包

为了快速启动您的 Backbone.js 开发,可以考虑使用超级简单的 Backbone 初学者工具包 ( https://github.com/azat-co/super-simple-backbone-starter-kit )或类似的项目:

摘要

到目前为止,我们已经讲述了如何:

  • 从头开始构建一个 Backbone.js 应用。
  • 使用视图、集合、子视图、模型和事件绑定。
  • 在苹果数据库应用的例子中使用 AMD 和 Require.js。

在这一章中,你已经对 Backbone.js 有了足够的了解,可以开始在你的网络或移动应用中使用它。如果没有像 Backbone 这样的框架,随着代码的增长,它将会变得更加复杂。另一方面,使用 Backbone 或类似的 MVC,您可以更好地扩展代码。

五、Backbone.js 和 Parse.com

Java 对于 JavaScript 就像汽车对于地毯一样。— 克里斯·海尔曼

在这一章中,我们将探索为一个 Backbone.js 应用利用 Parse.com 的实际方面。这一章将说明 Backbone.js 在修改后的留言板应用上与 Parse.com 及其 JavaScript SDK 一起使用。

如果您已经编写了一些复杂的客户端应用,您可能会发现维护 JavaScript 回调和 UI 事件的复杂代码很有挑战性。js 提供了一种轻量级但功能强大的方法来将您的逻辑组织成模型-视图-控制器(MVC)类型的结构。它还有一些不错的特性,比如 URL 路由、REST API 支持、事件监听器和触发器。有关从头构建 Backbone.js 应用的更多信息和分步示例,请参考“Backbone.js 简介”一章

带有 Parse.com 的留言板:JavaScript SDK 和 Backbone.js 版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqsQC

很容易看出,如果我们不断添加越来越多的按钮,如“删除”、“更新”和其他功能,我们的异步回调系统将变得更加复杂。我们必须知道何时根据数据是否有变化来更新视图(即消息列表)。js 模型-视图-控制器(MVC)框架可用于使复杂的应用更易于管理和维护。

如果您对前面的例子感到满意,让我们使用 Backbone.js 框架来构建它。在这里,我们将一步一步地使用 Backbone.js 和 Parse.com JavaScript SDK 创建一个留言板应用。如果你对它足够熟悉,你可以在 github.com/azat-co/super-simple-backbone-starter-kit 下载这个超级简单的主干初学者工具包。与 Backbone.js 的集成将允许通过将用户动作绑定到集合的异步更新来直接实现用户动作。

该应用可从 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得,但同样鼓励您从头开始,并尝试使用示例编写自己的代码,仅供参考。

下图显示了 Parse.com、JavaScript SDK 和 Backbone.js 版本的留言板结构:

/06-board-backbone-parse-sdk

-index.html

-home.html

-footer.html

-header.html

-app.js

/css

-bootstrap.css

-bootstrap.min.css

/js

-backbone.js

-jquery.js

-underscore.js

/libs

-require.min.js

-text.js

创建一个文件夹;在文件夹中创建一个具有以下内容框架的index.html文件:

<!DOCTYPE html>

<html``lang="en"

<head>

...

</head>

<body>

...

</body>

</html>

从 Google API 下载必要的库或热链接它们。现在,在 head 元素中加入 JavaScript 库和 Twitter 引导样式表,以及其他重要但不是必需的 meta 元素。

<head>

<meta``charset="utf-8"

<title>``Message Board

<meta``name="author" content="Azat Mardan"

我们需要这种响应行为:

<meta name="viewport"

content="width=device-width, initial-scale=1.0" />

从本地文件链接 jQuery v2.1.4:

<script``src="js/jquery.js"

对下划线版本 1.8.3 和主干版本 1.2.3 进行同样的操作:

<script``src="js/underscore.js"

<script``src="js/backbone.js"

解析 JavaScript SDK v1.5.0 热链接自 Parse.com CDN。请注意版本号,因为旧版本可能无法在此示例中正常工作:

<script``src="//www.parsecdn.com/js/parse-1.5.0.min.js"``></script

Twitter 引导 CSS 包含:

<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"

我们需要 RequireJS v2.1.22 来加载依赖项:

<script``type="text/javascript" src="libs/require.js"

这里是我们的 JS 应用包含:

<script``type="text/javascript" src="app.js"

</head>

用 Twitter Bootstrap 脚手架填充<body>元素(在“基础知识”一章中有更多相关信息):

<body>

<div``class="container-fluid"

<div``class="row-fluid"

<div``class="span12"

<div``id="header"

</div>

</div>

</div>

<div``class="row-fluid"

<div``class="span12"

<div``id="content"

</div>

</div>

</div>

<div``class="row-fluid"

<div``class="span12"

<div``id="footer"

</div>

</div>

</div>

</div>

</body>

创建一个app.js文件并将 Backbone.js 视图放入其中:

  • headerView:菜单和 app-常用信息
  • footerView:版权和联系链接
  • homeView:首页内容

我们对 HTML 模板使用 Require.js 语法和 shim 插件:

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’], function (

headerTpl,

homeTpl,

footerTpl) {

具有单一索引路由的应用路由:

var ApplicationRouter = Backbone.Router.extend({

routes: {

"": "home",

"*actions": "home"

},

在我们做任何事情之前,我们可以初始化将在整个应用中使用的视图:

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

这个代码负责回家的路线:

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

header Backbone 视图附加到#header元素,并使用headerTpl模板:

HeaderView = Backbone.View.extend({

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

console.log(this.template)

$(this.el).html(_.template(this.template))

}

})

为了呈现 HTML,我们使用了jQuery.html()函数:

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

家庭主干视图定义使用了#content DOM 元素:

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

要启动一个应用,我们创建一个新实例并调用Backbone.history.start():

app = new ApplicationRouter()

Backbone.history.start()

})

app.js文件的完整代码:

require([

’libs/text!header.html’,

// Example of a shim plugin use

’libs/text!home.html’,

’libs/text!footer.html’],

function (

headerTpl,

homeTpl,

footerTpl) {

var ApplicationRouter = Backbone.Router.extend({

routes: {

’’: ’home’,

’*actions’: ’home’

},

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

HeaderView = Backbone.View.extend({

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

console.log(this.template)

$(this.el).html(_.template(this.template))

}

})

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

app = new ApplicationRouter()

Backbone.history.start()

})

上面的代码显示了模板。所有的视图和路由都在里面,这要求模块在我们开始处理模板之前确保它们已经被加载。

下面是home.html的样子:

  • 信息表
  • 下划线. js 逻辑输出表中的行
  • 一种新的消息形式

让我们通过分配row-fluidspan12类来使用 Twitter 引导库结构(及其响应组件):

<div``class="row-fluid"  id="message-board"

<div``class="span12"

<table``class="table table-bordered table-striped"

<caption>``Message Board

<thead>

<tr>

<th``class="span2"``>``Username

<th>``Message

</tr>

</thead>

<tbody>

这部分有 Underscore.js 模板指令,只是一些 js 代码包裹在<%%>标记中。我们马上检查 models 变量是否已定义并且不为空:

< %

if (typeof models != ’undefined’ && models.length > 0) {

_.each()是 UnderscoreJS 库( underscorejs.org/#each )中的一个迭代函数,它确实像它听起来那样——遍历对象/数组的元素:

_.each(models, function (value, key, list) { %>

<tr>

在迭代器函数中,我们有一个模型值。我们可以用model.attributes.attributeName访问主干模型的属性。为了用下划线输出变量,我们用<%= NAME %>代替<% CODE %>:

<td><``%= value.attributes.username %>

<td><``%= value.attributes.message %>

</tr>

< % })

}

但是如果models未定义或者为空呢?在这种情况下,我们打印一条消息,说明还没有消息。它进入else块。我们使用colspan=2将两个单元格合并成一个:

else { %>

<tr>

<td``colspan="2"``>``No messages yet

</tr>

我们关闭表格和其他 HTML 标签:

< %}%>

</tbody>

</table>

</div>

</div>

对于新的消息表单,我们也使用row-fluid类,然后添加<input>元素:

<div``class="row-fluid"  id="new-message"

<div``class="span12"

<form``class="well form-inline"

input 元素必须有名称username,因为这是我们在 JavaScript 代码中找到该元素并获得用户名值的方式:

<input type="text"

name="username"

class="input-small"

placeholder="Username" />

类似于用户名<input>标签,消息文本标签需要有名称。在这种情况下,它是message:

<input type="text" name="message"

class="input-small"

placeholder="Message Text" />

最后,发送按钮必须有 IDsend。这是我们在HomeView类的主干事件属性中使用的:

<a``id="send" class="btn btn-primary"``>``SEND

</form>

</div>

</div>

为了方便起见,下面是home.html模板文件的完整代码:

<div``class="row-fluid"  id="message-board"

<div``class="span12"

<table``class="table table-bordered table-striped"

<caption>``Message Board

<thead>

<tr>

<th``class="span2"``>``Username

<th>``Message

</tr>

</thead>

<tbody>

< % if (typeof models != ’undefined’ && models.length>0) {

_.each(models, function (value, key, list) { %>

<tr>

<td><``%= value.attributes.username %>

<td><``%= value.attributes.message %>

</tr>

< % })

}

else { %>

<tr>

<td``colspan="2"``>``No messages yet

</tr>

< %}%>

</tbody>

</table>

</div>

</div>

<div``class="row-fluid"  id="new-message"

<div``class="span12"

<form``class="well form-inline"

<input type="text"

name="username"

class="input-small"

placeholder="Username" />

<input type="text" name="message"

class="input-small"

placeholder="Message Text" />

<a``id="send" class="btn btn-primary"``>``SEND

</form>

</div>

</div>

现在,我们可以将以下组件添加到:

  • Parse.com 收藏
  • Parse.com 模型
  • 发送/添加消息事件
  • 获取/显示消息功能

来自 Parse.com JS SDK 的主干兼容模型对象/类,具有强制的className属性(这是将出现在 Parse.com 网络界面的数据浏览器中的集合的名称):

Message = Parse.Object.extend({

className: ’MessageBoard’

})

指向模型的 Parse.com JavaScript SDK 的主干兼容集合对象:

MessageBoard = Parse.Collection.extend ({

model: Message

})

主视图需要在“发送”按钮上有一个点击事件监听器:

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

当我们创建 homeView 时,我们还要创建一个集合并将事件侦听器附加到它:

initialize: function() {

this.collection = new MessageBoard()

this.collection.bind(’all’, this.render, this)

this.collection.fetch()

this.collection.on(’add’, function(message) {

message.save(null, {

success: function(message) {

console.log(’saved ’ + message)

},

error: function(message) {

console.log(’error’)

}

})

console.log(’saved’ + message)

})

},

saveMessage()的定义调用了“发送”按钮的点击事件:

saveMessage: function(){

首先,我们通过 ID ( #new-message)获取表单对象,因为使用存储的对象比每次都使用 jQuery 选择器更有效,可读性更好。

var newMessageForm = $(’#new-message’)

接下来的两行将获得名为usernamemessage的输入字段的值:

var username = newMessageForm.find(’[name="username"]’).val()

var message = newMessageForm.find(’[name="message"]’).val()

一旦我们有了新消息的值(文本和作者),我们就可以调用this.collection.add:

this.collection.add({

’username’: username,

’message’: message

})

},

最后,我们通过对模板使用_.template输出集合,然后用数据this.collection调用它:

render: function() {

$(this.el).html(_.template(this.template)(this.collection))

}

我们在app. js中操作的最终结果可能是这样的:

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’], function (

headerTpl,

homeTpl,

footerTpl) {

Parse.initialize(’your-parse-app-id’, ’your-parse-js-sdk-key’)

var ApplicationRouter = Backbone.Router.extend({

routes: {

’’: ’home’,

’*actions’: ’home’

},

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

HeaderView = Backbone.View.``extend

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

Message = Parse.Object.extend({

className: ’MessageBoard’

})

MessageBoard = Parse.Collection.extend ({

model: Message

})

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

initialize: function() {

this.collection = new MessageBoard()

this.collection.bind(’all’, this.render, this)

this.collection.fetch()

this.collection.on(’add’, function(message) {

message.save(null, {

success: function(message) {

console.log(’saved ’ + message)

},

error: function(message) {

console.log(’error’)

}

})

console.log(’saved’ + message)

})

},

saveMessage: function(){

var newMessageForm = $(’#new-message’)

var username = newMessageForm.find(’[name="username"]’).val()

var message = newMessageForm.find(’[name="message"]’).val()

this.collection.add({

’username’: username,

’message’: message

})

},

render: function() {

$(this.el).html(_.template(this.template)(this.collection))

}

})

app = new``ApplicationRouter

Backbone.history.start()

})

Backbone.js 和 Parse.com 留言板应用的完整源代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得。

将留言板带得更远

一旦你对你的前端应用在本地运行良好感到满意,不管有没有像 MAMP 或 XAMPP 这样的本地 HTTP 服务器,把它部署到 Windows Azure 或 Heroku。“jQuery 和 Parse.com”一章中描述了深入的部署说明。

在最后两个例子中,留言板有非常基本的功能。您可以通过添加更多功能来增强应用。

面向intermediate级开发人员的附加功能:

  • 在显示消息列表之前,通过 updateAt 属性对其进行排序。
  • 添加一个“刷新”按钮来更新消息列表。
  • 在运行时内存或会话中保存第一个消息条目后的用户名。
  • 在每封邮件旁边添加一个向上投票按钮,并存储投票。
  • 在每封邮件旁边添加一个向下投票按钮,并存储投票。

面向高级开发人员的附加功能:

  • 添加用户集合。
  • 防止同一用户多次投票。
  • 使用 Parse.com 函数添加用户注册和登录操作。
  • 在用户创建的每封邮件旁边添加一个“删除邮件”按钮。
  • 在用户创建的每条消息旁边添加“编辑消息”按钮。

摘要

这短短的一章给了你另一种只用 JavaScript(当然还有 HTML 和 CSS)构建应用的方法。使用 Parse.com 或类似的后端即服务(BaaS)解决方案,无需编写自己的后端代码就可以直接保存数据。BaaS solutions event 通过允许访问级控制、身份验证、服务器端逻辑和第三方集成而更进一步。

除了 Parse.com,在这一章中我们看到了 Backbone 是如何灵活的,你可以重载它的类来构建你自己的定制类。这是一种使用 Backbone 构建自己的框架的方法。这就是我们在 DocuSign 所做的,我们有基本的主干模型,并为定制用例扩展了它们。我们甚至在服务器和浏览器之间共享主干模型,允许更快的数据加载。说到服务器 JavaScript,下一章我们将探索如何用 Node.js 在服务器上编写 JavaScript。

六、Node.js 简介

任何傻瓜都能写出计算机能理解的代码。优秀的程序员编写人类能够理解的代码。——马丁·福勒

在本章中,我们将介绍以下内容:

  • 在 Node.js 中构建“Hello World”
  • Node.js 核心模块
  • npm Node 程序包管理器
  • 带有 Node.js 的留言板:内存存储版本
  • 单元测试 Node. js

Node.js 是一个用于构建 web 应用的非阻塞平台。它使用 JavaScript,所以它是我们 fullstack JavaScript 开发的核心。我们将从 Hello World 开始,讨论核心模块和 npm。然后,我们将 Hello World 应用部署到云中。

在 Node.js 中构建“Hello World”

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqFmF

要检查您的计算机上是否安装了 Node.js,请在终端中键入并执行以下命令:

$ node -v

在撰写本文时,最新版本是 5.1.0。如果你没有安装 Node.js,或者你的版本落后,可以在 nodejs.org/#download 下载最新版本。您可以使用这些工具之一进行版本管理(例如,在 Node.js 版本之间切换):

通常,你可以在 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 复制示例代码,或者从头开始编写自己的程序。如果您想做后者,为您的“Hello World”node . js 应用创建一个文件夹hello。然后创建一个server.js文件,一行一行地输入下面的代码。

这将加载服务器的核心http模块(稍后将详细介绍这些模块):

var http = require(’http’)

我们需要 Node.js 服务器的端口号。要从环境中获取或分配 1337(如果未设置环境),请使用:

var port = process.env.PORT || 1337

这将创建一个服务器,一个回调函数将包含响应处理程序代码:

var server = http.createServer(function (req, res) {

要设置正确的标题和状态代码,请使用:

res.writeHead(200, {’Content-Type’: ’text/plain’})

要输出带有行尾符号的“Hello World ”,请使用:

res.end(’Hello World\n’)

})

要设置端口并显示服务器的地址和端口号,请使用:

server.listen(port, function() {

console.log(’Server is running at %s:%s ’,

server.address().address, server.address().port)

})

从您有server.js的文件夹中,在您的终端中启动以下命令:

$ node server.js

打开 localhost:1337 或 127.0.0.1:1337 或任何其他你在终端中看到的地址作为console.log()功能的结果,你应该在浏览器中看到“Hello World”。要关闭服务器,请按 Control + C。

Note

主文件的名称可以不同于 server.js(例如 index.js 或 app.js)。如果需要启动app.js文件,只需使用$ node app.js即可。

Node.js 核心模块

与其他编程技术不同,Node.js 没有附带沉重的标准库。node.js 的核心模块是最少的,其余的可以通过 Node 包管理器(NPM)注册中心挑选。主要的核心模块、类、方法和事件包括:

这些是最重要的核心模块。让我们逐一介绍。

http

这是负责 Node.js HTTP server 的主要模块。以下是主要方法:

  • http.createServer():返回一个新的 web 服务器对象
  • http.listen():开始接受指定端口和主机名上的连接
  • http.createClient() : node app 可以是客户端,向其他服务器发出请求
  • http.ServerRequest():传入的请求被传递给请求处理程序
    • data:收到一段消息体时发出
    • end:每个请求只发出一次
    • request.method():字符串形式的请求方法
    • request.url():请求 URL 字符串
  • http.ServerResponse():这个对象是由 HTTP 服务器内部创建的——而不是由用户创建的,并被用作请求处理程序的输出
    • response.writeHead():发送请求的响应头
    • response.write():发送响应正文
    • response.end():发送并结束响应体

效用

该模块提供了用于调试的实用程序。一些方法包括:

  • util.inspect():返回一个对象的字符串表示,对调试很有用

查询字串

这个模块提供了处理查询字符串的工具。一些方法包括:

  • querystring.stringify():将对象序列化为查询字符串
  • querystring.parse():将查询字符串反序列化为对象

url

这个模块有 URL 解析和解析的工具。一些方法包括:

  • parse():取一个 URL 字符串,返回一个对象

fs

文件系统处理文件系统操作,例如读写文件。库中有同步和异步方法。一些方法包括:

  • fs.readFile():异步读取文件
  • fs.writeFile():将数据异步写入文件

不需要安装或下载核心模块。要将它们包含在您的应用中,您只需遵循以下语法:

var http = require(’http’)

非核心模块列表可在以下位置找到:

如果你想知道如何编写你自己的模块,看看这篇文章: https://quickleft.com/blog/creating-and-publishing-a-node-js-module/

npm Node 程序包管理器

Node 程序包管理器(或称 NPM)为您管理依赖关系并安装模块。Node.js 安装自带 NPM,其网址为 npmjs.org

包含关于 Node.js 应用的元信息,比如版本号;作者姓名;最重要的是,我们在应用中使用什么依赖关系。所有这些信息都在 JSON 格式的对象中,由 NPM 读取。

如果您想安装package.json中指定的软件包和依赖项,请键入:

$ npm install

典型的package.json文件可能如下所示:

{

"name": "Blerg",

"description": "Blerg blerg blerg.",

"version": "0.0.1",

"author": {

"name" : "John Doe",

"email" : "john.doe@gmail.com"

},

"repository": {

"type": "git",

"url": "http://github.com/johndoe/blerg.git

},

"engines": [

"node >= 0.6.2"

],

"scripts": {

"start": "server.js"

},

"license" : "MIT",

"dependencies": {

"express": ">= 2.5.6",

"mustache": "0.4.0",

"commander": "0.5.2"

},

"bin" : {

"blerg" : "./cli.js"

}

}

虽然上面的package.json示例中的大多数属性,如descriptionname,都是不言自明的,但是其他的属性需要更多的解释。Dependencies 是一个对象,每一项左边有名称,右边有版本号(如“express”:>= 2 . 5 . 6)。版本可以是精确的:例如," express": "2.5.6 ",或者大于,或者通配符,例如," express": "* "(一种在生产中使用新的未经测试的依赖项来放大应用的好方法:因此不推荐)。

bin属性用于命令行实用程序。它告诉系统启动什么文件。而scripts对象有你可以用$ npm run SCRIPT_NAME启动的脚本。start脚本和测试是例外。你可以用$ npm start$ npm test来运行它们。

要将软件包更新到当前最新版本或package.json中定义的版本规范允许的最新版本,请使用:

$ npm update name-of-the-package

或者对于单模块安装:

$ npm install name-of-the-package

本书示例中使用的唯一模块是mongodb,它不属于核心 Node.js 包。我们将在本书的后面安装它。

Heroku 将需要package.json在服务器上运行 NPM。

想了解更多关于 NPM 的信息,可以看看“游 NPM”(http://tobyho.com/2012/02/09/tour-of-npm)这篇文章。

将“Hello World”部署到 PaaS

对于 Heroku 和 Windows Azure 部署,我们需要一个 Git 存储库。要从项目的根目录创建它,请在终端中键入以下命令:

$ git init

Git 将创建一个隐藏的.git文件夹。现在,我们可以添加文件并进行第一次提交:

$ git add .

$ git commit -am "first commit"

提示要查看 Mac OS X Finder 应用上的隐藏文件,请在终端窗口中执行此命令:defaults write com.apple.finder AppleShowAllFiles -bool true。要将标志改回隐藏状态:defaults write com.apple.finder AppleShowAllFiles -bool false

部署到 Windows Azure

为了将我们的“Hello World”应用部署到 Windows Azure,我们必须添加 Git remote。您可以从网站下的 Windows Azure 门户复制 URL,并通过以下命令使用它:

$ git remote add azure yourURL

现在,我们应该能够使用这个命令进行推送了:

$ git push azure master

如果一切顺利,您应该会在终端中看到成功日志,并在 Windows Azure 网站 URL 的浏览器中看到“Hello World”。

要推动更改,只需执行:

$ git add .

$ git commit -m "changing to hello azure"

$ git push azure master

更细致的指导可以在 https://azure.microsoft.com/en-us/documentation/articles/web-sites-nodejs-develop-deploy-mac 教程中找到。

部署到 Heroku

对于 Heroku 部署,我们需要创建两个额外的文件:Procfilepackage.json。你可以从 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 获得源代码,或者自己写一个。

“Hello World”应用的结构如下所示:

/07-hello

-package.json

-Procfile

-server.js

Procfile 是一种机制,用于声明 Heroku 平台上应用的 dynos 运行哪些命令。基本上,它告诉 Heroku 运行什么进程。在这种情况下,Procfile 只有一行:

web: node server.js

对于这个例子,我们保持package.json简单:

{

"name": "node-example",

"version": "0.0.1",

"dependencies": {

},

"engines": {

"node": ">=0.6.x"

}

}

在项目文件夹中有了所有文件之后,我们可以使用 Git 来部署应用。除了我们需要添加 Git remote,并使用以下命令创建 Cedar 堆栈之外,这些命令与 Windows Azure 非常相似:

$ heroku create

完成后,我们推送并更新:

$ git push heroku master

$ git add .

$ git commit -am "changes :+1:"

$ git push heroku master

如果一切顺利,您应该会在终端中看到成功日志,并在 Heroku 应用 URL 的浏览器中看到“Hello World”。

带有 Node.js 的留言板:内存存储版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqO9P

第一版的留言板后端应用为了亲亲只在运行时内存存储中存储消息( http://en.wikipedia.org/wiki/KISS_principle )。这意味着每次我们启动/重置服务器时,数据都会丢失。

我们将首先从一个简单的测试用例开始,来说明测试驱动的开发方法。完整代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/08-test 获得。

单元测试 Node. js

我们应该有两种方法:

Get all of the messages as an array of JSON objects for the GET /message endpoint using the getMessages() method   Add a new message with properties name and message for POST /messages route via the addMessage() function

我们将从创建一个空的mb-server.js文件开始。在它出现之后,让我们切换到测试并创建包含以下内容的test.js文件:

var http = require(’http’)

var assert = require(’assert’)

var querystring = require(’querystring’)

var util = require(’util’)

var messageBoard = require(’./mb-server’)

assert.deepEqual(’[{"name":"John","message":"hi"}]’,

messageBoard.getMessages())

assert.deepEqual (’{"name":"Jake","message":"gogo"}’,

messageBoard.addMessage ("name=Jake&message=gogo"))

assert.deepEqual(’[{"name":"John","message":"hi"},{"name":"Jake",message":"gogo"}]’,

messageBoard.getMessages())

请记住,这是一个非常简单的字符串比较,而不是 JavaScript 对象。所以每个空格、引用和大小写都很重要。您可以通过将一个字符串解析成一个 JSON 对象来使比较“更智能”,方法是:

JSON.parse(str)

为了测试我们的假设,我们使用 Node.js 模块的核心断言。它提供了一堆有用的方法,如equal()deepEqual()等。

更高级的库包括 TDD 和/或 BDD 方法的替代接口:

对于更多的测试驱动开发和前沿自动化测试,您可以使用以下库和模块:

您可以暂时将“Hello World”脚本复制到mb-server.js文件中,或者甚至保留为空。如果我们通过终端命令运行test.js:

$ node test.js

我们应该会看到一个错误。大概是这样的:

TypeError: Object #<Object> has no method ’getMessages’

那完全没问题,因为我们还没有写getMessages()方法。因此,让我们这样做,并通过添加两个新方法使我们的应用更有用:获取聊天消息列表和向集合中添加新消息。

带有全局exports对象的mb-server.js文件:

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

我们导入依赖关系:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

并设置端口。如果它是在 env 变量中设置的,我们使用那个值;如果没有设置,我们使用硬编码值 1337:

var port = process.env.PORT || 1337

到目前为止,没什么特别的,对吧?为了存储消息列表,我们将使用一个数组:

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

一般来说,像虚拟数据这样的设备属于测试/规范文件,而不属于主要的应用代码库。

我们的服务器代码看起来会稍微有趣一些。为了获得消息列表,根据 REST 方法,我们需要发出一个 GET 请求。对于创建/添加新消息,它应该是 POST 请求。因此,在我们的 createServer 对象中,我们应该添加req.method()req.url()来检查 HTTP 请求类型和 URL 路径。

让我们加载 http 模块:

var http = require(’http’)

我们将需要来自utilquerystring模块的一些方便的函数(来序列化和反序列化对象和查询字符串):

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

创建一个服务器并将其暴露给外部模块(即test.js):

exports.server=http.createServer(function (req, res) {

// Creates server

在请求处理程序回调中,我们应该检查请求方法是否为 POST,URL 是否为messages/create.json:

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

如果上述条件为真,我们向数组中添加一条消息。但是,data必须在添加之前转换为字符串类型(编码为 UTF-8 ),因为它是一种缓冲区类型:

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

这些日志将帮助我们监控终端中的服务器活动:

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

输出应该是文本格式,状态为 200(正常):

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

我们输出一条带有新创建的对象 ID 的消息:

res.end(message)

// Out put message, should add object id

})

如果方法是 GET,URL 是/messages/list.json,则输出消息列表:

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

获取邮件列表:

var body = exports.getMessages()

// Body will hold our output

响应主体将保存我们的输出:

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

下一个else是当前面的任何条件都不匹配时。这将设置正确的标题和状态代码:

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

如果不是上面的两个端点,我们输出一个带有行结束符号的字符串:

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

启动服务器:

}).listen(port)

// Sets port and IP address of the server

现在,我们应该设置服务器的端口和 IP 地址:

console.log(’Server running at``http://127.0.0.1:%s/

我们在test.js (exports 关键字)中公开了单元测试的方法,这个函数以字符串/文本的形式返回一组消息:

exports.getMessages = function() {

return JSON.stringify(messages)

}

addMessage()使用 querystring 中的 parse/deserializer 方法将字符串转换为 JavaScript 对象:

exports.addMessage = function (data){

messages.push(querystring.parse(data))

还以 JSON-as-a-string 格式返回新消息:

return JSON.stringify(querystring.parse(data))

}

下面是mb-server.js减去注释后的完整代码。也可在08-测试中获得:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

var port = process.env.PORT || 1337

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

exports.server=http.createServer(function (req, res) {

// Creates server

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(message)

// Out put message, should add object id

})

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

var body = exports.getMessages()

// Body will hold our output

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

}).listen(port)

// Sets port and IP address of the server

console.log(’Server running at``http://127.0.0.1:%s/

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

要查看它,请访问。您应该会看到一条示例消息。

或者,您可以使用终端命令来获取消息:

$ curl http://127.0.0.1:1337/messages/list.json

使用命令行界面发出 POST 请求:

$ curl -d "name=BOB&message=test"http://127.0.0.1:1337/messages/create.json

当您刷新时,您应该在服务器终端窗口中得到输出和一条新消息“test”。不用说,这三项测试都应该通过。

您的应用可能会随着更多的方法、要解析的 URL 路径和条件而变得更大。这就是框架派上用场的地方。它们提供了处理请求的助手和其他好东西,如静态文件支持、会话等。在这个例子中,我们故意没有使用任何类似 Express ( http://expressjs.com/ )或 Restify ( http://mcavage.github.com/node-restify/ )的框架。其他值得注意的 Node.js 框架:

要获得精心挑选的框架列表,请查看(node framework。com 。改进应用的方法:

  • 通过添加对象比较而不是字符串比较来改进现有的测试用例
  • 将种子数据从mb-server.js移动到test.js
  • 添加测试用例来支持您的前端(例如,向上投票、用户登录)
  • 添加方法来支持您的前端(例如,向上投票、用户登录)
  • 为每条消息生成唯一的 id,并将它们存储在哈希中,而不是存储在数组中
  • 安装 Mocha 和 re-factor test.js,以便它使用这个库

到目前为止,我们一直将消息存储在应用内存中,所以每次应用重启时,我们都会丢失消息。要修复它,我们需要添加一个持久性,方法之一就是使用 MongoDB 这样的数据库。

摘要

在这一章中,我们已经讨论了一些重要的主题,这些主题将为我们打下基础。他们展示了 Node.js 中的“Hello World”应用,一些最重要的核心模块列表,NPM 工作流,将 Node.js 应用部署到 Heroku 和 Windows Azure 的详细命令;也是测试驱动开发实践的一个例子。

七、MongoDB 简介

What is Oracle bone inscriptions? A bunch of people. All our products are just the ideas in these people's minds-the ideas that people put into computers and tested, which proved to be the best ideas for databases or programming languages. - Larry Ellision

在本章中,我们将探讨以下主题:

  • 蒙戈布贝壳
  • Node.js 的 MongoDB 本机驱动程序
  • MongoDB on Heroku with MongoLab
  • 留言板:MongoDB 版本

MongoDB 是一个 NoSQL 文档存储数据库。它具有可扩展性和高性能。它没有模式,所以所有的逻辑和关系都在应用层实现。你可以使用像水线或者猫鼬这样的 ODM。MongoDB 使用 JavaScript 接口,完成了浏览器、服务器、数据库层的全栈 JavaScript 栈拼图。有了 MongoDB,我们可以对所有三层使用一种语言。开始使用 MongoDB 最简单的方法是使用它的 shell,也就是 REPL (read-eval-print-loop)。

蒙戈布贝壳

如果您还没有安装,请从 mongodb.org/downloads 安装最新版本的 MongoDB。更多说明,请参考第二章中的数据库:MongoDB 部分。您可能需要按照说明创建一个数据文件夹。

现在,从解压归档文件的文件夹中,使用以下命令启动mongod服务:

$ ./bin/mongod

localhost:28017时,您应该能够在您的终端和浏览器中看到信息。

对于 MongoDB shell,或mongo,在新的终端窗口中启动(重要!),并在同一文件夹中执行以下命令:

$ ./bin/mongo

根据您的 MongoDB shell 版本,您应该会看到类似这样的内容:

MongoDB shell version: 2.0.6

connecting to: test

要测试数据库,使用类似 JavaScript 的界面和命令savefind:

> db.test.save( { a: 1 } )

> db.test.find()

更详细的分步说明可以在数据库中找到:第二章的的 MongoDB 部分。

其他一些有用的 MongoDB shell 命令来自 MongoDB 和 mongose cheat sheet(https://gum.co/mongodb/git-874e6fb4):

  • > show dbs:显示服务器上的数据库
  • > use DB_NAME:选择数据库DB_NAME
  • > show collections:显示所选数据库中的收藏
  • > db.COLLECTION_NAME.find():对名称为 COLLECTION_NAME 的集合执行查找查询,查找任何项目
  • > db.COLLECTION_NAME.find({"_id": ObjectId("549d9a3081d0f07866fdaac6")}):对 COLLECTION_NAME 名称的集合执行查找查询,查找 ID 为 549d9a3081d0f07866fdaac6 的项目
  • > db.COLLECTION_NAME.find({"email": /gmail/}):对 COLLECTION_NAME 名称的集合执行查找查询,查找邮件属性与/gmail匹配的项目
  • > db.COLLECTION_NAME.update(QUERY_OBJECT, SET_OBJECT):对 COLLECTION_NAME 名称的集合执行更新查询,更新 QUERY_OBJECT 与 SET_OBJECT 匹配的项目
  • > db.COLLECTION_NAME.remove(QUERY_OBJECT):对 COLLECTION_NAME 集合中符合 QUERY_OBJECT 条件的项目进行删除查询
  • > db.COLLECTION_NAME.insert(OBJECT):将对象添加到名称为集合名称的集合中

因此,从一个新的 shell 会话开始,您可以执行以下命令来创建、更改和删除文档:

> help

> show dbs

> use board

> show collections

> db.messages.remove();

> var a = db.messages.findOne();

> printjson(a);

> a.message = "hi";

> a.name = "John";

> db.messages.save(a);

> db.messages.find({});

> db.messages.update({name: "John"},{$set: {message: "bye"}});

> db.messages.find({name: "John"});

> db.messages.remove({name: "John"});

你可以下载 MongoDB 和 Mongoose cheatsheet 作为 PDF ( https://gumroad.com/l/mongodb/fsjs-CB07C579 #)或者在 https://github.com/mongodb/node-mongodb-native/#data-types 在线查看。

MongoDB 交互 shell 的完整概述可以在 mongodb.org 上找到:概述——MongoDB 交互 Shell ( https://docs.mongodb.org/manual/tutorial/getting-started-with-the-mongo-shell/ )。

断续器

二进制 JSON 或 BSON 是 MongoDB 使用的一种特殊数据类型。它在符号上类似于 JSON,但是支持更多更复杂的数据类型,比如 buffer 或 date。

关于 BSON 有一点需要注意:MongoDB 中的 ObjectID 相当于 MongoDB Native Node.js 驱动程序中的 ObjectId(即,确保使用正确的大小写)。否则你会得到一个错误。更多关于类型:MongoDB 中的 ObjectId(http://www.mongodb.org/display/DOCS/Object+IDs)vsMongoDB 原生 Node 中的数据类型。js 干燥机 ( https://github.com/mongodb/node-mongodb-native/#data-types )。带有mongodb.ObjectID() : collection.findOne({_id: new ObjectID(idString)}, console.log) // ok的 Node.js 代码示例。另一方面,在 MongoDB shell 中,我们使用了:db.messages.findOne({_id:ObjectId(idStr)});

MongoDB 本地驱动程序

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqZSk

我们将使用 MongoDB 的 Node.js 本地驱动程序( https://github.com/christkv/node-mongodb-native )从 Node.js 应用访问 MongoDB。完整文档也可在 http://mongodb.github.com/node-mongodb-native/api-generated/db.html 获取。

要为 Node.js 安装 MongoDB 本机驱动程序,请使用:

$ npm install mongodb

更多详情请见 http://www.mongodb.org/display/DOCS/node.JS

不要忘记在package.json文件中包含依赖关系:

{

"name": "node-example",

"version": "0.0.1",

"dependencies": {

"mongodb":"",

...

},

"engines": {

"node": ">=0.6.x"

}

}

或者,对于您自己的开发,您可以使用其他映射器,它们是本地驱动程序的扩展:

这个小例子将测试我们是否可以从 Node.js 脚本连接到本地 MongoDB 实例。

在我们安装了库之后,我们可以在我们的app.js文件中包含mongodb库:

var util = require(’util’)

var mongodb = require (’mongodb’)

这是建立到 MongoDB 服务器的连接的方法之一,其中 DB 变量将保存对指定主机和端口上的数据库的引用:

var Db = mongodb.Db

var Connection = mongodb.Connection

var Server = mongodb.Server

var host = ’127.0.0.1’

var port = 27017

var db=new Db (’test’, new Server(host,port, {}))

要实际打开一个连接:

db.open(function(error, connection){

// Do something with the database here

db.close()

})

为了检查我们是否有连接,我们需要处理error。同样,让我们用db.admin()获取管理对象,用listDatabases()获取数据库列表:

var db=new Db (’test’, new Server(host, port, {}))

db.open(function(error, connection){

console.log(’error: ’, error)

var adminDb = db.admin()

adminDb.listDatabases(function(error, dbs) {

console.log(’error: ’, error)

console.log(’databases: ’, dbs.databases)

db.close()

})

})

这段代码片段可在https://github.com/mongodb/node-mongodb- native/#data-types获得。如果我们运行它,它应该在终端中输出“connected”。当你有疑问并且需要检查一个对象的属性时,在util模块中有一个有用的方法:

console.log(util.inspect(db))

现在,您可能希望在云中设置数据库,并从 Node.js 脚本测试连接。

蒙戈布 on Heroku:蒙戈布

引导您完成实施并演示项目的补充视频: http://bit.ly/1Qnr8Fn

在您使显示“已连接”的应用在本地工作之后,是时候稍微修改它并将其作为服务部署到平台上了(例如 Heroku)。

我们推荐使用 MongoLab 附加组件( https://elements.heroku.com/addons/mongolab )。MongoLab 插件提供了一个基于浏览器的 GUI 来查找和操作数据和集合。更多信息请访问 https://elements.heroku.com/addons/mongolab#docs

请注意,即使您选择了免费版本,也可能需要提供您的信用卡信息才能使用 MongoLab。不过,你不应该被起诉。

为了连接到数据库服务器,有一个数据库连接 URL(又名 MongoLab URL/URI),这是一种传输所有必要信息的方法,以便在一个字符串中连接到数据库。

数据库连接字符串MONGOLAB_URI具有以下格式:

mongodb://user:pass@server_NAME.mongolab.com:PORT/db_name

您可以从 Heroku 网站复制 MongoLab URL 字符串(并对其进行硬编码),或者从 Node.js process.env对象获取字符串:

process.env.MONGOLAB_URI

或者

var connectionUri = url.parse(process.env.MONGOLAB_URI)

全局对象进程通过process.env访问环境变量。这些变量通常用于传递数据库主机名和端口、密码、API 键、端口号以及其他不应该硬编码到主逻辑中的系统信息。

为了让我们的代码在本地和 Heroku 上都能工作,我们可以使用逻辑 OR 操作符||并在环境变量未定义的情况下分配一个本地主机和端口:

var port = process.env.PORT || 1337

var dbConnUrl = process.env.MONGOLAB_URI ||

’mongodb://127.0.0.1:27017/test’

这里是我们更新的跨环境就绪app.js文件( https://github.com/azat-co/fullstack-javascript/tree/master/10-db-connect-heroku )。我添加了一个方法来获取集合列表listCollections,而不是获取数据库列表(我们现在在 MongoLab 中只有一个数据库):

var util = require(’util’)

var url = require(’url’)

var client = require (’mongodb’).MongoClient

var dbConnUrl = process.env.MONGOLAB_URI ||

’mongodb://127.0.0.1:27017/test’

console.log(’db server: ’, dbConnUrl)

client.connect(dbConnUrl, {}, function(error, db){

console.log(’error: ’, error)

db.listCollections().toArray(function(err, collections) {

console.log(’error: ’, error)

console.log(’collections: ’, collections)

db.close()

})

})

通过添加MONGOLAB_URIapp.js进行修改后,我们现在可以初始化 Git 存储库,创建一个 Heroku 应用,向其中添加 MongoLab 附加组件,并使用 Git 部署该应用。

利用与前面示例中相同的步骤创建一个新的 git 存储库:

$ git init

$ git add .

$ git commit -am ’initial commit’

创建雪松栈 Heroku 应用:

$ heroku create

如果一切顺利,你应该可以看到一条消息,告诉你新的 Heroku 应用名称(和网址)以及一条消息,远程已被添加。在您的本地 git 中拥有 remote 是至关重要的;您可以随时通过以下方式查看遥控器列表:

$ git remote show

要在现有的 Heroku 应用上安装免费的 MongoLab(加载项基于每个应用),请使用:

$ heroku addons:create mongolab:sandbox

或者使用您的 Heroku 凭证登录 Heroku ( https://elements.heroku.com/addons/mongolab ),并为特定的 Heroku 应用选择 MongoLab Free(如果您知道该应用的名称)。

项目文件夹需要有Procfilepackage.json。你可以从 https://github.com/azat-co/fullstack-javascript/tree/master/10-db-connect-heroku 中复制它们。

现在,您可以通过以下方式将代码推送到 Heroku:

$ git push heroku master

享受应该告诉您部署成功的日志。现在查看以下命令的输出:

$ heroku logs

结果将是这样的:

2015-12-01T12:34:51.438633+00:00 app[web.1]: db server:  mongodb://heroku_cxgh54g6:9d76gspc45v899i44sm6bn790c@ds035617.mongolab.com:34457/heroku_cxgh54g6

2015-12-01T12:34:53.264530+00:00 app[web.1]: error:  null

2015-12-01T12:34:53.236398+00:00 app[web.1]: error:  null

2015-12-01T12:34:53.271775+00:00 app[web.1]: collections:  [ { name: ’system.indexes’, options: {} },

2015-12-01T12:34:53.271778+00:00 app[web.1]:   { name: ’test’, options: { autoIndexId: true } } ]

如果您让app.js和修改过的app.js文件工作,让我们通过添加一个 HTTP 服务器来增强,这样‘已连接’消息将显示在浏览器中,而不是终端窗口中。为此,我们将把服务器对象实例化封装在一个数据库连接回调中(file 11-d b-server/app . js athttps://github.com/azat-co/fullstack-javascript/blob/master/11-db/app.js)。

引导您完成实施并演示项目的补充视频: http://bit.ly/1Qnrmwr

var util = require(’util’)

var url = require(’url’)

var http = require(’http’)

var mongodb = require (’mongodb’)

var client = require (’mongodb’).MongoClient

var port = process.env.PORT || 1337

var dbConnUrl = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/test’

client.connect(dbConnUrl, {}, function(error, db) {

console.log(’error: ’, error)

db.listCollections().toArray(function(error, collections) {

console.log(’error: ’, error)

console.log(’collections: ’, collections)

var server = http.createServer(function (request, response) { // Creates server

response.writeHead(200, {’Content-Type’: ’text/plain’})   // Sets the right header and status code

response.end(util.inspect(collections))  // Outputs string with line end symbol

})

server.listen(port, function() {

console.log(’Server is running at %s:%s ’, server.address().address, server.address().port) // Sets port and IP address of the server

})

db.close()

})

})

最终 Heroku 部署就绪项目位于 https://github.com/azat-co/fullstack-javascript/tree/master/11-db-serverunder

部署完成后,您应该能够打开 Heroku 提供的 URL 并查看收藏列表。如果它是一个新创建的应用,数据库为空,则不会有收藏。您可以使用 Heroku 中的 MongoLab web 界面创建一个集合。

关于原生 MongoDB 驱动程序的更多信息,请查看 http://mongodb.github.io/node-mongodb-native/api-articles/nodekoarticle1.html

留言板:MongoDB 版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsfoE .

我们应该已经为编写 Node.js 应用做好了一切准备,它既可以在本地运行,也可以在 Heroku 上运行。源代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/12-board-api-mongonder 获得。应用的结构很简单:

/12-board-api-mongo

-web.js

-Procfile

-package.json

这就是web.js的样子;首先,我们包括我们的图书馆:

var http = require(’http’)

var util = require(’util’)

var querystring = require(’querystring’)

var client = require(’mongodb’).MongoClient

然后输出一个连接 MongoDB 的神奇字符串:

var uri = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/messages’

注意,URI/URL 格式包含可选的数据库名称,我们的集合将存储在其中。请随意将其更改为其他名称:例如,“rpjs”或“test”。

我们将所有逻辑以回调函数的形式放在开放连接中:

client.connect(uri, function(error, db) {

if (error) return console.error(error)

我们用下面的语句获取集合:

var collection = db.collection(’messages’)

现在,我们可以实例化服务器并设置逻辑来处理我们的端点/路由。我们需要在 GET /messages/list.json上获取文档:

var app = http.createServer( function (request, response) {

if (request.method === ’GET’ && request.url === ’/messages/list.json’) {

collection.find().toArray(function(error,results) {

response.writeHead(200,{ ’Content-Type’: ’text/plain’})

console.dir(results)

response.end(JSON.stringify(results))

})

在帖子/messages/create.json上,我们插入了文档:

} else if (request.method === ’POST’ && request.url === ’/messages/create.json’) {

request.on(’data’, function(data) {

collection.insert(querystring.parse(data.toString(’utf-8’)), {safe:true}, function(error, obj) {

if (error) throw error

response.end(JSON.stringify(obj))

})

})

} else {

如果客户端请求与上述任何条件都不匹配,就会显示这一信息。当我们试图去http://localhost:1337而不是http://localhost:1337/messages/list.json时,这是一个很好的提醒:

response.end(’Supported endpoints: \n/messages/list.json\n/messages/create.json’)

}

})

var port = process.env.PORT || 1337

app.listen(port)

})

Note

我们不必在集合/实体名称后使用额外的单词;也就是说,对于所有的 HTTP 方法,比如 GET、POST、PUT、DELETE,只使用/messages,而不是/messages/list.json/messages/create.json,是非常好的。如果您在应用代码中更改它们,请确保使用更新的 CURL 命令和前端代码。

要通过 CURL 终端命令运行测试:

$ curl http://localhost:5000/messages/list.json

或者在http://locahost:1337/messages/list.json位置打开浏览器。

它应该会给你一个空数组:[],没问题。然后发布一条新消息:

$ curl  -d "username=BOB&message=test" http://localhost:5000/messages/create.json

现在我们必须看到一个包含新创建元素的 ObjectID 的响应,例如:[{"username":"BOB","message":"test","_id":"51edcad45862430000000001"}]。您的 ObjectId 可能有所不同。

如果在本地一切正常,尝试将其部署到 Heroku。

要在 Heroku 上测试应用,您可以使用相同的 CURL 命令( http://curl.haxx.se/docs/manpage.html ),用您独特的 Heroku 应用的主机/URL 替换http://localhost/ or “ http://127.0.0.1 “:

$ curlhttp://your-app-name.herokuapp.com/messages/list.json

$ curl -d "username=BOB&message=test"

http://your-app-name.herokuapp.com/messages/create.json

也可以通过 Mongo shell: $ mongo终端命令,然后是use twitter-clonedb.messages.find()来仔细检查数据库;或通过 MongoHub ( https://github.com/bububa/MongoHub-Mac )、mongoui ( https://github.com/azat-co/mongoui )、mongo-express( https://github.com/andzdroid/mongo-express )或在 MongoLab 的情况下通过其可在 heroku.com 网站访问的网络界面。

如果你想用另一个域名代替 http://your-app-name.herokuapp.com ,你需要做两件事:

Tell Heroku your domain name: $ heroku domains:add www.your-domain-name.com   Add the CNAME DNS record in your DNS manager to point to http://your-app-name.herokuapp.com .

有关自定义域名的更多信息,请访问 devcenter.heroku.com/articles/custom-domains

提示为了更有效地开发,我们应该尽可能地自动化;也就是说,使用测试代替 CURL 命令。在奖励章节中有一篇关于 Mocha 库的文章,与superagentrequest库一起,为这类任务节省了时间。

摘要

在本章中,我们已经介绍了 MongoDB 数据库及其 shell。MongoDB 使用 JSON 的扩展版本,称为 BSON。然后我们用原生 MongoDB 驱动切换到 Node.js。许多其他 MongoDB Node.js 库依赖于原生驱动程序,并在其上构建。正因如此,知道就好。为了在 Heroku 上使用 MongoDB,我们使用了 MongoLab 插件(神奇的MONGOLAB_URI)。最后,我们使用获得的知识为留言板应用添加持久性。

八、把所有的放在一起

调试比一开始写代码要难两倍。因此,如果你尽可能聪明地编写代码,从定义上来说,你没有足够的聪明去调试它。——布莱恩·w·克尼根

在本章中,我们将介绍:

  • 为不同的域部署添加 CORS
  • 留言板用户界面
  • 留言板 API
  • 部署到 Heroku
  • 相同的域部署服务器
  • 部署到 Amazon Web 服务

现在,如果我们能够将我们的前端和后端应用放在一起,使它们能够协同工作,那就太好了。有几种方法可以做到:

  • 前端和后端应用的不同域(Heroku 应用):通过使用 CORS 或 JSONP,确保没有跨域问题。稍后将详细介绍这种方法。
  • 相同域部署:确保 Node.js 为前端应用处理静态资源和资产——不推荐用于严肃的生产应用。

为不同的域部署添加 CORS

到目前为止,这是生产环境的最佳实践。后端应用通常部署在http://app .http://api .子域。

使不同的域部署工作的一种方法是用 JSONP 克服 AJAX 技术的同域限制:

var request = $.ajax({

url: url,

dataType: ’jsonp’,

data: {...},

jsonpCallback: ’fetchData,

type: ’GET’

})

另一种更好的方法是在输出之前向 Node.js 服务器应用添加 OPTIONS 方法和特殊的头,它们被称为跨源资源共享或 CORS ( https://en.wikipedia.org/wiki/Cross-origin_resource_sharing ):

...

response.writeHead(200,{

’Access-Control-Allow-Origin’: origin,

’Content-Type’:’text/plain’,

’Content-Length’:body.length

})

...

或者

...

res.writeHead(200, {

’Access-Control-Allow-Origin’, ’your-domain-name’,

...

})

...

对选项方法的需求在 HTTP 访问控制(https:// developer。mozilla。org/en-US/docs/Web/HTTP/Access _ control _ CORS。选项请求可以用以下方式处理:

...

if (request.method=="OPTIONS") {

response.writeHead("204", "No Content", {

"Access-Control-Allow-Origin": origin,

"Access-Control-Allow-Methods":

"GET, POST, PUT, DELETE, OPTIONS",

"Access-Control-Allow-Headers": "content-type, accept",

"Access-Control-Max-Age": 10, // Seconds.

"Content-Length": 0

})

response.end();

};

...

留言板用户界面

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsvEb

我们的前端应用使用 Parse.com 作为后端应用的替代品。现在,我们可以切换到我们自己的后端,替换端点以及一些其他无痛的更改。让我带你浏览一下。

app.js文件的开头,取消本地运行的第一行的注释,或者用 Heroku 或 Windows Azure 后端应用公共 URL 替换 URL 值:

// var URL = ’``http://localhost:1337/

var URL =’http://your-app-name.herokuapp.com/

除了用原来的 Backbone.js 模型替换 Parse.com 模型和集合之外,app.js中的大部分代码和文件夹结构在06-board-backbone-parse-sdk项目中保持不变。因此,继续键入或复制 RequireJS 块来加载依赖项(在本例中是模板):

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’],

function (

headerTpl,

homeTpl,

footerTpl) {

ApplicationRouterHeaderViewFooterView06-board-backbone-parse-sdk项目中的相同,在此不再赘述。

我们需要使用Parse.ObjectParse.Collection将模型和集合更改为这个。这些是 Backbone.js 查找对应于特定集合和模型的 REST API URLs 的地方:

Message = Backbone.Model.extend({

url: URL + ’messages/create.json’

})

MessageBoard = Backbone.Collection.extend ({

model: Message,

url: URL + ’messages/list.json’

})

接下来是大多数逻辑驻留的HomeView。我对渲染过程做了一些改进,这很好地说明了在 Backbone 中可以对事件做些什么。首先,创建视图,并为 SEND 按钮定义元素选择器、模板(通过 RequireJS 和文本插件加载)和事件:

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

现在,在视图的构造函数中,将homeView设置为this,这样我们就可以在闭包内部通过名字使用this(否则,this会在闭包内部变异):

initialize: function() {

var homeView = this

然后,我附加了一个事件监听器refresh来完成渲染。在此之前,我们有了all事件,这不是很好,因为它触发了每条消息的添加的重新呈现。你看,fetch会根据消息触发add很多次(10,100,1000,等等。)如果我们使用all事件监听器,addall的一部分。而使用这个自定义事件refresh我们可以在适当的地方触发渲染(稍后您将看到它们)。

homeView.collection = new MessageBoard()

homeView.collection.bind(’refresh’, homeView.render, homeView)

homeView.collection.fetch({

fetch方法将执行获取 XHR 请求,它有successerror回调:

success: function(collection, response, options){

console.log(’Fetched ’, collection)

只有在所有消息都在集合中(并且来自服务器响应)后,下一行才会触发呈现:

collection.trigger(’refresh’)

},

error: function(){

console.error(’Error fetching messages’)

}

})

这个事件监听器将由 SEND 按钮和fetch触发。为了避免用message.save()保存现有记录,我们添加了对message.attributes._id的检查。换句话说,如果这是一个现有的消息,并且它来自服务器(fetch),那么它将有_id,我们停止执行流程。否则,我们保存消息并在成功时触发呈现:

homeView.collection.on(’add’, function(message) {

if (message.attributes._id) return false

message.save(null, {

success: function(message) {

homeView.collection.trigger(’refresh’)

console.log(’Saved ’, message)

},

error: function(message) {

console.log(’error’)

}

})

})

},

HomeView对象的其余部分与06-board-parse-sdk项目中的相同。在saveMessage中,我们获得用户名和消息文本的值,并用collection.add()将新的消息对象添加到集合中。这将调用事件监听器add,这是我们在initialize中实现的。

saveMessage: function(){

var newMessageForm = $(’#new-message’)

var username = newMessageForm.find(’[name="username"]’).val()

var message = newMessageForm.find(’[name="message"]’).val()

this.collection.add({

’username’: username,

’message’: message

})

},

最后,我们编写或复制render方法,该方法获取模板和集合,然后将结果 HTML 注入到具有 ID 内容的元素中(this.el):

render: function() {

console.log(’Home view rendered’)

$(this.el).html(_.template(this.template)(this.collection))

}

})

app = new ApplicationRouter()

Backbone.history.start()

})

这里是 13-board-ui/ app 的完整源代码。js 文件( https://github.com/azat-co/fullstack-javascript/blob/master/13-board-ui/app.js ):

var URL = ’``http://localhost:1337/

// var URL =’http://your-app-name.herokuapp.com/

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’],

function (

headerTpl,

homeTpl,

footerTpl) {

var ApplicationRouter = Backbone.Router.extend({

routes: {

’’: ’home’,

’*actions’: ’home’

},

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

HeaderView = Backbone.View.extend({

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

Message = Backbone.Model.extend({

url: URL + ’messages/create.json’

})

MessageBoard = Backbone.Collection.extend ({

model: Message,

url: URL + ’messages/list.json’

})

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

initialize: function() {

this.collection = new MessageBoard()

this.collection.bind(’all’, this.render, this)

this.collection.fetch()

this.collection.on(’add’, function(message) {

message.save(null, {

success: function(message) {

console.log(’saved ’ + message)

},

error: function(message) {

console.log(’error’)

}

})

console.log(’saved’ + message)

})

},

saveMessage: function(){

var newMessageForm=$(’#new-message’)

var username=newMessageForm.find(’[name="username"]’).val()

var message=newMessageForm.find(’[name="message"]’).val()

this.collection.add({

’username’: username,

’message’: message

})

},

render: function() {

console.log(this.collection)

$(this.el).html(_.template(this.template, this.collection))

}

})

app = new ApplicationRouter()

Backbone.history.start()

})

就是这里。供你参考,前端 app 源代码在 GitHub 文件夹中的 https://github.com/azat-co/fullstack-javascript/tree/master/13-board-u 。我不会在这里列出它,因为与 Parse SDK 项目相比,我们只有一些变化。拼图的下一块是后端。

留言板 API

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsvEb

后端 Node.js 应用源代码在 GitHub 文件夹的 https://github.com/azat-co/fullstack-javascript/tree/master/14-board-api 中,其结构如下:

/14-board-api

-web.js

-Procfile

-package.json

Procfile 用于 Heroku 部署,package.json用于项目元数据和 Hekoru 部署。

web.js文件与12-board-api-mongo非常相似,但是有 CORS 头文件和选项请求处理程序代码。该文件从依赖项的导入开始:

var http = require(’http’)

var util = require(’util’)

var querystring = require(’querystring’)

var client = require(’mongodb’).MongoClient

然后我们设置 MongoDB 连接字符串:

var uri = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/messages’

//MONGOLAB_URI=mongodb://user:pass@server.mongohq.com:port/db_name

我们使用字符串和client.connect方法连接到数据库。重要的是处理错误,如果有错误,用return完成执行流程:

client.connect(uri, function(error, db) {

if (error) return console.error(error)

在我们确定没有错误之后(否则执行流不会进入下一行),我们选择集合,在本例中是messages:

var collection = db.collection(’messages’)

服务器代码如下。我们创建服务器实例,并根据来自请求的信息设置 origin 变量。该值将在Access-Control-Allow-Origin中。这个想法是,响应将具有客户端 URL 的值:

var app = http.createServer(function (request, response) {

var origin = (request.headers.origin || ’*’)

检查 HTTP 方法谓词。如果是OPTIONS,我们必须为 CORS 实现它,我们开始向响应对象写入头:

if (request.method == ’OPTIONS’) {

response.writeHead(’204’, ’No Content’, {

’Access-Control-Allow-Origin’: origin,

下一个标题将说明支持哪些方法:

’Access-Control-Allow-Methods’:

’GET, POST, PUT, DELETE, OPTIONS’,

’Access-Control-Allow-Headers’: ’content-type, accept’,

’Access-Control-Max-Age’: 10, // In seconds

’Content-Length’: 0

})

response.end()

我们已经完成了OPTIONS,但是我们仍然需要实现GETPOST:

} else if (request.method === ’GET’ && request.url === ’/messages/list.json’) {

collection.find().toArray(function(error,results) {

if (error) return console.error(error)

var body = JSON.stringify(results)

我们需要给GET的响应添加几个头:

response.writeHead(200,{

’Access-Control-Allow-Origin’: origin,

’Content-Type’: ’text/plain’,

’Content-Length’: body.length

})

console.log(’LIST OF OBJECTS: ’)

console.dir(results)

response.end(body)

})

最后但同样重要的是,我们处理POST:

} else if (request.method === ’POST’ && request.url === ’/messages/create.json’) {

request.on(’data’, function(data) {

console.log(’RECEIVED DATA:’)

console.log(data.toString(’utf-8’))

我们需要解析data来获取对象,以便稍后我们可以将它保存到数据库中。下一行通常会导致错误,因为前端应用以一种格式发送数据,而服务器解析另一种格式。请确保在浏览器和服务器上使用相同的格式:

collection.insert(JSON.parse(data.toString(’utf-8’)),

{safe:true}, function(error, obj) {

if (error) return console.error(error)

console.log(’OBJECT IS SAVED: ’)

console.log(JSON.stringify(obj))

var body = JSON.stringify(obj)

我们再次添加标题。也许我们应该编写一个函数并调用它,而不是手动编写头文件。等等?Express.js 实际上会为我们做一些工作,但这是另一本书的主题:

response.writeHead(200,{

’Access-Control-Allow-Origin’: origin,

’Content-Type’: ’text/plain’,

’Content-Length’: body.length

})

response.end(body)

})

})

}

})

var port = process.env.PORT || 1337

app.listen(port)

})

下面是一个用 CORS 头文件实现的 Node.js 应用web.js的源代码:

var http = require(’http’)

var util = require(’util’)

var querystring = require(’querystring’)

var client = require(’mongodb’).MongoClient

var uri = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/messages’

//MONGOLAB_URI = mongodb://user:pass@server.mongohq.com:port/db_name

client.connect(uri, function(error, db) {

if (error) return console.error(error)

var collection = db.collection(’messages’)

var app = http.createServer(function (request, response) {

var origin = (request.headers.origin || ’*’)

if (request.method == ’OPTIONS’) {

response.writeHead(’204’, ’No Content’, {

’Access-Control-Allow-Origin’: origin,

’Access-Control-Allow-Methods’:

’GET, POST, PUT, DELETE, OPTIONS’,

’Access-Control-Allow-Headers’: ’content-type, accept’,

’Access-Control-Max-Age’: 10, // Seconds.

’Content-Length’: 0

})

response.end()

} else if (request.method === ’GET’ && request.url === ’/messages/list.json’) {

collection.find().toArray(function(error,results) {

if (error) return console.error(error)

var body = JSON.stringify(results)

response.writeHead(200,{

’Access-Control-Allow-Origin’: origin,

’Content-Type’: ’text/plain’,

’Content-Length’: body.length

})

console.log(’LIST OF OBJECTS: ’)

console.dir(results)

response.end(body)

})

} else if (request.method === ’POST’ && request.url === ’/messages/create.json’) {

request.on(’data’, function(data) {

console.log(’RECEIVED DATA:’)

console.log(data.toString(’utf-8’))

collection.insert(JSON.parse(data.toString(’utf-8’)),

{safe:true}, function(error, obj) {

if (error) return console.error(error)

console.log(’OBJECT IS SAVED: ’)

console.log(JSON.stringify(obj))

var body = JSON.stringify(obj)

response.writeHead(200,{

’Access-Control-Allow-Origin’: origin,

’Content-Type’: ’text/plain’,

’Content-Length’: body.length

})

response.end(body)

})

})

}

})

var port = process.env.PORT || 1337

app.listen(port)

})

部署到 Heroku

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsvEb

为了您的方便,我们在 https://github.com/azat-co/fullstack-javascript/tree/master/13-board-ui 有前端 app,在 https://github.com/azat-co/fullstack-javascript/tree/master/14-board-api 有 CORS 的后端 app。到目前为止,您可能已经知道该做什么了,但是作为参考,下面是将这些示例部署到 Heroku 的步骤。

我们将从 API 开始。在14-board-api文件夹中,执行下面的代码($ heroku login是可选的):

$ git init

$ git add .

$ git commit -am "first commit"

$ heroku login

$ heroku create

$ heroku addons:create mongolab:sandbox

$ git push heroku master

观察终端消息。如果 API 部署成功,可以用 CURL 或者 Postman 测试一下。然后从 Heroku 复制 URL(例如 https://guarded-waters-1780.herokuapp.com ),粘贴到13-board-ui/app.js文件中,将值赋给 URL 变量。然后,在13-board-ui文件夹中,执行:

$ git init

$ git add .

$ git commit -am "first commit"

$ heroku create

$ git push heroku master

$ heroku open

就这样。到现在为止,你应该可以看到留言板在云中运行,在一个域上有 UI(浏览器应用),在另一个域上有 API。在高流量的应用中,API 将隐藏在负载平衡器的后面,因此您可以在一个 IP/URL 上拥有多个 API 服务器。这样,他们将处理更多的流量,系统将变得更有弹性。您可以一次取出、重启或部署一个 API,而无需停机。

相同的域部署服务器

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsvEb

对于严肃的生产应用,不建议使用相同的域部署,因为静态资产更适合使用 web 服务器,如 Nginx(而不是 Node.js I/O 引擎),并且分离 API 有助于降低测试的复杂性,提高健壮性,并加快故障排除/监控。然而,相同的应用/领域方法可以用于阶段化、测试、开发环境和/或小型应用。

这个想法是,API 也为浏览器应用提供静态文件,而不仅仅是处理对其路由的动态请求。因此,您可以将 14 板 api 代码复制到一个新文件夹 15-board-web 中。新服务器文件的开头是相同的;我们有 GET 和 POST 逻辑(这次不需要 CORS)。if/else链中的最后一个条件需要处理静态文件。我们可以这样做。

...

} else {

我们使用URL v 0。​11.0 模块 from https://github.com/defunctzombie/node-url 解析 URL 中的路径名。路径名是域之后的所有内容;例如在 http://webapplog.com/es6 中,路径名是/es6。这将是我们的文件夹和文件名。

var uri = url.parse(request.url).pathname

最好进行一些日志记录,以了解我们的系统正在正常工作:

console.log(’Processing path: ’, uri)

下一行处理根 URI;也就是说,当你访问网站时,路径是空的或者是一条斜线。在这种情况下,我们来服务于index.html(如果存在的话):

if (uri == ’’ || uri == ’/’) uri = ’index.html’

path.join()方法将通过创建一个带有适当斜线的字符串来使代码跨平台:也就是说,\/作为分隔符。您可以在日志中看到结果路径和文件名:

filename = path.join(__dirname, staticFolder, uri)

console.log(’Processing file: ’, filename)

我总是说不要在 Node.js 中使用同步函数,除非万不得已。情况就是这样。如果没有同步方法,我们的文件会出现竞争情况,这意味着有些文件会比其他文件加载得更快,从而导致冲突:

stats = fs.statSync(filename)

if (error) {

console.error(error)

显然,如果文件不存在,我们要发送 404 Not Found:

response.writeHead(404, {

’Content-Type’: ’text/plain’})

response.write(’404 Not Found\n’)

return response.end()

}

让我们确保请求的资源是文件。如果不是这个文件,您可以像我们对根一样添加index.html。我这里没有这个代码。我们的前端应用只需要包括文件,所以这个代码将服务于文件!

if(!stats.isFile()) {

response.writeHead(404, {

’Content-Type’: ’text/plain’})

response.write(’404 Not Found\n’)

return response.end()

} else {

最后,我们读取文件。出于上述原因,我们再次使用同步功能。

var file = fs.readFileSync(filename)

if (!file) {

response.writeHead(500,

{’Content-Type’: ’text/plain’})

response.write(err + ’\n’)

return response.end()

}

我知道道格拉斯·克洛克福特不喜欢switch,但是我们将在这里使用它来确定响应头的正确内容类型。如果您省略了Content-Type头,大多数浏览器都能理解内容类型,但是为什么不多做一点呢?

var extname = path.extname(filename)

var contentType = ’text/html’

switch (extname) {

case ’.js’:

contentType = ’text/javascript’

break

case ’.css’:

contentType = ’text/css’

break

case ’.json’:

contentType = ’application/json’

break

case ’.png’:

contentType = ’image/png’

break

case ’.jpg’:

case ’.jpeg’:

contentType = ’image/jpg’

break

case ’.wav’:

contentType = ’audio/wav’

break

}

response.writeHead(200, {

’Content-Type’: contentType,

我们随响应发回的另一个报头是Content-Length:

’Content-Length’: file.length

})

response.end(file)

}

}

...

因此这段代码进入服务器的请求处理程序,它位于数据库连接调用的内部。就像俄罗斯套娃一样。迷惑?参考 https://github.com/azat-co/fullstack-javascript/tree/master/15-board-web 的完整源代码即可。

另一种更优雅的方式是使用 Node.js 框架作为 Connect ( http://www.senchalabs.org/connect/static.html ),或者 Express(http://expressjs.com/en/index.html);因为 JS 和 CSS 资产有专门的static中间件。但是这些框架本身就应该有一本书。

现在,在您掌握了 Node.js、MongoDB、Backbone.js 和 Heroku 的基础知识之后,还有一个额外的步骤要做。查看称为 EC2(云计算的基础设施即服务类别)的云解决方案 Amazon Web Services。

部署到 Amazon Web 服务

云正在吞噬计算世界。有私有云和公有云。AWS 可能是公共云产品中最受欢迎的选择,属于 IaaS 类别。与 PaaS-like Heroku 相比,使用 AWS 等 IaaS 的优势如下:

  • 它更具可配置性(任何服务、包或操作系统)。
  • 更可控。没有任何约束或限制。
  • 维护起来更便宜。PaaS 可能会很快为高性能资源花费一大笔钱。

在本教程中,我们将使用 64 位亚马逊 Linux AMI 和 CentOS ( http://aws.amazon.com/amazon-linux-ami/ )。

假设您已经启动并运行了 EC2 实例,使用 SSH 进入该实例,并使用yum安装所有系统更新:

$ sudo yum update

可以尝试用yum安装 Node。应该可以在 Enterprise Linux repository 的额外包中得到( https://fedoraproject.org/wiki/EPEL ):

$ sudo yum install nodejs npm --enablerepo=epel

这可能需要一段时间。在这个过程中用 y 回答。最后,您应该会看到类似这样的内容(您的结果可能会有所不同):

Installed:  nodejs.i686 0:0.10.26-1.el6          npm.noarch 0:1.3.6-4.el6Dependency Installed:...Dependency Updated:...Complete!

您可能知道这一点,但为了以防万一,要检查安装,请键入以下内容:

$ node –V$ npm –v

如果 yum Node 安装失败,查看您是否有 EPEL(只需查看下面的命令是否显示epel):

$ yum repolist

如果没有epel,运行:

$ rpm -Uvhhttp://download-i2.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

然后,尝试使用以下命令再次安装 Node.js 和 NPM:

$ sudo yum install nodejs npm --enablerepo=epel

或者,您可以从源代码编译 Node。为此,安装 C++编译器(同样使用yum):

$ sudo yum install gcc-c++ make

openSSL 也是如此:

$ sudo yum install openssl-devel

然后用 yum 安装 Git:

$ sudo yum install git

最后,直接从 GitHub 克隆 Node 库:

$ git clone git://github.com/joyent/node.git

并构建 Node.js:

$ cd node

$ git checkout v0.10.12

$ ./configure

$ make

$ sudo make install

注对于 Node.js 的不同版本,您可以使用$ git tag -l将它们全部列出,并检查您需要的版本。

要安装 npm,请运行:

$ git clonehttps://github.com/isaacs/npm.git

$ cd npm

$ sudo make install

有关使用 yum 的更多信息可在以下位置找到:

一旦有了 Git、npm 和 Node,就可以(手动)部署代码了。从存储库中取出代码。您可能需要提供凭证或将您的 SSH 密钥上传到 AWS。然后用 pm2 ( https://github.com/Unitech/pm2 )或者类似的进程管理器启动 Node 服务器(图 8-1 )。pm2很好,因为它有很多特性,不仅可以保持流程运行,还可以扩展流程;它甚至有负载平衡。

要安装 pm2:

$ npm install pm2 -g

要启动您的应用:

$ pm2 start app.js

要列出所有正在运行的进程:

$ pm2 list

A978-1-4842-1751-1_8_Fig1_HTML.jpg

图 8-1。

pm2 running multiple Node processes

这就是你需要做的全部。理想情况下,您希望自动化部署。此外,您可能希望添加一些d.initupstart脚本来自动启动您的pm2或另一个流程管理器。

AWS 上其他操作系统的步骤类似。您将使用他们的包管理器来安装 Node、Git 和 npm,然后获取代码(Git 或 rsync)并用进程管理器启动它。你不需要过程管理器。你可以用node本身启动,但是最好用一些进程管理器。

现在,当 Node.js 应用运行时,执行$ netstat -apn | grep 80,远程机器应该显示这个过程。例如,对于侦听端口 80 的 Node 应用:

tcp     0     0 0.0.0.0:80     0.0.0.0:*     LISTEN     1064/node

在 EC2 实例上,将防火墙配置为重定向连接(例如,port to Node.js 3000,但这对于我们的示例来说太高级了),或者禁用防火墙(对于我们的快速演示和开发目的来说没问题):

$ service iptables save$ service iptables stop$ chkconfig iptables off

在 AWS 控制台中,找到 EC2 实例并应用适当的规则来允许入站流量,例如,

Protocol: TCPPort Range: 80Source: 0.0.0.0/0

从您的本地机器,也就是您的开发计算机,您可以使用公共 IP 或公共 DNS(域名系统)域,它是从 AWS 控制台的实例描述下找到并复制的。例如,

$ curl XXX.XXX.XXX.XXX –v

值得一提的是,AWS 通过其 AWS Marketplace ( https://aws.amazon.com/marketplace )支持许多其他操作系统。虽然 AWS EC2 是一个非常受欢迎和实惠的选择,但也有其他替代选择: Joyent ( https://www.joyent.com/ ),Windows Azure(https://azure.microsoft.com/en-us/),Rackspace Open Cloud(http://www.rackspace.com/cloud),以及其他。

摘要

本章描述了不同的部署方法,留言板应用的最终版本,以及不同域和相同域两种部署方法。我们讨论了使用 Git 和 Heroku 命令行界面部署到 PaaS 的部署。我们还学习了在 AWS EC2 上安装和构建 Node.js 环境以及在 AWS 上使用 CentOS 运行 Node.js 应用的示例。

九、总结和深入阅读

本附录提供了本书的结论、JavaScript 博客文章、文章、电子书、书籍和其他资源的列表。

结论

我们希望你喜欢这本书。它的目的是小理论大实践,给你一个现代敏捷 web 开发中使用的多种技术、框架和技巧的概述。全栈 JavaScript 涉及如下主题:

  • jQuery、JSON 和 AJAX/XHR
  • Twitter Bootstrap、CSS 等等
  • Backbone.js、AMD 和 Require.js
  • Node.js、REST API 和 Parse.com
  • MongoDB 和 BSON
  • AWS、Heroku 和 MongoLab

如果你需要深入的知识或参考资料,向下滚动到建议阅读的列表或进行谷歌搜索。

实践方面包括构建多个版本的留言板应用:

  • jQuery + Parse.com JS REST API
  • 主干网和 Parse.com JS SDK
  • 主干和 Node. js
  • Backbone 和 Node.js + MongoDB

留言板应用具有典型 web/移动应用的所有基础:获取数据、显示数据、提交新数据。其他例子包括:

  • jquery+openweatherap resp API
  • Parse.com《拯救约翰》
  • node . js“Hello World”
  • MongoDB“打印集合”
  • Backbone.js“你好世界”
  • Backbone.js“苹果数据库”

如果您有任何反馈,请提交 GitHub 问题;评论;建议;或者你发现了错别字、bug、错误,或者其他的错误: https://github.com/azat-co/fullstack-javascript/issues

其他连接方式有 via: @azat_co ( http://twitter.com/azat_co ), http://webapplog.comhttp://azat.co

如果你喜欢 Node.js 并想了解更多关于使用 Express.js 构建生产 web 服务的信息——node . js web 应用的一个 de factor 标准——看看我的书 Pro Express。js

进一步阅读

以下是可供进一步阅读的资源、课程、书籍和博客列表。

JavaScript 资源和免费电子书

JavaScript 书籍

Node.js 资源和免费电子书

Node.js 书籍

互动在线课堂和课程

创业书籍和博客

posted @   绝不原创的飞龙  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示