简介

链式调用是一种十分友好的操作对象实例的形式。
在JavaScript语言界如jQuery、Promise等常用类库在操作对象实例时都是使用的链式调用的形式。
链式调用可以让我们在进行连续操作时,写出更简洁的代码。

示例

  1. new Promise((resolve, reject) => {
  2. resolve();
  3. })
  4. .then(() => {
  5. throw new Error('Something failed');
  6. })
  7. .then(() => {
  8. console.log('Do this whatever happened before');
  9. })
  10. .catch(() => {
  11. console.log('Do that');
  12. })

实现

链式调用通常的实现方式,就是在函数调用结果返回模块本身。

  1. export default {
  2. add(...args) {
  3. // add
  4. return this;
  5. },
  6. minus(...args) {
  7. // minus
  8. return this;
  9. },
  10. times(...args) {
  11. // times
  12. return this;
  13. },
  14. divide(...args) {
  15. // divide
  16. return this;
  17. },
  18. }

实例

简易计算类

思路

在构建了链式调用基本形式以后,存在一个问题,就是无法获取计算结果。

预留内部变量

现在我们需要对模块进行改造,使用一个内部变量来存储计算结果。

  1. export default {
  2. value: NaN,
  3. add(...args) {
  4. this.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
  5. return this;
  6. },
  7. }

这样,我们在最后一步,通过.value就可以拿到最终的计算结果了。
上面我们看似通过一个value变量解决了存储计算结果的问题,但是发生第二次链式调用时,value的值因为已经有了初始值,我们会得到错误的计算结果!

  1. const a = math.add(5, 6).value; // 11
  2. const b = math.add(5, 7).value; // 23 而非 12

重置内部变量

在获取内部变量时重置?

既然是因为value有了初始值,那么能不能粗暴地在获取value的值时重置掉呢?
答案是不能,因为我们并不能确定使用者会在什么时候取值。

每次链式调用前生成新实例?

另一种思路是在每次链式调用之前生成一个新的实例,这样就可以确保实例之间相互独立了。

  1. const math = function() {
  2. if (!(this instanceof math)) return new math();
  3. };
  4. math.prototype.value = NaN;
  5. math.prototype.add = function(...args) {
  6. this.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
  7. return this;
  8. };
  9. const a = math().add(5, 6).value;
  10. const b = math().add(5, 7).value;

但是这样也不能彻底解决问题,假设我们如下调用:

  1. const m = math().add(5, 6);
  2. const c = m.add(5).value; // 16
  3. const d = m.add(5).value; // 21 而非 16

每个方法都返回一个新实例!

最终要解决这个问题,只能每个方法都返回一个新的实例,这样可确保无论怎么调用,相互之间都不会被干扰到。

  1. math.prototype.add = function(...args) {
  2. const instance = math();
  3. instance.value = args.reduce((pv, cv) => pv + cv, this.value || 0);
  4. return instance;
  5. };

代码

  1. class Math {
  2. constructor(value) {
  3. let hasInitValue = true;
  4. if (value === undefined) {
  5. value = NaN;
  6. hasInitValue = false;
  7. }
  8. Object.defineProperties(this, {
  9. value: {
  10. enumerable: true,
  11. value: value,
  12. },
  13. hasInitValue: {
  14. enumerable: false,
  15. value: hasInitValue,
  16. },
  17. });
  18. }
  19. add(...args) {
  20. const init = this.hasInitValue ? this.value : args.shift();
  21. const value = args.reduce((pv, cv) => pv + cv, init);
  22. return new Math(value);
  23. }
  24. minus(...args) {
  25. const init = this.hasInitValue ? this.value : args.shift();
  26. const value = args.reduce((pv, cv) => pv - cv, init);
  27. return new Math(value);
  28. }
  29. times(...args) {
  30. const init = this.hasInitValue ? this.value : args.shift();
  31. const value = args.reduce((pv, cv) => pv * cv, init);
  32. return new Math(value);
  33. }
  34. divide(...args) {
  35. const init = this.hasInitValue ? this.value : args.shift();
  36. const value = args.reduce((pv, cv) => pv / cv, init);
  37. return new Math(value);
  38. }
  39. toJSON() {
  40. return this.valueOf();
  41. }
  42. toString() {
  43. return String(this.valueOf());
  44. }
  45. valueOf() {
  46. return this.value;
  47. }
  48. [Symbol.toPrimitive](hint) {
  49. const value = this.value;
  50. if (hint === 'string') {
  51. return String(value);
  52. } else {
  53. return value;
  54. }
  55. }
  56. }
  57. export default new Math();
  58. // 测试结果
  59. const result = new Math(1)
  60. undefined
  61. result
  62. Math {value: 1, hasInitValue: true}
  63. result.add(1,1)
  64. Math {value: 3, hasInitValue: true}
  65. result.add(1)
  66. Math {value: 2, hasInitValue: true}
  67. result.add(1).value
  68. 2
  69. result.times(2).value
  70. 2
  71. result.add(1).times(4).minus(5).value
  72. 3

参考链接 https://juejin.im/post/5e018025f265da33c028bac9