「面经」JavaScript 懒加载解析 & 手写题

这是我参与新手入门的第 1 篇文章

图片懒加载

前置知识

大多数 JavaScript 处理的是以下坐标系其中一种:

  • 相对于窗口:类似于 position: fixed,从 窗口 的顶部和两侧计算得出,用 clientX / clientY 表示
  • 相对于文档:类似于 position: absolute,从 文档 的顶部和两侧计算得出,用 pageX / pageY 表示

截屏2021-07-03 11.11.35.png

元素坐标

ele.getBoundingClientRect() 获取窗口坐标,eleDOMRect 对象,具有如下属性:

  • x / y矩形原点 相对于 窗口 的坐标
  • width / height 是矩形的宽高
  • top / bottom 是顶部/底部矩形边缘的 Y 坐标
  • left / right 是左/右矩形边缘的 X 坐标

截屏2021-07-03 11.37.36.png

从图中可以得出:

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

为什么需要 top / left 这种看起来重复的属性?

原点不一定是矩形的左上角,当原点变成例如右下角的时候 left !== xtop !== y

widthheight 从右下角开始「增长」,也就变成了负值

截屏2021-07-03 11.46.34.png

IE 不支持 x / y

可以自己写一个 polyfill(在 DomRect.prototype 中添加一个 getter),或者使用 left / top

坐标的 right / bottomCSS position 属性不同

相对于窗口(window)的坐标和 CSS position:fixed 之间有明显的相似之处。
但是在 CSS 定位中,right 属性表示距右边缘的距离,而 bottom 属性表示距下边缘的距离。
如果我们再看一下上面的图片,我们可以看到在 JavaScript 中并非如此。窗口的所有坐标都从左上角开始计数,包括这些坐标。

但是我目前不是很理解

文档坐标

获取文档坐标的方式:

  • pageY = clientY + 文档的垂直滚动出的部分的高度
  • pageX = clientX + 文档的水平滚动出的部分的宽度
function getCoords(ele) {
  let rect = ele.getBoundingClientRect()

  return {
    top: rect.top + window.pageYOffset,
    right: rect.right + window.pageXOffset,
    bottom: rect.bottom + window.pageYOffset,
    left: rect.left + window.pageXOffset
  }
}
复制代码

图片懒加载代码

rect.top 是相对于当前窗口 上边 的数值,也就是说当图片在文档的下方时 rect.top > window.innerHeight,只有当图片经过视窗时才会 rect.top <= window.innerHeight

let img_list = [...document.querySelectorAll('img')]
const num = img_list.length

/**
 * 图片懒加载
 */
const img_lazy_load = (() => {
  let cnt = 0
  return () => {
    let deleteIndexList = []
    img_list.forEach((img, index) => {
      let rect = img.getBoundingClientRect()
      console.log(rect.top, window.innerHeight)
      if (rect.top < window.innerHeight) {
        img.src = img.dataset.src
        deleteIndexList.push(index)
        cnt++
        if (cnt === num) {
          document.removeEventListener('scroll', img_lazy_load)
        }
      }
    })
    img_list = img_list.filter((_, index) => !deleteIndexList.includes(index))
  }
})()

document.addEventListener('scroll', img_lazy_load)
复制代码

数据类型判断

typeof 可以正确识别:

  • undefined
  • boolean
  • number
  • string
  • symbol
  • function

但是对于别的类型都会识别成 object,利用 Object.prototype.toString 来正确判断类型,截取 [object xxxxx]object 后边到 ] 的文本并且统一转小写:

const type_of = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()

type_of(new Date()) // date
type_of({}) // object
type_of(null) // null
type_of([]) // array
复制代码

数组去重

es5,利用 filter 判断当前元素是否是第一次出现在数组中:

const unique = (arr: any[]) => {
  return arr.filter((item, index, arr) => arr.indexOf(item) === index)
}
复制代码

es6,使用 Set 数据结构自动去重:

const unique_es6 = (arr) => [...new Set(arr)]
复制代码

不使用 Set,用空间换时间,模拟 Map 存键值对,时间复杂度 O(n)

const get_type_and_val = (obj) => {
  if (typeof obj === 'object' && obj) {
    return Object.prototype.toString.call(obj) + JSON.stringify(obj)
  }
  return Object.prototype.toString.call(obj) + obj.toString()
}

const unique_on = (arr: any[]) => {
  const temp = {}
  const result = []
  for (const item of arr) {
    const key = get_type_and_val(item)
    if (!temp.hasOwnProperty(key)) {
      result.push(item)
      temp[key] = true
    }
  }
  return result
}
复制代码

字符串反转

主要利用数组的 join() 方法,反向遍历,最后一种采用 es6 解构

const reverse_1 = (str: string) => str.split('').reverse().join('')

const reverse_2 = (str: string) => {
  let result = ''
  const len = str.length
  for (let i = len - 1; i >= 0; i--) {
    result += str[i]
  }
  return result
}

const reverse_3 = (str: string) => {
  const result = []
  const len = str.length
  for (let i = len - 1; i >= 0; i--) {
    result.push(str[i])
  }
  return result.join('')
}

const reverse_4 = (str: string) => {
  const result = str.split('')
  const len = str.length
  for (let i = 0; i < len >> 1; i++) {
    [result[i], result[len - i - 1]] = [result[len - i - 1], result[i]]
  }
  return result.join('')
}
复制代码

数组扁平化

将数组拍平成一层,并且添加参数控制拍平层数更加符合设计

[1, [2], [3, [4]]].flat(1) // [ 1, 2, 3, [ 4 ] ]
[1, [2], [3, [4]]].flat(2) // [ 1, 2, 3, 4 ]

const flat_es6 = (arr: any[], depth?: number) => {
  if (depth) {
    for (let i = 0; i < depth; i++) {
      if (!arr.some((item) => Array.isArray(item))) break
      arr = [].concat(...arr)
    }
  } else {
    while (arr.some((item) => Array.isArray(item))) {
      arr = [].concat(...arr)
    }
  }
  return arr
}
复制代码

写在最后

这是我发布的第一篇文章,但其实它已经躺在我的草稿箱里很久了,我一直纠结于是否应该发这种前辈称作「水文」的文章,但其实它也是我技术成长的一部分,同时也是我准备秋招必经的道路,借此掘金的活动我发布了它,也希望大佬们能给些意见,我最近正在学习 DartFlutter,应该会记录下来我学习的心路历程,敬请期待。

参考 javascript.info

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享