简介
链式调用是一种十分友好的操作对象实例的形式。
在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) {
// 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;
};
代码
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)
undefined
result
Math {value: 1, hasInitValue: true}
result.add(1,1)
Math {value: 3, hasInitValue: true}
result.add(1)
Math {value: 2, hasInitValue: true}
result.add(1).value
2
result.times(2).value
2
result.add(1).times(4).minus(5).value
3