类库,框架,层出不穷。
Web前端,似乎经历着历史上已经发生过很多次的事情。
很多人找不到自己的努力方向了,
Backbone,Ember,AngularJS,ReactJS,...
到底该学哪个?
一阵阵枪林弹雨,扫的我们抬不起头来,
各大公司为了抢占资源,霸占程序员的时间,
总是宣传它们的东西是最好的。
其实,我们不必太较真,
我们只需要抓住已有知识的精髓即可,
举一反三,用创造力改变世界。
编程,就好像古时候写诗一样。
伟大的诗人,不一定会背所有的诗词。
他们的伟大之处在于借鉴。
学习别人好的思想,拿来灵活运用,恰如其分的表达,
就足够了,
至于是否伟大,让后人去说吧。
比如说,jQuery过时了吗,
前端各种MVC框架,MVVM框架,说的比什么都好听。
好像不用框架就没办法生存似的。
作为一个学过辩证法的人,
我们马上就意识到了,这可能是有人在故意炒作它。
另一方面,我们也知道这些框架确实也有可取之处。
要做到不骄不躁,
只有强大自身,这一个办法了。
慢慢的,我们就发现了,
框架也只不过是别人活学活用的成果罢了。
扩展性
我们来学习一下jQuery的设计思想。
JavaScript经常做的,
大概就是选取页面的元素,然后做一些事情了。
而怎样选取元素呢,CSS已经给我们提供办法了,
那就是CSS选择器selector,
这样选中的其实是满足条件的HTML元素集合。
如果能在集合上定义自己的操作就好了,
我们就可以把选中的集合整体看做一个对象,
调用这个对象的方法。
这其实就是面向对象的思想呀。
有了这个想法之后,我们先设计测试用例。
也就是说,我们要考虑,假如我们已经有了某个类库,
我们将如何使用它呢?
这其实是编程活动中,最具创造力的环节。
也是自顶向下编程,或者测试驱动开发思想的一种应用。
jQuery作者是这样设计的,
类库的使用者接口如下,
$(selector).method(parameter);
类库的开发者接口如下,
扩展静态方法,
$.extend({
staticMethod:function(){}
});
扩展实例方法,
$.prototype.extend({
instanceMethod:function(){}
});
这种思路,很值得借鉴,
它说明,最灵活的设计,是那些只包含扩展方法的设计。
类库中所有的功能,都是用统一的方式扩展出来的。
类库自带的功能越多,
以后修改这些功能的可能性就越大。
实现
至于如何实现,网上可以找到很多教程,
下面只写了一下我自己的实现。
(function(global,document){
jQuery.prototype=InstanceCreation.prototype;
jQuery.extend=jQuery.prototype.extend=extend;
function jQuery(selector){
return new InstanceCreation(selector);
}
function InstanceCreation(selector){
var instance=this;
instance[0]=document.querySelector(selector);
return this;
}
function extend(material){
var depository=this;
for(var property in material){
if(!material.hasOwnProperty(property)){
continue;
}
depository[property]=material[property];
}
return this;
};
//export:
global.$=jQuery;
}(window,document));
对实现不详细描述,还有其他原因,
因为,实现是灵活的,而用户接口是用来表达思想的。
任何一种实现都能达到目的,
但并非所有人都能设计出具有表现力的用户接口。
贴到这里,而不是放置Github超链接,
是为了有个直观的印象,jQuery核心其实很小。
所有其他功能都是扩展出来的。
插件
类似jQuery这样,
提供基础核心,再提供对核心的扩展方式,
称为插件式开发。
我们只需要使用
$.prototype.extend({
pluginName:function(){}
});
\((selector)就有了一个pluginName方法了。\)(selector).pluginName
正因为有这么便捷的功能,
jQuery插件的数量才惊人的多。
我们还可以把页面切分为几个部分,
每个部分是一个插件对象,只对这一块元素进行操作,
这符合面向对象的思想。
$(container1).pluginName1(...);
$(container1).pluginName2(...);
我们把问题简化了,
对整个页面的控制,转换成对这些自定义插件的控制了。
缺点和改进
经过一段时间使用后,
我们发现jQuery插件有一个缺点,
$(container).pluginName(...);
一个插件的所有操作,都必须放到同一个pluginName方法中。
如果我们想初始化和取值,就不得不这样操作,
$(container).pluginName('init',data);
$(container).pluginName('getValue');
后果就是pluginName这个函数中,我们就要写很多switch语句。
$.prototype.extend({
pluginName:function(operationName,arg0){
switch(operationName){
case 'init':
//...
break;
case 'getValue':
//...
break;
//...
}
}
});
还算清晰,
不过最致命的就是,如果我们想添加一个对插件的操作,
我们必须去改同一个文件,在switch中添加case子句。
这太烦人了。
而且,取data的值也比较麻烦。
按理说它是init操作的第1个参数,
但是它实际上是作为pluginName的第2个参数传递的。
我们为什么不能设计一个工具,来改变现状呢?
让我们踏上程序设计之路吧。
假如我们已经设计好了,想这样用。pluginManager.extend
(function($){
$.pluginManager.extend('pluginName',{
init:initPlugin
});
function initPlugin(){
var $selector=this;
//arguments[0]===data
//...
}
}(jQuery));
(function($){
$.pluginManager.extend('pluginName',{
getValue:getValueFromPlugin
});
function getValueFromPlugin(){
var $selector=this;
//...
}
}(jQuery));
我们避免了为插件添加操作的麻烦,
还把data参数的位置调节好了。
并且将插件操作的具体实现,分离到了不同的文件中。
这能实现吗?
可以的,我已经做好了。
但是,实现并不重要,关键是思想。
持续改进
并不是技术本身想要更新,
是创新的业务需要,促进了创新的技术出现,
不适应性越强烈,改进工具的可能性就越大。
随着时间的发展,
我们发现了另一个问题。
插件在使用过程中,可能会设置一些缺省值,
例如:初始化,
$(container).pluginName('init',data);
$(container).pluginName('init');
都是有可能的。
每个插件中都单独处理缺省值,是很啰嗦的事情。
function initPlugin(){
var $selector=this,
data=arguments[0]||'Hello';
//... }
啰嗦也就罢了,
最不能容忍的是,代码中出现了硬编码。
'Hello'是业务数据,跟插件本身无关。
还记得吗,
类库自带的功能越多,
以后修改这些功能的可能性就越大。
所以,我们得想一个办法把他们分离出去。pluginManager.filter
(function($){
$.pluginManager.extend('pluginName',{
init:initPlugin
});
function initPlugin(){
var $selector=this,
data=arguments[0];
//...
}
}(jQuery));
(function($){
$.pluginManager.filter('pluginName',{
init:filterInit
});
function filterInit(){
var $selector=this;
return [
arguments[0]||'Hello'
];
}
}(jQuery));
因此,我们增加了一层,
作为插件核心的开发者,接受所有需要配置的参数。
作为实际插件的使用者,为了避免多次配置,
可以一次性为插件设置默认值。
filter将pluginName插件的init操作实际调用的实参,
转换一下,传递给了核心。
这样的话,插件核心的开发者,就可以提供更松散的功能,
不用假设实际的使用情况,
实际和具体业务相关的处理,放到了filter里面。
另外,我们看到filter是很灵活的,
它可以将任何实参,以任何方式转换成核心需要的实参,
哪怕核心需要的实参是一个函数。
这能实现吗?
也是可以的,我也已经做好了。
但是,实现并不重要,关键是思想。
结语
我们分析了jQuery的设计思想,
并拿来随机应变,设计了自己的插件管理器。
使用框架了吗?
并没有。
连jQuery核心也是自己写的。
但是,在思考过程中,无处不闪烁着许多优秀框架的思想。
因此,我们也应该知道了,
插件管理器本身,并没有什么,
关键是思考过程,以及我们想到什么样的办法解决实际问题。
我们的所能掌握的知识,总是不够的。
但这不影响我们去创造好用的工具。
比如,为了借鉴MVVM的思想,不必使用knockout.js,
我们可以自己写一个bindTemplate来进行模板的读写操作。
设置值
$(container).bindTemplate('setData',{
attr:'data-model',
data:json,
set:setFieldValue
});
读取值
$(container).bindTemplate('getData',{
attr:'data-model',
get:getFieldValue
});
我们可以把json对象,绑定到HTML对象上,再从HTML中取回同样结构的json对象。
而与HTML元素的排列方式无关。
json=[
0,
{
val:1
}
];
<span data-model="[0]"></span>
<div>
<span data-model="[1].val"></span>
</div>
setFieldValue与getFieldValue我设置成了函数,
用来询问对于某个HTML元素,如何绑定值,如何获取值,
以获得最大的灵活性。
并且,利用$.pluginManager.filter,接口可以简化成,
$(container).bindTemplate('setData',{
data:json
});
$(container).bindTemplate('getData');
源代码如下:
还好Github可以控制版本,如果以后文件更改了,还能找到今天的版本。
于是,阳光还是那么灿烂,日子还是那么美好。
关键是生活的方式。