在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数据缓存功能的实现思路及简单模拟
May 27 Javascript
简单常用的幻灯片播放实现代码
Sep 25 Javascript
JavaScript中的apply和call函数详解
Jul 20 Javascript
JQuery中serialize()、serializeArray()和param()方法示例介绍
Jul 31 Javascript
点击页面任何位置隐藏div的实现方法
Sep 05 Javascript
jQuery实现输入框邮箱内容自动补全与上下翻动显示效果【附demo源码下载】
Sep 20 Javascript
Vue.js一个文件对应一个组件实践
Oct 27 Javascript
怎样判断jQuery当前元素是隐藏还是显示
Nov 23 Javascript
node.js请求HTTPS报错:UNABLE_TO_VERIFY_LEAF_SIGNATURE\的解决方法
Dec 18 Javascript
前端必备插件之纯原生JS的瀑布流插件Macy.js
Nov 22 Javascript
详解vue 计算属性与方法跟侦听器区别(面试考点)
Apr 23 Javascript
详解vuex 渐进式教程实例代码
Nov 27 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
模仿OSO的论坛(二)
2006/10/09 PHP
一个简单的php加密解密函数(动态加密)
2013/06/19 PHP
在视频前插入广告
2006/11/20 Javascript
js中查找最近的共有祖先元素的实现代码
2010/12/30 Javascript
JSON为什么那样红为什么要用json(另有洞天)
2012/12/26 Javascript
Jquery的hide及toggle方法让超链接慢慢消失
2013/09/06 Javascript
JS实现网页滚动条感应鼠标变色的方法
2015/02/26 Javascript
canvas实现弧形可拖动进度条效果
2017/05/11 Javascript
JavaScript实现移动端轮播效果
2017/06/06 Javascript
JavaScript阻止表单提交方法(附代码)
2017/08/15 Javascript
jQuery中ajax请求后台返回json数据并渲染HTML的方法
2018/08/08 jQuery
Vue数据双向绑定的深入探究
2018/11/27 Javascript
js实现无缝轮播图
2020/03/09 Javascript
javascript实现前端input密码输入强度验证
2020/06/24 Javascript
TypeScript 引用资源文件后提示找不到的异常处理技巧
2020/07/15 Javascript
解决vue加scoped后就无法修改vant的UI组件的样式问题
2020/09/07 Javascript
[00:57]英雄,你的补给到了!
2020/11/13 DOTA
仅用500行Python代码实现一个英文解析器的教程
2015/04/02 Python
用Python将动态GIF图片倒放播放的方法
2016/11/02 Python
python-docx修改已存在的Word文档的表格的字体格式方法
2018/05/08 Python
Python闭包和装饰器用法实例详解
2019/05/22 Python
解决Pycharm双击图标启动不了的问题(JetBrains全家桶通用)
2020/08/07 Python
python xlsxwriter模块的使用
2020/12/24 Python
利用css3制作3D样式按钮实现代码
2013/03/18 HTML / CSS
HTML5的自定义属性data-*详细介绍和JS操作实例
2014/04/10 HTML / CSS
阿里巴巴国际站:Alibaba.com
2016/07/21 全球购物
RentCars.com巴西:汽车租赁网站
2016/08/22 全球购物
什么是Smart Navigation?
2016/07/03 面试题
介绍一些UNIX常用简单命令
2014/11/11 面试题
《我的第一本书》教学反思
2014/02/15 职场文书
2014年学校办公室工作总结
2014/12/19 职场文书
大三学生英语考试作弊检讨书
2015/01/01 职场文书
乔迁之喜答谢词
2015/01/05 职场文书
初中教师个人工作总结
2015/02/10 职场文书
初中毕业感言300字
2015/07/31 职场文书
Python数据结构之队列详解
2022/03/21 Python