“消失”的这俩个月里,我的前端项目如何从零开始

96
韩亦乐
0.2 2017.07.10 17:29* 字数 4202

“消失”的这俩个月里,虽然我的技术博客没有更新,却也发生了很多事情值得自己凝聚。

先是拜读了赵皓阳的新书《生而贫穷》,书中用大量的客观数据描绘了一个驱离的世界——贫者越贫与阶级固化。其中让我感悟到,当我们大学生觉得自己被高成就人才“碾压”而感叹自己的命运之时,还有很多根本走不到高考这一步的孩子们填充着金字塔的底层;国内互联网巨头公司即使形成了垄断资本链,不断贪婪地吞噬着新兴的中小型软件公司,人民日报一批判,腾讯市值也得蒸发个千百亿。

再是经历了自己喜欢的自媒体公号被整肃的痛心过程,其中包括对“毒舌电影”的封杀和对赵皓阳的公号的一个月禁言。难怪以特立独行为特点的自由主义者李敖先生,在 05 年来大陆进行神州文化之旅时曾表示愿意抛弃自己的自由主义,来换我国宪法中白纸黑字的“言论自由”,现在看来,这条路还挺久。

话说回重点,当然要凝聚一下这俩个月我对前端项目的实践心得,也是本文的主旨——曾经大谈方法论的我,在实践过程中如何快速填坑,又如何感悟到以后要避免像这次一样在编程上的大量“搬砖”?

“消失”的这俩个月里,我的前端项目如何从零开始。

项目背景及功能拆解

一切之前,先看一下提交作品阶段由我原声解说的真实系统演示与功能详解的视频,如下。

[图片上传失败...(image-39199-1509644428516)]](http://upload-images.jianshu.io/upload_images/2558748-b5c8c5142d99e406.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可见这场中国软件杯竞赛我们选的竞赛题目是基于微信公众号开发的借阅伴侣,其中的核心需求如下:

  • 用户可以进入借阅书城搜书、借书,包括扫码搜书
  • 用户可以收藏、预订书籍并及时得到公众号的主推提醒
  • 用户可以修改信息或查看自己的搜书、藏书、预订书籍的记录
  • 管理员需要授权用户借阅此书和验收用户归还书籍并确认无损
  • 管理员需要录入相关书籍信息,管理管理员用户

原型制作

有了上示所列的功能,根据微信公众号服务的是手机用户的特点,不难设计出一个通用的手机端风格的原型。原型底下的导航栏中的四个功能,便是整个应用的核心:图书导航、书库搜书、借阅书车和个人中心。这里使用“墨刀”原型设计工具,原型设计工具能仿真交互上的一些操作,最终还能将结果导出成 HTML 放置到服务器上,方便给他人发送链接进行功能交流。

架构项目

能在一个竞赛中展现自己的特点想必一定是好事,刚开始构想项目所需要用到的技术栈的时候,一股脑把我知道的(都没实战过的)所有技术点都诺列了出来,每个技术点各有分工,一时美哉,如下。

  • Bower 做 JavaScript 的包依赖管理
  • JQuery 封装 DOM 操作并进行跨域请求
  • NPM 做 Node.js 的包依赖管理(没用上)
  • ESLint 做代码风格规范检测(未实现)
  • Grunt 启动 Karma 统一项目管理(未实现)
  • Istanbul 检查单元测试代码覆盖率(有问题)
  • Jasmine 做单元测试(有问题)
  • JSDoc 规范代码注释风格(未实现)
  • Karma 自动化完成单元测试(有问题)
  • Webpack 最终打包整个项目文件(未实现)
  • Yeoman 最后封装成一个项目原型模板(未实现)

除了前俩个技术点使用起来真的很简单外,别的技术点在尝试的过程中都遇到阻碍,用 Karma 自动化完成 Jasmine 单元测试并依靠 Istanbul 将测试覆盖率输出成 HTML 这个本来是实现成功了的,但引入 JQuery 后,Jasmine 默认不识别 JQuery 的 $ 符号及其各种函数,这几个技术点便在最后暂时被抛弃。

其实,Karma、Jasmine、Istanbul 被抛弃的主要原因是自己还不习惯,或者说不会,先写测试再写程序,也就是测试驱动开发。虽然测试驱动开发在去年暑假的特训营里学过,但久久的未深入和这次项目功能的复杂性自己一时无法预测完毕,才导致自己刚开始就先写了注册时的输入框验证的测试代码,后期的所有功能代码都是想到就直接跳过测试来写实际代码,后期引入 JQuery 时遇到 Jasmine 默认不支持 JQuery 才发觉测试代码的坑已经很难及时弥补回来了,期末和竞赛就快要截止这两件事更重要。

最后的项目目录是这样的,鉴于本次项目开发过程构建在 Git 上,开源许可证书、说明文档、Git 忽略文件都必不可少;Bower 管理器需要 bower.json 配置文件并会据此生成 bower_components 依赖库、Node 包管理器需要 package.json 配置文件并回据此生成 node_modules 模块。prototype 放原型设计工具导出的有交互功能的 HTML 页面,项目最后合并后端代码时导入 java_api 和 php_wechat 库,就是最终的整个目录结构了。

├── LICENSE # 开源许可证书
├── README.md # 项目说明文档
├── app # 移动端前端项目正式源码
│   ├── admin # 移动端管理 APP 源代码
│   └── user # 移动端用户借阅官网源代码
├── bower.json # Bower 前端库依赖关系
├── bower_components # Bower 前端依赖库(.gi)
│   └── 相关配置详见 [bower.json](./bower.json)
├── images # 公用图片库
├── java_api # 服务端搜索引擎接口源码
├── karma.conf.js # Karma 自动化完成单元测试配置
├── node_modules # Node 安装模块(.gi)
│   └── 相关配置详见 [package.json](./package.json)
├── package.json # Node 配置
├── pc # 电脑端前端项目正式源代码
│   └── admin # 桌面端管理系统源代码
├── prototype # 原型图 HTML 版
├── unit-test # 前端单元测试
├── php_wechat # 微信端源代码
└── .gitignore # Git 版本管理忽略信息说明文件

根据项目要求,我们需要开发管理员手机端、管理员电脑端和用户手机端——统一起来就是手机端和电脑端,手机端包括用户模块和管理员模块,不难架构出 app 、app/admin、app/user 和 pc/admin 的文件目录,其中这三个目录里的子目录都是这样的:

├── *.html # 低耦合的 HTML 页面
├── css # CSS 源代码
├── images # 背景图、LOGO 等
└── js # JS 源代码

因为没用上测试和 Webpack 等技术,也就没有了其相应目录。

UI 开发

刚刚提到了制作出的原型能导出 HTML,那我们岂不美哉?把原型设计工具当成像 DreamWeaver 一样的可视化前端开发工具真的可以吗?我们来看看导出的源码。

源码里能看到项目被 webpack 打包,况且很多 CSS 也可能是用 SASS 等预处理器编译后的结果,并不是真正易懂的源码,前端开发人员依然需要运用 UI 技术从原生程序搭建自己的项目。

HTML 实现

在 app/user、app/admin、pc/admin 中的结构刚刚已经介绍,HTML 就分别地放在这些目录下。目录结构中提到的低耦合,要求我们将 CSS 写到 CSS 文件,将 JS 写到 JS 文件中,最后再分别合并 CSS 文件和 JS 文件到自己的文件夹中即可。方便起见,刚刚说到的三个项目目录结构再次放到这里。

├── *.html # 低耦合的 HTML 页面
├── css # CSS 源代码
├── images # 背景图、LOGO 等
└── js # JS 源代码

在我这次的 HTML 实现过程中,主要就在写 DIV 标签及其用到的 Class 属性名、偶尔修改一下外链。在刚用户进入的图书导航的首页上,我们可以在不写 CSS 的情况下构建出下列 DIV 层次结构。其中我写的这个可能并不是最优方案,甚至称不上好,但它达到了能用的效果,并让我及时的提交竞赛作品,后期改进即可。

  • 图书导航页原型图:
  • 图书导航页 DIV 层次结构

其中注释里说的“这里将被 ajax”加载、“这是书名等”可以在后期才开发 JS 代码需要填充页面时再加上,前期写一个死的文字模板反而方便在开发 CSS
代码时及时布局效果。

CSS 实现

实现 CSS 代码时,这里使用了一个看起来不那么方便的选择器方式 —— 从和当前 DIV (也可以看做“组件”)直接相关的根 DIV 开始向下写,CSS 的优先级并不需要这样写,但这样写好处之一是能快速看出层级关系,修改相应代码。这是我从“海投网”中借鉴的,以后学 SASS 或重构 UI 时可以考虑尝试另一种风格,渐渐找到较为合适自己的那一个。

/* 轮播图 */

.mSlideShowWrap {
    margin: 0;
    padding: .2rem 0 0 0;
    height: 8.6rem;
    width: 100%;
    display: block;
}

.mSlideShowWrap .mNewBooksSideBar {
    height: 8.6rem;
    width: 1.4rem;
    border: #bbb solid medium;
    display: block;
    float: left;
    background-color: #fff;
}

.mSlideShowWrap .mNewBooksSideBar .mSideBarTitle {
    margin: 0;
    padding: 0 0 .2rem .1rem;
    height: 2.15rem;
    width: auto;
    display: block;
    color: #000;
}

由于之前浅尝辄止 CSS,在其实现过程反而不会使用 position、float 等功能,布局方面多用 CSS3 的 flex 实现,快速学习 flex 之后的大部分写 CSS 的时间,都在搬 flex 的砖,这就是我搬砖的由来,却因之后的 JavaScript 编程实战而“高潮”。

逻辑与接口实现(JavaScript 编程)

大前端时代到来,简单的前端界面设计人员早已饱和,前端技术也迎来了新的高度,很多时候我们是将前端开发者和 JavaScript 程序员挂钩起来的,然而事实上并不是这样。前端工程师起初多叫重构工程师,从《Web 全栈工程的自我修养》作者余果的求职经历可以看到,最开始前端就是偏向设计和交互的,就是用来一丝不差的实现高保真原型图,现在用户体验的要求上来了,异步请求的需求量上来了,JavaScript 才渐渐成了前端工程师的必备技能,甚至渐渐的,“掌握至少一门后端语言”也加入到了前端工程师的招聘需求中。

看来又多说了几句,这次竞赛前端项目的最后就是用 JavaScript 进行逻辑与接口的实现。我们来分别谈一谈。

JavaScript 逻辑实现

逻辑实现包括输入框信息的验证、页面的跳转和用户登录状态的保存等。在引入 JQuery 后,这一切开发变得简单起来。

下面是一个用 JQuery 快速实现的输入框验证代码示例。

var password = $("input:eq(0)").val();
var newPassword = $("input:eq(1)").val();
var rePassword = $("input:eq(2)").val();
var identity = $("input:eq(3)").val();
var flag = 1; // 1 代表判断通过

if (!checkIDCard(identity)) flag = 0, tips = "不符合正确的身份证号格式";
if (!compareTwoPassWord(newPassword, rePassword)) flag = 0, tips = "密码与重复密码不一致";
if (!checkPassword(rePassword)) flag = 0, tips = "不符合正确的重复密码格式(6~13位)";
if (!checkPassword(newPassword)) flag = 0, tips = "不符合正确的新密码格式(6~13位)";
if (!checkPassword(password)) flag = 0, tips = "不符合正确的旧密码格式(6~13位)";

下面是一个根据逻辑需求进行页面跳转的代码示例。

if (data["IsSuccess"]) {
    $(".pContentMessage").text("录入成功!正在重新加载");
    setTimeout(function () {
        window.location.href = "admin_center.html?tab=0";
    }, 1000);
}

JavaScript 接口实现

前后端分离的项目中,后端只用注重接口的实现即可,一个一个很短的接口链接及其参数说明、返回结果说明抛给前端,前端就要进行各种相应的逻辑编程、UI 编程,正想不通,为什么在我周围,学前端的还是女生多。当然啦,前端业界的领头人物性别比例就不多说了。我曾在一个前端微信群中聊过这类有关性别平等的话题,偏见还是很多的,就在我们身边,这里就不多多说来。啊,前端和后端也平等。

一言不合,后端抛给你个接口,长得还很奇怪的样子:

补全图书信息
  • 请求地址

https://corefuture.cn/lib_api/book/finishBook.action

  • 请求类型

HTTP:POST

  • 参数
参数名称 参数说明 必填
isbn 书的isbn号 Y
  • 结果示例
{
  "next": "",
  "count": 1,  
  "books": [
    {
      "bId": null,
      "bName": "大型网站系统与Java中间件开发实践",
      "sId": null,
      "cId": null,
      "spell": null,
      "initial": null,
      "imgurl": "https://img1.doubanio.com/mpic/s27269837.jpg",
      "isbn": "9787121227615",
      "author": "曾宪杰",
      "count": null
    }
  ]
}
  • 返回参数说明
next:下一页得next值,""表示没有下一页
count:books中含有几本书
books:book的集合
book{
    bId:书籍唯一标识
    bName:书籍名称
    sId:书库的标识ID
    cId:书籍类型的标识ID
    spell:全拼
    initial:首字母
    imgurl:书籍图片路径
    isbn:书籍的isbn号
    author:作者
    count:书库中该书的总量
}

当然,前端进行数据访问的跨域权限需要后端支持,具体怎么支持这就不是前端的事了。当后端有上述接口示例交给前端时,如果不是需要带有 Cookie 等参数的接口,前端可以直接用代码实战访问,根据浏览器开发者工具里的具体报错信息来修改代码;更推荐在写跨域代码前先用 postman 这类的可视化 HTTP 请求工具/ API 测试工具来测试。

postman - 可视化 HTTP 请求工具

编写跨域请求代码时,需要一些异步回调相关的知识,当请求结果返回时触发回调函数,进行相应的操作。这里用道德回调函数,主要就是页面跳转和数据渲染两个功能,例如一个完整的跨域请求函数,并将返回结果渲染到页面的代码可以是下面这个样子。

function finishBook () {
    // 隐藏详情页
    $(".pContentDetailWrap:eq(0)").hide();
    $(".pContentSubmitWrap:eq(0)").hide();
    var isbn = $("input:eq(0)").val();
    var post_data = {
        "isbn": isbn
    };
    if (!isbn) {
        $(".pContentMessage").text("ISBN 号不能为空");
        return;
    }
    var post_url = "https://wwwxinle.cn/Book/public/index.php/index/Manager/infoBook";
    $.post(post_url, post_data, function (data, status) {
        data = JSON.parse(data);
        if (data["count"] == 0) {
            $(".pContentMessage").text("没有找到相关信息");
            return;
        }
        var book = data["books"][0];
        $(".pContentDetailLeftWrap img").attr("src", book["imgurl"])
        $(".pContentDetailItemWrap:eq(0) input").val(book["bName"]);
        $(".pContentDetailItemWrap:eq(3) input").val(book["author"]);
        $(".pContentDetailItemWrap:eq(4) input").val(1);
        $(".pContentDetailHiddenWrap").text(JSON.stringify(book));
        $(".pContentDetailWrap:eq(0)").show();
        $(".pContentSubmitWrap:eq(0)").show();
    });
}

回调成功,一个完整的跨域流程和前端对接口的实现流程就到了收尾之处。回调函数还有很多其它的注意点,比如回调函数里的变量值不能被回调函数外访问的时候需要注意什么等,遇到这些坑时可以选择尽可能将外部变量及其功能放入内部实现,或用其他更前沿的技术来实现。

整体架构回顾

之前一直在说前端项目的构建过程,在我们团队三人的整体分工中,也是有其整体框架:Web 前端和没有数据库的 PHP 后端进行请求,PHP 后端和有数据库的 JAVA 数据接口进行请求,JAVA 数据接口直接访问数据库——沃,其实队友用的是基于 Apache Lucene(TM) 的开源搜索引擎,只不过我当时看到他们进行 SQL 操作了,不知者无罪~

独特吧?Node.js 除了做后端,也可以做“前台”,就是这时 Node 不操作数据库,操作数据库的还是典型的后端语言如 PHP、JAVA、Python 等;那么在这个项目中,PHP 微信端有点“前台”的感觉,主要承载着每个人专属微信号信息的获取和公众号的主推功能,并向 JAVA 后端进行数据访问。Web 前端只和 PHP 微信端进行联络。

如果可以和 MVC 架构联系起来的话,Web 前端是大 V,PHP 微信端是大 C,JAVA 服务端是大 M;其中每个大 V、大 C、大 M 都有自己小 V、小 C、小 M。神奇的逻辑。

舍不得收尾、说拜拜

转眼间,自己还在大学期间学 JavaScript 基础、学习 CSS 布局的时候,ES 7、ES8 都开始被各大社群讨论;React、Vue 等主流框架也开始加深诱惑着我们初学者;各大培训班疯狂输出的前端学员就光数量上也让大学生软件开发学习者有所担忧。

一切还好。正如文章开始的那些感悟中隐藏在背后的那句罗曼罗兰式英雄的风度一样——“世界上只有一种真正的英雄主义,就是认清了生活的真相后还依然热爱它”。

哈哈,希望我的引用不算错位。也希望我的这个项目总结能散发出本身应有的光芒。

从前端到全栈
Web note ad 1