我和我的广告前端代码(三):一次重来的机会,必要的技术选型
前两篇文章中多次提到我在的地方有两条业务线(甲、乙)。业务线甲算是在不断的完善中投入使用了好一段日子。业务线乙的老大想觉得现在的页面设计风格有点过时,提出要将业务线乙的大部分页面改版。这意味着他们的页面要重做,很多页面中的历史问题也将得到解决。同时广告组也在酝酿着新的挑战。
由于甲、乙两条线基本相当于两家公司,很多广告的需求并没有共通性。我们没有必要将两套工程合在一起。而且甲在做的时候太过仓促,而现在我也有了自己的团队,这半年来在很多非广告的项目中分批多次的实践了一些新技术,无论从资源上,还是技术储备上来说,都值得一试。我想把我的选型,和大家分享一下。
业务需求:
在业务线乙的所有页面上,加载一个 js 文件,通过异步接口,从 ADS v2(广告数据分发系统)中拉取对应页面当日的数据,并用于展示。其中,展示阶段要根据数据中的特定字段,不同批次、不同位置、不同效果的向页面中插入广告。并且保证原有的ADSv2 不做改变。
为什么要重新做一套系统,而不是将家居的广告补充到房产的代码中?
在做乙广告系统之前,我们是有一套 adsfev2.js (甲的广告的前端代码)。但是经过我们的比较发现在后台系统中同一种广告特性(即广告的种类)。在甲、乙中有着不同的展示效果。这种不同体现在显示权重上、 UI 样式和交互效果上。因此对于乙的广告前端代码,我们没有将新的特性扩充到甲的代码中,而是独立出来。本文中先称业务线乙广告的前端代码为——adsFEv2Home.js
adsFEv2Home.js 的基本结构:
adsFEv2Home.js 基本分为以下几部分:获取数据、广告控制层、广告的分类处理、页面渲染、模块管理、单元测试。根据以上的几部分,分别进行技术选型,并同时考虑不同部分所选用的技术方案是否有冲突点。
一、获取数据:
因为后台是一个专门处理广告数据分发的系统(本文中简称 ADS)我需要提供页面相关的参数,来获取相关的广告数据,并渲染。因此这部分我门需要发一个异步的 Ajax 请求。考虑到 ADS 与引用 adsFEv2Home.js 的页面(本文简称“宿主页面”)不同域。因此为防止跨域。我们采用 jsonp 的方式来获取数据(首先说明jsonp和ajax是两回事,上图是为了讲解方便,所以就用ajax来表示了)。jsonp 跨域可以采用原生 js 或 库/框架自带 API。因为现有的 js 库或框架中都会对网络
广告
后台
ADS
浏览器
分类处
理
分批加
载控制
特型 a
特型 b特型 1
特型 n
广告实例 a-1
广告实例 a-2
广告实例 b-1
广告实例 b-2
广告实例 n-1请求进行封装。因此,随后的技术选型中如果带这部分 API 的就直接使用,若没有原生实现成本也不高。
二、广告控制层( C)
这部分主要做的事情是广告间的控制。单独把这部分拿出来的目的是:将广告间的控制与单一广告实例的控制分离。这里有个大前提: ADS 过来的数据是一个 list。每一项是一个广告实例的数据。但是多个广告实例也可能属于同一类。同时我门的广告需要分批异步加载。因此广告控制层处理向页面插广告前的事情, 广告的分类处理维护每个广告实例自身的生命周期。
因为要写控制层,起初我考虑使用现有的前端 MV*框架,但是这些框架处理的 C 是SPA 中的页面控制。而广告代码中要处理的是业务逻辑。因此我打算依靠一些设计模式尽可能的时广告间的逻辑更加清晰可维护、可扩展。对于分批加载这部分中要求一批广告全部加载完后再加载第二批,以此类推。这部分我打算用 promise 的方式来处理。 许多 Javascript 库(比如 jQuery 和 Dojo)添加了一种称为 promise 的抽象(有时也称之为 deferred)。通过这些库,开发人员能够在实际编程中使用 promise 模式。 当然 ES6 中也提供了对应的功能。我们可以采用的方案有 jQuery.deferrd和 ES6 promise 。
三、 广告的分类处理
我们将广告分类为多种特性,关于类的继承,我打算沿用之前房产广告中使用的 JohnResig 写 的 Simple JavaScript Inheritance 链 接 http://ejohn.org/blog/simple-javascriptinheritance/ 它提供的_.super 可以让 js 代码看起来更像是面向对象的风格。 但我只打算在这部分(特性继承)使用它。
四、 页面渲染( V)
当控制层处理好了以后,剩下的就是广告实例自身的事情了,对于页面渲染,就是把拿到的每个广告数据分别整理成页面 DOM 节点并维护事件和交互。
方案 1: jQuery + 前端模板
这种方案的好处是兼容性好、长期以来天坑比较少、 jQuery 的插件和解决方案比较丰富。前端模板省去了拼装 html 字符串的繁琐。缺点: DOM 编程带来的 redraw 和 reflow ;没有广告实例的生存周期的概念,绑事件维护交互效果。
方案 2: React + ES6 + Babel
方案的优点是虚拟 DOM 会计算 diff,再去修改页面。比较适合交互比较复杂的页面。react 是面向组件设计, 一个组件就是一个密封舱, 很少会对所有虚拟 DOM 进行比较, 由于强制使用单向流动, 减少每次变动需要的 diff. 没有绑定对象与 wrapper 的内存占用高的问题.这里恰好在我们的广告 js 中可以将广告特性抽象为 Web Components 的类,每个广告是组件实例。每个广告实例维护自身的生命周期。缺点: React 库非常大,它基本上离开了 jsx 换转器就活不成。 如果通过 ES6 编译会增加工程的复杂程度; React 的快速迭代相比 jQuery 稍显稳定性不足;只兼容到 IE8。
方案 3: angular
优点双向数据绑定, AngularJS 内置的通用 directive,就能实现大部分操作了,也就是说,基本不必关心 Model 与 UI 视图的关系,直接操作 Model 就行了, UI 视图自动更新;自身提供了丰富的内置 directive; 提供的功能很强大,开发者更有精力关注业务;文件不是很大。
缺点: 要受制于整个框架,不太适合广告这种插入到各个页面的需求。如果处理页面自身的业务逻辑,可能会考虑,毕竟 angular 提供的功能很强大。我只需关注业务自身就行了; 对所有作用域对象进行 diff 的脏检查; IE 兼容性, AngularJS 1.2 将继续支持 E8,但核心团队已经不打算在解决 IE8 及之前版本的问题上花时间; AngularJS 2.0 变换较大;不太适合广告代码这种使用场景(全站每个页面应用 adsFEv2Home.js)。
结论:
我公司全站中还有好多 IE7 的页面(其实数量还不少)方案 2、 3 行不通。打算采用方案 1。如果全站升至 IE8,可考虑再对 React 做更细致的调研。页面渲染方案: jQuery + 前端模板
五、 模块管理
备选方案 requireJS、 seaJS、 webpack
方案 1: requireJS
优点: AMD 方案异步,自带 r.js 打包工具。
缺点:当依赖的模块较多时或需要按需加载时,又要使用 CMD 的代码风格;暴露
require 和 definde 方法容易与页面中的 require.config 冲突;因为广告代码是要被加载在不同的页面上,若有页面模块与广告代码中模块重名,则会冲突。
方案 2: seaJS
优点:语法自然;更适合按需加载的书写。
缺点:同步加载,但打包成一个文件后也没关系。与 requireJS 的 2、 3 点相同。
方案 3: webpack
优点:试代码直接支持 commonJS 语法, 不用像 requireJS 和 seaJS 一样将代码放入一个函数中;不暴露 require 和 define ,打包后不影响外部环境。编译后不用依赖模块管理的库; commonJS 的代码风格,可用 node 的单元测试工具测试。
缺点:需编译后才能使用。不然不识别 require 关键字;对外不能暴露通用模块;改动一个模块,整个都要重新编译;必须把可能用到的模块都打包进去,导致文件变大。
方案 4: browserify
优点: 让服务器端的 CommonJS 格式的模块可以运行在浏览器端。这意味着通过它,我们可以使用 Node.js 的 npm 模块管理器。所以,实际上,它等于间接为浏览器提供了npm 的功能;与 webpack 都是 commonJS 模式
缺点: 同 webpack;
结论:考虑到广告代码,本身就是把所有特定模块压成一个 js 文件,且宿主页面也可能用 requireJS,广告代码与页面本身的 js 代码没有交集。 考虑到我已在其他项目使用webpack,比较熟悉。因此打算使用 webpack 做模块管理,并做更加深入的调查。
六、单元测试
方案 1: mocha
优点: 在浏览器和 Node 环境以及编译环境都可以使用; 轻松上手,学习成本低,语法贴近自然语言; 支持多种断言库;方便导出测试结果;不需要混入被测试的代码; 支持ES6;支持异步测试。
缺点: 依赖模块化的编程
方案 2: QUnit
优点: jQuery 的官方测试套装(也适用测试别的);可以测多种形式的代码,不依赖被测试代码的模块化;在一个测试现在多个断言数,限制断言个数
缺点:浏览器端引用;混入式使用可能发生薛定谔猫式的影响;自带断言种类少,自己扩展,但相对比较麻烦; Qunit 用 module()进行分组,不能区分子木块;
方案 3: Jasmine
优点: 报表清晰,有子分组,而且分组很清晰; 12 种原生断言比较,同时我们可以很容易的定义自己的断言;用 describe()来进行分组和模块,可以嵌套,也就是可以很好的区
分子模块;独有的 Mock Clock 和 Spies
缺点: Jasmine 只有 it()一个用来操作测试的方法;异步控制方法,很长,很麻烦
总结: 这一块中,考虑到我们只是要在编译环境使用对 commonJS 模块的测试,没有使用太重的框架,使用 mocha 是个比较高效的选择。
七、结论:
综上,考虑到方案可行性、学习成本、 宿主页面环境、兼容性等因素结论如下:
数据加载: jQuery 的 jsonp 加载数据。
广告间控制 : jQuery 的 Deferred + js 设计模式
广告分类处理: John Resig 写的 Simple JavaScript Inheritance
页面渲染: jQuery + 前端模板
模块管理: commonJS + webpack
单元测试: mocha