让JavaScript拥有类似Lambda表达式编程能力的方法


Posted in Javascript onSeptember 12, 2010

但是我后来也跟人说,因为接受的参数太多,所以如果不把智能提示写得很清楚的话,连我自己都常常搞不清楚该怎么用。

不过,接受参数多,除了容易弄错用法以外,还会产生另一个问题,这也是我编写出今天发布的这个东西的原因。
来看一下JS版的页码呈现组件的完全版函数签名:

function pnView( 
currentPage, actionCurrent, 
beginPage, endPage, 
actionBegin, actionEnd, 
currentSiblings, actionCurrentSibling, 
preventFolding, actionFolding, 
endOfBegin, beginOfEnd, 
actionBeginSibling, actionEndSibling 
)

可以看到,这个可以最大幅度进行自定义的完全版函数签名,接受14个参数,而其中有一半——也就是7个参数,是要接受回调函数的。
由此产生的问题就是需要手工写入多次的“function(){}”等字符组合,就像这样:
function ww(s) { document.write(s); } 
function ws(n) { ww(' <A href="#">(' + n + ')</A> '); } 
pnView(14, function (n) { ww(' [' + n + '] ') }, 
1, 27, 
function (n) { ww('<A href="#">|< ' + n + '</A> '); }, function (n) { ww(' <A href="#">' + n + ' >|</A>'); }, 
2, ws, 
1, function () { ww(' ... '); }, 
2, 2, 
ws, ws 
);

当我在网页中测试这个组件的时候我就觉得自己非常想念C#中的Lambda表达式——虽然有些人、有些JS框架会把匿名函数称作“Lambda表达式”,但其实那只相当于C#中的“匿名委托/函数”,而Lambda的(表面)特征是使用简短的语法模式来反映一个(回调)函数/过程(或者说动作)的逻辑,而不被“delegate”或者别的什么关键字分散精力。

出于这样的原因,我编写了这个可以转译JS代码、把一种特定的模式(pattern)翻译成函数定义的模块。
在使用这个模块以后,前面的那个调用可以变成这个样子:

eval(function () { 
var ww = (s, document.write(s)); 
var ws = (n, ww(' <A href="#">(' + n + ')</A> ')); 
pnView(14, (n, ww(' [' + n + '] ')), 
1, 27, 
(n, ww('<A href="#">|< ' + n + '</A> ')), (n, ww(' <A href="#">' + n + ' >|</A>')), 
2, ws, 
1, (0, ww(' ... ')), 
2, 2, 
ws, ws 
); 
}.lamda())();

模块的完整代码如下:
/*! 
L-amda "a-Lambda", a module provides Alternate "Lambda" style programming ability for JavaScript. 
Created By NanaLich. 2010-09-08 
This module is published under WTFPL v2, so you just DO WHAT THE Fxxx YOU WANT TO with it. 
*/ 
!function () { 
function attachEntry(o, a, m) { 
var i, j, n; 
o = [].concat(o); 
//if (!(o instanceof Array)) o = [o]; 
while (i = o.shift()) { 
for (j in a) { 
if (!i[n = a[j]]) i[n] = m; 
} 
} 
} 
var rx0 = /^\s*(0|NaN|null)\s*,$/; 
var rx1 = /([\W]\s*)\((\s*0\s*,|(?:\s*[a-z_$][\w$]*\s*,)+)|"(\\[\s\S]|[^\x22])*"|'(\\[\s\S]|[^\x27])*'/gi; 
var rx2 = /\)/g; 
function rxGetFlags(rx) { // 取出正则表达式的创建选项 
return (rx.global ? 'g' : '') + (rx.ignoreCase ? 'i' : '') + (rx.multiline ? 'm' : ''); 
//return /\/([gim]*)$/.exec('' + rx)[1]; 
} 
attachEntry(RegExp, ['getFlags'], rxGetFlags); 
attachEntry(RegExp.prototype, ['getFlags'], function () { 
return rxGetFlags(this); 
}); function translateLambda(s) { 
if (typeof (s) != 'string') s = '' + s; 
var x = new RegExp(rx1.source, rx1.getFlags()); 
var y = new RegExp(rx2.source, rx2.getFlags()); // 由于firefox、safari等浏览器对全局匹配正则表达式有过度的优化,所以这里采用一种迂回的办法创建不重复的正则表达式实例 
var m, l = 0, r = ''; 
while (m = x.exec(s)) { 
var c = m[0], h, p, q, t; 
switch (c.charAt(0)) { // 判断期待的语法成分 
case '$': 
case ')': 
case ']': 
case '"': 
case "'": 
continue; // 函数传参,跳过 
} 
h = m[2]; 
if (rx0.test(h)) 
h = ''; 
else 
h = h.substr(0, h.length - 1); // 去掉末尾的逗号 
r += s.substring(l, p = m.index); // 在结果字符串上附加之前余留的内容 
y.lastIndex = l = p + c.length; // 从逗号之后开始寻找右括号 
while (q = y.exec(s)) { 
q = q.index; 
try { 
t = 'return ' + s.substring(l, q) + ';'; 
new Function(t); // 语法测试 
//r += c + 'function(' + h + '){ ' + translateLambda(t) + ' }'; // 翻译里面的内容 
r += m[1] + 'function(' + h + '){ ' + translateLambda(t) + ' }'; // 翻译里面的内容 
x.lastIndex = l = q + 1; // 下一次查找从括号之后开始 
break; 
} catch (ex) { } 
} 
if (!q) l = p; // 说明找不到右括号或者有效的代码,直接附加所有匹配的内容 
} 
try { 
r += s.substr(l); 
if (/[\w$]*|"(\\[\s\S]|[^\x22])*"|'(\\[\s\S]|[^\x27])*'/.exec(r)[0] == 'function') // 粗略判断产生的是不是函数(可以应付绝大部分情况) 
r = '0,' + r; // 使用这种“怪”形式可以在所有浏览器(包括IE)中得到预期的效果 
new Function(r); // 语法测试 
return r; 
} catch (ex) { // 失败,返回原文 
return s; 
} 
}; 
var lamdaAliases = ["translateLambda", "lambda", "lamda"]; 
attachEntry([Function, String], lamdaAliases, translateLambda); 
attachEntry([Function.prototype, String.prototype], lamdaAliases, function () { 
return translateLambda(this); 
}); 
} ();

如果和C#中的Lambda表达式相比的话,我的这个模块还是有很多不足的,不过现在这个样子已经让我很满意了,至少我不用再写太多的“function”了。
简单来说,这个模块的规格特性是这样的——
优点:
减少编写代码时“function”的出现次数;
使用可以在一般的JavaScript编辑器中正常编辑的语法模式(pattern),直接写在函数体中不会导致语法错误。
局限性:
在任何时候使用这个模块都必须调用转译方法(“translateLambda”、“lambda”或者“lamda”)和eval函数,无法省略;
如果存在一个函数A,不可能通过对A进行处理来达到转译传递至A的参数的目的(也就是说a.lambda()或者类似的操作并不会让a((x, x * 2))等同于a(function(x){ return x * 2; }));
不能包含表达式之外的任何语句、不能包含使用“;”来分隔的多条语句。
缺点:
连续出现的括号可能会让代码变得难以理解;
任何编辑器都无法为Lambda表达式提供语法高亮;
存在错误地转译现有代码的可能性——这个模块选择进行匹配的模式是在正常的代码中没有实用价值、也通常不会出现的模式,如:(x, x * 2)等价于单纯的x * 2、(0, a.method())等价于单纯的a.method(),所以这个缺点影响到实际代码的可能性无限趋近于0。
以下是几种不当的用法:
1、使用这个模块并不会节省很多代码量的时候:本末倒置。
eval(function(){ // 不仅没减少代码量,反而还增加了 
window.onload = (0, alert('载入完成!')); 
}.lambda());

2、对接受参数的函数进行转译处理:之前已经提到过这种情况。
eval(somefunction.lambda())((x, x.toString(16))); // somefunction可能会产生预料外的结果,而且收到的参数会是x.toString(16)的结果(如果x在此处并没有被定义过,还会产生一个“变量不存在”的异常),而非一个回调函数。

3、为了使用此模块而规避语法检查:
因为使用的是在JavaScript中有效但无实用价值的语法,所以规避语法检查是完全没有必要的。
eval("somefunction((x, x.toString(16)))".lamda()); // 失去了语法检查,可能在运行的时候产生意外

4、在(伪)Lambda中使用过多的操作,甚至多条语句:
在设计这个模块的时候我并没有找出可以使用多条语句且可以通过语法检查的模式(pattern),原因是在Lambda表达式中使用多条语句时,“function”、“return”等几个字符所增加的代码量通常是可以忽略的,这样去使用Lambda表达式本身就属于一种偏离了初衷的做法。
eval(function(){ somefunction((x, y.something(x); return x.toString(16))); }.lamda())(); // 语法错误 
eval(function(){ somefunction((x, y.something(x), x.toString(16))); }.lamda())(); // 容易产生理解上的歧义 
eval(function(){ somefunction((x, ++x)); }.lamda())(); // 简单的表达式可以被接受

最佳使用场合:
现在很多人写JavaScript的时候喜欢把自己的代码都写在一个闭包里面,这样可以避免全局作用域污染问题,就像这样:
(function(){ 
// 所有的代码都放在这里 
})();

——而这种“大”闭包正好是使用本模块的最佳场合:
eval(function(){ // 括号前增加eval 
// 所有的代码都放在这里 
}.lamda())(); // 括号里增加.lamda()

昨天codeplex抽疯,代码和release上传总出错。再考虑到这个模块的使用场合比较有限,不适合缺乏JavaScript经验的人使用,所以不另外提供源代码打包下载——有需要的话请直接从文中复制。
Javascript 相关文章推荐
javascript编程起步(第五课)
Jan 10 Javascript
javaScript 读取和设置文档元素的样式属性
Apr 14 Javascript
基于Jquery的将DropDownlist的选中值赋给label的实现代码
May 06 Javascript
js简单判断移动端系统的方法
Feb 25 Javascript
jQuery针对input的class属性写了多个值情况下的选择方法
Jun 03 Javascript
js实现微博发布小功能
Jan 12 Javascript
vue 2.0组件与v-model详解
Mar 27 Javascript
js实现随机数字字母验证码
Jun 19 Javascript
ES6中javascript实现函数绑定及类的事件绑定功能详解
Nov 08 Javascript
小程序实现抽奖动画
Apr 16 Javascript
vue项目中常见问题及解决方案(推荐)
Oct 21 Javascript
openLayer4实现动态改变标注图标
Aug 17 Javascript
JQuery FlexiGrid的asp.net完美解决方案 dotNetFlexGrid-.Net原生的异步表格控件
Sep 12 #Javascript
Asp.net下使用Jquery Ajax传送和接收DataTable的代码
Sep 12 #Javascript
Asp.net下利用Jquery Ajax实现用户注册检测(验证用户名是否存)
Sep 12 #Javascript
jQuery EasyUI 的EasyLoader功能介绍
Sep 12 #Javascript
jQuery UI 应用不同Theme的办法
Sep 12 #Javascript
基于jquery的表格排序
Sep 11 #Javascript
javascript多种数据类型表格排序代码分析
Sep 11 #Javascript
You might like
PHP mb_convert_encoding 获取字符串编码类型实现代码
2009/04/26 PHP
如何使用Strace调试工具
2013/06/03 PHP
php常用的url处理函数总结
2014/11/19 PHP
PHP解密Unicode及Escape加密字符串
2015/05/17 PHP
php数字运算验证码的实现代码
2015/07/30 PHP
php加密解密字符串示例
2016/10/13 PHP
layui框架实现文件上传及TP3.2.3(thinkPHP)对上传文件进行后台处理操作示例
2018/05/12 PHP
jquery 表单下所有元素的隐藏
2009/07/25 Javascript
jquery中文乱码的多种解决方法
2013/06/21 Javascript
JS cookie中文乱码解决方法
2014/01/28 Javascript
七个很有意思的PHP函数
2014/05/12 Javascript
Javascript学习笔记之函数篇(四):arguments 对象
2014/11/23 Javascript
浅谈Javascript数组(推荐)
2016/05/17 Javascript
原生js仿jquery一些常用方法(必看篇)
2016/09/20 Javascript
关于Vue在ie10下空白页的debug小结
2018/05/02 Javascript
微信打开网址添加在浏览器中打开提示的办法
2019/05/20 Javascript
Vue实现日历小插件
2019/06/26 Javascript
vue3.0封装轮播图组件的步骤
2021/03/04 Vue.js
[01:03:37]Secret vs VGJ.S Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
linux下python抓屏实现方法
2015/05/22 Python
在Django中创建动态视图的教程
2015/07/15 Python
python中实现指定时间调用函数示例代码
2017/09/08 Python
Python中的defaultdict与__missing__()使用介绍
2018/02/03 Python
python通过文本在一个图中画多条线的实例
2020/02/21 Python
在matplotlib中改变figure的布局和大小实例
2020/04/23 Python
python interpolate插值实例
2020/07/06 Python
css3实现背景动态渐变效果
2019/12/10 HTML / CSS
韩国著名的在线综合购物网站:Akmall
2016/08/07 全球购物
英语简历自我评价
2014/01/26 职场文书
税务干部个人整改措施思想汇报
2014/10/10 职场文书
商务司机岗位职责
2015/04/10 职场文书
一个独生女的故事观后感
2015/06/04 职场文书
大学生暑期实践报告
2015/07/13 职场文书
保姆聘用合同
2015/09/21 职场文书
CSS3 制作的书本翻页特效
2021/04/13 HTML / CSS
巧用 -webkit-box-reflect 倒影实现各类动效(小结)
2021/04/22 HTML / CSS