动机:任何有返回值的函数,都不应该有看得到的副作用。
一种常见的优化办法是:将查询所得记过缓存于某个字段中,后续重复查询可以大大加快速度。
Before:
// Miscreant - 恶的
function alertForMiscreant(people) {
for (const p of people) {
if (p === "Don") {
setOffAlarms();
return "Don";
}
if (p === "John") {
setOffAlarms();
return "John";
}
}
return "";
}
const found = alertForMiscreant(people);
After:
function findMiscreant(people) {
for (const p of people) {
if (p === "Don") {
return "Don";
}
if (p === "John") {
return "John";
}
}
return "";
}
function alertForMiscreant(people) {
if (findMiscreant(people) !== "") setOffAlarms(); // 替换算法
}
const found = findMiscreant(people);
alertForMiscreant(people);
动机:两个函数逻辑非常相似,可以将其合并为一个函数,以参数形式传入不同值,从而消除重复。
Before:
function baseCharge(usage) {
if (usage < 0) return usd(0);
const amount =
bottomBand(usage) * 0.03 + middleBand(usage) * 0.05 + topBand(usage) * 0.07;
return usd(amount);
}
function bottomBand(usage) {
return Math.min(usage, 100);
}
function middleBand(usage) {
return usage > 100 ? Math.min(usage, 200) - 100 : 0;
}
function topBand(usage) {
return usage > 200 ? usage - 200 : 0;
}
After:
function withinBand(usage, bottom, top) {
return usage > bottom ? Math.min(usage, top) - bottom : 0;
}
function baseCharge(usage) {
if (usage < 0) return usd(0);
const amount =
withinBand(usage, 0, 100) * 0.03 +
withinBand(usage, 100, 200) * 0.05 +
withinBand(usage, 200, Infinity) * 0.07;
return usd(amount);
}
动机:用标记参数来指示被调函数应该执行哪一部分逻辑,影响了函数内部控制流。移除标记参数是代码更加整洁。
Before:
function deliveryDate(anOrder, isRush) {
if (isRush) {
let deliveryTime;
if (["MA", "CT"].includes(anOrder.deliveryState)) deliveryTime = 1;
else if (["NY", "NH"].includes(anOrder.deliveryState)) deliveryTime = 2;
else deliveryTime = 3;
return anOrder.placedOn.plusDays(1 + deliveryTime);
} else {
let deliveryTime;
if (["MA", "CT", "NY"].includes(anOrder.deliveryState)) deliveryTime = 2;
else if (["ME", "NH"].includes(anOrder.deliveryState)) deliveryTime = 3;
else deliveryTime = 4;
return anOrder.placedOn.plusDays(2 + deliveryTime);
}
}
After:
function deliveryDate(anOrder, isRush) {
if (isRush) return rushDeliveryDate(anOrder);
else return regularDeliveryDate(anOrder);
}
function rushDeliveryDate(anOrder) {
let deliveryTime;
if (["MA", "CT"].includes(anOrder.deliveryState)) deliveryTime = 1;
else if (["NY", "NH"].includes(anOrder.deliveryState)) deliveryTime = 2;
else deliveryTime = 3;
return anOrder.placedOn.plusDays(1 + deliveryTime);
}
function regularDeliveryDate(anOrder) {
let deliveryTime;
if (["MA", "CT", "NY"].includes(anOrder.deliveryState)) deliveryTime = 2;
else if (["ME", "NH"].includes(anOrder.deliveryState)) deliveryTime = 3;
else deliveryTime = 4;
return anOrder.placedOn.plusDays(2 + deliveryTime);
}
动机:如果一个函数需要传入一个对象的多个属性值,传递对象本身是更好地方式。
Before:
const low = aRoom.daysTempRange.low;
const high = aRoom.daysTempRange.high;
if (!aPlan.withinRange(low, high))
alerts.push("room temperature went outside range");
class HeatingPlan {
withinRange(bottom, top) {
return (
bottom >= this._temperatureRange.low && top <= this._temperatureRange.high
);
}
}
After:
if (!aPlan.withinRange(aRoom.daysTemRange))
alerts.push("room temperature went outside range");
class HeatingPlan {
withinRange(aNumberRange) {
return (
aNumberRange.low >= this._temperatureRange.low &&
aNumberRange.high <= this._temperatureRange.high
);
}
}
动机:如果函数的一个参数只需要向另一个参数查询就能得到,则参数列表应避免重复。
Before:
class Order {
get finalPrice() {
const basePrice = this.quantity * this.itemPrice;
let discountLevel;
if (this.quantity > 100) discountLevel = 2;
else discountLevel = 1;
return this.discountedPrice(basePrice, discountLevel);
}
discountedPrice(basePrice, discountLevel) {
switch (discountLevel) {
case 1:
return basePrice * 0.95;
case 2:
return basePrice * 0.9;
}
}
}
After:
class Order {
get finalPrice() {
const basePrice = this.quantity * this.itemPrice;
return this.discountedPrice(basePrice);
}
get discountLevel() {
return this.quantity > 100 ? 2 : 1;
}
discountedPrice(basePrice) {
switch (discountLevel) {
case 1:
return basePrice * 0.95;
case 2:
return basePrice * 0.9;
}
}
}
动机:在负责逻辑处理的模块中只有纯函数,其外再包裹处理 I/O 和其他可变元素的逻辑代码,使其更容易测试及理解。JavaScript 的类模型无法强制要求类的不可变形——始终有办法修改对象的内部数据,以参数取代查询是达成让类保持不可变的利器。
Before:
class HeatingPlan {
get targetTemperature() {
if (thermostat.selectedTemperature > this._max) return this._max;
else if (thermostat.selectedTemperature < this._min) return this._min;
else return thermostat.selectedTemperature;
}
}
if (thePlan.targetTemperature > thermostat.currentTemperature) setToH;
else if (thePlan.targetTemperature < thermostat.currentTemperature) setToC;
else setOff();
After:
class HeatingPlan {
targetTemperature(selectedTemperature) {
if (selectedTemperature > this._max) return this._max;
else if (selectedTemperature < this._min) return this._min;
else return selectedTemperature;
}
}
if (
thePlan.targetTemperature(thermostat.selectedTemperature) >
thermostat.currentTemperature
)
setToHeat();
else if (
thePlan.targetTemperature(thermostat.selectedTemperature) <
thermostat.currentTemperature
)
setToCool();
else setOff();
动机:如果不希望在对象创建之后某个属性还有机会被改变,就不要为它提供 set 函数。
Before:
class Person {
get name() {
return this._name;
}
set name(arg) {
this._name = arg;
}
get id() {
return this._id;
}
set id(arg) {
this._id = arg;
}
}
const martin = new Person();
martin.name = "martin";
martin.id = "1234";
After:
class Person {
constructor(id) {
this._id = id;
}
get name() {
return this._name;
}
set name(arg) {
this._name = arg;
}
get id() {
return this._id;
}
}
const martin = new Person("1234");
martin.name = "martin";
动机:与一般函数相比,构造函数常有一些丑陋的局限性,只能返回当前所调用类的实例,构造函数名称是固定的类名,需要通过特殊操作符调用。工厂函数的实现内部可以调用构造函数,也可以换别的方式实现。
Before:
class Employee {
constructor(name, typeCode) {
this._name = name;
this._typeCode = typeCode;
}
get name() {
return this._name;
}
get type() {
return Employee.legalTypeCodes[this._typeCode];
}
static get legalTypeCodes() {
return { E: "Engineer", M: "Manager", S: "Salesman" };
}
}
const candidate = new Employee(document.name, document.empType);
const leadEngineer = new Employee(document.leadEngineer, "E");
After:
class Employee {
constructor(name, typeCode) {
this._name = name;
this._typeCode = typeCode;
}
get name() {
return this._name;
}
get type() {
return Employee.legalTypeCodes[this._typeCode];
}
static get legalTypeCodes() {
return { E: "Engineer", M: "Manager", S: "Salesman" };
}
}
function createEmployee(name, typeCode) {
return new Employee(name, typeCode);
}
const candidate = createEmployee(document.name, document.empType);
function createEngineer(name) {
return new Employee(name, "E");
}
const leadEngineer = createEngineer(document.leadEngineer);
动机:将函数封装成自己的对象,称为“命令对象”,简称“命令”,只服务于单一函数,获得对该函数的请求,执行函数。
Before:
function score(candidate, medicalExam, scoringGuide) {
let result = 0;
let healthLevel = 0;
let highMedicalRiskFlag = false;
if (medicalExam.isSmoker) {
healthLevel += 10;
highMedicalRiskFlag = true;
}
let certificationGrade = "regular";
if (scoringGuide.stateWithLowCertification(candidate.originState)) {
certificationGrade = "low";
result -= 5;
}
// lots more code like this
result -= Math.max(healthLevel - 5, 0);
return result;
}
After:
function score(candidate, medicalExam, scoringGuide) {
return new Scorer().execute(candidate, medicalExam, scoringGuide);
}
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate;
this._medicalExam = medicalExam;
this._scoringGuide = scoringGuide;
}
execute() {
this._result = 0;
this._healthLevel = 0;
this._highMedicalRiskFlag = false;
this.this.scoreSmoking();
this._certificationGrade = "regular";
if (
this._scoringGuide.stateWithLowCertification(this._candidate.originState)
) {
this._certificationGrade = "low";
this._result -= 5;
}
// lots more code like this
this._result -= Math.max(healthLevel - 5, 0);
return this._result;
}
scoreSmoking() {
if (this._medicalExam.isSmoker) {
this._healthLevel += 10;
this._highMedicalRiskFlag = true;
}
}
}
动机:借助命令对象可以轻松地将原本复杂的函数拆解为多个方法,彼此间通过字段共享状态,拆解后的方法分别调用,开始调用前的数据状态也可以逐步构建。但如果这个函数不太复杂,可以考虑将其变回普通函数
Before:
class ChargeCalculator {
constructor(customer, usage, provider) {
this._customer = customer;
this._usage = usage;
this._provider = provider;
}
get baseCharge() {
return this._customer.baseRate * this._usage;
}
get charge() {
return this.baseCharge + this._provider.connectionCharge;
}
}
After:
function charge(customer, usage, provider) {
const baseCharge = customer.baseRate * usage;
return baseCharge + provider.connectionCharge;
}
原文:https://blog.51cto.com/8851255/2635866