backbone.js的使用报告

一、准备阶段

1.1 框架选型

随着对MV*架构模式的逐步理解,越来越发觉对于一般的业务场景,mvvm是前端架构的不二选择。在我所了解的框架范围内,与mvvm架构模式最贴合的框架只有angular,无论是双向数据绑定、指令,还有强化的html标签,angular提出的很多新概念都像是为mvvm理论量身定做的一套外衣。很可惜angular对ie8的支持不太友好,兼容ie8的angular插件也非常稀缺,对于国内环境,ie8仍然占有较大的市场份额,无奈之下只能转向轻量级的backbone。

1.2 官方文档vs电子书

通过阅读backbone的官网简介,总结下backbone特点(并非优点):

  • 提供了基本的MVC结构,可以做到业务逻辑与视图的分离(MV*框架最基本的功能)
  • 内置支持restful风格的API(做过项目后会发觉,内置的那套基本没什么用,自定义会更合适)
  • 路由的支持(大部分MV*框架也都支持,也不是亮点)
  • 方便与第三方插件集成(因为backbone太轻量了,对于插件没什么要求,这勉强可以算优点)
  • 兼容ie8(对比其他MV*框架,感觉这是唯一的优点,也是选择它的理由)
  • 粗粒度的单向数据绑定(缺点,每次更新视图,都只能全部更新,当然你可以自己写局部更新,会比较繁琐)
  • 太轻量了(缺点,连angular这种重量级的框架写法都会五花八门,backbone这种就更别提了,新手根本无法驾驭它写出优良的代码)

由于backbone功能单薄,无法形成固有的mvc或是mvp模式,但是为了让它变得好用,我们可以尝试增强它的功能,让它更接近我们想要的mvvm框架。mvvm框架最大特点是:双向数据绑定,于是通过google:backbone data binding,找到了以下几个backbone插件:Backbone.ModelBinder、Rivets.js、Backbone.Stickit。通过测试发现Rivets.js很好用,可惜不兼容ie8,ModelBinder配置不够灵活,于是Stickit成为最佳选择。

关于框架的学习,我的思路是这样的:官方文档是一定要看的,但是没必要从头看到尾,官方开头那部分介绍是一定要看的,那里会告诉你框架是什么样的,有什么特性,而具体的API只要看能反应出框架特性的那几个API就够了,其他的都是用来查的。有些API不用查,你也知道它肯定有,就像你学完java后学C++,你知道C++里肯定有for循环。
<b>只看官网文档是远远不够的,文档只是告诉你可以这样用,但是没有告诉你该不该这样用,所以当你大体了解了一个框架的基础知识后,应该找一本名叫《xxx框架最佳实践》的电子书,了解下怎么用才合理,对于轻量级的框架尤其如此。</b>

学计算机知识,一个wiki百科就够了(要翻墙),千万不要看国内某百科,连自己贴吧的内容都可以当参考文献,实在是太不靠谱了。

二、实践阶段

准备阶段做得越多,实践起来就越轻松,项目需求分析阶段,开发人员的时间如果用来做技术调研、框架选型、编写demo、测试性能、编写非业务组件等工作,时间还是会比较紧张的。总结了一下项目中的问题及解决方案:

2.1 如何编写model层代码

对于mvc新手,经常会误解m的含义,认为m只是表示数据。实际上mvc是按照职责来划分的,而非数据类型。m表示业务层,即包含表示业务逻辑的数据模型,同时也包含操作这些数据的方法。反应到backbone项目中,m层的写法如下:

var Product = Backbone.Model.extend({

    // 业务数据模型
    defaults: {
        name: “”,
        price: 0
    },
    
    // 操作数据的方法
    create: function() {...},
    remove: function() {...},
    modify: function() {...},
    query: function() {...}
});

不要在view中直接调用没有业务含义的底层方法:fetch、save等,这将导致model与view耦合在一起,view层的代码变得繁杂且难以维护。

2.2 事件与逻辑分离

backbone提供了View类,很多人并没有意识到事件与逻辑的解耦,他们通常这样写:

var ProductView = Backbone.View.extend({

    // 注册事件
    events: {
        "click #saveBtn": "create",
        ....
    },
    
    // 创建
    create: function() {

        // 针对click准备一些数据,可能涉及到dom操作
        var data = ....;
        
        // 执行创建的逻辑
        ...
    }
});

当触发创建逻辑的事件不止一个时,会变成这样:

var ProductView = Backbone.View.extend({

    // 注册事件
    events: {
        "click #saveBtn": "createForClick",
        "blur #xxInput": "createForBlur",
        ....
    },
    
    // 创建for click
    createForClick: function() {

        // 针对click准备一些数据,可能涉及到dom操作
        var data = ....;
        
        // 执行创建的逻辑
        ...
    },

    // 创建for blur
    createForBlur: function() {

        // 针对blur准备一些数据,可能涉及到dom操作
        var data = ....;
        
        // 执行创建的逻辑
        ...
    }
});

此时你会发现处理逻辑的代码重复了,所以将事件与逻辑分离的一个优点是:逻辑代码可以复用,正确的写法如下:

var ProductView = Backbone.View.extend({

    // 注册事件
    events: {
        "click #saveBtn": "createForClick",
        "blur #xxInput": "createForBlur",
        ....
    },
    
    // 响应click事件的创建
    createForClick: function() {

        // 针对click准备一些数据,可能涉及到dom操作
        var data = ....;
        
        // 执行创建的逻辑
        this.create(data);
    },

    // 响应blur事件的创建
    createForBlur: function() {

        // 针对blur准备一些数据,可能涉及到dom操作
        var data = ....;
        
        // 执行创建的逻辑
        this.create(data);
    },

    // 执行创建
    create: function(data) {...}
});

事件与逻辑的分离最大的好处是可以方便的进行单元测试,以上面为例,只需针对create方法进行测试,就能验证逻辑的正确性,而非逻辑的dom操作是不在单元测试范围之内的。

2.3 净化路由代码

backbone提供了路由功能,可以方便的根据网址跳转到指定的视图,很多人的写法是这样的:

var AppRouter = Backbone.Router.extend({
    "route1": "createView1",
    "route2": "createView2",
    ....,

    createView1: function() {
        // 1.操作model层方法,获取视图所需数据
        ...
        // 2.new一个视图对象,传递数据到视图中
        ...
        // 3.其他的逻辑
        ...
    },

    createView2: function() {
        // 1.操作model层方法,获取视图所需数据
        ...
        // 2.new一个视图对象,传递数据到视图中
        ...
        // 3.其他的逻辑
        ...
    }
});

随着需求的增加,创建视图的逻辑会变的越来越复杂,整个路由的代码会显得非常臃肿,还有一个问题,不单单路由里需要创建视图,其他的地方也会用到,这个时候,重复的代码又出现了。解决这个问题的方案是抽取出创建视图的逻辑,实际项目中我抽取了一个文件夹叫作:controllers,里面以业务功能为单位,存放创建视图逻辑的js文件,比如处理产品的controller,可以定义为productCtrl.js,代码如下:

var productCtrl= (function() {
    var create = function() {
        // 1.操作model层方法,获取视图所需数据
        ...
        // 2.new一个视图对象,传递数据到视图中
        ...
        // 3.其他的逻辑
        ...
    };

    return {
        create: create
    };
});

此时路由的代码就变得非常清爽了,同时其他地方需要创建视图时,只需要调用productCtrl.create()即可,路由代码:

var AppRouter = Backbone.Router.extend({
    "route1": "createView1",
    "route2": "createView2",
    ....,

    createView1: function() {
        productCtrl.create();
    },

    createView2: function() {
        ...
    }
});
2.4 引入bower-installer优化bower文件结构

前端构建时需要合并第三方的js、css文件,但是每个插件的目录结构不尽相同,导致grunt命令写起来非常繁琐,为了尽量统一处理,引入了bower-installer插件,主要目的是抽取出插件的核心文件,将其放入规则统一的目录中,方便进一步处理。 bower-installer的github地址:https://github.com/blittle/bower-installer

2.4.1 bower-installer使用
  • 安装:npm install -g bower-installer
  • 运行:bower-installer
    具体配置参考github上的文档
2.4.2 基于bower-installer引入第三方插件的流程(以jquery为例)
  1. 配置bower.json文件:"jquery": "1.11.3"
  2. 执行bower install下载jquery插件
  3. 执行bower installer抽取jquery核心文件到bower_main_files目录
  4. 引入文件:在index.html中引入插件的根目录由bower_components改为bower_main_files
2.5 mvvm的缺陷

开发过程一切都很顺利,只踩到两个坑:<b>性能</b>、<b>复杂界面的逻辑处理</b>,而这两个坑是由mvvm的基因决定的。

mvvm的双向数据绑定可以让开发变得非常便利,但同时也带来了两个问题:

  • 为了将model与dom中的元素一一对应进行绑定,即便最简单的界面,也会消耗大量性能,当需要一次性加载大量数据时,性能问题就会凸显。
  • 数据绑定是基于观察者模式构建的,当界面中存在多个组件,且组件间有复杂交互时,代码很难跟踪,调试会变得非常困难,当修改了某个model时,你无法清晰的了解后面会发生什么。

针对mvvm的缺点,以下业务场景是不适合的:

  1. 由于极端的用户体验要求,需要一次性渲染大量数据,不能分页的场景。
  2. 组件多且交互过复杂的场景。

据说angular2.0也倾向单向数据流,估计也是考虑到这个原因,react+flux也推崇单向数据流,也许这是一种趋势。但对于一般的业务场景来说,双向数据绑定还是非常实用的,所以具体采用什么方案还是要结合具体的业务场景。

三、总结

  • backbone.js是一个功能单薄的框架,它需要其他插件的辅助才能变得好用。
  • 没有了解过xxx最佳实践,就不要轻易在项目里使用,不然往往会得到xxx不好用的错误结论。
  • 没有一个模式永远是最佳的,只有适合业务场景的模式才是最佳模式。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 彼岸花开开彼岸
    柒染韵墨阅读 257评论 0 0
  • 鱼游在水里 有了鱼水之情 鱼游进了岩石之腹 再也不曾离开 这是幸运 还是缘分 爱上一个人 因为深刻 所以永恒
    足下阿蒙阅读 87评论 0 0
  • 大过年的人家都是写要好好过年,新年有什么好计划等等之类的,为什么我要写什么难的糊涂呢,好像这个词不该过年时候说,或...
    才少说阅读 105评论 0 0
  • 背景:1. 本地windows系统已有开发指定版本的jdk安装包,已有tomcat解压缩后的程序文件。无需另外下载...
    Joey_GZ阅读 606评论 1 2