JavaScript代码优化技巧示例详解

目录
  • 引言
  • 提炼函数
  • 函数参数化
  • 使用策略模式替换“胖”分支
  • 提炼变量
  • 内联变量
  • 封装变量
  • 拆分阶段
  • 拆分循环
  • 拆分变量
  • 分解条件表达式
  • 合并条件表达式
  • 以卫语句取代嵌套条件表达式
  • 将查询函数和修改函数分离

引言

我们先引入一句话:

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

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

提炼函数

what

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

where

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

how

// ==================重构前==================
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("***********************");
}

函数参数化

what

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

where

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

how

// ==================重构前==================
// 点击异常项
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}
    })
}

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

what

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

where

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

how

// ==================重构前==================
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)
}

提炼变量

what

提炼局部变量替换表达式

where

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

how

// ==================重构前==================
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;
}

内联变量

what

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

where

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

how

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

封装变量

what

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

where

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

how

// ==================重构前==================
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"});

拆分阶段

what

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

where

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

how

// ==================重构前==================
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;
}

拆分循环

what

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

where

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

how

// ==================重构前==================
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));
}

拆分变量

what

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

where

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

how

// ==================重构前==================
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);

分解条件表达式

what

将条件表达式提炼成函数

where

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

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

  • 提高可读性
  • 可以突出条件逻辑, 更清楚地表明每个分支的作用
  • 突出每个分支的原因

how

// ==================重构前==================
// 计算一件商品的总价,该商品在冬季和夏季的单价是不同的
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;
}

合并条件表达式

what

将多个条件表达式合并

where

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

how

// ==================重构前==================
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));
}

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

what

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

where

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

how

// ==================重构前==================
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();
}

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

what

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

where

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

how

// ==================重构前==================
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);

以上就是JavaScript代码优化技巧示例详解的详细内容,更多关于JavaScript优化技巧的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS前端性能优化解决内存泄漏页面崩溃

    目录 发生什么事了? 咋了?这是咋了? 死去的页面突然攻击我? 陷入僵局 垂死病中惊坐起 勿以善小而不为 修改参考 发生什么事了? 一个与往常一样的上午,当我沉浸在业务需求中不可自拔时,突然被拉入到一个事故大群.一脸懵逼的我还以为和之前的每次线上bug一样仅仅是个小问题时,殊不知是我简单了... 看着群里的聊天记录,瞬间一种不好的预感涌上心头.不会是哪个页面写了死循环了吧? 咋了?这是咋了? 死去的页面突然攻击我? 因为项目本身过于庞大,且用户反馈不特定页面崩溃,这使得问题定位难度较大. 经过团

  • 浅析JavaScript异步代码优化

    前言 在实际编码中,我们经常会遇到Javascript代码异步执行的场景,比如ajax的调用.定时器的使用等,在这样的场景下也经常会出现这样那样匪夷所思的bug或者糟糕的代码片段,那么处理好你的Javascript异步代码成为了异步编程至关重要的前提.下面我们从问题出发,一步步完善你的异步代码. 异步问题 1. 回调地狱 首先,我们来看下异步编程中最常见的一种问题,便是回调地狱.它的出现是由于异步代码执行时间的不确定性及代码间的依赖关系引发的,比如: // 一个动画结束后,执行下一个动画,下一个

  • JS尾递归的实现方法及代码优化技巧

    本文实例讲述了JS尾递归的实现方法及代码优化技巧.分享给大家供大家参考,具体如下: 在学习数据结构和算法的时候,我们都知道所有的递归都是可以优化成栈+循环的. 对于特定的递归函数,一般我们都是手动对它们进行优化的. 在学习scala的时候,接触到尾递归的概念.我们只要将递归写成尾递归方式,编译器会自动帮助我们优化. ps:并不是所有的递归都可以改写成尾递归 在js中,尾递归通常会被解释器优化.然而,并不是所有的js解释器都支持尾递归优化. 对于不支持尾递归优化的环境,我们需要手动将递归优化成栈+

  • JS代码优化技巧之通俗版(减少js体积)

    细读完这篇文章,够你优化大半天的了,关于JS优化方法大都脱离不了这三种方法. (网页总大小为155.k,而JS就占了100.3K) 是时候优化下JS了 关于JS优化的文章已经很多了,大多技术性很强,像什么变量.字符串.类型,就不做介绍了,也不需要介绍,我也不懂,你知道了也没用.大多数站长都是"拿来主义",我只需要告诉JS怎么放,删那里就可以了. 现在的网站都加的有统计代码.分享工具.评论列表.相关文章插件等工具,而要实现强大的功能,必须使用JS文件,正是这些JS插件,再增强了网站功能的

  • JS代码优化的8点建议

    一.松耦合 当修改一个组件而不需要更改其他组件时,就做到了松耦合 1.将JS从CSS中抽离:不要使用CSS表达式 //不好的做法 .box{width: expression(document.body.offsetWidth + 'px')} 2.将CSS从JS中抽离:通过JS修改CSS样式时,使用className或classList,不要逐条修改style样式 //不好的做法一 ele.style.color = 'red'; ele.style.left= '10px'; //不好的做法

  • vue项目打包优化方式(让打包的js文件变小)

    目录 我使用的命令是 vue ui 可视化打包操作 进入可视化面板 需要通过vue.config.js来配置 .js文件中,导致该js文件过大 通常在一个vue项目中会用到很多插件什么,swiper,axios,vuerouter,vuex,…,那么使用了很多插件势必会造成打包的js文件过大,影响加载速度,造成不好的用户体验,那么我就来讲一件我自己总结打包方式,(让js文件变小) 我使用的命令是 vue ui 可视化打包操作 进入可视化面板 默认情况下,vue-cli 3.0生成的项目,隐藏了w

  • JavaScript代码优化技巧示例详解

    目录 引言 提炼函数 函数参数化 使用策略模式替换“胖”分支 提炼变量 内联变量 封装变量 拆分阶段 拆分循环 拆分变量 分解条件表达式 合并条件表达式 以卫语句取代嵌套条件表达式 将查询函数和修改函数分离 引言 我们先引入一句话: 代码主要是为了写给人看的,而不是写给机器看的,只是顺便也能用机器执行而已. 代码和语言文字一样是为了表达思想.记载信息,所以写得清楚能更有效地表达.本文多数总结自<重构:改善既有代码的设计(第2版)>我们直接进入正题,上代码! 提炼函数 what 将一段代码提炼到

  • js前端上传文件缩略图技巧示例详解

    目录 引言 文件对象简介 Blob File FileReader FormData 文件对象之间的关系 缩略图的实现 总结 引言 通常情况下,前端提交给服务器的数据格式为JSON格式,但很多时候用户想上传自己的头像.视频等,这些非文本数据的时候,就不能直接以JSON格式上传到后端了. 当我们要获取用户上传的文件,可以使用input表单项,将type属性值设置为“file”. <form action=""> <input type="file"

  • MongoDB设计方法以及技巧示例详解

    前言 MongoDB是一种流行的数据库,可以在不受任何表格schema模式的约束下工作.数据以类似JSON的格式存储,并且可以包含不同类型的数据结构.例如,在同一集合collection 中,我们可以拥有以下两个文档document: { id: '4', name: 'Mark', age: '21', addresses : [ { street: '123 Church St', city: 'Miami', cc: 'USA' }, { street: '123 Mary Av', ci

  • Kotlin内存陷阱inline使用技巧示例详解

    目录 引言 错误示例 推荐示例 小结 总结 引言 inline ,翻译过来为 内联 ,在 Kotlin 中,一般建议用于 高阶函数 中,目的是用来弥补其运行时的 额外开销. 其原理也比较简单,在调用时将我们的代码移动到调用处使用,从而降低方法调用时的 栈帧 层级. 栈帧: 指的是虚拟机在进行方法调用和方法执行时的数据结构,每一个栈帧里都包含了相应的数据,比如 局部参数,操作数栈等等. Jvm在执行方法时,每执行一个方法会产生一个栈帧,随后将其保存到我们当前线程所对应的栈里,方法执行完毕时再将此方

  • JavaScript事件的委托(代理)的用法示例详解

    目录 简介 示例:事件委托 写法1:事件委托 写法2:每个子元素都绑定事件 示例:新增元素 写法1:事件委托 写法2:每个子元素都绑定事件 简介 说明 本文用示例介绍JavaScript中的事件(Event)的委托(代理)的用法. 事件委托简介 事件委托,也叫事件代理,是JavaScript中绑定事件的一种常用技巧.就是将原本需要绑定在子元素的响应事件委托给父元素或更外层元素,让外层元素担当事件监听的职务. 事件代理的原理是DOM元素的事件冒泡. 事件委托的优点 1.节省内存,减少事件的绑定 原

  • JavaScript中自带的 reduce()方法使用示例详解

    1.方法说明 , Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是: [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4) 2. 使用示例 'use strict'; function string2int(s){ if(!s){ alert('the params empty'); return; } if

  • JavaScript作用域示例详解

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域示例详解的介绍,希望能帮助大家更好的学习JavaScript. 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 一.JavaScript中无块级作用域 在Java或C#中存在块级作用域

  • JavaScript中的ajax功能的概念和示例详解

    AJAX即"Asynchronous Javascript And XML"(异步JavaScript和XML). 个人理解:ajax就是无刷新提交,然后得到返回内容. 对应的不使用ajax时的传统网页如果需要更新内容(或用php做处理时),必须重载整个网页页面. 示例: html代码如下 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>

  • 常用JavaScript正则表达式汇编与示例详解

    1.1 前言 目前收集整理了21个常用的javaScript正则表达式,其中包括用户名.密码强度.整数.数字.电子邮件地址(Email).手机号码.身份证号.URL地址. IP地址. 十六进制颜色. 日期. 微信号.车牌号.中文正则等.表单验证处理必备,赶紧收藏吧! 还会陆续加入新的正则进来,大家多提宝贵意见! 2.1 用户名正则 2.1.1 基本用户名正则 在做用户注册时,都会用到用户名正则校验. 定义基本用户名命名规则如下: 最短4位,最长16位 {4,16} 可以包含小写大母 [a-z]

  • C语言大厂面试技巧及strcpy()函数示例详解

    目录 1.什么是优秀的代码? 2.常见的coding技巧有哪些? 3.以模拟实现strcpy为例 (1)了解strcpy()函数 (2)正片开始(危) 1.第一阶段(面试官:最多5分) 2.第二阶段(面试官:最多7分) 3.第三阶段的代码:(面试官:最多8分) 4.第四阶段(面试官:完美代码!10分!) 1.什么是优秀的代码? 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全 2.常见的coding技巧有哪些? 1. 使用ass

随机推荐