>  기사  >  웹 프론트엔드  >  Backbone.js_javascript 클래스 라이브러리 사용에 대한 몇 가지 팁

Backbone.js_javascript 클래스 라이브러리 사용에 대한 몇 가지 팁

WBOY
WBOY원래의
2016-05-16 15:52:081027검색

3년 전 Backbone.js의 첫 번째 버전이 출시된 이후 Backbone.js는 인기 있는 오픈 소스 JavaScript "MV*" 프레임워크가 되어 사람들의 호감을 얻었습니다. Backbone.js는 JavaScript 애플리케이션을 위한 프레임워크를 제공하지만 여전히 개발자에게 선택할 수 있는 디자인 패턴이 많이 남아 있습니다. 그러나 개발자가 Backbone.js를 처음 사용할 때 많은 일반적인 문제가 발생합니다.

그래서 이 기사에서는 Backbone.js 애플리케이션에서 사용할 수 있는 다양한 디자인 패턴을 소개하고 개발자에게 발생하는 몇 가지 일반적인 성능 확장 문제도 살펴보겠습니다. 문제.
객체 전체 복사

JavaScript는 모든 기본 유형 변수를 값으로 처리합니다. 따라서 변수 값은 참조될 때 전달됩니다.

var helloWorld = “Hello World”;
var helloWorldCopy = helloWorld;

예를 들어 위 코드는 helloWorldCopy 변수의 값을 helloWorld 변수의 값으로 설정합니다. 이러한 방식으로 해당 값이 복사되므로 helloWorldCopy 값을 변경해도 helloWorld 값이 수정되지 않습니다. JavaScript는 기본이 아닌 모든 유형의 변수를 참조로 처리합니다. 즉, 변수가 전달되면 메모리 주소 참조도 전달됩니다.

var helloWorld = {
  ‘hello': ‘world'
}
var helloWorldCopy = helloWorld;


예를 들어, 위의 코드는 helloWorldCopy를 helloWorld에 대한 참조로 설정하며, 짐작할 수 있듯이 helooWorldCopy 값을 수정하면 helloWorld 값이 직접 변경됩니다. helloWorld의 복사본을 원하는 경우 복사본 개체를 만들 수 있습니다.

아마도 "왜 Backbone.js가 참조를 전달하여 모든 작업을 수행하는 것으로 해석될 수 있지?"라고 생각할 수도 있습니다. 실제로 Backbone.js는 객체를 복사하지 않습니다. 즉, 모델에서 .get()을 호출하면 메소드는 객체를 획득하며, 이 객체를 수정하면 원래 객체도 직접 수정됩니다. 이런 일이 발생할 수 있는 예를 살펴보겠습니다. 다음과 같은 Person 모델이 있는 경우:

var Person = Backbone.Model.extend({
  defaults: {
    'name': 'John Doe',
    'address': {
      'street': '1st Street'
      'city': 'Austin',
      'state': 'TX'
      'zipCode': 78701
    }
  }
});

이 방법으로 새 ​​사람 개체를 만들 수 있습니다.

var person = new Person({
  'name': 'Phillip W'
});

이제 새 개체의 일부 속성을 작동해 보겠습니다.

person.set('name', 'Phillip W.', { 유효성 검사: true });
위의 코드는 person 객체의 name 속성에 값을 성공적으로 할당했습니다. 이제 우리는 person 객체의 주소 속성을 조작하고 있습니다. 물론, 그 전에 주소 속성을 확인해 보겠습니다.

var Person = Backbone.Model.extend({
  validate: function(attributes) {
 
    if(isNaN(attributes.address.zipCode)) return "Address ZIP code must be a number!";
  },
 
  defaults: {
    'name': 'John Doe',
    'address': {
      'street': '1st Street'
      'city': 'Austin',
      'state': 'TX'
      'zipCode': 78701
    }
  }
});

이제 주소 속성에 잘못된 우편번호를 설정해 보겠습니다.

var address = person.get('address');
address.zipCode = 'Hello World';
// Raises an error since the ZIP code is invalid
person.set('address', address, { validate: true });
console.log(person.get('address'));
/* Prints an object with these properties.
{
  'street': '1st Street'
  'city': 'Austin',
  'state': 'TX'
  'zipCode': 'Hello World'
}
*/


무슨 일이 일어날까요? 인증에 오류가 발생했습니다! 속성이 여전히 변경되는 이유는 무엇입니까? 앞서 말했듯이 Backbone.js는 모델 속성을 복사하지 않으며 요청한 내용을 반환합니다. 따라서 짐작할 수 있듯이 객체가 필요한 경우 객체에 대한 참조를 얻게 되며 객체에 대한 모든 작업은 모델의 객체를 직접 변경합니다. 디버깅을 하고 싶다면 끝이 보이지 않는 토끼굴에 빠질 수도 있습니다.
이 문제는 Backbone.js를 사용할 때 주의해야 할 사항이며, 노련한 JavaScript 프로그래머라도 가끔 주의를 기울이지 않는 경우가 있습니다. 이 문제는 GitHub의 Backbone.js 토론 그룹에서 많이 논의됩니다. Jeremy Ashkenas가 지적했듯이 깊은 개체 참조를 실행하는 것은 해결하기 어려운 문제이며 깊은 개체 참조는 비용이 매우 많이 듭니다.


다행히 jQuery에서는 $.extend를 구현하기 위한 Deep Copy 기능을 제공하는데, Backbone.js 종속성인 Underscore.js에서도 _.extend 메소드를 제공하지만 Deep Copy를 수행하지 않기 때문에 사용을 피해야 합니다. , Underscore.js의 포크인 Lo-Dash는 _.clone 메소드를 사용하여 객체의 심층 복제 옵션을 제공합니다. 그러나 저는 모델의 $.extend 메서드에서 사용하는 구문 규칙을 사용하여 임의 개체의 전체 복제를 수행합니다. 이를 전달한 후에는 딥 클로닝 방법을 실행한다는 것이 밝혀졌습니다

var address = $.extend(true, {}, person.address);

我们现在快速准确的复制一个theaddressobject?,并且我们能够更改它对于我们要点没有包括在内的我们不用担心会更改它原有的模型。你必须要意思到这个父工厂对于上面所有的事例因为它所有的地址对象成员都是不可变的(numbers, strings, etc.),与此同时这上面所有的事例工厂当你要深度复制对象里面包含的对象时你都必须小心的使用。你必须也要知道一个小小的性能影响都来自于执行一个深度的克隆,但是我从来没有看到过很明显的问题。然而,如果你要深度的克隆一个大对象或者成千上万的对象所有的立即复制,你将有可能做大量的性能分析。这将领导我们直接到下一个模式。

为对象创建外观

在现实世界中,需求经常变化,JavaScript对象符号(或者说JSON)也是一样,这些对象是由模型和集合所在的端点返回的。这或许会成为你的基础代码中的一个真正的大麻烦,如果你的视图与底层的数据模型是紧耦合的话。因此,我为所有对象创建了getters和setters。

支持这个模式的人非常多。如果任何底层的数据结构改变了,那么视图层并不需要更新许多;你将有一个数据的访问点,所以你不太可能忘记做一个深度拷贝,你的代码将会更易于维护更易于调试。负面因素在于这个模式可能导致模型或集合的一点点膨胀。


我们看一个例子来阐明这个模式。想像我们有一个Hotel模型,包含有rooms和目前可获得的rooms,而且我们希望可以通过床位大小来获得rooms。
 

var Hotel = Backbone.Model.extend({
  defaults: {
    "availableRooms": ["a"],
    "rooms": {
      "a": {
        "size": 1200,
        "bed": "queen"
      },
      "b": {
        "size": 900,
        "bed": "twin"
      },
      "c": {
        "size": 1100,
        "bed": "twin"
      }
    },
 
    getRooms: function() {
      $.extend(true, {}, this.get("rooms"));
    },
 
    getRoomsByBed: function(bed) {
      return _.where(this.getRooms(), { "bed": bed });
    }
  }
});

现在我们假设明天你就要发布你的代码,而你又发现端点开发者忘记告诉你rooms的数据结构改变了,由一个对象变为一个数组。你的代码现在看起来会像下面这样。
 

var Hotel = Backbone.Model.extend({
  defaults: {
    "availableRooms": ["a"],
    "rooms": [
      {
        "name": "a",
        "size": 1200,
        "bed": "queen"
      },
      {
        "name": "b",
        "size": 900,
        "bed": "twin"
      },
      {
        "name": "c",
        "size": 1100,
        "bed": "twin"
      }
    ],
 
    getRooms: function() {
      var rooms = $.extend(true, {}, this.get("rooms")),
       newRooms = {};
 
      // transform rooms from an array back into an object
      _.each(rooms, function(room) {
        newRooms[room.name] = {
          "size": room.size,
          "bed": room.bed
        }
      });
    },
 
    getRoomsByBed: function(bed) {
      return _.where(this.getRooms(), { "bed": bed });
    }
  }
});

我们仅仅更新了一个函数,以便将Hotel的结构转变为这个应用的其余部分所期望的结构,同时整个应用仍然像我们所期待的一样运作。如果这里没有一个getter,我们很可能不得不为rooms更新每个访问点。理想情况下,你会希望更新所有的函数,以适应新的数据结构,但如果你在时间方面有压力急于发布的话,这个模式将可以拯救你。

离题说一句,这个模式既可以被认为是装饰模式,因为它隐藏了创建对象拷贝的复杂性,也可以认为是桥接模式,因为它可以用来将数据转换为所期望的形式。一个好的经验是对任何对象元素使用getters 和setters 。

存储数据不是通过服务器保存

尽管Backbone.js有模型和集合映射的规定去具象状态的传输(or REST-ful)的端点,你将花大量的时间去找你想要的存储数据在你的模型或者不是在服务器上的连接。另外一些关于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通过SupportBee的Prateek Dayal ,这个模式还有其他的描述。让我们一起来快速的看一个小例子来帮助我们说明它可能会派上用场。假设你有一个集合。
 

<ul>
  <li><a href="#" data-id="1">One</a></li>
  <li><a href="#" data-id="2">Two</a></li>
  . . .
  <li><a href="#" data-id="n">n</a></li>
</ul>

当使用者点击其中一个项目时,这个项目成为了被选中状态并且对于使用者作为选中项目是通过 aselectedclass 添加的是可视化的。以下这是一种方式:
 

var Model = Backbone.Model.extend({
  defaults: {
    items: [
      {
        "name": "One",
        "id": 1     
      },
      {
        "name": "Two",
        "id": 2     
      },
      {
        "name": "Three",
        "id": 3     
      }
    ]
  }
});
 
var View = Backbone.View.extend({
  template: _.template($('#list-template').html()),
 
  events: {
    "#items li a": "setSelectedItem"
  },
 
  render: function() {
    $(this.el).html(this.template(this.model.toJSON()));
  },
 
  setSelectedItem: function(event) {
    var selectedItem = $(event.currentTarget);
    // Set all of the items to not have the selected class
    $('#items li a').removeClass('selected');
    selectedItem.addClass('selected');
    return false;
  }
});
 
<script id="list-template" type="template">
<ul id="items">
    <% for(i = items.length - 1; i >= 0; i--) { %>
  <li>
        <a href="#" data-id="<%= item[i].id %>"><%= item[i].name %></a></li>
<% } %></ul>
</script>

现在我们能够很容易的判断被选中的项目,并且我们没有必要通过对象模型去判断。这种模式对于存储无用的数据是非常有用的以至于 你可能非常想要去跟踪;请记住你能够创建一个模型并且没有必要去关联于他们存储的一些无用的图像数据。
 

var View = Backbone.View.extend({
  initialize: function(options) {
    // Re-render when the model changes
    this.model.on('change:items', this.render, this);
  },
 
  template: _.template($('#list-template').html()),
 
  events: {
    "#items li a": "setSelectedItem"
  },
 
  render: function() {
    $(this.el).html(this.template(this.model.toJSON()));
  },
 
  setSelectedItem: function(event) {
    var selectedItem = $(event.currentTarget);
    // Set all of the items to not have the selected class
    $('#items li a').removeClass('selected');
    selectedItem.addClass('selected');
    // Store a reference to what item was selected
    this.selectedItemId = selectedItem.data('id'));
    return false;
  }
});

现在我们可以很容易的确定哪些项已经被选中,并且我们没有必要通过这些对象模型来了解。这个模式对于存储无用的数据是非常有用的,请记住,您可以创建不一定有端点相关联的存储无关的视图数据的模型和集合。


这种模式的缺点是你存储了无用的数据在你的模型或者集合中,它们不能真正意义上的追随一个平静的架构是因为它们不会完美的去映射在web资源上;另外,这个模式会引起一些很膨胀的在你的模型中;;并且当你保存你的模型的时候如果你的端点严格的只接受JSON数据它会引起一个很大的烦恼。

你可能会问你自己,“我如何确定我是否应该讲把额外的数据放进视图或者是模型中?”。如果额外的属性你将要增加的是围绕性的呈现,例如一个容器的高度,我们应该要添加它的图形。如果这个属性跟底层的数据模型有一些关系,然后你想要将它放进这个模型中。例如,如果上面的例子更多的显露出,因为某些原因我仅仅只希望用户通过从模型返回的项目列表中选择一个特殊的项,我可能会增加这种逻辑模型。总而言之,大多数的事情,它实际上取决于这种依赖。你能够为保持你的模型而辩论并且你可以认为保持你的观点是可能的并且把尽可能多的逻辑放进你的模型中。

渲染部分视图,而不是整个视图

当你第一次开始开发Backbone.js应用时,典型的视图结构是像这样的:
 

var View = Backbone.View.extend({
  initialize: function(options) {
    this.model.on('change', this.render, this);
  },
 
  template: _.template($(‘#template').html()),
 
  render: function() {
    this.$el.html(template(this.model.toJSON());
    $(‘#a', this.$el).html(this.model.get(‘a'));
    $(‘#b', this.$el).html(this.model.get(‘b'));
  }
});

在这里,任何对模型的改变都会触发对视图的一个全面的重新渲染。我第一次用Backbone.js开发时,我是这个模式的实践者。但随着视图代码的增长,我迅速的意识到,这种方法不利于维护或优化,因为当模型的任何一个属性发生变化时,视图将会完全的重新渲染。

当我遇到这个问题,我迅速的用Google搜索了一下,看看别人是怎么做的,结果找到了Ian Storm Taylor的博客,“分解你的Backbone.js渲染方法”,他在其中描述了在模型中监听单独的属性变化,然后仅仅重新渲染相对于变化属性的视图部分。Taylor也描述了返回对象的引用,以便单独的渲染函数可以很容易的链接在一起。上面的例子现在现在就变得更易于维护,性能更优。因为我们仅仅更新了模型变化的属性相对应的视图部分。
 

var View = Backbone.View.extend({
  initialize: function(options) {
    this.model.on('change:a', this.renderA, this);
    this.model.on('change:b', this.renderB, this);
  },
 
  renderA: function() {
    $(‘#a', this.$el).html(this.model.get(‘a'));
    return this;
  },
 
  renderB: function() {
    $(‘#b', this.$el).html(this.model.get(‘b'));
    return this;
  },
 
  render: function() {
    this
      .renderA()
      .renderB();
  }
});

我应该说一下有许多插件,比如Backbone.StickIt和Backbone.ModelBinder,提供了模型属性与视图元素的键-值绑定,这会让你省去编写许多样板代码,如果你具有复杂的表单字段检验一下它们。

保持模型与视图无关

正如 Jeremy Ashkenas 在 Backbone.js的 GitHub问题 之一中所指出的,Backbone.js 并没有实施数据与视图层之间关注点的任何真正分离,除非模型未引用视图而创建。因为Backbone.js并没有执行一个关注点分离,所以你应该将其分离吗?我和许多其他的Backbone.js开发人员,如Oz Katz 和 Dayal ,都相信答案毫无疑问是yes:模型与集合,也就是数据层,应该彻底的与绑定到它们的视图无关,保持一个清晰的关注点分离。如果你没有遵循关注点分离,你的基础代码将很快变成意大利面条式的代码,而没有人喜欢意大利面条式的代码。
保持模型与视图无关将会帮助你预防意大利面条式的代码,而没有人喜欢意大利面条式的代码!

保持你的数据层彻底的与视图层无关,这将会使你创建出更具模块化,可复用与可维护的基础代码。你可以非常容易的在应用程序各个地方复用与扩展模型和集合,而不需要考虑它们所绑定的视图。遵循这个模式使对你项目不熟悉的开发者能迅速的深入到基础代码之中,因为他们会确切的知道哪里发生了渲染,哪里存在有你的应用的所有商务逻辑。

这个模式也执行了单一职责原则,规定了每个类应该具有一个单一的职责,而且它的职责应该封装与这个类之中,因为模型与集合要处理数据,而视图要处理渲染。

路由中的参数
最好的演示这个模式工作方式是举个例子。比如说你需要对搜索页面进行排序,每个搜索页面都允许用户添加两个不同的过滤类型foo和bar,每个类型代表不同的观点。
因此,你的URL结构将会呈现如下:
 

'search/:foo'
'search/:bar'
'search/:foo/:bar'

现在,所有的路由都用的是同一个试图和模型,这样大多数人喜欢用同一个函数search()来实现。然而,你要是检查过Backbone.js代码的话,你会发祥它里面没有排序的参数映射;这些参数只是从左至右依次传入函数。这样,为了都能统一使用一个函数,你就要停止创建不同的函数正确的来为search()匹配参数。
 

routes: { 'search/:foo': 'searchFoo', 'search/:bar': 'searchBar', 'search/:foo/:bar': 'search' }, search: function(foo, bar) { }, // I know this function will actually still map correctly, but for explanatory purposes, it's left in. searchFoo: function(foo) { this.search(foo, undefined); }, searchBar: function(bar) { this.search(undefined, bar); },

你也许能想象的到,这个模式可以使路由功能很快膨胀。当我第一次遇到这个问题时,我试图创建了一些用正则表达式定义的解析函数来“神奇”的去匹配参数,当然这个是可以工作的-但这也是有约束条件的。这样,我废弃了这个想法(有时,我仍然可以用Backbone插件来解决)。我进入GitHub中的一个 议题,其中Ashkenas建议应该让所有的参数都和search函数匹配。
上面的代码现在转变为下面维护性更强的样子:
 

routes: {
  'search/:foo': 'searchFoo',
  'search/:bar': 'searchBar',
  'search/:foo/:bar': 'search'
},
 
search: function(foo, bar) {  
},
 
// I know this function will actually still map correctly, but for explanatory purposes, it's left in.
searchFoo: function(foo) {
  this.search(foo, undefined);
},
 
searchBar: function(bar) {
  this.search(undefined, bar);
},

这种模式可以戏剧性的减少路由的过分膨胀。然而,需要注意到它不会服务于不能区别的参数。比如,如果你有两个作为ID的参数,如模式XXXX-XXXX,你不能区分哪个ID是对哪个参数的回应。

model.fetch() 不会清除你的模型

这通常会将那些Backbone.js的新手给绊倒:model.fetch()并不能丢掉你的模型,而是扩展了你的模型的属性。因此,如果你的模型具有属性x,y和z,你获取到y和z,那么x将仍然是模型中的那个x,只有y和z会被更新。下面的例子将这个概念形象化了。
 

var Model = Backbone.Model.extend({
  defaults: {
    x: 1,
    y: 1,
    z: 1
  }
});
var model = new Model();
/* model.attributes yields
{
  x: 1,
  y: 1,
  z: 1
} */
model.fetch();
/* let's assume that the endpoint returns this
{
  y: 2,
  z: 2,
} */
/* model.attributes now yields
{
  x: 1,
  y: 2,
  z: 2
} */

PUTs 需要一个 ID 属性

这一条也经常将Backbone.js的新手绊倒。要想在调用.save()的时候让模型发送一个HTTP PUT请求,你的模型需要有一个ID属性集。记得HTTP PUT谓词是设计来做更新的吧,所以发送一个PUT请求,你的模型需要有一个ID,这么做是有意义的。在理想的世界里,你的所有模型都具有一个名为ID的完美的ID属性,但是你从端点接收到的JSON数据可能并不总是具有完美命名的IDs。

因此,如果你需要更新一个模型,请在保存之前确认模型上有ID。Backbone.js 的0.5以及更高版本允许你用id属性来更新模型的ID属性名称,如果你的端点返回的不是名为id的IDs的话。
如果困顿于使用的是版本低于0.5的Backbone.js,我建议你修改你的模型或集合的parse函数,以便将你期望的ID属性映射到属性ID。这里有一个快速上手的例子,说明了你应怎样修改parse函数来做到这一点。我们假设你有一个cars的集合,它的IDs是carID。
 

parse: function(response) {
 
  _.each(response.cars, function(car, i) {
    // map the returned ID of carID to the correct attribute ID
    response.cars[i].id = response.cars[i].carID;
  });
 
  return response;
},

页面加载时创建模型数据

有时你会发现你的模型或者集合需要在页面加载时被初始化赋值。许多关于Backbone.js模式的文章,例如Rico Sta Cruz的 “Backbone 模式” 和 Katz的 “ 避免常见的Backbone.js陷阱” ,讨论了这种模式。这种模式实现很容易,只需在页面中内联一段脚本,通过你选择的服务端语言,将单个模型属性或者JSON形式的数据呈现出来。例如,在Rails语言中,我采用下面方法之一:
 

// a single attribute
var model = new Model({
  hello: <%= @world %>
});
// or to have json
var model = new Model(<%= @hello_world.to_json %>);

应用这种模式可以通过“立即的”渲染页面,改善你的搜索引擎排名,而且它也可以通过限制应用初始化HTTP请求的方式,大大缩短你的应用启动与运行所需要的时间。

处理失败的模型属性验证

很多时候,你会想知道是哪个模型属性验证失败了。例如,如果你有一个极其复杂的表单,你或许想知道哪个模型属性验证失败,这样你就可以将这个属性对应的输入字段高亮显示。不幸的是,提醒视图到底是哪个模型属性验证失败并没有直接集成于Backbone.js,但是你可以用一些不同的模式去处理这个问题。
返回一个错误对象

一个给视图提醒哪个模型属性验证失败的模式是,返回一个对象,其中包含某种标志,它详细的记录了哪个属性验证为失败,就像下面这样:
 

// Inside your model
validate: function(attrs) {
  var errors = [];
 
  if(attrs.a < 0) {
    errors.push({
      'message': 'Form field a is messed up!',
      'class': 'a'
    });
  }
  if(attrs.b < 0) {
    errors.push({
      'message': 'Form field b is messed up!',
      'class': 'b'
    });
  }
 
  if(errors.length) {
    return errors;
  }
}
// Inside your view
this.model.on('invalid', function(model, errors) {
  _.each(errors, function(error, i) {
    $(‘.' + error.class).addClass('error');
    alert(error.message);
  });
});

这个模式的优点在于,你是在一个地方处理所有不合法的消息。缺点在于,如果你以不同的方式处理不合法的属性的话,你的invalid方法可能会成为一个很大的switch或者if语句。

广播自定义Error事件

我的一个朋友,Derick Bailey,推荐了一个可替代模式,就是为每个模型属性触发自定义的errors事件。这将允许你的视图能够针对单独的属性绑定到特定的error事件:
 

// Inside your model
validate: function(attrs) {
 
  if(attrs.a < 0) {
      this.trigger(‘invalid:a', 'Form field a is messed up!', this);
  }
  if(attrs.b < 0) {
      this.trigger(‘invalid:b', 'Form field b is messed up!', this);
  }
}
// Inside your view
this.model.on('invalid:a', function(error) {
    $(‘a').addClass('error');
    alert(error);
});
this.model.on('invalid:b', function(error) {
    $(‘b').addClass('error');
    alert(error);
});

这个模式的优点在于,你的视图明确的绑定到它们所绑定到的error类型,而且如果你对每一种属性error有特定的指令的话,它可以清理你的视图部分代码,使之更易于维护。这个模式的一个不好的地方在于,如果在你处理不同的属性error时并没有太多的不同的话,你的视图可能会变得极为膨胀。

这两种模式都有其利弊,你应该考虑清楚哪个模式对你的应用案例是最优的。如果你按照同样的方式处理所有失败的验证,那么第一个方法可能是最好的;如果你对每个模型属性有特定的UI变化,那么后一种方法更好。

HTTP状态代码200所触发的错误
如果你的浏览器端模型或者集合收到了无效的JSON,尽管HTTP的状态代码是200,但浏览器端依然会触发一个“错误”事件。这种事件常发生于本地模拟JSON数据造成的。那么,一个好的方法就是读取经过 JSON 验证器验证了的模拟JSON数据文件。或者从你的IDE获得相应的 插件来及时获取格式错误的JSON信息。

创建一个一般性错误显示模式

创建一个常见错误显示代码可以节省你的时间以及创建一个统一的模式来处理、可视化错误信息,而且它可以增加开发者的经验。我之前开发的每一个Backbone.js应用中我都会创建一个可以处理alert的视图:
 

var AlertView = Backbone.View.extend({
  set: function(typeOfError, message) {
    var alert = $(‘.in-page-alert').length &#63; $(‘.in-page-alert'): $(‘.body-alert');
    alert
      .removeClass(‘error success warning')
      .addClass(typeOfError)
      .html(message)
      .fadeIn()
      .delay(5000)
      .fadeOut();
  }
});

上面的代码首先会检查是否已在视图代码中创建了指定视图in-page-alert div。如果没有,则接着查看一般性的在其它地方声明的body-alert div。这样可以让你发送具有一致性的错误信息以及当你忘记指定一个in-page-alert div时提供有用且可靠的信息。如下面的模式简化了让你怎样在你的试图中处理错误信息:
 

var alert = new AlertView();
 
this.model.on('error', function(model, error) {
  alert.set('TYPE-OF-ERROR', error);
});

单页面应用中更新浏览器页面标题

这是一个比任何东西都重要的可用性问题。如果你正在开发一个单页面应用程序,谨记更新每个页面的标题。我写过一个的插件(Backbone.js Router Title Helper)来扩展 backbone.js router 的功能。它通过一个 Map 对象来控制路由,键来代表路由函数的名字,值则映射到页面的标题。
 

Backbone.Router = Backbone.Router.extend({
 
  initialize: function(options){
    var that = this;
 
    this.on('route', function(router, route, params) {
 
      if(that.titles) {
        if(that.titles[router]) document.title = that.titles[router];
        else if(that.titles.default) document.title = that.titles.default;
        else throw 'Backbone.js Router Title Helper: No title found for route:' + router + ' and no default route specified.';
      }
    });
  }
});

单页面应用中的缓存对象

当我们谈论单页面应用时,另一个叫缓存对象模式你将会经常用到!下面的例子直截了当而且简单:

 
// Inside a router
initialize: function() {
 
  this.cached = {
    view: undefined,
    model: undefined
  }
},
 
index: function(parameter) {
  this.cached.model = this.cached.model || new Model({
    parameter: parameter
  });
  this.cached.view = this.cached.view || new View({
    model: this.cached.model
  });
}

这个模式可以加速你得应用,因为你不用重复初始化你得Backbone.js对象。然而,它会过多的消耗内存;所以,缓存对象就要在整个应用中使用。如果以前你用过Backbone.js开发过应用,也许你会问你自己,“ 我要重取数据该怎么做?”你可以每次在如下路径中触发后重取数据:
 

// Inside a router
initialize: function() {
 
  this.cached = {
    view: undefined,
    model: undefined
  }
},
 
index: function(parameter) {
  this.cached.model = this.cached.model || new Model({
    parameter: parameter
  });
  this.cached.view = this.cached.view || new View({
    model: this.cached.model
  });
  this.cached.model.fetch();
}

当你的应用从端点(如,一个收件箱)必须检索最新数据时上面的模式就可以工作。当然,如果你要拿的数据时凭借应用的某个状态(假设这个状态是通过URL和参数来决定的),甚至是在用户上一个页面应用的状态没有改变, 你可以重取数据。一个好的解决方案去重拿数据时当应用(参数)发生变化时:
 

// Inside a router
initialize: function() {
 
  this.cached = {
    view: undefined,
    model: undefined
  }
},
 
index: function(parameter) {
  this.cached.model = this.cached.model || new Model({
    parameter:parameter
  });
  this.cached.model.set('parameter', parameter);
  this.cached.view = this.cached.view || new View({
    model: this.cached.model
  });
}
 
// Inside of the model
initialize: function() {
  this.on("change:parameter", this.fetchData, this);
}

JSDoc函数和Backbone.js类

我是文档注释和JSDoc的超级粉丝。我用JSDoc对所有的Backbone类添加了文档注释:
 

var Thing = Backbone.View.extend(/** @lends Thing.prototype */{
  /** @class Thing
   * @author Phillip Whisenhunt
   * @augments Backbone.View
   * @contructs Thing object */
  initialize() {},
 
  /** Gets data by ID from the thing. If the thing doesn't have data based on the ID, an empty string is returned.
   * @param {String} id The id of get data for.
   * @return {String} The data. */
  getDataById: function(id) {}
});

위와 같이 Backbone 클래스에 문서 주석을 추가하면 모든 클래스와 함수에 매개변수, 반환 유형, 설명 문서 주석을 추가할 수 있습니다. 초기화 함수를 선언된 함수로 유지해야 JSDoc을 생성하는 데 도움이 됩니다. JSDoc 예제 프로젝트를 보고 싶다면 HomeAway Calendar Widget에서 예제를 다운로드하세요. 문서 주석의 일부로 사용할 수도 있는 Grunt.js 플러그인인 grunt-jsdoc-plugin도 있습니다.

테스트 중심 개발 모델 문의

Backbone.js를 사용한다면 모델과 컬렉션을 개발할 때 TDD(Test Driven Development)를 따라야 한다고 생각합니다. Jasmine.js로 모델과 컬렉션을 생성할 때 처음으로 단위 테스트를 위해 TDD를 따랐을 때 실패했습니다. 단위 테스트를 작성했는데 실패하면 전체 모델과 컬렉션을 다시 작성합니다.

이제 모든 Jasmine 테스트를 통과했으며, 내 모델과 컬렉션이 예상대로 작동할 것이라고 확신합니다. 저는 TDD를 따르기 때문에 뷰 레이어는 작성하기가 매우 쉽고 매우 간단합니다. TDD를 사용하기 시작하면 속도는 물론 매우 느려지지만 TDD를 염두에 두면 프로그래밍 효율성과 품질이 마술처럼 향상됩니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.