`
zhouyrt
  • 浏览: 1159245 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JavaScript中链式调用之研习

阅读更多

 

方法链一般适合对一个对象进行连续操作(集中在一句代码)。一定程度上可以减少代码量,缺点是它占用了函数的返回值。

 

一、方法体内返回对象实例自身(this)

function ClassA(){
	this.prop1 = null;
	this.prop2 = null;
	this.prop3 = null;
}
ClassA.prototype = {
	method1 : function(p1){
		this.prop1 = p1;
		return this;
	},
	method2 : function(p2){
		this.prop2 = p2;
		return this;
	},
	method3 : function(p3){
		this.prop3 = p3;
		return this;
	}
}

 

定义了function/类ClassA。有三个属性/字段prop1,prop2,prop3,三个方法methed1,method2,method3分别设置prop1,prop2,prop3。

链式调用如下:

var obj = new ClassA();
obj.method1(1).method2(2).method(3); // obj -> prop1=1,prop2=2,prop3=3

 

可以看到对obj进行了连续三次操作,只要愿意ClassA的N多方法都这样定义,调用链会一直延续。


该方式缺点是链方法唯一地绑定于一种对象类型(ClaaaA),按这种方式实现链式操作,每定义一个类,其方法体中都要返回this。第二种方式可以解决这个问题。

 

二、对象传入后每次调用返回函数自身

/**
 * chain 精简版
 * @param {Object} obj
 */
function chain(obj){
	return function(){
		var Self = arguments.callee; Self.obj = obj;
		if(arguments.length==0){
			return Self.obj;
		} 
		Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1));
		return Self;
	}
}

//定义的function/类ClassB
function ClassB(){
	this.prop1 = null;
	this.prop2 = null;
	this.prop3 = null;
}
ClassB.prototype = {
	method1 : function(p1){
		this.prop1 = p1;
	},
	method2 : function(p2){
		this.prop2 = p2;
	},
	method3 : function(p3){
		this.prop3 = p3;
	}
}
 

注意ClassB的method1,method2,method3中不再返回this了。

 

链式调用如下:

var obj = new ClassB();
chain(obj)('method1',4)('method2',5)('method3',6); // obj -> prop1=4,prop2=5,prop3=6

 

第一种方式3次调用后返回了对象自身,这里使用一个空"()"取回对象

// result -> prop1=4,prop2=5,prop3=6
var result = chain(obj)('method1',4)('method2',5)('method3',6)();

 

这种方式写类时方法体中无须返回this,且可以对任何对象进行链式调用。

从写法上总结下两种的调用方式:

obj
	.method1(arg1)
	.method2(arg2)
	.method3(arg3)
	...
	
chain(obj)
	(method1,arg1)
	(method2,arg2)
	(method3,arg3)
	...

 

最后,感谢沐海,我是从wee库中获取以上灵感的。

 

分享到:
评论
21 楼 deepthink 2010-09-07  
补充一下,再jquery的weight机制中是默认使用chain的调用样式
举个例子可能说的更加明白:

var tabs = $("#tabs").tabs().tabs("switchTo:,'#tab-1').tabs("method1");

tabs.tabs("switchTo:,'#tab-2').tabs("method1");

weight对象,每一个方法返回的都是 tabs本身,这是LZ所说的第二种方法样式
20 楼 deepthink 2010-09-07  
笨笨狗 写道
另外,说起这个chaing call,不得不推荐dustin diaz的这篇文章:
Asynchronous method queue chaining in JavaScript
http://www.dustindiaz.com/async-method-queues/

异步链式调用更为有趣,也更为实用:)



这种调用方法很久以前用过,但是不是用的jquery,是用的prototype
19 楼 deepthink 2010-09-07  
第一种方法我经常用,有的时候在JAVA中也大量使用,比如构建SQL,URL,或者JSON,或者DataModel 比较繁琐的时候,一般会用
Hql hql = new Hql("from UserInfo o where o.xx = ? ",xx).append("and o.userName = ? and o.yy = ? ",userName,yy) ;
return orm().findFirst(hql);


new UrlEngine(request).remove("arg1").append("arg2",arg2).append("arg3",arg3).toString() ;


new JSONMap().append("k1",val1).appendList("list1").add("list item1").parentMap()


new AppDataModel().setEntity(entity).append("user",user).setRows(entity.getDepts()).setPager(pager);



18 楼 zhouyrt 2010-09-03  
To 笨笨狗,

探讨,学习,进步。

谢谢!
17 楼 笨笨狗 2010-09-03  
另外,说起这个chaing call,不得不推荐dustin diaz的这篇文章:
Asynchronous method queue chaining in JavaScript
http://www.dustindiaz.com/async-method-queues/

异步链式调用更为有趣,也更为实用:)
16 楼 笨笨狗 2010-09-03  
zhouyrt 写道

To 笨笨狗

首先,谢谢您给我戴上“降低代码可读性的典型反面教材”的帽子。既然是教材,虽然是反面的,仍然可以给旁人以警惕作用。

其次,我想说的是

1,代码可读性与精简的代码并不必然关系,可以慢慢多琢磨下。

2, 代码可读性与代码风格也不必然关系,多数喜欢的可能是过程式/面向对象式,为什么一尝试函数式就那么不习惯,不喜欢。

难道这样写才算“提高代码可读性”
if(a){
fn1();
}else{
fn2();
}
这样写就“降低代码可读性”
a ? fn1() : fn2();


3, 我承认我们应该写更良好风格的代码,我也曾经深受自己代码风格不良好之苦并幡然醒悟,但上面这个问题的本质,真的是追求更具可读性的代码风格吗?

顺便提下,其实回复 clue,tangjikey,ccyingfu时也说了附件中有chain的“易读版”,“最易读版”。这里贴出

/**
* chain 最易读版
* @param {Object} obj
*/
function singleChain(obj){
function chain(){
if (arguments.length == 0){
return chain.obj;
}
var methodName = arguments[0], methodArgs = [].slice.call(arguments,1);
chain.obj[methodName].apply(chain.obj,methodArgs);
return chain;
}
chain.obj = obj;
return chain;
}

组织代码的风格千千万万,按需权衡。

最后,感谢您的见解!




楼主不必发火,我没有给你带任何帽子的目的:)其实我说说的“可读性”是针对api的使用者,而不是针对api的具体实现,也就是说我们的关注点在于相比
chain(obj)(method1,arg1)(method2,arg2)(method3,arg3) 

下面这种风格
obj.method1(arg1).method2(arg2).method3(arg3) 

更容易阅读,也更容易理解,不会打断读者的思维,不是么?

当然,话说回来,对于初学者来说,多多了解不同的实现方式那是非常必要的,这方面的推荐去研究Prototype.js的源码,里面对高阶函数的运用可以称得上“炉火纯青”,另外,它的OO类模拟机制是非常新颖的,真让人眼前一亮:)
15 楼 zhouyrt 2010-09-03  
笨笨狗 写道
其实,这是一个降低代码可读性的典型反面教材……
如果嫌在每个方法最后返回this是重复代码,那大可以实现一个wrap函数,用AOP的思路来对需要链式调用的方法进行包装:
function chain(obj, methods){
    for(var i=0; i<methods.length; i++){
        var m = methods[i];
        obj[m] = (function(old){
            return function(){
                old.apply(obj, arguments);
                return obj;
            }
        })(obj[m])
    }
}

有了上面的包装函数,就可以这样使用:
function Test(){}

Test.prototype = {
   methodA: function(){
       alert('methodA');
   },
   methodB: function(arg){
       alert(arg);
   }
}

var test = new Test();
//声明需要链式调用的方法
chain(test, ['methodA', 'methodB']);
//try it:)
test.methodB('chain').methodA()

抛砖引玉,你还可以让chain去修改构造函数(比如这里的Test)的prototype,这样只需要设置一次,所有的实例都具有链式调用的能力了,是不是和rails中的dsl有点类似呢?呵呵……

总之,我们在写代码或者设计API的时候,首要的原则就是要保证代码的可读性,SICP里有句话说的好:“程序是写给人看的,机器顺便执行”。


To 笨笨狗

首先,谢谢您给我戴上“降低代码可读性的典型反面教材”的帽子。既然是教材,虽然是反面的,仍然可以给旁人以警惕作用。

其次,我想说的是

1,代码可读性与精简的代码并不必然关系,可以慢慢多琢磨下。

2, 代码可读性与代码风格也不必然关系,多数喜欢的可能是过程式/面向对象式,为什么一尝试函数式就那么不习惯,不喜欢。

难道这样写才算“提高代码可读性”
if(a){
fn1();
}else{
fn2();
}
这样写就“降低代码可读性”
a ? fn1() : fn2();


3, 我承认我们应该写更良好风格的代码,我也曾经深受自己代码风格不良好之苦并幡然醒悟,但上面这个问题的本质,真的是追求更具可读性的代码风格吗?

顺便提下,其实回复 clue,tangjikey,ccyingfu时也说了附件中有chain的“易读版”,“最易读版”。这里贴出

/**
* chain 最易读版
* @param {Object} obj
*/
function singleChain(obj){
function chain(){
if (arguments.length == 0){
return chain.obj;
}
var methodName = arguments[0], methodArgs = [].slice.call(arguments,1);
chain.obj[methodName].apply(chain.obj,methodArgs);
return chain;
}
chain.obj = obj;
return chain;
}

组织代码的风格千千万万,按需权衡。

最后,感谢您的见解!

14 楼 笨笨狗 2010-09-02  
其实,这是一个降低代码可读性的典型反面教材……
如果嫌在每个方法最后返回this是重复代码,那大可以实现一个wrap函数,用AOP的思路来对需要链式调用的方法进行包装:
function chain(obj, methods){
    for(var i=0; i<methods.length; i++){
        var m = methods[i];
        obj[m] = (function(old){
            return function(){
                old.apply(obj, arguments);
                return obj;
            }
        })(obj[m])
    }
}

有了上面的包装函数,就可以这样使用:
function Test(){}

Test.prototype = {
   methodA: function(){
       alert('methodA');
   },
   methodB: function(arg){
       alert(arg);
   }
}

var test = new Test();
//声明需要链式调用的方法
chain(test, ['methodA', 'methodB']);
//try it:)
test.methodB('chain').methodA()

抛砖引玉,你还可以让chain去修改构造函数(比如这里的Test)的prototype,这样只需要设置一次,所有的实例都具有链式调用的能力了,是不是和rails中的dsl有点类似呢?呵呵……

总之,我们在写代码或者设计API的时候,首要的原则就是要保证代码的可读性,SICP里有句话说的好:“程序是写给人看的,机器顺便执行”。
13 楼 lz12366 2010-09-02  
费解啊   发现自己js学的还是不好
12 楼 icedblog 2010-09-02  
挺巧妙的~

但看上去有点繁琐
11 楼 libmw 2010-08-31  
我来写个小小的注释吧
/**
* 精简版
* @param {Object} obj
*/
function chain(obj){
//执行chain直接返回一个方法(类),这样执行chain(obj)('method1',4)就相当于执行这个方法,参数是('method1',4)
return function(){
//定义Self = 本方法的调用者
var Self = arguments.callee;
//给调用者一个属性 即obj参数,这样每次链式调用的时候就可以获得最初的那个obj,而这个obj正好是被链式调用的那个方法,这里是classB
Self.obj = obj;
//一个参数都没有,这是链式调用结束的时候发生的事情,即:...('method3',6)()这里的最后一个括号,此时返回最初的那个obj,这里是classB
if(arguments.length==0){
return Self.obj;
}
/*执行Self.obj[arguments[0]]
比如我们第一次链式调用的时候:('method1',4)
这里我们通过 Self.obj[arguments[0]] 取得了method1方法,
Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1)); 代表把Self.obj[arguments[0]]当做Self.obj的一个方法执行,
参数是后面的[].slice.call(arguments,1)
[].slice.call(arguments,1) 截取arguments数组,从索引第一个截取到最后
*/
Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1));
//返回Self
return Self;
}
}
function ClassB(){
this.prop1 = null;
this.prop2 = null;
this.prop3 = null;
}
ClassB.prototype = {
method1 : function(p1){
this.prop1 = p1;
},
method2 : function(p2){
this.prop2 = p2;
},
method3 : function(p3){
this.prop3 = p3;
}
}
var obj = new ClassB();
chain(obj)('method1',4)('method2',5)('method3',6); // obj -> prop1=4,prop2=5,prop3=6
var result = chain(obj)('method1',4)('method2',5)('method3',6)(); // result -> prop1=4,prop2=5,prop3=6
10 楼 goddkiller 2010-08-30  
<div class="quote_title">clue 写道</div>
<div class="quote_div">
<div class="quote_title">zhouyrt 写道</div>
<div class="quote_div">
<p> </p>
<h3>
二、对象传入后每次调用返回函数自身</h3>
<p> </p>
<pre name="code" class="js">/**
* chain 精简版
* @param {Object} obj
*/
function chain(obj){
return function(){
var Self = arguments.callee; Self.obj = obj;
if(arguments.length==0){
return Self.obj;
}
Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1));
return Self;
}
}
</pre>
 
<p> </p>
</div>
<p> </p>
<p>第二种方式还是第一次见,很巧妙的用法</p>
<p> </p>
<p>不过可以简化一下,不使用callee,直接利用闭包:</p>
<pre name="code" class="js">function chain(obj){
var fn = function(method){
if(arguments.length &lt;= 0){
return fn;
}
var args = Array.prototype.slice.call(arguments, 1);
obj[method].apply(obj, args);
return fn;
}
return fn;
}</pre>
<p> PS:那个[].slice的用法还真是。。。够短,不过总觉得凭白无故创建一个数组只为借它的一个方法用用,有点……</p>
</div>
<p>可以Array.prototype.slice......</p>
9 楼 蔡华江 2010-08-30  
以前写个一个invoke方法,很是类似于第二个方法,只是一个返回的运算结果,而一个是调用对象callee。
楼主方法在链式调用功能更强大,更适合于无返回参数的运算
8 楼 ghyghoo8 2010-08-28  
这个……理解上有难度。。
7 楼 zhouyrt 2010-08-28  
To: clue,tangjikey,ccyingfu

附件中有“易读版”,“最易读版”的chain。
6 楼 ccyingfu 2010-08-28  
我在第二种方式上解理起来.....表示压力很大。
我经常用的是第一种。
5 楼 tangjikey 2010-08-27  
第一个还好理解,第二个Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1));   这句直接看晕了。
4 楼 zhouyrt 2010-08-27  
cloudgamer 写道
使用起来能更舒服就好了
例如
var result = chain(obj).method1(4).method2(5).method3(6);
要实现估计要对obj的方法进行重构 还要判断方法本身有没有return


谢.
3 楼 cloudgamer 2010-08-27  
使用起来能更舒服就好了
例如
var result = chain(obj).method1(4).method2(5).method3(6);
要实现估计要对obj的方法进行重构 还要判断方法本身有没有return
2 楼 clue 2010-08-27  
<div class="quote_title">zhouyrt 写道</div>
<div class="quote_div">
<p> </p>
<h3>
二、对象传入后每次调用返回函数自身</h3>
<p> </p>
<pre name="code" class="js">/**
* chain 精简版
* @param {Object} obj
*/
function chain(obj){
return function(){
var Self = arguments.callee; Self.obj = obj;
if(arguments.length==0){
return Self.obj;
}
Self.obj[arguments[0]].apply(Self.obj,[].slice.call(arguments,1));
return Self;
}
}
</pre>
 
<p> </p>
</div>
<p> </p>
<p>第二种方式还是第一次见,很巧妙的用法</p>
<p> </p>
<p>不过可以简化一下,不使用callee,直接利用闭包:</p>
<pre name="code" class="js">function chain(obj){
var fn = function(method){
if(arguments.length &lt;= 0){
return fn;
}
var args = Array.prototype.slice.call(arguments, 1);
obj[method].apply(obj, args);
return fn;
}
return fn;
}</pre>
<p> PS:那个[].slice的用法还真是。。。够短,不过总觉得凭白无故创建一个数组只为借它的一个方法用用,有点……</p>

相关推荐

    电子商务之价格优化算法:梯度下降:机器学习在价格优化中的角色.docx

    电子商务之价格优化算法:梯度下降:机器学习在价格优化中的角色.docx

    ToadforOracle与Oracle数据库版本兼容性教程.docx

    ToadforOracle与Oracle数据库版本兼容性教程.docx

    browser360-cn-stable-13.3.1016.4-1-amd64.deb

    360浏览器银河麒麟版 for X86 适配兆芯 / 海光 / intel / AMD CPU

    基于React.js和Material-UI个人作品集网站模板(附源码+说明文档).zip

    使用React.js构建,提供多种主题可供选择,并且易于定制。该项目旨在帮助开发者和自由职业者创建自己的个性化投资组合。 主要功能点 多种主题可供选择,包括绿色、黑白、蓝色、红色、橙色、紫色、粉色和黄色 易于定制,可以在src/data文件夹中更新个人信息 包含主页、关于、简历、教育、技能、经验、项目、成就、服务、推荐信、博客和联系等多个部分 支持通过Google表单收集联系信息 提供SEO优化建议 支持多种部署方式,如Netlify、Firebase、Heroku和GitHub Pages 技术栈主要 React.js Material-UI Axios React-fast-marquee React-helmet React-icons React-reveal React-router-dom React-router-hash-link React-slick Slick-carousel Validator

    中小型企业财务管理系统 SSM毕业设计 附带论文.zip

    中小型企业财务管理系统 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B

    apsw-3.38.5.post1-cp39-cp39-win_amd64.whl.rar

    python whl离线安装包 pip安装失败可以尝试使用whl离线安装包安装 第一步 下载whl文件,注意需要与python版本配套 python版本号、32位64位、arm或amd64均有区别 第二步 使用pip install XXXXX.whl 命令安装,如果whl路径不在cmd窗口当前目录下,需要带上路径 WHL文件是以Wheel格式保存的Python安装包, Wheel是Python发行版的标准内置包格式。 在本质上是一个压缩包,WHL文件中包含了Python安装的py文件和元数据,以及经过编译的pyd文件, 这样就使得它可以在不具备编译环境的条件下,安装适合自己python版本的库文件。 如果要查看WHL文件的内容,可以把.whl后缀名改成.zip,使用解压软件(如WinRAR、WinZIP)解压打开即可查看。 为什么会用到whl文件来安装python库文件呢? 在python的使用过程中,我们免不了要经常通过pip来安装自己所需要的包, 大部分的包基本都能正常安装,但是总会遇到有那么一些包因为各种各样的问题导致安装不了的。 这时我们就可以通过尝试去Python安装包大全中(whl包下载)下载whl包来安装解决问题。

    电子商务之价格优化算法:线性回归:价格优化策略实施.docx

    电子商务之价格优化算法:线性回归:价格优化策略实施.docx

    工业数字化转型的关键技术及其应用场景解析

    内容概要:报告详细介绍了企业数字化转型的驱动因素、数字化转型方案分类及其应用场景,重点关注了云计算、超连接、数字孪生、人工智能、分布式账本、增材制造、人机接口、数据共享、工业物联网等关键技术。这些技术不仅支持了企业的运营效率提升和业务模式创新,也为实现更快、更开放、更高效的数字化转型提供了支撑。报告最后提出了企业实施数字化转型的六个步骤。 适合人群:企业高级管理人员、技术人员、咨询顾问,以及对工业数字化转型感兴趣的读者。 使用场景及目标:帮助企业制定和实施数字化转型策略,优化运营模式,提升业务效率,增强市场竞争力。同时,也可作为政府部门、研究机构和行业协会的参考文献。 其他说明:报告中提到的关键技术及其应用场景对企业数字化转型具有重要的指导意义,特别是对于那些希望通过数字化转型实现业务创新和升级的企业。

    基于java的线上选课系统的设计与实现答辩PPT.pptx

    基于java的线上选课系统的设计与实现答辩PPT.pptx

    原版aggdraw-1.3.15-cp311-cp311-win_arm64.whl-下载即用直接pip安装.zip

    安装前的准备 1、安装Python:确保你的计算机上已经安装了Python。你可以在命令行中输入python --version或python3 --version来检查是否已安装以及安装的版本。 个人建议:在anaconda中自建不同python版本的环境,方法如下(其他版本照葫芦画瓢): 比如创建python3.8环境,anaconda命令终端输入:conda create -n py38 python==3.8 2、安装pip:pip是Python的包管理工具,用于安装和管理Python包。你可以通过输入pip --version或pip3 --version来检查pip是否已安装。 安装WHL安装包 1、打开命令行(或打开anaconda命令行终端): 在Windows上,你可以搜索“cmd”或“命令提示符”并打开它。 在macOS或Linux上,你可以打开“终端”。 2、cd到whl文件所在目录安装: 使用cd命令导航到你下载的whl文件所在的文件夹。 终端输入:pip install xxx.whl安装即可(xxx.whl指的是csdn下载解压出来的whl) 3、等待安装完成: 命令行会显示安装进度,并在安装完成后返回提示符。 以上是简单安装介绍,小白也能会,简单好用,从此再也不怕下载安装超时问题。 使用过程遇到问题可以私信,我可以帮你解决! 收起

    电子商务之价格优化算法:贝叶斯定价:贝叶斯网络在电子商务定价中的应用.docx

    电子商务之价格优化算法:贝叶斯定价:贝叶斯网络在电子商务定价中的应用.docx

    IMG_20241105_235746.jpg

    IMG_20241105_235746.jpg

    基于java的毕业设计选题系统答辩PPT.pptx

    基于java的毕业设计选题系统答辩PPT.pptx

    专升本考试资料全套.7z

    专升本考试资料全套.7z

    Trustwave DbProtect:数据库活动监控策略制定.docx

    Trustwave DbProtect:数据库活动监控策略制定.docx

    VB程序实例-CD-ROM开关.zip

    基于VB的程序实例,可供参考学习使用

    课设毕设基于SpringBoot+Vue的教育资源共享平台源码可运行.zip

    本压缩包资源说明,你现在往下拉可以看到压缩包内容目录 我是批量上传的基于SpringBoot+Vue的项目,所以描述都一样;有源码有数据库脚本,系统都是测试过可运行的,看文件名即可区分项目~ |Java|SpringBoot|Vue|前后端分离| 开发语言:Java 框架:SpringBoot,Vue JDK版本:JDK1.8 数据库:MySQL 5.7+(推荐5.7,8.0也可以) 数据库工具:Navicat 开发软件: idea/eclipse(推荐idea) Maven包:Maven3.3.9+ 系统环境:Windows/Mac

    基于Thinkphp5框架的Java插件设计源码

    该源码项目是一款基于Thinkphp5框架的Java插件设计,包含114个文件,其中Java源文件60个,PNG图片32个,XML配置文件7个,GIF图片7个,Git忽略文件1个,LICENSE文件1个,Markdown文件1个,Xmind文件1个,Idea项目文件1个,以及JAR文件1个。

    数据库开发和管理最佳实践.pdf

    数据库开发和管理最佳实践.pdf

    课设毕设基于SpringBoot+Vue的农场投入品运营线上管理系统源码可运行.zip

    本压缩包资源说明,你现在往下拉可以看到压缩包内容目录 我是批量上传的基于SpringBoot+Vue的项目,所以描述都一样;有源码有数据库脚本,系统都是测试过可运行的,看文件名即可区分项目~ |Java|SpringBoot|Vue|前后端分离| 开发语言:Java 框架:SpringBoot,Vue JDK版本:JDK1.8 数据库:MySQL 5.7+(推荐5.7,8.0也可以) 数据库工具:Navicat 开发软件: idea/eclipse(推荐idea) Maven包:Maven3.3.9+ 系统环境:Windows/Mac

Global site tag (gtag.js) - Google Analytics