6

JavaScript 代码优化之道

 2 years ago
source link: https://www.51cto.com/article/717613.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript 代码优化之道

作者:俺是谁 2022-08-31 12:15:09
代码和语言文字一样是为了表达思想、记载信息,所以写得清楚能更有效地表达。本文多数总结自《重构:改善既有代码的设计(第2版)》我们直接进入正题,上代码!
178327127cdb49ffb09287903d49dca071d9c6.jpg

我们先引入一句话:

  •  代码主要是为了写给人看的,而不是写给机器看的,只是顺便也能用机器执行而已。

代码和语言文字一样是为了表达思想、记载信息,所以写得清楚能更有效地表达。本文多数总结自《重构:改善既有代码的设计(第2版)》我们直接进入正题,上代码!

将一段代码提炼到一个独立的函数中,并以这段代码的作用命名。

where

如果需要花时间浏览一段代码才能弄清楚它到底要干什么,那么这时候就应该将其提炼到一个函数中,并根据它所做的事命名。以后再读这段代码时,一眼就能知道这个函数的用途。

// ==================重构前==================
function printOwing(invoice) {
   let outstanding = 0;
   console.log("***********************");
   console.log("**** Customer Owes ****");
   console.log("***********************");
}
// ==================重构后==================
function printOwing(invoice) {
   let outstanding = 0;
   printBanner()
}
function printBanner() {
   console.log("***********************");
   console.log("**** Customer Owes ****");
   console.log("***********************");
}
复制代码

函数参数化

以参数的形式传入不同的值,消除重复函数

where

如果发现两个函数逻辑非常相似, 只有一些字面量值不同, 可以将其合并成一个函数, 以参数的形式传入不同的值, 从而消除重复。

// ==================重构前==================
// 点击异常项
clickFaultsItem(item){
   this.$u.route({
       url:'xxx',
       params:{
           id: item.id,
           type: '异常'
       }
   })
}
// 点击正常项
clickNormalItem(item){
   this.$u.route({
       url:'xxx',
       params:{
           id: item.id,
           type: '正常'
       }
   })
}
// ==================重构后==================
clickItem(id, type){
    this.$u.route({
       url:'xxx',
       params:{id, type}
   })
}
复制代码

使用策略模式替换“胖”分支

使用策略模式替换“胖胖”的if-else或者switch-case

where

当if-else或者switch-case分支过多时可以使用策略模式将各个分支独立出来

// ==================重构前==================
function getPrice(tag, originPrice) {
   // 新人价格
   if(tag === 'newUser') {
       return originPrice > 50.1 ? originPrice - 50 : originPrice
   }
   // 返场价格
   if(tag === 'back') {
        return originPrice > 200 ? originPrice - 50 : originPrice
   }
   // 活动价格
   if(tag === 'activity') {
       return originPrice > 300 ? originPrice - 100 : originPrice
   }
}
// ==================重构后==================
const priceHandler = {
   newUser(originPrice){
       return originPrice > 50.1 ? originPrice - 50 : originPrice
   },
   back(originPrice){
       return originPrice > 200 ? originPrice - 50 : originPrice
   },
   activity(originPrice){
        return originPrice > 300 ? originPrice - 100 : originPrice
   }
}
function getPrice(tag, originPrice){
   return priceHandler[tag](originPrice)
}
复制代码

提炼局部变量替换表达式。

where

一个表达式有可能非常复杂且难以阅读。这种情况下, 可以提炼出一个局部变量帮助我们将表达式分解为比较容易管理的形式 ,这样的变量在调试时也很方便。

// ==================重构前==================
function price(order) {
   //价格 = 商品原价 - 数量满减价 + 运费
   return order.quantity * order.price -
   Math.max(0, order.quantity - 500) * order.price * 0.05 +
   Math.min(order.quantity * order.price * 0.1, 100);
}
// ==================重构后==================
function price(order) {
   const basePrice = order.quantity * order.price;
   const quantityDiscount = Math.max(0, order.quantity - 500) * order.price * 0.05;
   const shipping = Math.min(basePrice * 0.1, 100);
   return basePrice - quantityDiscount + shipping;
}
复制代码

用变量右侧表达式消除变量,这是提炼变量的逆操作。

where

当变量名字并不比表达式本身更具表现力时可以采取该方法。

// ==================重构前==================
let basePrice = anOrder.basePrice;
return (basePrice > 1000);
// ==================重构后==================
return anOrder.basePrice > 1000
复制代码

将变量封装起来,只允许通过函数访问

where

对于所有可变的数据, 只要它的作用域超出单个函数,就可以采用封装变量的方法。数据被使用得越广, 就越是值得花精力给它一个体面的封装。

// ==================重构前==================
let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
// 访问
spaceship.owner = defaultOwner;
// 赋值
defaultOwner = {firstName: "Rebecca", lastName: "Parsons"};
// ==================重构后==================
function getDefaultOwner() {return defaultOwner;}
function setDefaultOwner(arg) {defaultOwner = arg;}
// 访问
spaceship.owner = getDefaultOwner();
// 赋值
setDefaultOwner({firstName: "Rebecca", lastName: "Parsons"});
复制代码

把一大段行为拆分成多个顺序执行的阶段

where

当看见一段代码在同时处理两件不同的事, 可以把它拆分成各自独立的模块, 因为这样到了需要修改的时候, 就可以单独处理每个模块。

// ==================重构前==================
function priceOrder(product, quantity, shippingMethod) {
   const basePrice = product.basePrice * quantity;

   const discount = Math.max(quantity - product.discountThreshold, 0)
   * product.basePrice * product.discountRate;

   const shippingPerCase = (basePrice > shippingMethod.discountThreshold)
   ? shippingMethod.discountedFee : shippingMethod.feePerCase;

   const shippingCost = quantity * shippingPerCase;

   const price = basePrice - discount + shippingCost;
   return price;
}
/*
该例中前两行代码根据商品信息计算订单中与商品相关的价格, 随后的两行则根据配送信息计算配送成本。
将这两块逻辑相对独立后,后续如果修改价格和配送的计算逻辑则只需修改对应模块即可。
*/
// ==================重构后==================
function priceOrder(product, quantity, shippingMethod) {
   const priceData = calculatePricingData(product, quantity);
   return applyShipping(priceData, shippingMethod);
}
// 计算商品价格
function calculatePricingData(product, quantity) {
   const basePrice = product.basePrice * quantity;
   const discount = Math.max(quantity - product.discountThreshold, 0)
   * product.basePrice * product.discountRate;

   return {basePrice, quantity, discount};
}
// 计算配送价格
function applyShipping(priceData, shippingMethod) {
   const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold)
   ? shippingMethod.discountedFee : shippingMethod.feePerCase;
   const shippingCost = priceData.quantity * shippingPerCase;

   return priceData.basePrice - priceData.discount + shippingCost;
}
复制代码

将一个循环拆分成多个循环

where

当遇到一个身兼数职的循环时可以将循环拆解,让一个循环只做一件事情, 那就能确保每次修改时你只需要理解要修改的那块代码的行为就可以了。该行为可能会被质疑,因为它会迫使你执行两次甚至多次循环,实际情况是,即使处理的列表数据更多一些,循环本身也很少成为性能瓶颈,更何况拆分出循环来通常还使一些更强大的优化手段变得可能。

// ==================重构前==================
const people = [
   { age: 20, salary: 10000 },
   { age: 21, salary: 15000 },
   { age: 22, salary: 18000 }
]
let youngest = people[0] ? people[0].age : Infinity;
let totalSalary = 0;
for (const p of people) {
   // 查找最年轻的人员
   if (p.age < youngest) youngest = p.age;
   // 计算总薪水
   totalSalary += p.salary;
}
console.log(`youngestAge: ${youngest}, totalSalary: ${totalSalary}`);
// ==================重构后==================
const people = [
   { age: 20, salary: 10000 },
   { age: 21, salary: 15000 },
   { age: 22, salary: 18000 }
]
let totalSalary = 0;
for (const p of people) {
   // 只计算总薪资
   totalSalary += p.salary;
}
let youngest = people[0] ? people[0].age : Infinity;
for (const p of people) {
   // 只查找最年轻的人员
   if (p.age < youngest) youngest = p.age;
}  
console.log(`youngestAge: ${youngest}, totalSalary: ${totalSalary}`);
// ==================提炼函数==================
const people = [
   { age: 20, salary: 10000 },
   { age: 21, salary: 15000 },
   { age: 22, salary: 18000 }
]
console.log(`youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`);
function totalSalary() {
   let totalSalary = 0;
   for (const p of people) {
       totalSalary += p.salary;
   }
   return totalSalary;
}  
function youngestAge() {
   let youngest = people[0] ? people[0].age : Infinity;
   for (const p of people) {
       if (p.age < youngest) youngest = p.age;
   }
   return youngest;
}
// ==================使用工具类进一步优化==================
const people = [
   { age: 20, salary: 10000 },
   { age: 21, salary: 15000 },
   { age: 22, salary: 18000 }
]
console.log(`youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`);
function totalSalary() {
   return people.reduce((total,p) => total + p.salary, 0);
}
function youngestAge() {
   return Math.min(...people.map(p => p.age));
}
复制代码

将一个变量拆分成两个或多个变量

where

如果变量承担多个责任, 它就应该被替换为多个变量, 每个变量只承担一个责任。

// ==================重构前==================
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
// ==================重构后==================
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
复制代码

分解条件表达式

将条件表达式提炼成函数

where

在带有复杂条件逻辑的函数中,往往可以将原函数中对应的代码改为调用新函数。

对于条件逻辑, 将每个分支条件分解成新函数可以带来的好处:

  •  提高可读性
  •  可以突出条件逻辑, 更清楚地表明每个分支的作用
  •  突出每个分支的原因
// ==================重构前==================
// 计算一件商品的总价,该商品在冬季和夏季的单价是不同的
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
charge = quantity * plan.summerRate;
else
charge = quantity * plan.regularRate + plan.regularServiceCharge;
// ==================重构后==================
if (summer())
   charge = summerCharge();
else
   charge = regularCharge();
function summer() {
   return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}
function summerCharge() {
   return quantity * plan.summerRate;
}
function regularCharge() {
   return quantity * plan.regularRate + plan.regularServiceCharge;
}
// 进一步优化(使用三元运算符)
charge = summer() ? summerCharge() : regularCharge();
function summer() {
   return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}
function summerCharge() {
   return quantity * plan.summerRate;
}
function regularCharge() {
   return quantity * plan.regularRate + plan.regularServiceCharge;
}
复制代码

合并条件表达式

将多个条件表达式合并

where

当发现这样一串条件检查:检查条件各不相同, 最终行为却一致。如果发现这种情况,就应该使用“逻辑或”和“逻辑与”将它们合并为一个条件表达式。

// ==================重构前==================
if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;
// ==================重构后==================
if (isNotEligableForDisability()) return 0;
function isNotEligableForDisability() {
   return ((anEmployee.seniority < 2)
           || (anEmployee.monthsDisabled > 12)
           || (anEmployee.isPartTime));
}
复制代码

以卫语句取代嵌套条件表达式

如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”(guard clauses)。

where

如果使用if-else结构,你对if分支和else分支的重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。卫语句就不同了,它告诉阅读者:“这种情况不是本函数的核心逻辑所关心的, 如果它真发生了,请做一些必要的整理工作,然后退出。” 为了传递这种信息可以使用卫语句替换嵌套结构。

// ==================重构前==================
function payAmount(employee) {
   let result;
   if(employee.isSeparated) {
       result = {amount: 0, reasonCode:"SEP"};
   }
   else {
       if (employee.isRetired) {
           result = {amount: 0, reasonCode: "RET"};
       }
       else {
           result = someFinalComputation();
       }
   }
   return result;
}
// ==================重构后==================
function payAmount(employee) {
   if (employee.isSeparated) return {amount: 0, reasonCode: "SEP"};
   if (employee.isRetired) return {amount: 0, reasonCode: "RET"};
   return someFinalComputation();
}
复制代码

将查询函数和修改函数分离

将查询动作从修改动作中分离出来的方式

where

如果遇到一个“既有返回值又有副作用”的函数,此时可以将查询动作从修改动作中分离出来。

// ==================重构前==================
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);
// ==================重构后==================
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);
复制代码

Recommend

  • 113
    • 掘金 juejin.im 7 years ago
    • Cache

    重构 - 代码整洁之道

    作者简介 新茗 蚂蚁金服·数据体验技术团队 前言 之前也介绍过我们团队的前端项目从零开始经历8个月迭代业务代码10万行(仅为产品长期规划需求的20%),至今仍然在不断迭代的过程。 团队成员除了设计好的架构来管理这种复杂度极高的前端应用,还开始补充设计模式以及

  • 33
    • blog.720ui.com 6 years ago
    • Cache

    代码分层的设计之道

    分层思想,是应用系统最常见的一种架构模式,我们会将系统横向切割,根据业务职责划分。MVC 三层架构就是非常典型架构模式,划分的目的...

  • 55

  • 57
    • 掘金 juejin.im 6 years ago
    • Cache

    JavaScript 代码简洁之道

    测试代码质量的唯一方式:别人看你代码时说 f * k 的次数。 代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。 本文并不是代码风格指南,而是关于代码的可读性、复用性、扩展性探讨。 我们将从几个方面展开讨论: 变...

  • 47
    • 微信 mp.weixin.qq.com 6 years ago
    • Cache

    Knative:精简代码之道

  • 61

     很早就阅读过《代码整洁之道》(英文版Clean Code),当时博主是个青涩的菜鸟,正在为团队创造着混乱的代码。多年的工作中,屡次被别人的代码坑的苦不堪言,回想起当年我留下的代码,肯定也坑害了后来的同僚。当阅读JDK源码或者其他优秀开源...

  • 53
    • www.tuicool.com 5 years ago
    • Cache

    代码之美,正则之道

    导语 “如果罗列计算机软件领域的伟大发明,我相信绝对不会超过二十项,在这个名单当中,当然应该包括分组交换网络,Web,Lisp,哈希算法,UNIX,编译技...

  • 7
    • blogread.cn 3 years ago
    • Cache

    小文件优化之道-文件成组

    小文件优化之道-文件成组 浏览:2746次  出处信息 昨天在群中,又有很多人在问,我的服务器跑不上量,我的服务器只能...

  • 1

    有的放矢,远程操控中实时音视频的优化之道发布于 今天 10:53 点击

  • 18

    一、背景介绍随着vivo业务迁移到容器平台,vivo云原生监控体系面临着指标量快速上涨带来的一系列挑...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK