前端优化代码

更新日期: 2020-07-20阅读: 2.2k标签: 代码

所谓无规矩不成方圆,前端时间在团队 code-review 中发现,不同时期不同开发人员写的代码可谓五花八门。因此我们提出了一些相关代码方面的规范,希望日后能形成团队的编码规范。

本文主要针对一些 JavaScript 进行优化,使之更加健壮,可读性更强,更以维护。

gitthub地址:https://github.com/Michael-lzg/my--article/blob/master/other/前端代码优化.md
上一篇:code-review之前端代码规范


if 判断的优化

JavaScript 条件语句在我们平时的开发中是不可避免要用到的,但是很多时候我们的代码写的并不好,一连串的 if-else 或者多重嵌套判断都会使得代码很臃肿,下面举例进行优化。

需求:现在有 4 个产品,分别是手机、电脑、电视机、游戏机,当然每个产品显示的价格不一样。

1、最简单的方法:if 判断

let commodity = {
  phone: '手机',
  computer: '电脑',
  television: '电视',
  gameBoy: '游戏机',
}

function price(name) {
  if (name === commodity.phone) {
    console.log(1999)
  } else if (name === commodity.computer) {
    console.log(9999)
  } else if (name === commodity.television) {
    console.log(2999)
  } else if (name === commodity.gameBoy) {
    console.log(3999)
  }
}
price('手机') // 9999

缺点:代码太长了,维护和阅读都很不友好

2、好一点的方法:Switch

let commodity = {
  phone: '手机',
  computer: '电脑',
  television: '电视',
  gameBoy: '游戏机',
}
const price = (name) => {
  switch (name) {
    case commodity.phone:
      console.log(1999)
      break
    case commodity.computer:
      console.log(9999)
      break
    case commodity.television:
      console.log(2999)
      break
    case commodity.gameBoy:
      console.log(3999)
      break
  }
}
price('手机') // 9999

3、更优的方法: 策略模式

策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。它提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。

const commodity = new Map([
  ['phone', 1999],
  ['computer', 9999],
  ['television', 2999],
  ['gameBoy', 3999],
])

const price = (name) => {
  return commodity.get(name)
}
price('phone') // 1999


includes 的优化

includes 是 ES7 新增的 api,与 indexOf 不同的是 includes 直接返回的是 Boolean 值,indexOf 则 返回的索引值, 数组和字符串都有 includes 方法。

需求:我们来实现一个身份认证方法,通过传入身份 Id 返回对应的验证结果

传统方法

function verifyIdentity(identityId) {
  if (identityId == 1 || identityId == 2 || identityId == 3 || identityId == 4) {
    return '你的身份合法,请通行!'
  } else {
    return '你的身份不合法'
  }
}

includes 优化

function verifyIdentity(identityId) {
  if ([1, 2, 3, 4].includes(identityId)) {
    return '你的身份合法,请通行!'
  } else {
    return '你的身份不合法'
  }
}


for 循环

在 JavaScript 中,我们可以使用 for(), while(), for(in),for(in)几种循环,事实上,这三种循环中 for(in) 的效率极差,因为他需要查询散列键,所以应该尽量少用。

for 循环是最传统的语句,它以变量 i 作为索引,以跟踪访问的位置,对数组进行操作。

var arr = ['a', 'b', 'c']
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]) //结果依次a,b,c
}

以上的方法有一个问题:就是当数组的长度到达百万级时,arr.length 就要计算一百万次,这是相当耗性能的。所以可以采用以下方法就行改良。

var arr = ['a', 'b', 'c']
for (var i = 0, length = arr.length; i < length; i++) {
  console.log(arr[i]) //结果依次a,b,c
}

此时 arr.length 只需要计算一次,优化了性能。

for-in 一般用来来遍历对象的属性的,不过属性需要 enumerable(可枚举)才能被读取到。同时 for-in 也可以遍历数组,遍历数组的时候遍历的是数组的下标值。

var obj = { 0: 'a', 1: 'b', 2: 'c' }
for (var key in obj) {
  console.log(key) //结果为依次为0,1,2
}

var arr = ['a', 'b', 'c']
for (var key in a) {
  console.log(key) //结果为依次为0,1,2
}

for-of 语句看着有点像 for-in 语句,但是和 for-of 语句不同的是它不可以循环对象,只能循环数组。

var arr = ['a', 'b', 'c']
for (var value of arr) {
  console.log(value) // 结果依次为a,b,c
}

for-of 比 for-in 循环遍历数组更好。for-of 只要具有 Iterator 接口的数据结构,都可以使用它迭代成员。它直接读取的是键值。for-in 需要穷举对象的所有属性,包括自定义的添加的属性也能遍历到。且 for-in 的 key 是 String 类型,有转换过程,开销比较大。

所以在开发过程中循环数组尽量避免使用 for-in。


数组去重

数组去重是实际开发处理数据中经常遇到的,方法有很多,这里就不一一例举了。

1、最传统的方法:利用数组的 indexOf 下标属性来查询。

function unique4(arr) {
  var newArr = []
  for (var i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i])
    }
  }
  return newArr
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]

2、优化:利用 ES6 的 Set 方法。

Set 本身是一个构造函数,用来生成 Set 数据结构。Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。

function unique4(arr) {
  return Array.from(new Set(arr)) // 利用Array.from将Set结构转换成数组
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]


箭头函数

箭头函数表达式的语法比函数表达式更简洁。所以在开发中更推荐使用箭头函数。特别是在 vue 项目中,使用箭头函数不需要在更 this 重新赋一个变量。

// 使用functions
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map(function (x) {
  return x * x
})
console.log(arrFunc)

// 使用箭头函数
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map((x) => x * x)

要注意的是,箭头函数不绑定 arguments,取而代之用 rest 参数…解决。

// 不能使用 arguments
let fun1 = (b) => {
  console.log(arguments)
}
fun1(2, 92, 32, 32) // Uncaught ReferenceError: arguments is not defined

// 使用rest 参数
let fun2 = (...c) => {
  console.log(c)
}
fun2(3, 82, 32, 11323) // [3, 82, 32, 11323]


dom 的创建

创建多个 dom 元素时,先将元素 append 到 DocumentFragment 中,最后统一将 DocumentFragment 添加到页面。

常规方法;

for (var i = 0; i < 1000; i++) {
  var el = document.createElement('p')
  el.innerhtml = i
  document.body.appendChild(el)
}

使用 DocumentFragment 优化多次 append

var frag = document.createDocumentFragment()
for (var i = 0; i < 1000; i++) {
  var el = document.createElement('p')
  el.innerHTML = i
  frag.appendChild(el)
}
document.body.appendChild(frag)

更优的方法:使用一次 innerHTML 赋值代替构建 dom 元素

var html = []
for (var i = 0; i < 1000; i++) {
  html.push('<p>' + i + '</p>')
}
document.body.innerHTML = html.join('')


内存泄漏

系统进程不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。当内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

引起内存泄漏的原因

全局变量

1、未声明变量或者使用 this 创建的变量(this 的指向是 window)都会引起内存泄漏

function fn() {
  a = "Actually, I'm a global variable"
}
fn()

function fn() {
  this.a = "Actually, I'm a global variable"
}
fn()

解决方法:

  • 避免创建全局变量
  • 使用严格模式,在 JavaScript 文件头部或者函数的顶部加上 use strict。

2、在 vue 单页面应用,声明的全局变量在切换页面的时候没有清空

<template>
  <div id="home">
    这里是首页
  </div>
</template>

<script>
  export default {
    mounted() {
      window.test = {
        // 此处在全局window对象中引用了本页面的dom对象
        name: 'home',
        node: document.getElementById('home')
      }
    }
  }
</script>

解决方案: 在页面卸载的时候顺便处理掉该引用。

destroyed () {
  window.test = null // 页面卸载的时候解除引用
}

闭包

闭包引起的内存泄漏原因:闭包可以维持函数内局部变量,使其得不到释放。

function fn() {
  var a = "I'm a"
  return function () {
    console.log(a)
  }
}

解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 dom 的引用。

定时器或事件监听

由于项目中有些页面难免会碰到需要定时器或者事件监听。但是在离开当前页面的时候,定时器如果不及时合理地清除,会造成业务逻辑混乱甚至应用卡死的情况,这个时就需要清除定时器事件监听,即在页面卸载(关闭)的生命周期函数里,清除定时器。

methods:{
  resizeFun () {
    this.tableHeight = window.innerHeight - document.getElementById('table').offsetTop - 128
  },
  setTimer() {
    this.timer = setInterval(() => { })
  },
  clearTimer() {//清除定时器
		clearInterval(this.timer)
    this.timer = null
	}
},
mounted() {
  this.setTimer()
  window.addEventListener('resize', this.resizeFun)
},
beforeDestroy() {
  window.removeEventListener('resize', this.resizeFun)
  this.clearTimer()
}


防抖与节流

在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。这时候就用到防抖与节流。

案例 1:远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的。

<Select :remote-method="remoteMethod">
    <Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
</Select>

<script>
function debounce(fn, wait) {
  let timeout = null
  return function () {
    if (timeout !== null) clearTimeout(timeout)
    timeout = setTimeout(fn, wait)
  }
}

export default {
  methods:{
    remoteMethod:debounce(function (query) {
        // to do ...
    }, 200),
  }
}
<script>

案例 2:持续触发 scroll 事件时,并不立即执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发一次 handle 函数。

function debounce(fn, wait) {
  let timeout = null
  return function () {
    if (timeout !== null) clearTimeout(timeout)
    timeout = setTimeout(fn, wait)
  }
}
function handle() {
  console.log(Math.random())
}
window.addEventListener('scroll', debounce(handle, 1000))


异步加载 js

默认情况下,浏览器是同步加载 js 脚本,解析 html 过程中,遇到 <script> 标签就会停下来,等脚本下载、解析、执行完后,再继续向下解析渲染。

如果 js 文件体积比较大,下载时间就会很长,容易造成浏览器堵塞,浏览器页面会呈现出“白屏”效果,用户会感觉浏览器“卡死了”,没有响应。此时,我们可以让 js 脚本异步加载、执行。

<script src="path/to/home.js" defer></script>
<script src="path/to/home.js" async></script>

上面代码中,<script> 标签分别有 defer 和 async 属性,浏览器识别到这 2 个属性时 js 就会异步加载。也就是说,浏览器不会等待这个脚本下载、执行完毕后再向后执行,而是直接继续向后执行

defer 与 async 区别:

  • defer:DOM 结构完全生成,以及其他脚本执行完成,才会执行(渲染完再执行)。有多个 defer 脚本时,会按照页面出现的顺序依次加载、执行。
  • async:一旦下载完成,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染(下载完就执行)。有多个 async 脚本时,不能保证按照页面出现顺序加载、执行


链接: https://fly63.com/article/detial/9479

不要浪费时间写完美代码

一个系统可以维持5年,10年,甚至20年以上,但是代码和设计模式的生命周期非常短,当对一个解决方案使用不同的方法进行迭代的时候,通常只能维持数月,数日,甚至几分钟的时间

Google内部在代码质量上的实践

良好的编程习惯涉及到很多方面,但在软件行业内,大多数的公司或组织都不会把良好的编程习惯列为主要关注点。 例如,具有可读性和可维护性的代码比编写好的测试代码或使用正确的工具更有意义,前者的意义在于可以让代码更易于理解和修改。

减少嵌套,降低代码复杂度

减少嵌套会让代码可读性更好,同时也能更容易的找出bug,开发人员可以更快的迭代,程序也会越来越稳定。简化代码,让编程更轻松!

关于 Google 发布的 JS 代码规范

Google为了那些还不熟悉代码规范的人发布了一个JS代码规范。其中列出了编写简洁易懂的代码所应该做的最佳实践。代码规范并不是一种编写正确JavaScript代码的规则,而是为了保持源代码编写模式一致的一种选择。

你解决的问题比你编写的代码更重要!

程序员似乎忘记了软件的真正目的,那就是解决现实问题。您编写的代码的目的是为了创造价值并使现有世界变得更美好,而不是满足您对自我世界应该是什么的以自我为中心的观点。有人说:如果你拥有的只是一把锤子,那么一切看起来都像钉子一样

tinymce与prism代码高亮实现及汉化的配置

TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着非常良好的支持。功能方强大,并且功能配置灵活简单。另一特点是加载速度非常快的。

js函数式编程与代码执行效率

函数式编程对应的是命令式编程, 函数式编程的核心当然是对函数的运用. 而高阶函数(Higher-order)是实现函数式编程的基本要素。高阶函数可以将其他函数作为参数或者返回结果。所以JS天生就支持函数式编程

接手代码太烂,要不要辞职?

朋友发表了一条说说:入职新公司,从重构代码到放弃”,我就问他怎么了?他说,刚进一家新公司,接手代码太烂,领导让我先熟悉业务逻辑,然后去修复之前项目中遗留的bug,实在不行就重构

js高亮显示关键词_页面、搜索关键词高亮显示

页面实现关键词高亮显示:在项目期间遇到一个需求,就是搜索关键词时需要高亮显示,主要通过正则匹配来实现页面关键词高亮显示。在搜索结果中高亮显示关键词:有一组关键词数组,在数组中筛选出符合关键字的内容并将关键字高亮

写优雅的代码,做优雅的程序员

软件工程学什么? 学计算机,写程序,做软件,当程序员。听说学计算机很辛苦? 是的,IT行业加班现象严重。在计算机世界里,技术日新月异,自学能力是程序员最重要的能力之一。选了这个专业,就要时刻保持好奇心和技术嗅觉,不能只满足于完成课内作业。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!