在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 相关文章推荐
Mootools 1.2教程 Fx.Tween的使用
Sep 15 Javascript
奉献给JavaScript初学者的编写开发的七个细节
Jan 11 Javascript
javascript实现获取图片大小及图片等比缩放的方法
Nov 24 Javascript
浅谈js函数的多种定义方法与区别
Nov 29 Javascript
jQuery得到多个值只能用取Class ,不能用取ID的方法
Dec 04 Javascript
Jquery Easyui验证组件ValidateBox使用详解(20)
Dec 18 Javascript
基于JS实现bookstore静态页面的实例代码
Feb 22 Javascript
详解vuejs之v-for列表渲染
Jun 22 Javascript
小程序实现列表多个批量倒计时
Jan 29 Javascript
JS中FormData类实现文件上传
Mar 27 Javascript
uni-app从安装到卸载的入门教程
May 15 Javascript
js实现双人五子棋小游戏
May 28 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
PDO版本问题 Invalid parameter number: no parameters were bound
2013/01/06 PHP
基于initPHP的框架介绍
2013/04/18 PHP
php cli 小技巧
2013/06/03 PHP
PHP字符串word末字符实现大小写互换的方法
2014/11/10 PHP
php使用PDO操作MySQL数据库实例
2014/12/30 PHP
php实现计数器方法小结
2015/01/05 PHP
php简单复制文件的方法
2016/05/09 PHP
PHP 微信扫码支付源代码(推荐)
2016/11/03 PHP
php版微信小店调用api示例代码
2016/11/12 PHP
你必须知道的JavaScript 中字符串连接的性能的一些问题
2013/05/07 Javascript
jquery实现input输入框实时输入触发事件代码
2014/01/28 Javascript
node.js中使用q.js实现api的promise化
2014/09/17 Javascript
Node.js的特点和应用场景介绍
2014/11/04 Javascript
修改js confirm alert 提示框文字的简单实例
2016/06/10 Javascript
javascript 开发之网页兼容各种浏览器
2017/09/28 Javascript
Element input树型下拉框的实现代码
2018/12/21 Javascript
vue实现简单计算商品价格
2020/09/14 Javascript
Python中Collections模块的Counter容器类使用教程
2016/05/31 Python
K-近邻算法的python实现代码分享
2017/12/09 Python
python 实现将txt文件多行合并为一行并将中间的空格去掉方法
2018/12/20 Python
解决pyqt5异常退出无提示信息的问题
2020/04/08 Python
在python中使用pyspark读写Hive数据操作
2020/06/06 Python
Python获取excel内容及相关操作代码实例
2020/08/10 Python
Python进行统计建模
2020/08/10 Python
CSS去掉A标签(链接)虚线框的方法
2014/04/01 HTML / CSS
html2canvas生成清晰的图片实现打印的示例代码
2019/09/30 HTML / CSS
澳大利亚实惠时尚女装商店:Katies
2019/06/16 全球购物
EJB timer的种类
2014/10/28 面试题
中层干部竞争上岗演讲稿
2014/01/13 职场文书
你的创业计划书怎样才能打动风投
2014/02/06 职场文书
开朗女孩的自我评价
2014/02/10 职场文书
食堂采购员岗位职责
2014/03/17 职场文书
幼儿园六一亲子活动方案
2014/08/26 职场文书
党的群众路线教育实践活动对照检查材料(个人)
2014/09/24 职场文书
SQL实现LeetCode(177.第N高薪水)
2021/08/04 MySQL
使用Redis做预定库存缓存功能
2022/04/02 Redis