在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判断单个复选框是否被选中的代码
Sep 03 Javascript
jQuery extend 的简单实例
Sep 18 Javascript
Jquery节点遍历next与nextAll方法使用示例
Jul 22 Javascript
js创建对象的方式总结
Jan 10 Javascript
Jquery动态替换div内容及动态展示的方法
Jan 23 Javascript
JS实现的简易拖放效果示例
Dec 29 Javascript
jquery基于layui实现二级联动下拉选择(省份城市选择)
Jun 20 jQuery
vue js秒转天数小时分钟秒的实例代码
Aug 08 Javascript
浅谈VUE-CLI脚手架热更新太慢的原因和解决方法
Sep 28 Javascript
在vue-cli 3中给stylus、sass样式传入共享的全局变量
Aug 12 Javascript
javascript json对象小技巧之键名作为变量用法分析
Nov 11 Javascript
JS中多层次排序算法的实现代码
Jan 06 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
IIS下PHP连接数据库提示mysql undefined function mysql_connect()
2010/06/04 PHP
解析php中var_dump,var_export,print_r三个函数的区别
2013/06/21 PHP
PHP与MYSQL中UTF8 中文排序示例代码
2014/10/23 PHP
Fleaphp常见函数功能与用法示例
2016/11/15 PHP
PHP实现权限管理功能示例
2017/09/22 PHP
JS代码优化技巧之通俗版(减少js体积)
2011/12/23 Javascript
在jQuery中 关于json空对象筛选替换
2013/04/15 Javascript
使用JS或jQuery模拟鼠标点击a标签事件代码
2014/03/10 Javascript
IE 下Enter提交表单存在重复提交问题的解决方法
2014/05/04 Javascript
Jquery响应回车键直接提交表单操作代码
2014/07/25 Javascript
JavaScript前端图片加载管理器imagepool使用详解
2014/12/29 Javascript
javascript函数式编程实例分析
2015/04/25 Javascript
HTML5实现留言和回复页面样式
2015/07/22 Javascript
纯JavaScript代码实现移动设备绘图解锁
2015/10/16 Javascript
神奇!js+CSS+DIV实现文字颜色渐变效果
2016/03/16 Javascript
去除字符串左右两边的空格(实现代码)
2016/05/12 Javascript
JS中传递参数的几种不同方法比较
2017/01/20 Javascript
Angular中的interceptors拦截器
2017/06/25 Javascript
layui前端时间戳转化实例
2019/11/15 Javascript
详解JavaScript修改注册表的方法
2020/01/05 Javascript
vue 路由守卫(导航守卫)及其具体使用
2020/02/25 Javascript
javascript实现的图片预览和上传功能示例【兼容IE 9】
2020/05/01 Javascript
微信小程序清空输入框信息与实现屏幕往上滚动的示例代码
2020/06/23 Javascript
Vue使用CDN引用项目组件,减少项目体积的步骤
2020/10/30 Javascript
Python 调用DLL操作抄表机
2009/01/12 Python
在Python中操作时间之strptime()方法的使用
2020/12/30 Python
CSS3实现圆角、阴影、透明效果并兼容各大浏览器
2014/08/08 HTML / CSS
HTML5实现WebSocket协议原理浅析
2014/07/07 HTML / CSS
美国相机和电子产品零售商:Beach Camera
2020/11/26 全球购物
北京泡泡网网络有限公司.net面试题
2012/07/17 面试题
计算机专业学生的自我评价
2013/12/15 职场文书
同事打架检讨书
2014/02/04 职场文书
Python实现天气查询软件
2021/06/07 Python
JavaScript实现两个数组的交集
2022/03/25 Javascript
处理canvas绘制图片模糊问题
2022/05/11 Javascript
Vue 打包后相对路径的引用问题
2022/06/05 Vue.js