简介
整理一下遇到的与学习到的JS编码优化方式,可提升性能。
具体实现
1. 使用for循环
如果arr.length需要变化则使用++,否则使用–
i–操作本身会影响CPSR(当前程序状态寄存器),CPSR常见的标志有N(结果为负), Z(结果为0),C(有进位),O(有溢出)。i > 0,可以直接通过Z标志判断出来。
i++操作也会影响CPSR(当前程序状态寄存器),但只影响O(有溢出)标志,这对于i < n的判断没有任何帮助。所以还需要一条额外的比较指令,也就是说每个循环要多执行一条指令。
2. 减少if…else
else if过多,影响读代码的速度,验证情况不方便,也不简洁美观,就可以通过几种方式进行优化。
if (a = 1) {
// ...
} else if (a = 2) {
// ...
} else if (a = 3) {
// ...
} else {
// ...
}
2.1 方式一:switch
switch (a) {
case 1:
// ...
break
case 2:
// ...
break
case 3:
// ...
break
default:
// ...
break
}
2.2 方式二:map结构存储已知情况
这种方式有一个更好的优点是每次只需要改动loopMap对象的属性值。
const loopMap = {
NONE: 0,
ONE: 1,
TWO: 2,
THREE: 3
}
const getCode = (key) => {
return loopMap[key]
}
console.log(getCode('NONE')) // 0
3. 简写循环
3.1 方式一:filter与map过滤数据,取代冗余循环
const data = [{ name: 'a', age: 1 }, { name: 'b', age: 2 }, { name: 'c', age: 3 }]
const names = data.filter(i => i.age > 2).map(i => i.name)
console.log(names) // ['c']
3.2 方式二:some/every/find,取代冗余循环
a、some()
不创建新数组
不改变原数组
判断为true,则马上跳出循环,并return成true, 否则 return false
b、every()
不创建新数组
不改变原数组
判断为false,则马上跳出循环,并return成false,否则 return true
c、find()
不创建新数组
不改变原数组
输出的是,一旦判断为true,则跳出循环,并输出符合条件的数组元素
// 都是条件判断,用法类似,暂时只举例find
const data = [{ name: 'a', age: 1 }, { name: 'b', age: 2 }, { name: 'c', age: 3 }]
const result = data.find(i => i.name === 'a' && i.age === 1)
console.log(result) // {name: 'a', age: 1}
3.3 方式三:includes,判断是否包含指定元素
const data = [1, 2, 3]
const isHaveOne = data.includes(1)
console.log(isHaveOne) // true
3.4 方式四:解构赋值(…),合并对象、数组
// 对象
const obj1 = {
a: 1,
b: 3,
}
const obj2 = {
b: 2
}
const obj = {...obj1, ...obj2} // 元素相同时,后者会覆盖前者,如b
console.log(obj) // {a: 1, b: 2}
// 数组
const arr1 = [1, 2, 3]
const arr2 = [1, 2, 4]
// 一般是这样
// const arr = [...arr1, ...arr2]
// 但是要考虑元素可能重复,所以需要(...new Set())去重,避免不必要的问题
const arr = [...new Set([...arr1, ...arr2])]
console.log(arr) // [1, 2, 3, 4]
3.5 方式五:Object.assign(),合并对象
Object.assign()
该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign(target, …sources)【target:目标对象】,【souce:源对象(可多个)】。
// 对象有重复元素时
const obj01 = { a: 1, b: 1 }
const obj02 = { b: 2, c: 2 }
const obj03 = { c: 3 }
const objAll = Object.assign({}, obj01, obj02, obj03)
console.log(objAll) // {a:1, b:2, c:3}
注意:
1.如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性,所以一般用以合并对象,不用来合并数组;
2.Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。
3.6 方式六:flat,数组的扁平化处理
// 此处举例:获取对象中所有属性值,集合到一个数组中
const deps = {
'a': [1,2,3],
'b': [4,5,6],
'c': [7,8],
otherArr: [
[11,12],
[13],
[]
],
otherObj: {
'd': [15, 16],
'f': []
}
}
const arr = Object.values(deps).flat(Infinity) // 使用Infinity作为flat的参数,使得无需知道被扁平化的数组的维度
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, {'d': [15, 16], 'f': []}]
4. function注意点
4.1 方法、变量的命名规范,可读性
一般取函数名,都是偏向于该函数的功能,为了更清晰,可以加前缀、后缀。
但是需要注意,单词的准确性,缩写后的易理解。
4.2 函数返回值时
我们通常在写一些拥有返回值的函数的时候常常会以返回值变量命名而纠结,甚至对于一些长函数的时候还不使用变量而是直接return,这样的习惯其实是不好的,因为等我们下次再去参照这段代码的时候还需要重新捋清逻辑。
通常的,在一个小函数中,我们可以使用result作为返回值。
getUrl (content, prefix, suffix) {
let result = content
if (prefix) {
result = `${prefix}${result}`
}
if (suffix) {
result = `${suffix}${result}`
}
return result
}
4.3 提前返回
然而上面用result作为返回值并不适用于所有情况,往往有些时候我们需要提前结束函数体来避免后面的同事阅读多余的程序。
// 普通写法
activeParentKeys(selectedkey) {
let result = []
if (selectedKey) {
if (selectedkey !== 'key1'){
result = getParentKeys(selectedkey)
}
return result
}
if (selectedKey) {
result = getParentKeys(selectedkey)}
}
return result
}
// 优化
activeParentKeys(selectedkey) {
if (!selectedKey) return []
if (selectedKey !== 'key1'){
return getParentKeys(selectedKey)
}
return []
}
4.4 保持参数对象的完整
let data = {}
// 普通传参方式
getSomeDetail(icon, content) {
console.log(icon, content)
}
getSomeDetail(data.icon, data.content)
// 优化:使用对象解构的方式
getSomeDetail(data) {
let {icon, content} = data // 函数名不是想要的时,可以重命名,如 { iconClass: icon, text: content } = data
console.log(icon, content)
}
getSomeDetail(data)
// 拓展:对象解构时,是可以给默认值的
more(data) {
let {title = 'Default'} = data
console.log(title) // Default
}
more(data)
5. 巧用运算符
5.1 三元运算符(?)
// 普通
let v = null
if (a) {
v = a
} else if (b) {
v = b
} else {
v = ''
}
console.log(v)
// 优化
let v = a ? a : b ? b : ''
console.log(v)
5.2 可选链操作符 (?.)
// 普通
let v = null
if (a && a.b && a.b.c) {
v = a.b.c
}else {
v = ''
}
console.log(v)
// 优化
let v = a?.b?.c || '' // 等价于 a && a.b && a.b.c
console.log(v)
5.3 空值合并操作符 (??)
// 普通
let v = null
if (a !== null && a !== undefind) {
v = a
} else {
v = ''
}
console.log(v)
// 优化
let v = a ?? '' // 等价于(a !== undefined && a !== null) ? a : ''
console.log(v)
5.4 a && b || c
// 普通
let v = null
if (a && b) {
v = b
} else {
v = c
}
console.log(v)
// 优化
let v = a && b || c // 等价于a ? b ? b : c : c
console.log(v)
5.5 模板字符窜(`${}
`)
注意 前 与 后的符号:反引号(`)。
它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通
let user = {
name: 'AAA',
age: 10
}
let info = '我是' + user.name + ',今年' + user.age + '岁了。'
console.log(info)
// 优化
let info = `我是${user.name},今年${user.age}岁了。`
console.log(info)
// 模板字符窜支持内容换行,即定义多行字符串
let info = `
name: a,
age: 1
`
console.log(info)
6. 多使用const、let,少用var
ES6 增加这两个关键字不仅有助于改善代码风格, 而且同样有助于改进垃圾回收的过程。 因为 const 和 let 都以块( 而非函数) 为作用域, 所以相比于使用 var, 使用这两个新关键字可能会更早地让垃圾回收程序介入, 尽早回收应该回收的内存。 在块作用域比函数作用域更早终止的情况下, 这就有可能发生。
7. 避免DOM的循环引用
如下就是循环引用
let element = document.getElementById(" some_element");
let myObject = new Object();
myObject.element = element;
element.someObject = myObject;
由于存在循环引用, 因此 DOM元素的内存永远不会被回收, 即使它已经被从页面上删除了也是如此。
当然,JS中也减少循环引用,避免预期外的问题。
8. 清除变量的引用值
垃圾回收程序会周期性运行, 如果内存中分配了很多变量, 则可能造成性能损失, 因此垃圾回收的时间调度很重要。 尤其是在内存有限的移动设备上, 垃圾回收有可能会明显拖慢渲染的速度和帧速率。开发者不知道什么时候运行时会收集垃圾, 因此最好的办法是在写代码时就要做到: 无论什么时候开始收集垃圾, 都能让它尽快结束工作。
变量使用完毕后(不再用到时),把变量设置为null,就会切断变量与其之前引用值之间的关系,当下次垃圾回收程序运行时, 这些值就会被删除, 内存也会被回收。
ES6新特性可参考:ES6之新特性汇总
最后
觉得有用的朋友请用你的金手指点一下赞,或者评论留言一起探讨技术!