在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 相关文章推荐
extjs tabpanel限制选项卡数量实现思路及代码
Apr 02 Javascript
js网页版计算器的简单实现
Jul 02 Javascript
第二章之Bootstrap 页面排版样式
Apr 25 Javascript
gulp-htmlmin压缩html的gulp插件实例代码
Jun 06 Javascript
javascript 闭包详解及简单实例应用
Dec 31 Javascript
jQuery html表格排序插件tablesorter使用方法详解
Feb 10 Javascript
node.js爬虫爬取拉勾网职位信息
Mar 14 Javascript
webpack打包后直接访问页面图片路径错误的解决方法
Jun 17 Javascript
js实现图片旋转 js滚动鼠标中间对图片放大缩小
Jul 05 Javascript
Three.js如何用轨迹球插件(trackball)增加对模型的交互功能详解
Sep 25 Javascript
vue axios请求频繁时取消上一次请求的方法
Nov 10 Javascript
Vue记住滚动条和实现下拉加载的完美方法
Jul 31 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
分享3个php获取日历的函数
2015/09/25 PHP
php设计模式之单例模式用法经典示例分析
2019/09/20 PHP
让任务管理器中的CPU跳舞的js代码
2008/11/01 Javascript
跟着Jquery API学Jquery之一 选择器
2010/04/07 Javascript
使用jQuery+HttpHandler+xml模拟一个三级联动的例子
2011/08/09 Javascript
图片动画横条广告带上下滚动可自定义图片、链接等等
2013/10/20 Javascript
jquery清空表单数据示例分享
2014/02/13 Javascript
从零学JS之你需要了解的几本书
2014/05/19 Javascript
JavaScript 控制字体大小设置的方法
2016/11/23 Javascript
JS定时器实现数值从0到10来回变化
2016/12/09 Javascript
JavaScript函数基础详解
2017/02/03 Javascript
整理关于Bootstrap排版的慕课笔记
2017/03/29 Javascript
vue2.0实现导航菜单切换效果
2017/05/08 Javascript
详解vue渲染从后台获取的json数据
2017/07/06 Javascript
vue template中slot-scope/scope的使用方法
2018/09/06 Javascript
详解微信UnionID作用
2019/05/15 Javascript
送你43道JS面试题(收藏)
2019/06/17 Javascript
Vue搭建后台系统需要注意的问题
2019/11/08 Javascript
小程序接口的promise化的实现方法
2019/12/11 Javascript
OpenLayers3加载常用控件使用方法详解
2020/09/25 Javascript
[03:24]2014DOTA2国际邀请赛 神秘商店生意火爆
2014/07/18 DOTA
C#返回当前系统所有可用驱动器符号的方法
2015/04/18 Python
Python基于列表list实现的CRUD操作功能示例
2018/01/05 Python
python 通过字符串调用对象属性或方法的实例讲解
2018/04/21 Python
Python绘制正余弦函数图像的方法
2018/08/28 Python
Python使用Slider组件实现调整曲线参数功能示例
2019/09/06 Python
Python enumerate() 函数如何实现索引功能
2020/06/29 Python
python使用建议技巧分享(三)
2020/08/18 Python
天美时手表加拿大官网:Timex加拿大
2016/09/01 全球购物
简单说下OSPF的操作过程
2014/08/13 面试题
十佳家长事迹材料
2014/08/26 职场文书
农民工预备党员思想汇报
2014/09/14 职场文书
护士医德医风自我评价
2014/09/15 职场文书
2014年健康教育工作总结
2014/11/20 职场文书
redis调用二维码时的不断刷新排查分析
2022/04/01 Redis
Win11如何查看显卡型号 Win11查看显卡型号的方法
2022/08/14 数码科技