简介
链式调用是一种十分友好的操作对象实例的形式。
在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');})
实现
链式调用通常的实现方式,就是在函数调用结果返回模块本身。
export default {add(...args) {// addreturn this;},minus(...args) {// minusreturn this;},times(...args) {// timesreturn this;},divide(...args) {// dividereturn 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; // 11const 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; // 16const 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;};
代码
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();// 测试结果const result = new Math(1)undefinedresultMath {value: 1, hasInitValue: true}result.add(1,1)Math {value: 3, hasInitValue: true}result.add(1)Math {value: 2, hasInitValue: true}result.add(1).value2result.times(2).value2result.add(1).times(4).minus(5).value3
