在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写的日历类(基于pj)
Dec 28 Javascript
基于jquery的loading 加载提示效果实现代码
Sep 01 Javascript
javascript生成大小写字母
Jul 03 Javascript
如何用js 实现依赖注入的思想,后端框架思想搬到前端来
Aug 03 Javascript
jQuery实现每隔几条元素增加1条线的方法
Jun 27 Javascript
AngularJs Understanding the Controller Component
Sep 02 Javascript
javascript工厂模式和构造函数模式创建对象方法解析
Dec 30 Javascript
滚动条的监听与内容随着滚动条动态加载的实现
Feb 08 Javascript
微信小程序 点击控件后选中其它反选实例详解
Feb 21 Javascript
通过源码分析Vue的双向数据绑定详解
Sep 24 Javascript
es6 filter() 数组过滤方法总结
Apr 03 Javascript
小程序两种滚动公告栏的实现方法
Sep 17 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具体实现代码
2010/10/12 PHP
浅析PHP递归函数返回值使用方法
2013/02/18 PHP
php加密算法之实现可逆加密算法和解密分享
2014/01/21 PHP
php约瑟夫问题解决关于处死犯人的算法
2015/03/23 PHP
完美解决phpexcel导出到xls文件出现乱码的问题
2016/10/29 PHP
jQuery.Validate 使用笔记(jQuery Validation范例 )
2010/06/25 Javascript
JQ获取动态加载的图片大小的正确方法分享
2013/11/08 Javascript
javascript操作字符串的原生方法
2014/12/22 Javascript
JavaScript函数作用域链分析
2015/02/13 Javascript
gulp-uglify 与gulp.watch()配合使用时报错(重复压缩问题)
2016/08/24 Javascript
node+experss实现爬取电影天堂爬虫
2016/11/20 Javascript
浅谈angularjs中响应回车事件
2017/04/24 Javascript
详解node nvm进行node多版本管理
2017/10/21 Javascript
angularjs实现分页和搜索功能
2018/01/03 Javascript
关于AngularJS中ng-repeat不更新视图的解决方法
2018/09/30 Javascript
jquery层次选择器的介绍
2019/01/18 jQuery
微信小程序工具函数封装
2019/10/28 Javascript
vue中对象数组去重的实现
2020/02/06 Javascript
Python最基本的数据类型以及对元组的介绍
2015/04/14 Python
对python中的xlsxwriter库简单分析
2018/05/04 Python
Python应用库大全总结
2018/05/30 Python
解决DataFrame排序sort的问题
2018/06/07 Python
Python实现的绘制三维双螺旋线图形功能示例
2018/06/23 Python
python正则表达式之对号入座篇
2018/07/24 Python
在Python中获取操作系统的进程信息
2019/08/27 Python
一款利用纯css3实现的超炫3D表单的实例教程
2014/12/01 HTML / CSS
html5本地存储之localstorage 、本地数据库、sessionStorage简单使用示例
2014/05/08 HTML / CSS
巴西男士胡须和头发护理产品商店:Beard
2017/11/13 全球购物
北美Newegg打造的全球尖货海购平台:tt海购
2018/09/28 全球购物
《第一次抱母亲》教学反思
2014/04/16 职场文书
公安学专业求职信
2014/07/27 职场文书
2014旅游局领导班子四风问题对照检查材料思想汇报
2014/09/19 职场文书
2015年全国科普日活动总结
2015/03/23 职场文书
读《儒林外史》有感:少一些功利,多一些真诚
2020/01/19 职场文书
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
2021/05/25 Vue.js
Python3中最常用的5种线程锁实例总结
2021/07/07 Python