[Backbone.js](http://backbonejs.org/)


## Simple Starting Point

### 创建 Model

```javascript
var Person = Backbone.Model.extend();
```

### 实例化 && 读写

```javascript
var person = new Person({
	id: 1,
	name: "Albert Yu"
});

person.get("name"); // "Albert Yu"

person.set({name: "Yu Fan"});
```

### 创建 View

```javascript
var PersonView = Backbone.View.extend({
	render: function() {
		var person = "<p>" + this.model.get("user") + "</p>";
		$(this.el).html(person);
	}
});
```

最后一行的 `this.el` 蕴含了丰富的意义：

1. 每一个 View Model 事实上创建了一个页面作用域（Page Scope），`el` 即是该作用域下的顶级标签，其默认标签是 `<div></div>`；

2. `this` 在这里就是指代了此 View Model 所定义的作用域；

3. `render` 方法里生成的页面元素都会被包裹在 `el` 之内，并受到 `this` 的控制。

### 实例化 && 添加页面元素（获取 Model 的数据）

```javascript
var personView = new PersonView({
	model: person;
})

personView.render();
console.log(personView.el); // check the el
```

## Better Model

### 缺省数据

在创建 Model 的时候，可以指定缺省数据：

```javascript
var Person = Backbone.Model.extend({
	defaults: {
		name: "unnamed";
		dob: new Date()
	}
});
```

**注意**：在 Javascript 中，对象是通过引用传递的，这就意味着动态生成的数据会保持第一次执行时的状态。像上面那个例子，每次初始化一个 `Person` 的实例都会产生相同的 `dob` 数字。可以把 `defaults` 定义为一个函数来解决这个问题。如下：

```javascript
var Person = Backbone.Model.extend({
	defaults: function() {
		return {
			name: "unnamed";
			dob: new Date()
		}
	}
});
```

### 从服务器获得数据（RESTful way）

```javascript
var Person = Backbone.Model.extend({urlRoot: "/people"}); // 指定数据来源

var person = new Person({id: 1}); // 指定对象目标

person.fetch(); // GET /people/1

person.set({name: "Albert Yu"}); // 修改数据属性
person.save(); // PUT /people/1

var newPerson = new PersonModel(); // 生成新对象
newPerson.set({name: "John Doe"}); // 设置对象属性
newPerson.save(); // POST /people

newPerson.get("id"); // 2
newPerson.destroy(); // DELETE /people/2
```

### 监听对象的变化（并做出响应）

```javascript
var person = new Person({id: 1}); // 生成实例对象

person.on("change", function() {
	alert("Something changed on " + this.get("name") + " you!");
});
```

注册在 `person` 对象下的 `change` 事件是 [Backbone.js 的内置事件](http://documentcloud.github.com/backbone/#Events-catalog)之一。

- 另外，我们还可以只针对对象的个别属性进行监听：

```javascript
var person = new Person({id: 1});

person.on("change:name", function() { // 现在只监听 name 属性是否变化
	alert("Your name has changed!");
});
```

### 将数据转化成 JSON 对象格式

```javascript
var person = new Person({id: 1});

console.log(person.toJSON());

// or, maybe you need this:
console.log(JSON.stringify(person));
```

## Better View

### 定义 View 的顶级标签（el）属性

```javascript
var PersonView = Backbone.View.extend({
	tagName: "article",
	id: "personOne",
	class: "person",
	attributes: {
		title: this.model.get("name")
	},

	render: function() {
		var person = "<p>" + this.model.get("name") + "</p>";
		$(this.el).html(person);
	}
});
```

这将会生成下面这样的 HTML 片段：

```html
<article id="personOne" class="person" title="Albert Yu">
	<p> Albert Yu </p>
</article>
```

### 缓存化的 jQuery 对象

像上面的 HTML 代码，如果要使用 jQuery 操作它们，那就像这样：

```javascript
$("#personOne").html();
```

当然，Backbone.js 提供了更好的选择：

```javascript
$(this.el).html();
```

甚至更好：

```javascript
this.$el.html();
```

`$el` 是 Backbone.js 为 `el` 生成的 jQuery 对象的缓存，使其可以反复使用而不用担心产生多个 jQuery 的实例对象。

### 内置的模板引擎（underscore template）

之前的 `render` 方法可不怎么优雅，好在 Backbone.js 内置了 [Underscore.js](http://underscorejs.org/) 库，其中包含了一个简洁的模板引擎：

```javascript
var PersonView = Backbone.View.extend({
	tagName: "article",
	id: "personOne",
	class: "person",
	attributes: {
		title: this.model.get("name")
	},

	template: _.template('<p><%= name %></p>'),

	render: function() {
		data = this.model.toJSON();
		$(this.el).html(template(data));
	}
});
```

Cool! 这样就好看多啦~

如果内置模板引擎不合你的胃口，你当然可以选用其他的方案，比如说：

- [ECO](https://github.com/sstephenson/eco)
- [Mustache.js](http://mustache.github.com/)
- [Handlebars.js](http://handlebarsjs.com/)
- ...

### 视图事件

Backbone.js 能够监视数据对象的变化并为其注册事件及回调函数，当然对于视图元素也是一样的，并且由于视图对象为我们提供了页面作用域的控制，使得我们可以更简单地和作用域内的对象交互而不至于发生混乱。

继续扩展上面的例子：

```javascript
var PersonView = Backbone.View.extend({
	tagName: "article",
	id: "personOne",
	class: "person",
	attributes: {
		title: this.model.get("name")
	},

	template: _.template('<p><%= name %></p>'),

	render: function() {
		data = this.model.toJSON();
		$(this.el).html(template(data));
	},
	
	events: {
		"mouseover" : "showMore",
		"click p"	: "greeting"
	},

	showMore: function() {...},
	greeting: function() {...}
});
```

视图事件的语法结构是：`"<event> <selector>: <method>"`，解读一下上面注册的两个事件：

1. `mouseover` 事件被注册在作用域内的顶级标签上（即 `el`，也就是本例中的 `<article></article>`），于是 `showMore` 方法会在鼠标经过 `this.el` 标签的时候触发执行；

2. 类似的，`click` 事件被注册在 `<p></p>` 标签上……等一下，哪一个 `p` 标签？

OK，这就是作用域的体现了，事实上 Backbone.js 执行此事件调用时的后台代码差不多是这样的：

```javascript
$(this.el).delegate('p', 'click', greeting);
```

_注：现在的 jQuery 早已经全面采用 `on` 方法来执行事件委托调用了，这个例子只是为了强调这是一种委托调用而已。_

我们可以看到，事件的委托方法（delegate）执行在 `this.$el` 这个 jQuery 对象上，因此 `p` 就是被包裹在 `this.el` 之内的那一个；事实上你可以用完全兼容的 CSS 选择符来指定你要的元素，这和使用 jQuery 是一样的！只不过 Backbone.js 事先帮你选择了一个上级作用域，使得筛选工作变得更简单，更具针对性。

## 进阶：模型与视图的交互

是时候了解些复杂的东东了！首先我们根据目前所学，知道了 Backbone.js 的（部分）内部构造：

>
> 服务器 <=（获取）模型（数据）=> 视图（渲染）=> DOM
>

这个过程当然也可以反向回来形成一个回圈。

### 视图的变化通知模型

我们先让视图能够接受用户交互并产生变化

```javascript
var PersonView = Backbone.View.extend({
	// ...
	input: _.template('<input type="text"><%= name %></input>'),
	submit: _.template('<button type="submit">Change Name</button>),

	render: function() {
		data = this.model.toJSON();
		$(this.el).html(input(data)).html(submit);
	}
});
```

现在咱们可以改名了，问题是模型如何知道数据发生了改变呢？这就需要视图通过事件通知它了：

```javascript
var PersonView = Backbone.View.extend({
	// ...
	events: {
		"submit button": "updateName"
	}

	updateName: function() {
		newName = $(this).val(); // this = button!

		if (newName !== this.model.get('name')) {
			this.model.set({name: newName});
		} else {
			return;		
		}
	}
});
```

Well… 这么做的确能行，但问题是应该属于 Model 处理的逻辑现在散落在 View 当中，这样不太妥呀！没关系，我们重构一下，这一次 View 的工作仅仅是通知：

```javascript
var PersonView = Backbone.View.extend({
	// ...
	events: {
		"submit button": "updateName"
	}

	updateName: function() {
		newName = $(this).val();
		this.model.updateName(newName);
	}
});

var Person = Backbone.Model.extend({
	// ...
	updateName: function(newName) {
		if (newName !== this.get('name')) {
			this.set({name: newName});
			this.save(); // 通知服务器保存
		} else {
			return;		
		}
	}
});
```

就像这样，我们通过参数传递把改变的值转交给 Model，再由 Model 做进一步的处理就是了。

现在，回路模型就像这样：

>
> 服务器 <=（更新）模型（处理）<=（通知）视图 <=（用户交互）DOM
>

不过事情还没有结束：如果模型的数据变化了，视图又如何知道呢？你或许立刻想到可以在视图完成通知之后立刻执行 `render` 方法：

```javascript
var PersonView = Backbone.View.extend({
	// ...
	updateName: function() {
		newName = $(this).val();
		this.model.updateName(newName);
		this.render();
	}
});
```

嗯，好主意……不过它并不总是有用的，因为模型的值很有可能会在别处发生改变，比如在另外一个视图里。那么，要如何通知指定的视图响应模型数据的变化呢？

这件事情需要在视图实例化的时候就去做，让视图去监听模型的变化吧：

```javascript
var PersonView = Backbone.View.extend({
	// ... 省略其他的代码，添加以下初始化代码
	initialize: function() {
		this.listenTo(this.model, "change", this.render);
		// this.model.on("change", this.render, this);
	}
});
```

被注释掉的那一行是以前的另外一种写法，也能达到目的，但是稍微难以理解一点——最后一个参数传递的是视图实例对象本身。

## 数据集合

### 收集多个模型数据

当模型的实例越来越多的时候，为了方便的处理它们，我们通常会想到用数组把它们集中起来。Backbone.js 提供了一个 Collection 模块来帮助我们简化这些事情：

```javascript
var People = Backbone.Collection.extend({
	model: person // 之前已经生成的模型实例
});
```

看起来挺像 Model 的，不过这一次获得的数据都是数组了。我们需要用处理数组的办法来处理集合里的数据（当然，Backbone.js 提供了许多内置方法）：

```javascript
var people = new.People();

people.add([
	{ name: "John Doe", id: 1 },
	{ name: "Jane Smith", id: 2 }
]);

people.length // => 2

people.get(2) // => { name: "Jane Smith", id: 2 }

people.at(0) // => { name: "John Doe", id: 1 }
```

如果数据来自于服务器，那就需要指明 `url`：

```javascript
var People = Backbone.Collection.extend({
	model: Person,
	url: '/people'
});

var people = new.People();
people.fetch(); // 可以批量获取了

people.add(person1);

people.reset(); // reset 方法可以重置发生了变化的集合实例，确保里面的数据是完整的
```

### 集合事件

集合事件的注册及使用和模型非常相似，只不过集合多了几个专属的事件，这些差别可以通过查阅文档来获知。

我们可以在触发事件的时候传递 `silent` 参数进去，阻止事件回调函数的执行：

```javascript
people.on("reset", function() {
	alert("You have " + this.length + " people now!");
});

people.fetch(); // will alert
people.fetch({silent: true}); // will not alert
```

### 集合视图

集合视图的故事也没什么特别之处，只不过这一次视图里要处理的是一组数据，所以免不了要渲染两个层级，一层是集合，一层是遍历集合里的模型：

```javascript
var PeopleView = Backbone.View.extend({
	render: function() { // 集合视图的 render 当然要渲染整个集合了
		this.collection.forEach(this.addPerson, this)
	},

	addPerson: function(person) { // 这还是老的模型视图那一套
		var personView = new PersonView({model: person});
		this.$el.append(personView.render().el);
	}
});

var peopleView = PeopleView.new({
	collection: people; // people 是之前定义过的集合实例
})
```

### 监听集合的变化

Same old stories...

```javascript
var PeopleView = Backbone.View.extend({
	initialize: {
		this.collection.on("change", this.render, this), // 集合发生了变化，比如 reset, fetch
		this.collection.on("add", this.addPerson, this), // 集合里添加了新的数据
		this.model.on("hide", this.removePerson, this)   // 集合里某一个数据被移除了
	},

	render: function() {
		this.collection.forEach(this.addPerson, this)
	},

	addPerson: function(person) {…},
	
	removePerson: function() {
		this.$el.remove();
	}
});
```
...but not always as old as

前两个事件处理都不难理解，关键是第三个：

1. 集合中的某个数据被移除了（注意，不是这个数据在服务器那里删除了，而是把它从集合中移出去了），这件事情发生在 `collection.remove(item)` 的时候，按道理我们应该监听 collection 的；
2. 然而在视图里真正发生改变的仅仅是那个数据，而不是全部集合，换句话说，视图里执行回调函数接受的 `this` 是那个数据，而不是整个集合；
3. 这就是为什么用 `this.model.on` 而不是 `this.collection.on` 的原因。但是——数据是被 `collection.remove(item)` 移出，而不是它自身的方法调用，我们监听 `this.model.on` 要怎么知道它确切发生了呢？

OK，这是一件稍微复杂一点的事情，在 collection 内部事实上发生了以下的变化：

```javascript
var People = Backbone.Collection.extend({
	initialize: {
		this.on("remove", this.hideModel)
	},

	hideModel: function(model) {
		model.trigger("hide"); // 我们让 model 触发自定义事件 hide
	}
});
```

就这样，在视图里我们不监听 collection 的 remove 事件（因为我们不打算改变 collection），当 `collection.remove(item)` 发生的时候，collection 内部会让那个被移除的 item 触发它自己的自定义事件 `hide`。于是，我们就可以在视图里监听这个自定义事件，一旦监听到就说明 `collection.remove(item)` 确实发生了，然后就把 item 给 remove 掉。

## 让 App 飞~

So far so good, 但是我们始终在一页里面动作，这对用户来讲是远远不够的。如果 url 发生了改变会怎样？

在浏览器的世界里，使用 javascript 来控制 url 的历史记录有两种主要方式：

### Hash mark(#people/1)

当有这样的链接存在时：

```html
<a href="#people/1">Albert Yu</a>
```

点击后，浏览器的 url 会是这个样子：

```
http://www.example.com/#people/1
```

于是，我们可以获得用户的 url 历史记录并控制它们。但是，这样并不够好，我们真正想要的是这样的 url：

```
http://www.example.com/people/1
```

只不过若是没有任何处理的话，访问这样的 url 会造成浏览器刷新，所有数据重新载入，这就体现不出我们使用现代 javascript 技术的优势了。

Backbone.js 可以处理 Hash mark 的 url 历史记录，但我们重点要讲的是另外一种方式，来自新的 HTML5 API

### HTML5 Push State API

使用 Push State，我们可以截取常规的 url 链接，然后专用 javascript 去处理它们，而不是让整个浏览器刷新。要启用对 Push State 的支持，我们需要调用 Backbone.js 的 history api：

```javascript
Backbone.history.start({pushState: true});
```

要截取 url 的变化转交由 Backbone.js 来处理，我们就要注册 routes：

```javascript
var App = Backbone.Router.extend({
	routes: {
		"people"				: "index", // /people
		"people/:id"			: "show", // people/1
		"help/:subject/p:page"	: "help" // help/intro/p3
	},

	index: function() {
		...
	},

	show: function(id) {
		...
	},

	help: function(subject, page) {
		...
	}
});
```

那些忽略了内部实现的方法，事实上就是之前我们所学到的一切，这要根据你的应用程序来定。比如说 index 方法，我们就是想要获取 people collection ，然后把它们都显示出来，于是：

```javascript
var App = Backbone.Router.extend({
	...

	index: function() {
		var people = new People();
		people.fetch();
		var peopleView = new PeopleView({collection: people});
		$("#app").append(peopleView.render().el);
	},

	...
});
```

或者，我们可以直接初始化集合与视图的实例：

```javascript
var App = Backbone.Router.extend({
	initialize: function() {
		this.people = new People();
		this.peopleView = new PeopleView({collection: this.people});
		$("#app").append(this.peopleView.el);
	},

	index: function() {
		this.people.fetch();
	},

	...
});
```

## Wrap up!

重新组织一下我们应用的入口吧，与其创建一堆类然后再分别实例化，我们完全可以一起做了：

```javascript
var App = new (Backbone.Router.extend({
	initialize: function() {
		this.people = new People();
		this.peopleView = new PeopleView({collection: this.people});
		$("#app").append(this.peopleView.el);
	},

	index: function() {
		this.people.fetch();
	},

	...

	start: function() { // 封装一下 Backbone.history API
		Backbone.history.start({pushState: true});
	}
}));

// 于是，我们可以这样初始化整个应用程序

$(function() { App.start(); });
```

最后，一休哥说了：“就到这里，再见吧！”