在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 相关文章推荐
Javascript 网页水印(非图片水印)实现代码
Mar 01 Javascript
jQuery each()方法的使用方法
Mar 18 Javascript
JS中怎样判断undefined(比较不错的方法)
Mar 27 Javascript
悬浮广告方法日常收集整理
Mar 18 Javascript
JavaScript 数组中最大最小值
Jun 05 Javascript
解析JavaScript模仿块级作用域
Dec 29 Javascript
基于js的变量提升和函数提升(详解)
Sep 17 Javascript
Vue.js简易安装和快速入门(第二课)
Oct 17 Javascript
bootstrap下拉分页样式 带跳转页码
Dec 29 Javascript
VeeValidate 的使用场景以及配置详解
Jan 11 Javascript
微信实现自动跳转到用其他浏览器打开指定APP下载
Feb 15 Javascript
Vue 实现前进刷新后退不刷新的效果
Jun 14 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
PHP中foreach循环中使用引用要注意的地方
2011/01/02 PHP
JavaScript 高级篇之DOM文档,简单封装及调用、动态添加、删除样式(六)
2012/04/07 Javascript
jQuery 顶部导航跟随滚动条滚动固定浮动在顶部
2014/06/06 Javascript
原生javascript实现获取指定元素下所有后代元素的方法
2014/10/28 Javascript
jquery实现全选、反选、获得所有选中的checkbox
2020/09/13 Javascript
JavaScript程序设计之JS调试
2015/12/09 Javascript
jQuery each函数源码分析
2016/05/25 Javascript
JS实用技巧小结(屏蔽错误、div滚动条设置、背景图片位置等)
2016/06/16 Javascript
JavaScript中闭包之浅析解读(必看篇)
2016/08/25 Javascript
Angular的MVC和作用域
2016/12/26 Javascript
微信JSAPI Ticket接口签名详解
2020/06/28 Javascript
vue监听scroll的坑的解决方法
2017/09/07 Javascript
强大的JavaScript响应式图表Chartist.js的使用
2017/09/13 Javascript
Vuejs实现购物车功能
2017/11/05 Javascript
vue内置指令详解
2018/04/03 Javascript
浅谈JavaScript 代码简洁之道
2019/01/09 Javascript
[03:55]2014DOTA2国际邀请赛 Fnatic经理采访赢DK在情理之中
2014/07/10 DOTA
[01:05:29]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster BO3 第二场 1月24日
2021/03/11 DOTA
[01:07:02]DOTA2-DPC中国联赛 正赛 iG vs PSG.LGD BO3 第三场 2月26日
2021/03/11 DOTA
python获取图片颜色信息的方法
2015/03/18 Python
Python中的并发处理之asyncio包使用的详解
2018/04/03 Python
Python简单定义与使用二叉树示例
2018/05/11 Python
对python数据切割归并算法的实例讲解
2018/12/12 Python
把JSON数据格式转换为Python的类对象方法详解(两种方法)
2019/06/04 Python
详解pyppeteer(python版puppeteer)基本使用
2019/06/12 Python
Tensorflow 模型转换 .pb convert to .lite实例
2020/02/12 Python
TUMI马来西亚官方网站:国际领先的高品质商旅箱包品牌
2018/04/26 全球购物
什么是java序列化,如何实现java序列化
2012/11/14 面试题
拾金不昧的表扬信
2014/01/16 职场文书
中秋节礼品促销方案
2014/02/02 职场文书
好习惯伴我成长演讲稿
2014/05/21 职场文书
土木工程求职信
2014/05/29 职场文书
奥巴马经典演讲稿
2014/09/13 职场文书
业务员工作态度散漫检讨书
2014/11/02 职场文书
MySQL 8.0 Online DDL快速加列的相关总结
2021/06/02 MySQL
图解排序算法之希尔排序Java实现
2021/06/26 Java/Android