无论如何,你都必须得知道的js知识(续)

lxf2023-03-17 10:46:01

前言

  大家好,我是前端贰货道士。在整理无论如何,你都必须得知道的js知识时,由于超出AdminJS字数限制,所以诞生了这篇文章。书接上文岁岁念第24点reduce函数剖析,如果本文对您有帮助,烦请大家一键三连哦, 蟹蟹大家~

碎碎念

  此分类用于记载我认为需要整理的js知识以及我不知道的js知识。因为是利用零碎时间去整理一些笔记,所以这篇文章后续会持续更新,有兴趣的小伙伴可以先收藏吃灰,哈哈哈。

24. reduce函数剖析(补充)

7. 利用reduce函数将二维数组转换为一维数组

let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre, cur) => pre.concat(cur), [])
console.log(newArr)  // [0, 1, 2, 3, 4, 5]

8. 利用reduce函数将多维数组转换为一维数组

let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
`相较于二维数组的转换,多了一层递归处理`
let newArr = arr => arr.reduce((pre,cur) => pre.concat(Array.isArray(cur) ? newArr(cur) : cur), [])
newArr(arr)  // [0, 1, 2, 3, 4, 5, 6, 7]

25. 关于js中的内存机制

  js栈内存和堆内存详解—图解基本数据类型和引用数据类型的区别。堆栈这种数据结构,具有先进后出,后进先出的特点,先明细几个概念:

  • 栈内存: 计算机为浏览器执行js代码,在内部开辟的空间,也被称为执行环境栈里面存放的是基本数据类型以及对象类型数据的引用地址,地址指向了堆内存里的对象内容。由于栈内存中存放的基础数据类型的大小是固定的,所以栈内存的内存都是操作系统自动分配和释放回收的。

  • 堆内存: 堆内存里存放的是除函数的引用类型的值,如数组和对象。由于堆内存所存大小不固定,系统无法自动释放回收,所以需要JS引擎来手动释放这些内存

  • 执行上下文: 大致分为三类

    a. 全局执行上下文,有一个全局对象window

    b. 函数级上下文:任何一个函数都有自己特有的执行上下文(函数里面的区域可以访问到函数外部的变量,但是函数外部的区域,是访问不到函数里面的)

    c. 块级上下文: 由let或者const加一个{}所组成的区域,就是一个块级上下文(括号区域外是无法访问到括号区域里面声明的let或者const变量的,但是可以访问到括号区域里面的var变量)

  • 变量对象:存储某个区块代码里声明的值和变量

  • 真实JS变量在堆栈中的存储:

function foo() {
    var a = 1
    var obj = {
        name: 'xiaoming'
    }
}

foo()

  原始类型的值会直接存储在上下文中,而上下文则存储在栈内存中;

  引用类型的值实际上会被存储在堆内存中,每一个值都对应着一个地址,然后在栈内存的执行上下文中将变量的值赋值成对应的地址。 无论如何,你都必须得知道的js知识(续)

  • 栈和堆的溢出:

a. 栈:当递归调用方法时,随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,直到内存不够分配,产生栈溢出。

b. 堆:循环创建对象,通俗点就是不断的new一个对象。

26. 调试小技巧

1. 打印时添加快照

import { cloneDeep } from 'lodash'

// 调试时,由于data是引用类型,在初始化时深层字段可能会发生变化,需要给个快照,记录打印时的真实data值
console.log("this.data", deepClone(this.data))

2. console.time()和console.timeEnd()必须成对出现,且文案需保持一致才能正常匹配及记录时间      

console.time('doSomething花费的时间为:')
...doSomething
console.timeEnd('doSomething花费的时间为:')

27. map(Number)、map(String)、map(Boolean)的使用

1. 使用map(Number)将数组中的每项数据转换为Number类型

let arr1 = [1, "2", 3]
arr1.map(Number)  // [1, 2, 3]

let arr2 = ['我是cxk', 18]
arr2.map(Number)  // [NaN, 18]

`扩展:判断在函数中传入的Number类型的形参个数`
function getLength(...data) {
  return data.map(Number).filter((num) => num || num === 0).length
}

getLength()   // 0
getLength(1, 2, 'dsadsa', 's', '3')  // 3
 
2. 同样,可以使用map(String)或map(Boolean)将数组中的每项数据转换为String或者Boolean类型

3. 扩展: 

ABCDEF16进制中对应101112131415
['1', '2', '3'].map(parseInt)结果解析:https://blog.csdn.net/weixin_44135121/article/details/88050214

4. let num = parseInt('1234abcd');  num //1234
   let num2 = parseInt('a1234');  num2 //NaN
   parseInt(12.6) // 12

28. 细品i = i + x()i = x() + i的区别

`1. i += x()可以转换为i = i + x(), 是先拿i的值, 再执行x()`

let i = 0

function x() { 
  i++ 
  return 10 
} 

i += x() 
console.log(i)  // 输出结果为10

`
解析:
   i += x() 可以转换为i = i + x(), 因为x()是放在i的右边, 还未执行, 所以计算的时候, i的值还未变化。
   因此结果为i = 0 + 10 = 0
`

`2. i = x() + i: 是先执行x(), 再加i`

let i = 0

function x() {
  i++
  return 10
}

i = x() + i
console.log(i)  // 输出结果为11

`
解析:
   i = x() + i, 是先执行x(), 再加i, x()执行后, i的值变为1。
   因此结果为i = 10 + 1 = 11
`

29. js偷懒小技巧

`1. 使用array.at()来获取数组元素:索引从0开始,需要传入数字,如果不是数字,会自动转换为数字`

const arr = [1, 3, 5, 6]

`最常用的功能:获取数组的最后一个值`
arr.at(-1) // 6

arr.at(0) // 1
`倒数第二个`
arr.at(-2) // 5
`取不到则为undefined`
arr.at(4) // undefined
arr.at("3123") // undefined
arr.at("大苏打实打实大") // 1
arr.at(NaN) // 1
`传入的如果是正浮点数,则会向下取整`
arr.at(1.4)  // 3
arr.at(1.7) // 3
`传入的如果是负浮点数,则会向上取整`
arr.at(-1.4) // 6
arr.at(-1.8) // 6
arr.at(-0.7) // 1

`2. ??的使用:与或比较类似,区别在于??只有在运算符左侧的值为`null`或`undefined`时,才返回右侧的值,否则返回左侧的值`
0 || 1  // 1
0 ?? 1  // 0

30. for infor of的碰撞

  • for in能够遍历对象和数组,对于对象得到的是key值, 对于数组得到的是索引index值。
  • for of无法遍历对象,能够遍历数组和带有iterator接口的,例如Set, Map, String, 得到的是value

遍历对象:

无论如何,你都必须得知道的js知识(续)

遍历数组:

无论如何,你都必须得知道的js知识(续)

31. 十几行递归算法,助你轻松比对国外系统多语言配置的差异

32. 字体翻译的自定义动态文案

  可以考虑定义$变量,使用replace方法,将静态文案使用动态变量替换,然后再用v-html解析需要展示的样式,比如:

`配置字体翻译:`

`1. 在en.js中:`

helpCenter: {
  results: '$total results for <span style="color: #384edb">"$keyWord"</span> in All Categories'
}

`2. 在zh-CN.js中:`

helpCenter: {
  results: '在所有分类中搜索到关于<span style="color: #384edb">“$keyWord”</span>的$total个结果'
}
<template>
  <div class="result" v-html="results"></div>
</template>

<script>
export default {
  props: {
    totalTitle: Number,
    keyWord: String
  },

  computed: {
    results({ totalTitle, keyWord }) {
      return this.$t('page.helpCenter.results').replace('$total', totalTitle).replace('$keyWord', keyWord)
    }
  }
}
</script>

<style lang="scss" scoped>
.result {
  color: $color-light-gray;
  font-size: 14px;
  margin-bottom: 40px;
}
</style>

效果浏览:

无论如何,你都必须得知道的js知识(续)

33. 谨防解构中的this陷阱

import msgpack5 from 'msgpack5'

const { encode, decode, register} = msgpack5()

`相当于window.register, register内置方法中,this指向的是window, 会报错`
register(0x42, MyType, this.mytipeEncode(data), this.mytipeDecode(data))

`register内置方法中this指向的是msgpack5(),不会报错`
msgpack5().register(0x42, MyType, this.mytipeEncode(data), this.mytipeDecode(data))

34. 使用定义好的方法替代map进行多层循环拿到数组数据

import { flatMapDeep, map } from 'lodash'

export function flatMapDeepByArray(data, mapArr = [], mapKeyArr = []) {
  let flatMapArr = []
  if (!mapArr.length) return []
  if (isPlainObject(data)) {
    const shiftData = data[mapArr.shift()]
    //传递进来的data是一个空对象,返回[]
    if(!shiftData) return []
    flatMapArr = Array.isArray(shiftData) ? shiftData : [shiftData]
  } else {
    flatMapArr = data
  }
  //重置mapKeyArr
  mapKeyArr = mapKeyArr.slice(0, mapArr.length)
  for (let i = 0; i < mapArr.length; i++) {
    flatMapArr = flatMapDeep(flatMapArr, (n) => {
      const arr = $GET(n, `${[mapArr[i]]}`, [])
      const sliceKeyArr = mapKeyArr.slice(0, i + 1)
      const sliceMapArr = mapArr.slice(0, i + 1)
      sliceKeyArr.map((key, k) => {
        arr.map((nItem) => {
          if (k == sliceMapArr.length - 1) {
            return (nItem[`$${key}`] = n)
          }
          nItem[`$${key}`] = n[`$${key}`]
        })
      })
      return arr
    })
  }
  return flatMapArr
}

// flatMapDeepByArray的第一个参数为对象, 第二个参数是一个数组,接收N个循环字段,以逗号隔开
// 先扁平化数组,再获取数组里面的某个属性,形成新的数组
const pictureList = map(
        flatMapDeepByArray(selectedData, ['customProductList', 'customShowImageList']),
        'showImagePath'
      )

35. 闭包中的内存泄漏问题

1.由于闭包中内层函数需要使用外层函数中定义的变量,这样就会导致函数作用域中的局部变量不会立即销毁。
因此,闭包也可以看做是外层函数中定义变量的生命的延续。当闭包达到一定数量,就会引起内存泄漏问题。

2.`垃圾回收机制的原理:
   https://blog.csdn.net/sheng0113/article/details/124366002
   https://blog.csdn.net/qq_35246620/article/details/80522720
   `
3. `可以利用debugger来调试,查看闭包中的方法和闭包中需要引用的变量`
function getClosure() {
  const obj = {
    name: 'cxk'
  }
  return () => {
    debugger
    console.log(`我是${obj.name}`)
    console.log('此处的闭包是getClosure')
  }
}
`当然, 一个函数如果多次嵌套函数并引用上个外层函数定义好的变量,也可以有多个闭包`

4. 当删除定义好的变量时,弱引用也会随之删除。因此,可以在页面初始化时,为需要排查可能造成内存泄漏的对象定义weakMap。
当weakMap中对应的key消失时,这也就意味着该对象变量也随之销毁。

<template>
  <div class="app-container system-home">
    {{ obj}}
    <el-button size="small" type="primary" @click="clickHandler">清除</el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      obj: {
        name: 'cxk'
      }
    }
  },

  mounted() {
    this.initWeakMap()
  },

  methods: {
    clickHandler() {
      this.obj = null
    },

    initWeakMap() {
      const weakMap = new WeakMap()
      // key必须为对象
      weakMap.set(this.obj, 'obj')
      setInterval(() => {
        console.log('weakMap', weakMap)
      }, 1000)
    }
  }
}
</script>

5. 在js中与WeakMap对应地,还存在一个强引用Map,它们之间的区别在于:
   (1) 强引用的对象不会被垃圾回收机制回收,但是弱引用对象会被回收。 
   (2) 强引用可能导致内存无法释放,造成内存泄漏;而弱引用不存在这个问题。
   (3) 强引用的key和value,都可以为任意类型;弱引用的key只能是对象,value可以为任意类型。

闭包情况截图如下:

无论如何,你都必须得知道的js知识(续)

  垃圾回收机制是存在一个周期的,它不会马上就回收垃圾,需要有一个过程。但是我们可以通过点击谷歌浏览器工具栏——内存上的回收垃圾图标,将它的垃圾回收过程提前触发。

无论如何,你都必须得知道的js知识(续)

36. 变量提升

变量与函数的变量提升传送门:https://blog.csdn.net/qq_43692768/article/details/117458927
letconst的暂时性死区:https://Admin.net/post/7153231797113847845

37. 类

  类,相当于vue中的混入。但是相比于混入,它可以通过new 类名,产生一个对应构造函数的原型对象。这个对象中定义的方法不会和其他混入中定义的同名方法相互污染,因为它有自己独特的作用域。

`类相关知识的传送门:https://blog.csdn.net/Han_Zhou_Z/article/details/122380478` 

38. 下载后端返回的单个二进制文件

1.我们需要在接口请求中使用`responseType: 'blob'`, 来获取后端返回的二进制文件

2.封装公共方法`downloadBlob`downloadBlob(blob, fileName) {
  `根据所提供的url地址,创建对象URL, 存储在内存中`
  const blobUrl = URL.createObjectURL(blob)
  `创建超链接标签link`
  const link = document.createElement('a')
  `将超链接标签link的跳转指向blobUrl`
  link.href = blobUrl
  `为超链接标签link下载的文件重命名`
  link.download = fileName || '下载文件'
  `触发超链接标签的点击事件,开始下载`
  link.click()
  `释放对象URL`
  URL.revokeObjectURL(blobUrl)
}

3. 使用封装好的公共方法:

async exportHandler(row) {
  const res = await declareApi.matchExport({
    batchIds: [row.id]
  })
  this.downloadBlob(res, `${row.batchCode}_${row.expressCompanyName}`)
}

39. 对象动态key值的赋予

const a = 'name'
const obj = {
  [a]: 'cxk'
}

obj.a  // undefined
obj.name // 'cxk'

40. 使用递归思想对比两个数据(只存在对象、数组和基本类型时)之间的差异

  使用递归,必须得明细以下二个规则:

  • 递归必须要有一个出口
  • 对于数组和对象这类可递归的数据,需要直接调递归方法本身,并接收递归方法返回的值,而剩下的逻辑交给出口处理就好
import { isPlainObject, isArray } from 'lodash'

data() {
    return {
      oldArr: [
        {
          a: 1,
          b: 2,
          c: {
            name: 'cxk'
          },
          d: [
            {
              name: 'cs'
            },
            1,
            2
          ]
        },
        {
          a: 2,
          b: 2,
          c: 3
        },
        7
      ],

      newArr: [
        {
          a: 1,
          b: 2,
          c: {
            name: 'cxk1'
          },
          d: [
            {
              name: 'cs1'
            },
            5,
            2
          ]
        },
        {
          a: 1,
          b: 4,
          c: 3
        },
        8
      ]
    }
}

`对传入的基本类型数据的数组进行简单比较:`
compare(oldArr, newArr) {
  const res = []
  const keyList = Object.keys(oldArr?.[0]) || []
  oldArr.map((item, index) => {
    keyList.map((key) => {
      if (item[key] != newArr[index][key]) {
        if (!res[index]) res[index] = {}
        res[index][key] = newArr[index][key]
        res[index]['index'] = index
      }
    })
  })
  return res.filter((bool) => bool)
}


`对数组、对象之类数据进行递归比较`
compare(oldVal, newVal) {
  const res = []
  if (isArray(oldVal)) {
    oldVal.map((item, index) => {
      const resArr = this.compare(item, newVal[index])
      if (resArr.length) {
        res.push(resArr)
      }
    })
  } else if (isPlainObject(oldVal)) {
    Object.keys(oldVal).map((key) => {
      const val = oldVal[key]
      const val1 = newVal[key]
      const objRes = this.compare(val, val1)
      if (objRes.length) {
        res.push(objRes)
      }
    })
  } else {
    if (oldVal !== newVal) {
      res.push(newVal)
    }
  }
  return res
}

无论如何,你都必须得知道的js知识(续)

41. 使用Promise.all处理element多表单校验问题

async beforeSubmit() {
  try {
    await this.$refs.orderRef.validate()
    `存放多个Promise`
    const pArr = this.formList.map(async (item, index) => {
      return await this.$refs[`form_${index}`]?.[0].validate()
    })
    `获取多个Promise的值,如果有一个Promise失败,则直接被catch方法捕获,
     如果没有失败的Promise,则直接返回一个数组,数组的长度为Promise的个数,数组中的每一个值都为true,
     如[true, true]
    `
    await Promise.all(pArr)
    return true
  } catch (error) {
    return false
  }
}

42. 在模态框弹出时,可以直接使用clearValidate方法消除element表单的初始校验

43. window.open参数详解

  • 下载文件:
window.open(
'zip地址',
'_self'
)
  • vue新开页面进行路由跳转
let routeUrl = this.$router.resolve({ name: 'exportRecords' })
window.open(routeUrl.href, '_blank')

44. http教程解析

45. 下载文件

`获取文件后缀名`
export function getFileSuffix(path = '') {
  path = path || ''
  const chaLastIndex = path.lastIndexOf('.')
  const name = path.slice(chaLastIndex + 1)
  return name
}

`转换为base64文件,减少网页请求`
export async function getURLBase64(url, config = {}) {
  let res = await getURLData(url, (config = {}))
  if (res) return res.target.result
  return res
}

export function getURLData(url, config = {}) {
  return axios
    .get(url, {
      `因为是文件,所以使用blob流`
      responseType: 'blob',
      ...config
    })
    .then((res) => {
      const { status, data } = res || {}
      if (status >= 200 && status < 300) {
        const fileReader = new FileReader()
        const p = new Promise((resolve, reject) => {
          `订阅必须最快,需要先订阅图片的加载事件,再读取图片文件`
          `因为如果极限情况下,文件读取这一过程很快就结束了,此时还没订阅,则无法触发图片的加载事件`
          fileReader.onloadend = function (e) {
            e.data = data
            e.size = data.size
            resolve(e)
          }
        })
        `以url的形式读取图片文件`
        fileReader.readAsDataURL(data)
        return p
      }
    })
    .catch((err) => {
      const { message } = err
      if (message && message.cancelMessage) {
        Message.success('取消下载成功')
      }
      console.log(err)
      return false
    })
}

`下载主方法`
export async function downloadImageByAixos(src, name) {
  if (name === undefined) {
    let tempArr = src.split('/')
    name = tempArr[tempArr.length - 1].split('.')[0]
  }
  const suffix = getFileSuffix(src)
  const url = await getURLBase64(src)
  if (!url) return Message.warning('下载失败')

  `1. 生成一个a元素`
  var a = document.createElement('a')

  `2. 将a的download属性设置为我们想要下载的图片名称,若name不存在则使用‘下载图片名称’作为默认名称`
  a.download = name + '.' + suffix // one是默认的名称
  
  `3. 设置文件转换为base64格式的跳转路径`
  a.href = url
  
  `4(第一种方法):直接触发a的点击事件`
  a.click()
  
  `(第二种方法):创建一个单击事件`
  var event = new MouseEvent('click')
  // 触发a的单击事件
  a.dispatchEvent(event)
  
  return true
}

46. 加载所有图片文件

// 打印时,需要先加载所有图片
export function loadAllImages(images) {
  const promises = Array.from(images).map((image) => {
    `此处判断一种图片路径和网页打开路径不同的情况`
    if (image.src && image.src !== window.location.href) {
      return loadImage(image)
    }
  })
  return Promise.all(promises)
}

export function loadImage(image) {
  return new Promise((resolve) => {
    try {
      if (image && (typeof image.naturalWidth === 'undefined' || image.naturalWidth === 0 || !image.complete)) {
        `图片加载成功,就reslove`
        image.onload = resolve
        `此处有处理图片加载失败的情况,使用默认图片进行占位。所以在后续catch时,直接reslove`
        `而不需要在Promise.all后,再使用catch方法对图片加载失败的情况进行处理`
        image.onerror = () => {
          image.src = defaultImg
        }
      } else {
        resolve()
      }
    } catch (e) {
      resolve()
    }
  })
}

47. 关于html代码中input标签的写法

  • 事件的写法:on小写事件名 = "点击事件方法()",方法必须带有括号。参数可以为空,或者为 event,又或者是 this(指向input标签本身)
  • dom的简写: 在原生 html 代码的 js 模块,可以直接使用 html上的id 名称(哪怕这个名称未定义过) 获取对应的 html dom(相当于querySelectorAll,获取所有和这个id相同的dom元素)
  • 多选的支持: 直接在 input 标签上使用 multiple 即可

48. 开发工具网站(JSON格式化、base64转换、pdf转图片)

49. 浏览器上的图像转换(经典)

  MDN-blob的定义

  给定一个url, 经过aixos请求(responseType: 'blob'), 就可以得到一个blob(二进制大文件)

无论如何,你都必须得知道的js知识(续)

无论如何,你都必须得知道的js知识(续)

结语

  大概就这样吧~