在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 typeof 用法
Dec 28 Javascript
JavaScript快速切换繁体中文和简体中文的方法及网站支持简繁体切换的绝招
Mar 07 Javascript
浅析jquery unbind()方法移除元素绑定的事件
May 24 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
Dec 26 Javascript
vue.js指令v-model使用方法
Mar 20 Javascript
JavaScript函数表达式详解及实例
May 05 Javascript
ES6深入理解之“let”能替代”var“吗?
Jun 28 Javascript
Vue2(三)实现子菜单展开收缩,带动画效果实现方法
Apr 28 Javascript
js实现幻灯片轮播图
Aug 14 Javascript
如何在Vue项目中添加接口监听遮罩
Jan 25 Vue.js
JavaScript原始值与包装对象的详细介绍
May 11 Javascript
Vue中Object.assign清空数据报错的解决方案
Mar 03 Vue.js
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,不用COM,生成excel文件
2006/10/09 PHP
一个颜色轮换的简单例子
2006/10/09 PHP
php读取mysql乱码,用set names XXX解决的原理分享
2011/12/29 PHP
php中namespace use用法实例分析
2016/01/22 PHP
jQuery Ajax之load()方法
2009/10/12 Javascript
js 页面传参数时 参数值含特殊字符的问题
2009/12/13 Javascript
JS操作图片(增,删,改) 例子
2013/04/17 Javascript
jquery实现简单的拖拽效果实例兼容所有主流浏览器
2013/06/21 Javascript
js同比例缩放图片的小例子
2013/10/30 Javascript
使用jQuery简单实现模拟浏览器搜索功能
2014/12/21 Javascript
javascript伸缩菜单栏实现代码分享
2015/11/12 Javascript
javascript实现获取指定精度的上传文件的大小简单实例
2016/10/25 Javascript
详解vue-router基本使用
2017/04/18 Javascript
页面间固定参数,通过cookie传值的实现方法
2017/05/31 Javascript
实时监控input框,实现输入框与下拉框联动的实例
2018/01/23 Javascript
axios全局请求参数设置,请求及返回拦截器的方法
2018/03/05 Javascript
详解Next.js页面渲染的优化方案
2019/01/27 Javascript
vue中的mescroll搜索运用及各种填坑处理
2019/10/30 Javascript
微信小程序实现页面浮动导航
2020/01/08 Javascript
JavaScript 实现下雪特效的示例代码
2020/09/09 Javascript
python判断windows隐藏文件的方法
2014/03/21 Python
Python multiprocessing.Manager介绍和实例(进程间共享数据)
2014/11/21 Python
简单介绍Python的Django框架的dj-scaffold项目
2015/05/30 Python
基于python的Tkinter编写登陆注册界面
2017/06/30 Python
Python网络编程详解
2017/10/31 Python
python爬取盘搜的有效链接实现代码
2019/07/20 Python
解析python实现Lasso回归
2019/09/11 Python
浅谈matplotlib 绘制梯度下降求解过程
2020/07/12 Python
VIVOBAREFOOT赤脚鞋:让您的脚做自然的事情
2017/06/01 全球购物
亚洲最大的运动鞋寄售店:KicksCrew
2020/11/26 全球购物
电信专业毕业生推荐信
2013/11/18 职场文书
十岁生日父母答谢词
2014/01/18 职场文书
2014年百日安全生产活动总结
2014/05/04 职场文书
Python 读写 Matlab Mat 格式数据的操作
2021/05/19 Python
Oracle中update和select 关联操作
2022/01/18 Oracle
一文搞懂PHP中的抽象类和接口
2022/05/25 PHP