首先说一下我个人接触计算机的历史, 小学的时候学校开设微机课, 虽然上课不多, 而且主要以理论为主, 但是也算是对计算机有了初步印象, 见过了电脑, 软盘, DOS系统, Logo小海龟, 还有波斯王子. 中学时代由于学业紧张, 学校也没有太多计算机资源, 基本没接触计算机. 上大学之后, 开始学习计算机基础的公共课, 接触Windows, 感觉完全没有头绪. 好多同学至少有去网吧的基础, 可我也没去过. 然后就开始寻找各种机会努力学习, 图书馆, 机房, 因为没有自己的电脑, 很多时候是先读书, 在脑子里想出一个大概的可能性, 然后再上机实践验证. 几年下来, 也对计算机初步有一些认识, 也去网吧当过网管, 自己装系统, 写脚本, 尝试Linux等. 虽然有兴趣, 但是还是没有下决心从事软件行业的工作, 因为能够感受到软件开发其实是一种效率很低的工作, 一个简单的按钮都要用代码写一大段, 而且要保证每个符号都正确, 语法通过之后, 还要验证逻辑是否符合要求, 更别说界面的调整了. 这还只是一个简单的按钮, 当把界面需要的众多元素拼在一起之后, 还需要对整体进行测试和调整, 这个工作量感觉是难以想象的. 所以尽管当时也考虑接触一下编程, 但是自学一段时间ruby之后, 还是没有进入软件开发行业. 当时已经有同学报Java培训班并且找到工作, 也一直会有各种培训机构发广告约面谈试听, 而在毕业后我仍然是在电脑城做一些简单的装机工作. 直到2016年有一个机会, 才参加了培训. 当时也考虑过自学, 但是我觉得比技术更重要的是能接触比较真实的开发场景, 这个是自学很难达到的, 因此选择报班, 当时的环境下Java的培训比较多, 因此我就报了一个班. 学完之后找工作当然没有那么容易. 多数公司不愿意接收没有经验的学生, 因此培训班一般会让学生包装简历, 虚构几个工作和项目经验, 以获得面试和入职机会. 我当时不愿采取这种方式, 而是选择先在电脑城打工还贷款(培训班一般是贷款入学), 另外也继续学习和寻找机会. 终于有一天, 有一家买电脑的客户, 是一个正在组建的公司, 他们也正在招聘程序员, 聊了之后, 他们表示没有经验可以, 只要能干活就行, 于是就以最基础的工资开始了软件开发工作. 最开始确实要学的比较多, 基本没有休息时间, 在一些之前遗留的代码基础上, 实现了交易界面和通信服务. 后来发现小公司做的业务属于法律边缘的灰色地带, 压力也比较大, 就离职了. 之后又找过一些工作, 都不太稳定, 因为经验年限不够, 找不到太好的工作. 直到后来在一家教育机构做了两年的前端开发之后, 才算有了基础的两年经验, 也有了前端技术的基础.
由于这些背景, 我一直在思考什么才是软件开发合适的方式. 对于很少接触计算机的用户, 图形化拖拽的方式当然是最简单的, 对于经常需要操作计算机的用户, 用脚本处理重复任务是更合适的方式, 而使用编程语言编写代码实现的, 应该是那些基础性的功能, 高度规范化和可复用的部分, 比如操作系统, 浏览器等基础工具. 而日常我们开发的业务系统, 对于用户来说最重要的数据的存储和展现, 中间可以对数据做一些运算分析和处理. 这种系统最重要的是数据和规则, 当然通过编程也可以实现这些规则, 但是需要极高的成本, 需要有一个开发团队, 产品经理整理业务需求, 同步输出给开发人员编写代码, 再通过测试验证逻辑是否一致. 而实际上大部分需求是简单的数据处理和展现, 用Excel和PPT也基本能完成, 但是一旦进入开发流程, 成本就会急剧上升, 由此获得的一些灵活性, 是否足以抵消开发和验证的高额成本, 是一个值得深思的问题. 所以近些年业界开始流行低代码和无服务平台. 核心就是让数据规则和技术代码尽可能解耦, 这其实也是计算机发展历史中永恒的追求. 各种汇编符号 , 高级语言, DSL, 以及图形化编程界面都是在朝这个方向努力. 但是为什么这么多努力却一直无法成为主流呢, 根本原因是无法建立统一的规范. 各方都有自己的利益诉求, 动辄自己开发一套不兼容的内部系统, 又无法推广完善去满足更多的需求, 因此越多发展, 也只是分裂的平台越多. 历史上是这样, 今天的各种低代码平台, 看似热闹非凡, 却也无法摆脱这一困境.
作为技术人员, 我们能做的也只是尽可能从公开流行的技术和标准中, 建立接近最终客户需要的规范, 缩短技术到产品的路径. 当然一个完整的产品涉及的技术不仅仅是前端, 但是前端代码是距离用户最近的, 所以我们先从前端入手去考虑开发流程的规范化.
其实前端不仅仅是浏览器开发, 而是所有用户界面, 因此从整体上分类, 包括字符界面, 图形界面, 以及最近的VR体感交互技术. 而今天我们多数面对的都是平面图形界面, 这里又根据技术分为原生界面开发和浏览器界面开发. 其实浏览器中的前端开发, 是适应互联网跨平台和远程传输的需要, 从原生界面开发中抽象出来的, 由于技术上简单, 成果又可以在各平台复用, 逐渐成为了最主要的界面开发方式. 当然由于历史和技术各种原因, 前端的开发环境不是完全合适和标准化的. 只不过在目前这个时代, 基于Web标准和现代浏览器开发, 是最低成本最常用的界面开发方式.
一般把前端所用的技术分为HTML, CSS和Javascript三块. 其中HTML和CSS其实都是静态标记语言, 产生一些固定的界面样式. 当然现代的规范会添加一些交互, 像HTML的表单和CSS的伪类等等. 不过总体来说, 这两部分是密切配合, 共同完成页面样式的展现. 尤其是HTML, 是网页的基础, 浏览的入口, CSS和Javascript都需要HTML来引入. 所以首先我们从HTML开始.
HTML是一种使用尖括号的标记语言, 可以通过树状的层级结构构造复杂的页面. 标签内部可以包含文本内容或者子元素标签, 标签也可以有属性. 这个简单的规则基本就是HTML的全部, 剩下的主要是一些约定好的内置标签和属性.
<!DOCTYPE html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1, viewport-fit=cover">
<meta name=renderer content=webkit>
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="referrer" content="never">
<meta http-equiv=Cache-Control content="no-cache, no-store, must-revalidate">
<meta http-equiv=Pragma content=no-cache>
<meta http-equiv=Expires content=0>
<title>首页</title>
<script src="pg.js" type="module"></script>
<style>
*, ::before, ::after {
background-repeat: no-repeat;
background-size: 100%;
background-position: center;
text-overflow: ellipsis;
border-style: solid;
border-width: 0;
justify-content: center;
align-items: center;
flex-wrap: wrap;
box-sizing: border-box;
}
</style>
<body><div id="app"></div></body>
在开发中, 我会使用这样的HTML文件头作为模版. 第一行声明文件类型为HTML, 第二行声明编码格式. 其他声明主要是为了兼容各种PC和移动端浏览器. 以及开发调试所用的. 网页的内容主体其实就是最后一行, 在body标签下声明了一个空的div元素, 其他的页面内容都使用js动态添加到这个节点下. 因为我们开发的主要都是动态网页, 数据是从其他来源获取之后在网页上展现的.
通常的网页模版会包含html和head标签, 但是按照HTML5标准, 这些标签可以省略, 浏览器会自动添加, 为了简洁就没写, 如果有可能引起混淆, 也可以加上.
除此之外, HTML作为入口, 还要负责引入CSS和js, CSS使用style标签, js使用script标签, 可以直接在标签里写入内容, 也可以通过src引入文件路径, 但是目前CSS引入用的是link标签和href属性. 这就是由于历史原因造成的不一致. 随着时间Web技术标准也在进化, 目前CSS中支持@import导入外部CSS, js中也支持import外部js, 但是这些语法也是不同时代由不同组织制定的, 各自都不太一样. 而且浏览器支持的情况也不一致.
因此除了一部分全局的CSS和js在首页引入, 其他大部分我们都是通过一个比较传统的属性innerHTML引入, 在各个浏览器中有较好的兼容性. 还有一个insertAdjacentHTML方法也类似, 可以在标签里添加内容. 内容的加载使用xhr或者fetch, 功能是类似的. 有一个需要注意的地方是HTML和CSS插入页面后可以立即生效, 而js比较特殊, 直接插入不会执行, 需要自己创建script元素再添加内容才会执行, 这是浏览器的一种安全保护措施, 不过更简单的是采用eval方法也就可以运行了.
然后是页面内容的写法, 原则上我们用一个html文件, 在三个标签内分别写html, css, js就可以了, 这也是Vue等框架采用的方式, 只不过vue要求使用.vue作为文件扩展名, html写在template标签内. 我们也可以先按照这一约定.
关于html, 标准的写法是尖括号开始, 关闭的时候也是需要尖括号, 这样比较繁琐, 因此社区有很多爱好者创建了一些简化书写的项目, 比如Emmet和Pug, 其实html需要表达的内容不多, 就是标签名, 属性, 层级就足够了, 因此我参考pug的语法, 写了一个简单的解析器dev/qht.js · Onesimu/u.js), 在简写和标准html之间转换. 代码实现的比较粗糙, 格式化时借助了JSON.stringify, 其实在代码本身格式良好的情况下, 也可以直接根据缩进层级添加闭合标签. 不过总体来说, 代码是轻量级的, 可以在获取到文件内容之后运行转换, 不需要安装庞大的解析器.
<template>
#pg
h2#title Pug for PostHTML
p greeting
#i1
#i2.i.i2
#i3.i.i3
#i0.i.i0
.first-level
.second-level
.third-level Hi!
.second-level2
</template>
语法规则很简单, #代表id, .代表class, 缩进代表开始一个新的层级. 本来也考虑过类似Markdwon的写法, 后来发现Markdown还是主要面向文档书写, 没有表达块级标签和层级的机制. 当然了, Markdownn目前非常流行, 方便非专业人士学习, 如果有合适的思路, 还是想做一个接近Markdown的版本.
CSS也是按照类似的原则, 参考gulp-qcss, 实现一个简化版的转换器dev/qcs.js · Onesimu/u.js, 简化了CSS的书写.
<style>
.f2 { w: 154; h: 154; mt: 200; bd: 2 s #F1F1F1; bo: 50%; bgi: url(/static/imgs/logo.png); }
.f4 { fw: bd; f: 48; c: #333; m: 36 a; }
.f1 { w: 158; h: 2; bgi: lg(270deg, #B1B1B1 0%, rgba(213,213,213,.5) 95.59%); o: 0.2; }
.f3 { f: 36; c: #999; mb: 150; }
.f5 { f: 30; c: #999; mt: 30; }
</style>
在命名上, 我也是比较简化的, 大致是一个字母加数字, 本意是用字母区分不同类型的元素, f表示flex, i代表inline-block, e代表element, a代表absolute. 后来发现代码修改几次之后, 基本上就都对应不上了, 因此字母类别只能参考.
很多人建议类名要语义化, 不过在开发实践中, 发现很难去编写和维护语义化的命名, 多数时候名字只是为了绑定样式, 样式能解析, 源码能查找, 就足够了. 甚至于像Tailwind CSS这样的解决方案, 直接在html里书写一大堆预定义类名, 据说还非常流行. 不过我觉得没必要, 首先是不美观, 我觉得还是要保持html的清晰易读, 方便js对dom树进行操作, 其次用简化写法, 书写量也不大, 又不会丢失任何灵活性, 何必都去挤在一起呢.
至于js的书写, 之前也尝试了安装各种babel插件, 体验新语法, 后来发现LiveScript基本包含了我想要的各种功能, 也可以在浏览器中编译运行, 因此先用上了. 感觉的问题是功能太多了, 日常开发用不到那么多新特性, 增加记忆和维护负担, 目前这个项目好像没有活跃开发, 因此考虑自己修改, 不过一个完整的解析器确实不是那么好编写和修改的, 暂时还是通过正则替换进行预处理, 把一些简单的语法改成自己想要的格式. 尤其是一些常用关键字太长, 写多了感觉好啰嗦, 可以替换成短名字, 简化代码.
const qjs = e => {
const dt = {
val: 'const',
fn: 'function',
rn: 'return',
bool: 'boolean',
aw: 'await',
ay: 'async',
ti: 'this',
es: 'else',
ei: 'else if',
ex: 'export',
im: 'import',
del: 'delete',
tof: 'typeof',
sw: 'switch',
de: 'default',
cat: 'catch',
fin: 'finally'
}
return e.e(dt)
}
至此一个页面的基本组成部分就都有了. 至于运行也采用了最简单的方式, 通过hashchange监听hash改变, 从hash中获取页面路径, 加载对应路径下的页面, 转换成标准格式之后添加到页面.
至于之后的页面交互, 采用的方式也很简单, 在js中用类似CSS的语法声明元素和数据的对应关系, 当数据变化时, 根据新数据修改dom元素属性. 而CSS中可以通过属性选择器设置不同的样式. 数据变化也没有用复杂的监听方法, 而是像React或者微信小程序那样, 直接调用特定方法设置数据. 我觉得把函数调用的括号换成赋值的等于号, 并没有给开发带来太大的简化, 却增加了实现者的负担, 并不是一个理想的选择. 如果确实想要对开发者有帮助, 优化语法, 实现更方便的函数调用才是更有用的, 毕竟开发者每天所做的最多的就是调用函数.
有一个可以运行, 可以实现功能的基础结构之后, 就是要考虑怎么建立高层的规范, 简化常用业务开发. 目前业界主要采用的方式是建立组件库, 但是由于没有统一的标准, 组件无法通用. 我也考虑在基础结构的原则之下, 建设一些常用的组件, 按照数据类型对组件进行分类, 比如select, checkbox, 对应的数据结构都是列表, 只不过展现样式不同, 因此希望组件尽可能灵活, 只表达核心的数据逻辑, 样式可以自由选择和定制.
而另一个方向就是低代码, 直接根据业务需要输出产品, 不需要关心组件等实现细节. 这方面目前见到的比较成熟的开源产品是百度的Amis, 也已经在项目中开始使用, 并且在考虑怎么样和整个开发流程结合, 实现完整的低代码开发流程. 这些方向都还在探索之中.
用到的所有代码都已开源在u.js, 目前实现较为简单, 但是也已经用了实际项目中, 开发过程中也会根据业务需要对代码进行调整. 希望能够对大家的开发有帮助.