在JavaScript中实现链式调用的实现


Posted in Javascript onDecember 24, 2019

链式调用实现本身比较简单,也有很多文章详细阐述了其实现方式。本文更多从链式调用方法返回值的角度,更进一步来说明如何实现链式调用。

什么是链式调用

链式调用在 JavaScript 语言界很常见,如 jQuery 、 Promise 等,都是使用的链式调用。链式调用可以让我们在进行连续操作时,写出更简洁的代码。

new Promise((resolve, reject) => {
 resolve();
})
.then(() => {
 throw new Error('Something failed');
})
.then(() => {
 console.log('Do this whatever happened before');
})
.catch(() => {
 console.log('Do that');
})

逐步实现链式调用

假设,我们要实现一个 math 模块,使之能够支持链式调用:

const math = require('math');
const a = math.add(2, 4).minus(3).times(2);
const b = math.add(2, 4).times(3).divide(2);
const c = { a, b };

console.log(a.times(2) + b + 1); // 22
console.log(a.times(2) + b + 2); // 23
console.log(JSON.stringify(c)); // {"a":6,"b":9}

基本的链式调用

链式调用通常的实现方式,就是在函数调用结果返回模块本身。那么 math 模块的代码大致应该是这样子的:

export default {
 add(...args) {
  // add
  return this;
 },
 minus(...args) {
  // minus
  return this;
 },
 times(...args) {
  // times
  return this;
 },
 divide(...args) {
  // divide
  return this;
 },
}

方法如何返回值

上述代码实现了链式调用,但是也存在一个问题,就是无法获取计算结果。所以我们需要对模块进行改造,使用一个内部变量来存储计算结果。

export default {
 value: NaN,
 add(...args) {
  this.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
  return this;
 },
}

这样,我们在最后一步,通过 .value 就可以拿到最终的计算结果了。

问题真的解决了吗

上面我们看似通过一个 value 变量解决了存储计算结果的问题,但是发生第二次链式调用时, value 的值因为已经有了初始值,我们会得到错误的计算结果!

const a = math.add(5, 6).value; // 11
const b = math.add(5, 7).value; // 23 而非 12

既然是因为 value 有了初始值,那么能不能在获取 value 的值时重置掉呢?答案是不能,因为我们并不能确定使用者会在什么时候取值。

另外一种思路是在每次链式调用之前生成一个新的实例,这样就可以确保实例之间相互独立了。

const math = function() {
 if (!(this instanceof math)) return new math();
};

math.prototype.value = NaN;

math.prototype.add = function(...args) {
 this.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
 return this;
};

const a = math().add(5, 6).value;
const b = math().add(5, 7).value;

但是这样也不能彻底解决问题,假设我们如下调用:

const m = math().add(5, 6);
const c = m.add(5).value; // 16
const d = m.add(5).value; // 21 而非 16

所以,最终要解决这个问题,只能每个方法都返回一个新的实例,这样可确保无论怎么调用,相互之间都不会被干扰到。

math.prototype.add = function(...args) {
 const instance = math();
 instance.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
 return instance;
};

如何支持不通过 .value 对结果进行普通运算

通过改造 valueOf 方法或者 Symbol.toPrimitive 方法。其中 Symbol.toPrimitive 方法优先 valueOf 方法被调用,除非是ES环境不支持。

如何支持 JSON.stringify 序列化计算结果

通过自定义 toJSON 方法。 JSON.stringify 将值转换为相应的JSON格式时,如果被转换值有 toJSON 方法,则优先使用该方法返回的值。

最终的完整实现代码

class Math {
 constructor(value) {
  let hasInitValue = true;
  if (value === undefined) {
   value = NaN;
   hasInitValue = false;
  }
  Object.defineProperties(this, {
   value: {
    enumerable: true,
    value: value,
   },
   hasInitValue: {
    enumerable: false,
    value: hasInitValue,
   },
  });
 }

 add(...args) {
  const init = this.hasInitValue ? this.value : args.shift();
  const value = args.reduce((pv, cv) => pv + cv, init);
  return new Math(value);
 }

 minus(...args) {
  const init = this.hasInitValue ? this.value : args.shift();
  const value = args.reduce((pv, cv) => pv - cv, init);
  return new Math(value);
 }

 times(...args) {
  const init = this.hasInitValue ? this.value : args.shift();
  const value = args.reduce((pv, cv) => pv * cv, init);
  return new Math(value);
 }

 divide(...args) {
  const init = this.hasInitValue ? this.value : args.shift();
  const value = args.reduce((pv, cv) => pv / cv, init);
  return new Math(value);
 }

 toJSON() {
  return this.valueOf();
 }

 toString() {
  return String(this.valueOf());
 }

 valueOf() {
  return this.value;
 }

 [Symbol.toPrimitive](hint) {
  const value = this.value;
  if (hint === 'string') {
   return String(value);
  } else {
   return value;
  }
 }
}

export default new Math();

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用jquery实现下拉菜单效果的代码
Jul 25 Javascript
JavaScript下利用fso判断文件是否存在的代码
Dec 11 Javascript
jquery中使用$(#form).submit()重写提交表单无效原因分析及解决
Mar 25 Javascript
js模仿hover的具体实现代码
Dec 30 Javascript
js canvas实现擦除动画
Jul 16 Javascript
基于JS实现网页中的选项卡(两种方法)
Jun 16 Javascript
vue实现商城购物车功能
Nov 27 Javascript
vue引用js文件的多种方式(推荐)
May 17 Javascript
JavaScript的级联函数用法简单示例【链式调用】
Mar 26 Javascript
详解Vue依赖收集引发的问题
Apr 22 Javascript
vue-cli设置css不生效的解决方法
Feb 07 Javascript
js实现踩五彩块游戏
Feb 08 Javascript
vue实现分页加载效果
Dec 24 #Javascript
微信小程序如何获取地址
Dec 24 #Javascript
浅析vue-router中params和query的区别
Dec 24 #Javascript
JavaScript实现英语单词题库
Dec 24 #Javascript
iSlider手机端图片滑动切换插件使用详解
Dec 24 #Javascript
微信小程序自定义模态弹窗组件详解
Dec 24 #Javascript
js实现鼠标点击页面弹出自定义文字效果
Dec 24 #Javascript
You might like
推荐一款MAC OS X 下php集成开发环境mamp
2014/11/08 PHP
WIN8.1下搭建PHP5.6环境
2015/04/29 PHP
java模拟PHP的pack和unpack类
2016/04/13 PHP
Yii 2.0如何使用页面缓存方法示例
2017/05/23 PHP
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
2020/12/16 PHP
js获取dom的高度和宽度(可见区域及部分等等)
2013/06/13 Javascript
用JQuery实现全选与取消的两种简单方法
2014/02/22 Javascript
使用upstart把nodejs应用封装为系统服务实例
2014/06/01 NodeJs
推荐 21 款优秀的高性能 Node.js 开发框架
2014/08/18 Javascript
node.js下when.js 的异步编程实践
2014/12/03 Javascript
JQuery操作元素的css样式
2015/03/09 Javascript
javascript中offset、client、scroll的属性总结
2015/08/13 Javascript
javascript实现滚动效果的数字时钟实例
2016/07/21 Javascript
js 自带的 map() 方法全面了解
2016/08/16 Javascript
简述Angular 5 快速入门
2017/11/04 Javascript
使用use注册Vue全局组件和全局指令的方法
2018/03/08 Javascript
详解如何在Vue里建立长按指令
2018/08/20 Javascript
koa源码中promise的解读
2018/11/13 Javascript
Vue动态修改网页标题的方法及遇到问题
2019/06/09 Javascript
在Layui中操作数据表格,给指定单元格添加事件示例
2019/10/26 Javascript
VueCli生产环境打包部署跨域失败的解决
2020/11/13 Javascript
[01:04:01]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS DT第一场
2014/05/24 DOTA
采用Psyco实现python执行速度提高到与编译语言一样的水平
2014/10/11 Python
python 实现矩阵上下/左右翻转,转置的示例
2019/01/23 Python
python 表格打印代码实例解析
2019/10/12 Python
Python英文文章词频统计(14份剑桥真题词频统计)
2019/10/13 Python
Python collections.deque双边队列原理详解
2020/10/05 Python
台湾租车首选品牌:IWS艾维士租车
2019/05/03 全球购物
工程项目经理岗位职责
2013/12/15 职场文书
互联网创业计划书的书写步骤
2014/01/28 职场文书
股权收购意向书
2014/04/01 职场文书
关于感恩的演讲稿400字
2014/08/26 职场文书
辩护词范文大全
2015/05/21 职场文书
党员转正意见怎么写
2015/06/03 职场文书
2015年城乡环境综合治理工作总结
2015/07/24 职场文书
情人节单身感言
2015/08/03 职场文书