帝都的三月春暖花开、惠风和畅。在这美好的时节,我们接了个“大项目”——京东PLUS会员M端频道改版。

京东为向核心客户提供更优质的购物体验,推出了京东PLUS会员,包含购物回馈、自营运费补贴、畅读电子书、退换无忧、专属客服和专享商品等权益,全方位提升和丰富网购特权。PLUS会员项目作为国内第一个电商付费会籍项目,由传统的以流量为核心向以用户为核心进行转变,通过付费服务锁定高净值核心用户,向其提供更优质的购物体验,实现用户价值最大化。 这是京东很重要的一个产品,刘总在各种不同场合向雷军、刘中国、杨元庆、周鸿祎等知名人士赠予过PLUS终身荣誉会员。京东APP 6.0计划在首屏增加PLUS会员频道的入口,预计将会带来大量的新用户访问,我们以此为契机对PLUS会员频道M端进行了整体改版,以提升用户体验和转化率。

PLUS会员移动端首页

▲ PLUS会员移动端首页


 ▲ PLUS会员移动端首页2

▲ PLUS会员移动端首页

这个项目还是蛮有挑战的:

  • 一方面要赶 APP 新版上线这个 deadline,另一方面需求提出的太晚,导致工期特别紧张。
  • 业务逻辑本身比较复杂,近10种用户账户状态、实名认证情况、小白信用达标状态、自动续费开通状态、会员级别、后台配置等维度都将直接影响页面的楼层顺序、样式和逻辑。而根据需求,频道首页特别重,复杂程度远超现有页面和其他频道页。
  • 我们前端和后端、产品团队身处异地,沟通成本较高。
  • 项目页面既要作为京东M站的一部分适配主流手机的各种浏览器,同时还需要内嵌在 iOS/Android 平台的京东、京东金融、微信、手Q等 App 中,兼容性要求高。在项目后期,产品方对个别机型上的低版本浏览器也提出了兼容要求。
  • 页面需要兼容一些其他团队提供的公共代码。

我们开工时,需求文档还未完全定稿,对需求了解的不充分和对业务逻辑复杂度认识不够,让我们一度非常被动,开发工作充满了挑战。为保证按时上线,我们顶着压力,加班加点,那段时间几乎连轴转。无论如何,我们拿下了这场硬仗,现在回过头来看,还是感慨无限。这里记录一下这个项目开发中遇到的一些问题。

 ▲ 尊享商品列表页

▲ 尊享商品列表页


 ▲ 我的PLUS页

▲ 我的PLUS页

选型之坑

旧版采用的是前端开发静态页面,后端再套页面的开发模式,在这种模式下前端人员可发挥的空间受到了很大制约,也给后期的维护和二次开发工作带来了麻烦。于是,此次改版我们提出采用前后端分离模式开发,以明确前后端权责、提高开发效率,同时提高后期可维护性,这一方案得到了后端团队的支持。而难点在于这次是改版,并不是完全从零开始,所以一旦前后端分离,之前在view层面的很多业务逻辑就也要拿到前端来做,而我们团队作为一个公共部门并不熟悉他们的业务,这无疑进一步增加了我们的压力。

我们在前端采用了目前比较流行的 Vue.js 2.0 开发框架,脚手架工具和之前开发的很多项目一样,仍然选择了京东前端自己开发的 JDF,而当时的 JDF 版本尚不支持解析 Vue 模板文件,所以我们使用了 Vue.js 的独立构建模式,也就是在页面上直接引入完整版的 Vue.js 文件(包含模板编译器),模板的编译在浏览器端完成,后来发现这给我们挖了个大坑。

Vue.js的不同构建版本

▲ Vue.js的不同构建版本


项目测试阶段发现,iOS版京东APP 频道首页的打开速度明显慢于其他浏览器和 APP,每次进入页面都会首先渲染出 loading 浮层,之后3-4秒才渲染出页面的楼层。我们马上开始排查。在 APP 中调试还是比较困难的,而在调试方便的浏览器中又不存在这个问题,所以这个问题的排查,我们还真是下了一番功夫。

我们初步判断这不是常规的因为请求多、资源数据量大、JS逻辑等原因造成的问题,因为这些问题一旦存在必然影响不止一个浏览器,且通过浏览器性能监控并未发现明显异常情况。

我们和测试团队利用抓包工具对 iOS版京东APP 中这个页面的资源请求情况进行了监测(这个阶段我们还踩了iOS 10.3 版本 https 请求抓包的坑,下文细说),也没有发现明显异常,基本印证了我们的判断。

另一方面,我们发现同一部 iPhone 的 Safari 浏览器和京东金融、微信、手机QQ等 APP 中页面加载速度都明显快于 iOS版京东APP,所以我们把关注点放到了 iOS版京东APP 内置的浏览器组件上,经过深入调查及与APP团队沟通,我们了解到 iOS版京东APP 使用的是比较老的 UIWebView。

苹果公司在 iOS8 之后提供了替代 UIWebView 的 WKWebView,较 UIWebView 在性能、稳定性、功能方面有大幅提升,目前主流 iOS APP 大部分都在使用或已经迁移到 WKWebView。

wkwebview

▲ 苹果官方从 iOS8 开始就建议不再使用 UIWebView

所以我们判断频道首页在 iOS版京东APP 中加载速度慢的原因是:性能低下的 UIWebView 在编译逻辑复杂的频道首页 Vue.js 模板时耗时过多。那么,怎么解决这个问题呢?等待 APP 升级 WKWebview 明显解决不了燃眉之急。要解决当下的问题还得从页面自身入手。我们计划尝试把模板编译工作拿到本地来做,减轻浏览器负担。

我们基于 webpack 搭建了一套新的脚手架,使用 Vue.js 官方提供的 Vue-template-loader 在本地 nodejs 环境对页面 Vue 模板进行了预编译,把模板文件提前编译成了 JavaScript 渲染函数。频道首页在新的脚手架跑通之后,我们在 iOS版京东APP 中测试了一下,页面渲染速度大幅提升,随后我们把整个项目都迁移到了新的脚手架中,问题得以彻底解决。
来,感受一下。

回过头来看,我们团队可能不是第一个踩此坑的,也不会是最后一个。所以无论是从节约公司研发资源角度,还是从提升用户体验的角度,我们都希望 iOS 团队的兄弟们能尽快升级 WKWebView。我们在撰写这篇文章的时候去苹果官网看了一下,截至2017年7月,iOS 9.0 及以上版本已经占到97%。

▲ 苹果官方的 iOS 版本分布数据(数据截至2017年7月)

开发阶段的跨域问题

前端后端分离,又一次把跨域问题摆在了我们面前。页面上线之后与接口是在同一个域下的,只有在开发阶段才面临跨域问题,所以只要把我们自己开发环境的跨域问题解决即可。
早期解决开发阶段的跨域,通常采用给 Chrome 设置启动参数 “–disable-web-security” 禁用浏览器安全策略的办法,可这招在新版本 Chrome 上已经不灵了。再想想,移动端项目不用考虑低版本 PC端浏览器,完全可以采用 CORS(跨域资源共享)技术来实现跨域。对于 GET/POST 这类简单请求来说,只要服务器响应头的 Access-Control-Allow-Origin 字段值包含当前源(协议+域名+端口),支持 CORS 的浏览器便会放行此次请求。既然是开发阶段的问题,我们还是不要麻烦后端同学修改接口了,Chrome 浏览器有个名叫 “Allow-Control-Allow-Origin: *” 的插件,可以自动给服务器响应头加上 “Allow-Control-Allow-Origin: *” ,装上它, Chrome 就可以跨域了。那……其他浏览器呢,当然我们可以说我开发阶段只用 Chrome,可是手机浏览器呢?APP呢?它们可未必有这个插件。

搞清楚 CORS 原理之后会发现这个问题不难解决,没有插件我们可以使用抓包工具来给 response 加上 “Allow-Control-Allow-Origin: *”,比如 fiddler。在 fiddler 菜单中找到 Rules → Customize Rules…,弹出 fiddler 脚本编辑器(第一次按提示安装即可),如下图所示,在 OnBeforeResponse 函数中加上红框中的代码,保存成功之后,fiddler 便会自动给经过它的 response 增加 “Allow-Control-Allow-Origin: *” 了,我们只需要设置好浏览器和手机的代理,让请求和响应经过 fiddler 即可实现跨域。

Flexbox布局问题

本次改版的页面布局,大量使用了 CSS3 Flexbox 弹性盒模型。Flexbox 不是什么特别新的技术,但是由于相关规范经历多次迭代,写法不尽相同,而不同时期的浏览器实现了不同版本的规范,这就造成了一些同学对 Flexbox 布局写法和浏览器兼容性的疑惑,迟迟不敢在项目中大规模应用。

其实,算上所有版本规范,移动端对 Flexbox 的支持还是比较好的,不信你看 http://caniuse.com/#search=flexbox。我们完全可以只按照最新的标准来写,然后借助 autoprefixer 等工具来自动补全旧版代码兼容低版本浏览器,主流脚手架、IDE都支持。

flex

不可否认,在个别老旧机型上,Flexbox 确实会遇到些问题(主要是旧版的问题),比如旧版 inline 元素需要设置 display:block 才能正常显示,不支持 flex-wrap 等,但这些问题都是可以解决的。我们不能因噎废食,因为这些小问题存在就完全故步自封,抛弃 Flexbox。关于 Flexbox 布局总结几点:

  • 需要明确 Flexbox 规范有过多个版本,不同时期的浏览器可能实现了不同版本规范。
  • 按照最新版规范(https://www.w3.org/TR/css-flexbox-1/)编码,通过工具针对我们设置的浏览器范围自动补全代码。
  • 总结个别老旧机型的兼容问题,编码时注意绕过。
  • Flexbox 也不是万能的,有它适用的布局,也有不适用的,勿滥用。

iOS https抓包问题

开发阶段在真机上调试网页,我们通常需要配置手机的代理服务器为开发电脑的 fiddler 等抓包工具,然后即可调试开发电脑上的页面。https请求抓包相对麻烦,需要安装根证书,在手机浏览器里通过ip+端口访问电脑上的 fiddler,会提示下载证书,下载安装即可。可是在这个项目,我们遇到点麻烦,Android 手机正常调试没毛病,手头的 iPhone 的 https 请求却抓不到包,不管装不装证书都不行。后来又找了几台 iPhone,发现安装证书之后,有的可以访问,有的不可以,于是我们怀疑是iOS版本问题,遂查了一下 iOS 近期版本更新内容,发现果然是新发布的10.3版本有变化,需要在“设置”>“通用”>“关于本机”>“证书信任设置”中对已安装的证书进行信任设置,之后才能抓包 https!!这个问题耽误了不少时间,最后知道真相的我们眼泪掉下来~以后得多长个心,多关注主流手机系统、浏览器的更新内容。

3

ES6兼容问题

对于手机端项目,最让人头疼的莫过于兼容问题了。项目完成后,被测试人员告知在华为个别机型自带的浏览器中,商品列表页面显示异常,商品一直处于加载状态,如下图所示:
4

我们尝试在这些手机的京东APP、UC等浏览器中访问,没有发现异常。于是判断是原生的浏览器的兼容问题,但是具体是哪里不兼容呢?我们首先猜测华为手机原生浏览器的内核版本过低,无法支持 Vue.js 2.0 框架。然而,在之前上线的一些项目就是使用 Vue.js 2.0 开发的,在这些手机原生浏览器下也能打开,因此排除了这个问题。最后发现原因是项目中使用了 ES6 的 API,而华为部分机型原生浏览器内核版本过低,不支持 ES6 的 API。其实对于 ES6 的兼容问题我们之前是有所注意的,在脚手架中使用了 Babel 来转化 ES6 代码。Babel 是一个广泛使用的转码器,几乎可以编译所有新潮的 JavaScript 语法,但对于 API 来说却并非如此。比方说,下列含有箭头函数的 ES6 代码:

经过 Babel 插件转化为:

它仍然并非随处适用,因为并非所有的 JavaScript 环境都支持 Array.from 这个全局对象。Babel 默认只转换新的 JavaScript 句法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)。而 polyfill 可以在当前运行环境中用来模拟性复制尚不存在的原生 API 的代码,能让我们提前使用还不可用的 API。所以为了完整使用 ES6 的 API,我们安装了 babel-polyfill。在此过程中,需要注意的是尽量使用 NPM 中提供的最新版的 babel-polyfill,因为尝试使用过一些较老的版本,个别华为机型仍然不能打开页面。

安装后,只需要在文件顶部导入 babel-polyfill 就可以了。但是这种方案显然有些缺陷,babel-polyfill 构建压缩后体积为80~90k,这对于移动端还是有点大的。为了一些非主流的浏览器来引入 polyfill 而影响主流浏览器的性能,值不值得呢?所以如何取舍?要不要使用 babel-polyfill?还是需要评估和权衡的。

Vue数组问题

在 PLUS会员频道M端中有个页面为商品列表页,该页面如下图所示:

从图中可以看出,该页面顶部有导航栏,点击导航栏可以切换下面商品列表部分,而每一个商品列表页内部都分为多页,具体可以参考下面的示意图:

6

从上图可以看出,每个商品列表页都会根据用户在其内部向下滑动的程度来触发请求下一页的数据,所以每个商品列表页对应的商品区域都要有独立的“当前页码”、“当前提示是否有更多商品状态”、“当前向下滑动位置”、“当前是否出现回到顶部的按钮”等信息,因此我们使用了对象来保存每一商品列表页的相关数据,而这些对象又组成了数组。于是,不可避免地要使用 Vue 来监听数组的变化,从而动态渲染页面。但是这里遇到一个问题,例如我们使用数组 arrtab[i] 来保存导航栏第i个标签的信息:

当该商品列表页对应的商品列表向下滑时,page 发生变化,但是对应的页面没有变化,也就是 Vue 没有检测到该数组 arrtab 发生了变化,但是我明明改变了数组 arrtab 了呀?于是重新查看Vue文档,发现 Vue 对数组的监听还有“特殊”的处理,文档指出:

  1. 由于 JavaScript 的限制,Vue 无法检测到以下数组变动:
    • 当你使用索引直接设置一项时,例如
    • 当你修改数组长度时,例如
  2. 可以使用 splice 方法来解决这一问题。

根据文档给出的方法,首先改变某个标签下的 page,然后再使用 splice 方法来更新 arrtab 数组,这样 Vue 就可以正常监听到数组的变化了,代码示意如下:

Webview异步请求缓存问题

为了配合大促活动,在频道主页添加了运营弹窗功能。功能要求在运营活动当天,用户首次进入页面并满足一定条件,系统会自动弹出一次弹窗,关闭后将不再出现。但是当用户关闭弹窗,点击链接跳走后,再返回首页,弹窗又出现了!!
经过排查,确认后退时 APP 的 Webview 没有重新发送请求,而是用的缓存。在解决这个问题的时候我们走了一些弯路,比如使用 sessionStorage 记录页面状态,区分是否是后退行为等。后来在确认 Webview 的缓存机制之后,发现解决问题很简单,后退的时候只要 url 与之前请求过的 url 不一致,Webview 便会发送请求而不使用缓存,所以给请求的 url 加一个随机参数即可。事实上,jQuery/Zepto 的 ajax 方法里都有一个名为 cache 参数,是专门用来处理这种情况的,只要 cache 的值为 false,请求的 url 便会自动加上一个随机字符串,以保证请求总能发出去。而 Vue 框架中常用的数据交互组件 Vue-resource 却没有提供类似功能,遇到这种场景,只能用户手动处理了。

Swiper 与 Vue

整个项目中多处使用到滑动图片的场景,移动端页面中的滑动组件我们常用 swiper,同样在这个项目初期页面重构时我们又一次引入了 swiper.js。通过 watch 方法或计算属性监听相关数据,数据变化且满足一定条件,就实例化一个swiper。这在 Chrome 浏览器模拟测试时没问题,样式、功能都能实现,如下图:

但是,在真机上测试时,就不是这么回事了。在一些版本较低的浏览器上会出现滑不动,页码消失,滑动时卡顿,自动轮播失效等问题。判断与某些情况下多次实例化了 swiper 有关。于是我们加以改进,在每次实例化 swiper 之前先判断是否已存在,如果有就先调用 destroy 方法销毁掉:

做过以上处理后 swiper 滑动时顺畅多了,但这还不是最终解决方案,在个别手机上依然存在一些偶现的小问题。我们在更换脚手架时,顺便把滑动组件也更换为了vue插件 Vue-awesome-swiper,除标签外,其 API 与 swiper.js 基本一致。更换之后,那些问题随之解决。

总结

从接手PLUS会员移动端改版项目,到一次次的升级、改版、完善、上线,这个过程中我们遇到了很多难题,项目逻辑复杂、时间紧迫,走了不少的弯路。回想一路走过来磕磕绊绊,但团队人员并没有因此气馁,而是逢山开道,遇水搭桥,想尽各种办法,克服各种困难,来解决遇到的问题,实现所需的功能。我们也从中学到了很多东西。值得欣慰的是在大家共同努力下,PLUS会员项目取得了不错的成绩,用户体验和相关数据有了大幅提升。项目方报喜的时候,便是我们最开心的时刻!

喜欢(174) 评论(15) 分享
  1. 您好,我想问一下,你们在做上拉更新的时候,有没有跟你们的nav或者是swiper冲突?
    我目前遇到一个问题就是,下拉更新,但是又有一个滑动菜单(类似于QQ消息左滑删除)。这个时候两个事件会同时被检测,导致操作容易出错。
    以上,

    1. 初始化swiper时把passiveListeners的值设为false试试

  2. -.-不是都是正常开发过程中应该做的吗

    webpack有个东西叫proxytabel的 不需要你这种骚操作的

    1. 项目早期未使用webpack,且通过fiddler/whistlejs代理工具解决开发阶段的跨域问题比proxytabel更方便

  3. 想请教一下前端用vue是如何解决seo问题的。还是根本不需要seo。

    1. 可考虑vue服务端渲染

  4. Vue-awesome-swiper 不是 swiper.js 团队做的。只是在swiper.js 上包装了一层,体积很大,在移动端并不适合。简单的轮播需求一百多行代码就可以实现。会员频道是不用考虑 SEO 了。 -_-!

    1. 这个项目轮播需求复杂,普通的轮播组件满足不了需求。后续确实有替换掉swiper的计划。

  5. 哈哈哈哈哈 我想说 跨域、css兼容性、ESX兼容… 都是Webpack可以一并解决的呀,写的很用心呢!

    1. 嗯,这个项目前期并未使用webpack

  6. 请教个问题,京东首页如果是用mobile浏览器访问会跳转到m.jd.com。这个跳转是用js来检测UA跳转。直接在nginx,或者后端里307重定向肯定会快一点吧。我看qq.com也是用js重定向的,能说说用js重定向的好处么?

    1. 能js做到的事,干嘛占用后端资源呢

Leave a Reply to tangdw

© 2017 JDC. All Rights Reserved.