JavaScript开发技巧

1.初始化数组(初始化一个指定长度的一维数组,并指定默认值)

  const array=Array(5).fill('')
  // ['','','','','']
  //Array().fill() 的方式,在数据量大的时候,性能远不如写一个 for 循环往空数组 push 

2.初始化一个指定长度的二维数组,并指定默认值

const matrix = Array(6).fill(0).map(() => Array(5).fill(0)); 
// [[0, 0, 0, 0, 0], 
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0], 
// [0, 0, 0, 0, 0], 
// [0, 0, 0, 0, 0],
// [0, 0, 0, 0, 0]]

3.数组求和、求最大值、最小值

const array = [5,4,7,8,9,2];
// 求和
array.reduce((a,b) => a+b);
// 最大值
array.reduce((a,b) => a > b ? a : b);
Math.max(...array)
// 最小值
array.reduce((a,b) => a < b ? a : b);
Math.min(...array)

4.过滤错误值(滤数组中的false、0、null、undefined等值)

const array = [1, 0, undefined, 6, 7, '', false];
array.filter(Boolean); // [1, 6, 7]

5.使用逻辑运算符

// 如果有一段这样的代码:
if(a > 10) { doSomething(a) }
// 可以使用逻辑运算符来改写:
a > 10 && doSomething(a)
// 这样写就会简洁很多,如果逻辑与&&操作符前面的值为假,就会发生短路操作,直接结束这一句的执行;如果为真,就会继续执行&&后面的代码,并返回后面代码的返回值。使用这种方式可以减少很多if...else判断。

6.判断简化

// 如果有下面的这样的一个判断:
if(a === undefined || a === 10 || a=== 15 || a === null) { //... }
// 就可以使用数组来简化这个判断逻辑:
if([undefined, 10, 15, null].includes(a)) { //... }

7.清空数组

// 如果想要清空一个数组,可以将数组的length置于0:
let array = ["A", "B", "C", "D", "E", "F"] 
array.length = 0 console.log(array) // []

7. 计算代码性能

// 可以使用以下操作来计算代码的性能:
const startTime = performance.now(); 
// 某些程序 
for(let i = 0; i < 1000; i++) { console.log(i) }
const endTime = performance.now();
const totaltime = endTime - startTime; 
console.log(totaltime); // 30.299999952316284

8. 拼接数组

// 如果我们想要拼接几个数组,可以使用扩展运算符:
const start = [1, 2] 
const end = [5, 6, 7] 
const numbers = [9, ...start, ...end, 8] // [9, 1, 2, 5, 6, 7 , 8]
// 或者
start.concat(end);
// 但是使用concat()方法时,如果需要合并的数组很大,那么concat() 函数会在创建单独的新数组时消耗大量内存。这时可以使用以下方法来实现数组的合并:
Array.prototype.push.apply(start, end)

9. 对象验证方式

// 如果我们有一个这样的对象:
const parent = {
    child: {
      child1: {
        child2: {
          key: 10
      }
    }
  }
}
// 很多时候我们会这样去写,避免某一层级不存在导致报错:
parent && parent.child && parent.child.child1 && parent.child.child1.child2
// 这样代码看起来就会很臃肿,可以使用JavaScript的可选链运算符:
parent?.child?.child1?.child2
// {key:10}
// 可选链运算符同样适用于数组:
const array = [1, 2, 3]; array?.[1]
// 2

10. 验证undefined和null

// 如果有这样一段代码:
if(a === null || a === undefined) {
    doSomething()
}
// 可以使用空值合并运算符来简化上面的代码:
a ?? doSomething()
// 这样,只有a是undefined或者null时,才会执行控制合并运算符后面的代码。空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

11. 数组元素转化为数字

// 如果有一个数组,想要把数组中的元素转化为数字,可以使用map方法来实现:
const array = ['12', '1', '3.1415', '-10.01'];
array.map(Number);  // [12, 1, 3.1415, -10.01]
// 通过这种方式,map会在遍历数组时,对数组的每个元素执行Number构造函数并返回结果。

12. 类数组转为数组

// 可以使用以下方法将类数组arguments转化为数组:
Array.prototype.slice.call(arguments);
[...arguments]

13. 对象动态声明属性

// 如果想要给对象动态声明属性,可以这样
const dynamic = 'color';
var item = {
    brand: 'Ford',
    [dynamic]: 'Blue'
}
console.log(item); 
// { brand: "Ford", color: "Blue" }

14. 缩短console.log()

const c = console.log.bind(document) 
c(996) 
c("hello world")

15. 获取查询参数

// 如果我们想要获取URL中的参数,可以使用window对象的属性:
window.location.search
// 如果一个URL为www.bai.com?project=js&type=1 那么通过上面操作就会获取到?project=js&type=1。如果在想获取到其中某一个参数,可以这样:
// let type = new URLSearchParams(location.search).get('type');

16. 数字取整

//如果有一个数字包含小数,我们想要去除小数,通过会使用math.floor、math.ceil或math.round方法来消除小数。其实可以使用~~运算符来消除数字的小数部分,它相对于数字的那些方法会快很多。
~~3.1415926 // 3
// 其实这个运算符的作用有很多,通常是用来将变量转化为数字类型的,不同类型的转化结果不一样:
-   如果是数字类型的字符串,就会转化为纯数字;
-   如果字符串包含数字之外的值,就会转化为0;
-   如果是布尔类型,true会返回1,false会返回0;
// 除了这种方式之外,我们还可以使用按位与来实现数字的取整操作,只需要在数字后面加上`|0`即可:
 23.9 | 0   // 23
-23.9 | 0   // -23
// 这个操作也是直接去除数字后面的小数。这个方法和上面方法类似,使用起来性能都会比JavaScript的的API好很多。

17. 删除数组元素

const array = ["a", "b", "c", "d"] 
array.splice(0, 2) // ["a", "b"]

18. 检查对象是否为空

Object.keys({}).length  // 0
Object.keys({key: 1}).length  // 1
// Object.keys()方法用于获取对象的 key,会返回一个包含这些key值的数组。如果返回的数组长度为0,那对象肯定为空了。

19. 使用 switch case 替换 if/else

switch case 相对于 if/else 执行性能更高,代码看起来会更加清晰。
if (1 == month) {days = 31;}
else if (2 == month) {days = IsLeapYear(year) ? 29 : 28;}
else if (3 == month) {days = 31;}
else if (4 == month) {days = 30;} 
else if (5 == month) {days = 31;} 
else if (6 == month) {days = 30;} 
else if (7 == month) {days = 31;} 
else if (8 == month) {days = 31;} 
else if (9 == month) {days = 30;} 
else if (10 == month) {days = 31;} 
else if (11 == month) {days = 30;} 
else if (12 == month) {days = 31;} 
// 使用switch...case来改写:
switch(month) {
        case 1: days = 31; break;
        case 2: days = IsLeapYear(year) ? 29 : 28; break;
        case 3: days = 31; break;
        case 4: days = 30; break;
        case 5: days = 31; break;
        case 6: days = 30; break;
        case 7: days = 31; break;
        case 8: days = 31; break;
        case 9: days = 30; break;
        case 10: days = 31; break;
        case 11: days = 30; break;
        case 12: days = 31; break;
        default: break;
}
// 更优的方法: 策略模式
// 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。它提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
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
const commodity = new Map([
  ['phone', 1999],
  ['computer', 9999],
  ['television', 2999],
  ['gameBoy', 3999],
])

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

20. 获取数组中的最后一项

arr.slice(-1)
// 当我们将slice方法的参数设置为负值时,就会从数组后面开始截取数组值,如果我们想截取后两个值,参数传入-2即可。
// [].slice(-1) 可以获取最后一项,需要注意这是数组

21. 值转为布尔值

// 在JavaScript中,以下值都会在布尔值转化时转化为false,其他值会转化为true:
-   undefined
-   null
-   0
-   -0
-   NaN
-   ""
// 通常我们如果想显式的值转化为布尔值,会使用Boolean()方法进行转化。其实我们可以使用!!运算符来将一个值转化我布尔值。我们知道,一个!是将对象转为布尔型并取反,两个!是将取反后的布尔值再取反,相当于直接将非布尔类型值转为布尔类型值。这种操作相对于Boolean()方法性能会快很多,因为它是计算机的原生操作:
!!undefined // false
!!"996"     // true
!!null      // false
!!NaN       // false

24. 函数参数使用对象而不是参数列表

function getItem(price, quantity, name, description) {}
getItem(15, undefined, 'bananas', 'fruit')
// 使用对象传参:
function getItem(args) {
    const {price, quantity, name, description} = args
}

getItem({
    name: 'bananas',
    price: 10,
    quantity: 1, 
    description: 'fruit'
})

25. 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 '你的身份不合法'
  }
}
// 优化
function verifyIdentity(identityId) {
  if ([1, 2, 3, 4].includes(identityId)) {
    return '你的身份合法,请通行!'
  } else {
    return '你的身份不合法'
  }
}

for 循环

// 在 JavaScript 中,我们可以使用 `for()`, `while()`, `for(in)`,`for(of)`几种循环,事实上,这三种循环中 `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`。

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 单页面应用,声明的全局变量在切换页面的时候没有清空
<div id="home">
    // 这里是首页
  </div>
  export default {
    mounted() {
      window.test = {
        // 此处在全局window对象中引用了本页面的dom对象
        name: 'home',
        node: document.getElementById('home')
      }
    }
  }
 // 解决方案: 在页面卸载的时候顺便处理掉该引用。
 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))

原作者:https://juejin.cn/post/7039142750503534599?utm_source=gold_browser_extension

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。