说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

【前端框架】Backbone.js在大型单页面应用中的应用实践

HTML文档下载 WORD文档下载 PDF文档下载
本文选自《程序员》2013年3月刊,全面介绍了轻量JavaScript MVC框架Backbone.js,并分享了豌豆荚PC客户端借助Backbone.js在开发大型单页面应用上做出的大量实践。

Backbone.js是什么?

Backbone.js是一个JavaScript MVC框架,提供了良好的代码组织能力,可以方便地将应用程序解耦成可以复用的部分,为建立大型的单页面应用提供框架支持,目前的版本是0.9.10(注:现在已到1.2.1版本)。通过将应用程序分解成MVC模式中不同职责的模块,带来了以下几点好处。

  1. 降低维护成本。数据、控制器、视图的更新都是独立进行的,互相之间都是松散耦合的。
  2. 解耦数据和视图,便于直接对业务逻辑进行单元测试。
  3. 便于团队合作,负责UI开发和业务逻辑开发的工程师可以分工并行工作。对于Web前端开发来讲,负责HTML模板和CSS的界面工程师和负责业务逻辑的JavaScript工程师可以协同工作。

Backbone.js算是比较轻量的MVC框架,所谓轻量,是说它只关注一个框架应该关注的最基本的事情——如何给应用分层、如何组织各种功能的代码。至于在实际开发中需要用到的Utils或UI组件,Backbone.js则没有提供任何支持。但Backbone.js所依赖的Underscore.js是一个功能比较全面的非侵入式工具函数类库,算是在Utils方面的一个补充。

轻量并不意味着功能薄弱。首先,Backbone.js的精髓是它定义前端MVC的方式和编码哲学,并依据这些规定了如何去给代码分层,因此Backbone.js能够让前端工程在可维护性和扩展性上都得到质的提升;同时,由于其良好且易于理解的结构,各个模块之间都是松散耦合的,虽然目前官方并没有提供根据实际需求build文件的功能,但如果你愿意,完全可以自己手工删掉源码中的Bakcbone.View只使用Model和Collection;最后,Backbone.js的任何一个部分都是非常容易扩展的。因此,Backbone.js的功能实际上非常强大的。下面将介绍Backbone.js的主要组件(架构如图1所示)。


图1 架构图

Backbone.Events和Backbone.Sync

Backbone.Events和Backbone.Sync两个组件是Backbone.js异步通信、事件驱动的编程模型的基础。

Backbone.js中所有的组件都通过_.extend()的方法“继承”了Backbone.Events所提供的功能,可以维护一套自己的事件订阅和回调列表。通过Events.on(event,[callback],[context])和Events.off([event], [callback], [context])两个方法来实现对事件的订阅和取消订阅。


早期版本中的事件订阅和取消订阅是通过Events.bind()和Events.unbind()两个方法实现的,目前的版本中还保留了这两个别名方法,但不推荐使用。

Backbone.js的所有组件都有一些内置的事件,可以查阅官方文档。除了预置事件外,通过Events.trigger(event,[*args])方法也可以方便地触发自定义事件。


从0.9版开始,Backbone.Events提供了Events.listenTo(other,event,callback)和Events.stopListening([other],[event],[callback])两个新方法来通过另外一种形式实现事件的订阅和取消订阅。

与on()和of f()不同,这种方式将监听的主动权转换了。举个例子来说:有对象A和对象B,B.on('someThingHappened',A.doSomeThing)是当对象BsomeThingHappened时候知对象A去doSomeThing;而A.listenTo(B,'someThingHappend',A.doSomeThing)是对象A主动去盯着对象B,当它someThingHappend的时候去doSomeThing。

第二种方式最大的意义是变被动为主动,从而实现了IoC(Inversionofcontrol,控制反转)。监听者和被监听者之间没有了耦合,只要被监听的对象能够抛出指定的事件,就可以和监听者组合在一起,甚至不需要去关心被监听对象的类型,这对代码的复用和行为抽象有很大的帮助。在测试层面,可以轻易地把被监听对象换成mock的测试代码来模拟真实情况。

Backbone.Sync则将同服务器的通信封装了起来,当Collection和Model需要和服务器通信交换数据时,会去调用Backbone.Sync中对应的方法并发送请求,如果服务器端支持RESTfulAPI就可以将整个通信过程描述得非常优雅并易于扩展。Sync的实现可以是jQuery.ajax()的封装,也可以是其他的类库提供的异步通信工具的封装。

Backbone.Model和Backbone.Collection

Backbone.js中的Model和Collection共同构成了MVC中的M层。Model的本质就是一组以keyvalue形式保存的数据,可以通过Backbone.Model.extend()来定义自己的Model。


上面的示例代码中defaults属性定义了一组默认值,当Model初始化时,如果没有指定defualts中所定义的属性的值,就会用默认值来填充Model;initialize()方法会在Model被实例化时调用,用来进行一些初始化的操作;validate()方法会在Model的save()或set()方法被调用时执行,可以根据具体需求进行扩展。

通过Model的getters和setters可以实现对Model中属性的读写,并且当set()方法被调用时,Model会将属性变化的事件广播给所有订阅者(通常是视图),驱动视图重新渲染或其他关联的Model的数据更新。

Collection是一组Model的集合,通过Collection可以将一组数据结构相同的Model有序地组织在一起,进行批量操作和管理等。同时,Collection代理了Undersore.js中众多用来操作Collection的工具方法,例如find、filter、map等。

Model和Collection都可以通过RESTfulAPI同服务器进行数据交换。

Backbone.View

Backbone.View是基于Backbone.js开发的Web App中的核心部分,负责用户交互事件的捕捉和处理、把用户输入导向Model或Collection、渲染视图、操作DOM等。Backbone.js的内部实现依赖$变量,因此DOM操作的库可以在jQuery、Zepto或Ender等中选择。从目前的情况来看,在桌面浏览器中,Sizzle.js(jQuer y所使用的SelectorEngine)的性能还是甩开Zepto几条街的,因此面向桌面浏览器的开发还是推荐使用jQuery,移动端考虑到文件体积等因素推荐使用Zepto。

对于一个Backbone.View来讲,最重要的就是$el属性,$el是一个jQuery对象(取决于所采用的DOM操作类库),是一个视图的最外层容器。容器所采用的HTML标签可以通过tagName属性来指定,可以是ul也可以是header或其他任何标签,默认情况下是div。

容器内部的DOM渲染可以通过模板引擎来完成。Underscore.js本身提供了一个_.template()的方法,因此Backbone.js不需要额外的模板引擎支持。当然,如果有特殊的需求,例如和后端共用模板文件,也可以选用Mustache等其他的模板引擎。


这样一个View就被渲染到界面上了。上面的代码中还监听了Model的change事件,当数据发生变化时,驱动视图重新渲染。当Model的数据比较丰富时,只有一个属性变化就重新渲染整个视图显然会带来性能上的隐患,因此这里的最佳实践就是把render的过程break-down成粒度更细的片段。


值得注意的是,当一个View不再被需要时,一定要记得销毁,除了销毁DOM对象外,也要销毁所有的事件监听器。在只有Events.on()和Events.off()的年代,由于销毁View时需要逐一取消订阅所有的消息,经常由于忘记解除某个绑定导致产生被称为GhostView的垃圾对象,既无法被释放也无法被回收。这也是Events.listentTo()方法带来的另外一个好处——只要调用Events.stopListening()方法即可将此对象的所有事件监听器销毁。

所有的DOM事件也是通过$el来代理的,在Backbone.View中可以通过以下方法来方便地管理DOM事件。


Backbone.js的内部实现里,在View的构造方法中调用View. initialize( )后将继续调用View.delegateEvents()方法,这个方法将解析events属性所定义的事件和回调列表,并将全部事件代理到$el对象上。由于使用的是事件代理,某些不支持冒泡的DOM事件则必须另外监听,如滚动条事件。


Backbone.Router和Backbone.History

Router是用来在URLHash和特定的动作或视图之间做映射的。


最后一句History.start()是告诉Backbone.js开始对URLHash的变化进行监视,也可以随时通过History.stop()来停止监视。同时,如果目标平台是支持HTML5Histor yAPI的,那么在start时传入{pushstate:true}的参数,就可以去掉URL中的#字符,对SEO有一定帮助。

Backbone.js的适用场景

经常能在各种场合听到前端工程师们讨论“你们的XXX是用什么做的啊?”“为什么不用XXX啊?”这样的问题。前端的类库和框架林林总总,算在一起数量没有一千也有五百,因此在面对一个新项目时难免会产生选择恐惧症。

但说到底,技术方案都是由需求决定的,任何一个类库或框架都有其适用范围和最佳的使用场景,Backbone.js也不例外。Backbone.js的最佳使用场景是大型的单页面应用:通过RESTfulAPI同服务器通信,然后根据数据的变化来驱动视图重新渲染,整个程序建立在异步通信和事件驱动的编程模型之上。

单页面应用给用户带来的使用体验是沉浸式的、相对重型的,对于普通的Web Page和数据相对稳定、视图不需要频繁重新渲染的场景来讲,Backbone.js显然就没有用武之地了。

Backbone.js和MV*不得不说的那点事儿

四十年前,Trygve Reenskaug基于Smartalk语言设计出了MVC模式,经过几十年的发展,MVC出现了众多的衍生。而我们今日说的MVC在不加特殊说明的情况下,通常指的是在服务器端Web应用开发中大量使用的WebMVC。

对于典型MVC模式来讲,View是无法直接获得用户输入的,而Controller则是用户输入和View之间的桥梁。但在浏览器中,View层的载体是HTML,用户输入和交互行为都是基于HTML的,对事件的捕捉、输入都由浏览器代劳了,并且输入会首先进入View层,因此对于前端开发来讲,严格意义的MVC是无法实现的。

因此,包括Backbone.js在内的JavaScript MVC框架的实现并没有严格遵循MVC的定义,Controller的部分职责被转移到了View层。Backbone.js对于前端MVC的定义非常易于理解,但对于没接触过MVC模式的同学来说在初期会有一些迷惑,原因是Backbone.js核心组件的命名。Backbone.js的核心组件包括Backbone.Collection、Backbone.Model、Backbone.View和Backbone.Router,而在早期的版本中,Backbone.Router组件的名称是Backbone.Controller,这很容易让人直接将其和MVC三层中的C层联系起来。但事实上,Backbone.Controller的作用是根据URLHash来在对应的行为和事件中做路由的,其功能同MVC中的C相比要简单很多,因此在0.5前后Controller改名叫做Router了。从实体代码的角度看,View层其实是模板代码和Backbone.View中部分代码的综合体,而Backbone.View中剩余的部分才是MVC模式中Controller的概念,负责操作View以及数据在View和Model层中的流转。

对于前端开发来说,用户直接面对的一层并不是Controller而是View,用户输入也会首先进入View,因而用MVP和MVVM模式来描述架构更加合理。相比AngularJS等框架,Backbone.js的模式显然更易于理解,学习曲线比较平缓,因为它并没有引入过多的需要重新认知和理解的新概念而是尽量在靠近传统的MVC模式,对于以前接触过MVC模式开发的同学来说非常容易上手,而AngularJS中的Directive等概念还是需要一定认知成本的。

但如果从架构的角度讨论,AngularJS其实是更为纯粹并更接近严格意义上的MVC模式。为了把View的功能提升到一个应有的高度,引入了Directive的概念,通过扩展HTML标签和自定义属性来描述View,在Directive中来解析这些扩展出来的内容,理解成本和代码的复杂程度都有所提高,但View层功能则得到了质的提升。

反观Backbone.js,并没有在前端开发中真正的View的载体HTML上做太多文章,即便采用模板引擎也仅仅是把数据和HTML组合起来。但得益于其强大的扩展性,可以很容易将Knockout等Data-binding框架集成进来,从而实现MVVM的架构和分层。例如前文提到将render的过程Breakdown就完全可以用Data-binding来取代,省掉了手工更新DOM的烦琐。

Backbone.js在豌豆荚PC客户端2.0中的实践

豌豆荚PC客户端2.0的UI是完全建立在Web前端基础上的。借助Backbone.js,豌豆荚PC客户端在开发大型单页面应用中做了大量的实践。通过在客户端中捆绑一个Webkit引擎并对其进行扩展,使得跑在Webview中的前端代码跳出沙箱的限制,可以操作文件系统并调用系统API,以此来进行桌面应用的开发。这样做的好处有以下几点。

  1. 极大提高开发效率:桌面应用的开发中,UI开发效率一直很低,但借助HTML5和CSS3的新功能,Web前端可以轻易地做出精细程度和交互体验都不输桌面应用的UI,但开发效率和维护成本都极大降低。
  2. 易于跨平台:UI依赖于Webkit引擎,而Webkit引擎本身是跨平台的,因此可以很容易地移植到Web上或其他桌面平台。
  3. 快速迭代和改进:由于维护和扩展成本的下降,可以快速的将原型设计产品化并进行验证,提高产品迭代和改进的速度。

但与此同时,用Web前端技术开发桌面应用也要面临巨大的挑战。首先就是内存消耗。用户在使用浏览器和使用桌面应用时的心理预期是不一样的,即使一段有内存泄漏问题的前端代码跑在浏览器里,当出现运行缓慢时大不了一下刷新页面,但对桌面应用来讲,大多数情况下没有刷新Webview的机会,因此必须对内存实现更精细的控制。由于Webview本身依赖于GC,前端无法主动管理和回收内存,因此必须借助ChromeDeveloper tools中的Profiles等工具查找出现内存泄漏的地方从而进行改善,这也依赖于大量的经验积累。

其次是运行速度和界面响应速度。由于Webview是单线程的,但单页面应用面临的是数百倍于Web Page的业务逻辑复杂度,同时业务逻辑的执行和UI共用一个线程,如何优化执行速度也是一个很大的挑战。虽然目前WebUI的界面流畅程度无法完全达到桌面应用的水平,但依然是很有竞争力且有提高空间的。(责编:陈秋歌)

作者简介

赵望野:豌豆荚前端团队负责人。

本文选自《程序员》2013年3月刊。2000年创刊至今所有文章目录请查看程序员封面秀。欢迎订阅程序员电子版(含iPad版、Android版、PDF版)。

欢迎加入CSDN前端交流群:218126086,进行前端技术交流。  

数据库中图像数据的存取技术-Delphi资料 数据库转换-Delphi资料 数据浏览部件的应用及编程-Delphi资料 数据文件路径的管理-Delphi资料 随意控制DBGrid每一行的颜色-Delphi资料 通过DELPHI实现JAVA调用ORACLE数据库数据 通过编写代码来设置数据库的别名-Delphi资料 为Delphi数据表格增加彩色 为Delphi提供Pack和Undelete功能 为Delphi中数据库报表加网格 为数据库报表加网格-Delphi资料 为数据库建立索引-Delphi资料 掀开SQL Server 7.0的面纱-Delphi资料 限制数据库记录数-Delphi资料 向数据库发送密码-Delphi资料 一个关于字符串的小问题-Delphi资料 用 QuickReport for Delphi 制作报表 用Delphi 开发数据库程序经验三则 用Delphi 实 现 不 同 数 据 库 系 统 之 间 的 数 据 转 移 用DELPHI本身的控件输出TABLE表数据 用Delphi编程访问SQLServer数据库 用delphi编写医院的复杂报表 用Delphi程序维护Paradox 数据表的索引 用Delphi的InterBase数据库开发C&amp;S应用程序 用Delphi进行数据库之间转换 用Delphi开发“瘦”单机数据库应用系统 用Delphi如何实现VFP中的Cache特性 用Delphi设计数据库应用的几点体会 在我们日常工作中,常会碰到这样一个难题:一大堆-Delphi资料 用DELPHI制作应用软件 用Dephi 程 序 维 护Paradox 数 据 表 的 索 引-Delphi资料 高分急求数据库解决方案! 请问vb中浏览器控件是在哪个文件中,另外我想在vb程序中加入介绍性网页,应该怎么办! 存储类 在MainFrame里怎么得到一个指向MyDoc类的指针? 上海的弟兄前来领分!!!! 能ping通代理服务器,但不能上网(ftp等都不能用)。有那些原因! 谁有api做的串口通讯例子 很简单的Temporary used 问题,就是搞不清,请哪位详细点拨一下,铭谢 DataGrid中使用HyerLinkColumn,请怎样动态传递多个参数或改变DataNavigateUrlFormatString的值(在线等待答复) 在winsock1.1下怎样监听网络事件。(请给个API 的 sample ) 高分急求数据库解决方案! 高分求助软件稳定性问题! 高分请教exej4的问题!~ 我的代码可以被编译吗? 十万火急(在线等)关于发邮件问题!多谢了(最后50分了) 感谢各位的鼎力支持!!! 急:如何让sqlload使用命令行方式,把数据库中的指定几个表导出?(在线) 单元全局变量与工程全局变量有什么区别 问一个关于JSP和XML结合的问题. 请问怎样用ASP连接Mysql数据库,急! 有没有j2ee的qq群??? win2k里的smtp服务器,怎么是需要身份验证的? 怎样让智能ABC记住输入的词组呢? 你在delphi中,用到了多少面向对象的知识? Dragover事件什么情况下触发 北京还是深圳 如何实现两个类互相访问--急! <Code Complete>怎么翻译好呢? 急!!!!!在dcom中向数据源添加数据报错,为什么? 有哪些方法可以在ASP.NET设计文件.aspx(html格式WEB)与脚本文件.aspx.cs(.aspx.vb)中传递数,或者说传递变量的值!!! 我用CDONTS.NewMail发邮件,怎么判断邮件发送成功或者是不成功呢? asp.net的客户端键盘的事件处理的有关问题? 请问怎么在程序里随机产生CLSID代码?$$$$$$$$$$$$$$$$ 急! 如何定义一个全局的类的实例,请高手指招? 到底怎么创建桌面快捷方式? 给点参考吧! ADO连接带密码的数据库时出现的错误!!! (在线求助),哪位老大知道怎样写一个字符串在一个iframe内框架里? zzxxdd过来接分? 刚才没说清楚再问一下救急 allforly(白衣胜血) 请进,其他人解答也可以。 windows 2003 关于fso问题,求救! 请问通过表单上传文件是否有大小限制? 关于函数的返回类型 JavaScript在使用showModalDialog的问题,急!!!!!! 急!datagrid中的Checkbox问题? 各位仁兄:在Redhet8下为什莫打开中文pdf文档是乱码?下弟在此先谢过了。 请提供北京招聘信息(java方面)。毕业三年了,一直在一个公司里面工作,昨天辞职了,现正是开始重新找工作。 win2k服务器问题,请大家帮忙解决! asp生成excel文件的问题!! 出一个程序员需要做的智力题,也来让大家头痛一下。 大()大() 那两个字要是近义词的哦 哲理小故事,最好是名人的,或者是讽刺的 心肌细胞为什么细胞内为负电荷外面为正电荷 觉得、认为的近义词是什么?必须是两个字的 一个三位数 百位数比十位数大一,个位数比十位数小2 设十位数为X 这个三位数是? 神经细胞的独特结构与物理中的尖端放电有无关系,就是电荷都集中在尖端一侧,有利于传导的速度加快 财会类的英文词典哪个好最好说清 出版社 主编 还有英文名字 有一个两位数、个位数比十位数大一、十位数与个位数之和为5 can not t_____ it home 我想买本中日英会计词典,不知道哪里有卖我需要买本中日英会计词典,就是会计专用语的词典.找了好久都找不到. 个位数比十位数大一的两位数 已知M(3,-2),N(-5,-1)且MP向量=1/2MN向量,则点P的坐标为?A(-4,1/2) B(-1,-3/2)在这两个选项中存在争议, 求春节英语小报的内容,用英语说春节能干什么,最好有翻译,帮小妹一把! 千亿后面的位数是什么 一个善良,执着, 某校科学兴趣小组的同学在网上看到:茶叶是碱性食品,喝茶可以溶解脂肪而减肥某校兴趣小组的同学在网上看到:茶叶是碱性食品,喝茶可以溶浴解脂肪而减肥,他们设计了探究的课题是:比较 以关于春节的小报题目. 一个8位数比一个7位数肯定大一 我们是一所招收12-16孩子的国际性学校 we are____ _____ school____children of 12-16我哥哥经常在晚上和周末上班my brother often works____ and____在周末,他喜欢和同学一起参加体育活动on____,she likes____ _____with 春节小报帮忙做一下急语文的 “走出哲理的误区”的素材 李庄今年春季植树1480棵,比张庄植树少245棵,李,张两庄今年共植树多少钱? 热源质名词解释 kiss是形容词还是动词 ..嫣﹖这个字念什么? 栓质化名词解释 千亿后面是什么 求大神给我做一个字,和这个字一样颜色一样心形或者不同心形的昊和嫣, 细菌属于什么类?是动物还是其他类? 编程中有许多的词语,谁能告诉我有那些词语? 谁能帮我把“心”这个字组成一个心型?字数99个. 细菌属于那类 圆锥的底面半径3㎝,母线AB的长为9㎝,动点P从B点出发,沿着圆锥的侧面移动一圈,然后回到B点,P点移动的最短距要准确步骤,谢谢 迅猛的近义词 考研数学概率题例题1.26,答案为什么不选择B呢? 从右边起第几个数是千亿 占工作,摇出《易经》第二十七卦 颐 山雷颐 艮上震下,农历4月25(已巳月甲子日)摇出山雷颐六三,六四,九六三个爻动,测工作.农历4月19(已巳月戊午日)摇出山地剥六一,六四,两个爻动,测工 例题, 比千亿更大的数读作什么? 迅猛的近义词有? 材料一说明了什么问题 圆锥的底面半径3㎝,母线AB的长为9㎝,动点P从B点出发,沿着圆锥的侧面移动一圈,然后回到B点,P点移动的最短距 张村计划植树2000棵,用25天完成.实际每天比计划多植10棵实际用了多少天? 历史材料一说明了什么问题 创造一个有关"路"的富有哲理的句子 张村今年计划植树3500棵,已经植了2975棵,要超额完成计划的20分之3,还要植多少棵? 广州亚运会作文400就行!急! 在农业生产上,常需要用质量分数为16%的氯化钠溶液来选种.现要配置100g这种溶液,试计算需要固体氯化钠和水的质量各是多少懒人们要记得写解题过程 请问这个字是什么,有这个字么…… 广州亚运的新景象 作文 求问一道Haskell题目,是一道英文题目,我害怕翻译不到位,还是把原题po上来了:Give the type and definition for a funtion called 'matches' that take an element and a list where the element is of the same type as the elements in 集合A={k平方-k,2k}k得取值范围 台湾省,什么时候能够统一 It was ( ) during the winter in China this year snowy heavily snow heavily heavy snowy snow heavy 汉子经过了7种演变过程,汉子字形的总变化是(),汉字不断趋于(). 台湾什么时候能统一 请使用递归的形式,写出一个名为 ’sumAcc‘ 的函数,它用一个累积的参数来使一个list的整数加起来求和.原题是:Write a function called 'sumAcc' in recursive style that uses an accumulating parameter to sum a list o 两个字互为近义词 台湾什么时候能统一啊 鸿壑是不是词语 花莲地震能量超过一颗原子弹 在杭台商台湾花莲地震:杭州不少市民穿着睡衣冲普京登顶权力榜刺痛美国 美俄反应冰火今起浙江93号汽油每升降6分 创今年中俄驳斥日媒“俄罗斯联日本牵制中国”义乌乡村红糖飘香 百姓迎来“甜蜜”致外媒:中国不缺改革突破口 缺推进改革联合国称叙利亚完成对化武生产设施功能叙利亚当局表明愿无条件参加叙问题国际以色列攻击叙利亚军事基地 目标是俄制奥巴马豪华座驾细节曝光 可抵御化武攻普京与中国中央军委副主席许其亮举行会美国六大互联网巨头致信国会要求改革监美国被指反咬欧洲国家是监控活动合作方联合国敦促美方遵守国际法准则美大使称马来西亚民众隐私将不受监听影美财政部制裁俄流行歌手 称其涉嫌跨国格鲁吉亚称俄未被列入总统就职典礼受邀澳大利亚使馆被曝替美监听亚洲 澳总理斯诺登最新照:在莫斯科乘游览船 未戴以色列攻击叙利亚军事基地 目标是俄制101远程教育网闪耀第五届中国学习与对着墙嘘嘘前,请三思!内蒙古首届草原户外休闲体育大会在科右三星2014年在智能手表领域独占鳌头科目二考试,坡道定点停车与起步,总是登上影视工业网头条的航拍作品——《泰软王科技:软广告比硬广告更适合品牌建两会后四类股必涨!在世故的世界,妄想狂也不算太坏为什么说ResearchKit会颠覆私有云:信息时代的安全之锁虚拟现实在9大产业的运用,别以为只能唯品会与中大、华工等大学合作办电商技中国的百济神州选择SAFC的CHOZ传罗永浩将推出一款低价小锤子手机,主新飞线上增长329% 大容量冰箱推动苹果突然要在iPhone上换“芯”,毛泽东曾经三游故宫看了些什么?拍摄儿童的10个摄影技巧2015秋冬巴黎时装周模特配饰街拍合投哪网获2015年最具竞争力P2P借
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘