前端降维术
这里说的前端,指的是传统意义上的前端,即包括HTML、css、javascript三种技术手段的前端。“降维”,顾名思义,就是降低维度,使问题涉及的因素减少,变得更加容易解决,而且解决方案较为简单可靠。
在著名的刘慈欣的科幻小说《三体》里,”降维攻击“就作为一种常见于宇宙高级文明攻击低级文明的手段。比如,生活在四维世界的人们,他们是用”降维攻击“对付三位世界的人们。虽然与此同时,他们自身也会降维,但是从四维降低到三维,他们依然是活着的;而三维世界的人们,则被无情地抛入二维世界,不可逆转地失去生命和一切。不过,到前端这一块技术领域,还不至于这么残酷,我们只是想通过降维的手段使问题简化。
通常,由于开发人员对前端各项技术的细节了解不够透彻时,会提供需要综合运用多种技术手段解决的方案。而这几种方案的组合,彼此依赖,互相耦合,往往理解起来较为晦涩难懂,也不具备较好的可维护性和可扩展性。前端的降维术,则旨在通过对某一项技术更加彻底和升华地使用,显著减少对另一项技术的依赖,从而提供更简单易扩展的解决方案。比如,某些开发人员对于js的使用不够熟练,但由于对html和css的理解也不够深厚,很容易通过js去组装页面元素,或者拼接表单数据,然后提交到后端页面实现页面的保存。这样的技术方案,后期如果需要修改界面展示效果,或者对表单交互效果进行调整,一方面还得通过js调整达成效果,另一方面则容易导致不容易发现的bug产生。如果我们掌握一定的技巧,完全可以巧妙地避开对js的使用,使问题只依赖于html和css两个维度,就可以使问题得以解决。
排序
网站常用到的功能就是排序,一般的思路大抵是在排序的子元素上定义一个属性,将需要排序的id放到上面,保存排序的时候,通过js依次将这些属性id提取出来,合并成一个字符串,提交给后端处理处理。
首先,设定一下开发环境:使用jQuery,且假设有一个plugin,名字叫sortable,设置后可以对元素启用排序支持。
方案一
前端代码如下:
<ol id="sortList">
<li data-id="1000">1</li>
<li data-id="1001">2</li>
<li data-id="1002">3</li>
</ol>
<input value="保存排序" id="btnSave"/>
<script>
$('#sortList').sortable();
$('#btnSave').click(function() {
var ids = [];
$('#sortList li').each(function() {
ids.push($(this).data('id'));
});
$.ajax(
type: 'post',
url: '/data/sort',
data: 'ids=' + ids.join(',')
success: function () {
alert('保存成功');
}
);
return false;
})
</script>服务端的代码大抵如下:
$ids = explode(',', $_POST['ids']);
DataModel::sortIds($ids);在这个方案里边,需要我们掌握html元素自定义属性以及js的数组的基本用法。而一些对js数组不熟的同学,可能还会想到通过各个id后面拼接逗号构建id的字符串。
var ids = '';
$('sortList li').each(function() {
ids = ids + $(this).data('id') + ',';
});甚至,为了去掉拼接的字符串中最后的“,”,他们需要掌握一些额外的技巧:
var ids = '';
$('sortList li').each(function(i) {
if (i == 0) {
ids = $(this).data('id');
} else {
ids = ids + ',' + $(this).data('id');
}
});然而,对于这类需要将数据保存到服务端的交互,使用form和基础表单控件来做,根本就不需要掌握自定义属性以及js数组算法等知识。
方案二
改进后的前端代码:
<form method="post" action="/data/sort" id="formSort">
<ol id="sortList">
<li><input type="hidden" value="1" name="id[]"/>1</li>
<li><input type="hidden" value="2" name="id[]"/>2</li>
<li><input type="hidden" value="3" name="id[]"/>3</li>
</ol>
<input value="保存排序" type="submit"/>
</form>
<script>
$('#sortList').sortable();
$('#formSort').submit(function() {
$.ajax({
type: this.method,
url: this.action,
data: $(this).serialize(),
success: function() {
alert('保存成功');
}
})
return false;
});
</script>改进后的后端代码:
DataModal::sortIds($_POST['id']);乍一看,改进的方案的代码量并没有减少,甚至还有增多。但这样的方案,却只需要掌握基本的html用法,也没有涉及到js数组的用法和自定义属性的获取。这些基础的知识点,是大部分web开发者所熟知的,而且是很稳定的,不存在任何不稳定性。
再对比一下服务端代码,第一种需要掌握字符串分隔成数组的用法,而第二种,php自动将name为id[]的表单项自动转化成了数组,不再需要额外处理了。
再从扩展性来看,第一种方案的click方法里边,存在的外部变量有:$('#sortList li')这里边的元素#sortList以及li子元素的元素名称、自定义属性的名称data-id、ajax提交的type和url、以及回调函数success。而第二种方案,表单提交的这块代码,则只有回调函数是外部变量。甚至,我们可以将这样的表单提交行为封装成一个jQuery组件。
jQuery.fn.ajaxform = function(callback) {
this.submit(function() {
type: this.method,
url: this.action,
data: $(this).serialize(),
success: callback
});
};这个组件,作用就是将form表单提交转化为ajax提交。在这个基础上,第二种方案的前端部分可以简化为:
<form method="post" action="/data/sort" id="formSort">
<ol id="sortList">
<li><input type="hidden" value="1" name="id[]"/>1</li>
<li><input type="hidden" value="2" name="id[]"/>2</li>
<li><input type="hidden" value="3" name="id[]"/>3</li>
</ol>
<input value="保存排序" type="submit"/>
</form>
<script>
$('#sortList').sortable();
$('#formSort').ajaxform(function(){
alert('提交成功');
});
</script>而要想将第一种方案的交互模式也组件化,则有着更大的困难,而且涉及的参数过多,即使组件化,使用起来的体验也不一定爽。
小结
对于前后端数据交互的场景,尽量使用form表单来处理,结合对隐藏域,name属性的数组化(及name="xxx[]"的模式)等,就能对表单处理异步化的应用,就能显著降低对js和html更多额外知识的需求,提升功能的稳定性和可扩展性。