Skip to content

Latest commit

 

History

History
366 lines (305 loc) · 10 KB

前端技巧(2).md

File metadata and controls

366 lines (305 loc) · 10 KB

HTML元素方法

一些大部分人不知道的HTML元素方法。

  • hidden

隐藏元素,类似于 display: none;:

element.hidden = true;

浏览器支持度:

IE 11+ Android 4.0+

  • toggle()

这是一种为元素添加或删除某个 class 的方法,具体做法是:

myElement.classList.toggle('some-class', ifHas);

上述代码的 toggle方法的第二个参数为 Boolean类型,true表示添加 some-class类,false表示删除。

  • closest

浏览器支持度:

IE系列全不支持,Android 和 IOS 支持度很差

  • matches
element.className.indexOf('some-class') > -1
// 相当于
element.matches('.some-class')

浏览器支持度:

IE9+,需要加前缀

尾递归

function tail(i) {
  if (i > 3) return
  console.log('a:', i)
  tail(i + 1)
  console.log('b:', i)
}
tail(1)

函数每次执行到 tail(i + 1)处,都会进入到下一个递归自身的函数中,JS会记录当前函数的堆栈,直到函数依次出栈退出,所以上述代码将输出:

a: 1
a: 2
a: 3
b: 3
b: 2
b: 1

尾递归会记录堆栈,所以可能会导致栈溢出,改为以下写法:

function tail(i) {
  if (i > 3) return
  console.log('a:', i)
  // 直接 return
  return tail(i + 1)
  console.log('b:', i)
}
tail(1)

由于每次都 return掉当前函数,所以不会记录堆栈,上述代码将输出:

a: 1
a: 2
a: 3

获取滚动条宽度

在某些情况下,例如页面弹出模态框时,禁止页面滚动,可能会给页面设置 overflow: hidden属性,滚动条消失,会带来页面横向的跳动,这个时候可以在滚动条消失的提示,给页面的右侧设置一个等于滚动条宽度的边距,这个时候就就需要首先获取滚动条宽度

各浏览器的滚动条宽度不一而同:

  • macOS: 15px
  • Chrome FireFox IE: 17px
  • Edge: 16px
  • Opera: 15px

除此之外,可以通过 js进行测量:

const outer = document.createElement('div');
const inner = document.createElement('div');
outer.style.overflow = 'scroll';
document.body.appendChild(outer);
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);
// scrollbarWidth 即为滚动条宽度
console.log(scrollbarWidth);

Tips: 当设置 html::-webkit-scrollbar { display: none; },会让页面隐藏滚动条,但仍然可以滚动

滚动边界

如下图所示: scroll-over

模态框滚动结束后,页面的滚动条会紧接着被触发导致页面滚动,想要将滚动限制在模态框中,有如下两种解决方案。

  • 使用 js进行限制
<div class="outer">
  <div class="inner"></div>
</div>
var elem = document.querySelector('.outer')
// 注意,这里监听的是 wheel 而不是 scroll 事件
elem.addEventListener('wheel',handleOverscroll)
function handleOverscroll(event) {
  const delta = -event.deltaY;
  if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) {
    elem.scrollTop = elem.scrollHeight;
    event.preventDefault();
    return false;
  }
  if (delta > elem.scrollTop) {
    elem.scrollTop = 0;
    event.preventDefault();
    return false;
  }
  return true;
}
  • 使用 CSS新特性
.element {
  overscroll-behavior: contain;
}

上述 css新特性兼容性较差,目前只能作为备选方案

Passive event listeners

现代的浏览器虽然知道如何使得滚动变得平滑,但为确认(滚动)事件处理函数中是否执行了 event.preventDefault() 以取消默认行为,有时仍可能需要花费 500毫秒来等待事件处理函数执行完毕。 即使是一个空的事件监听器,从不取消任何行为,鉴于浏览器仍会期待 preventDefault 的调用,也会对性能造成负面影响。 为了准确地告诉浏览器不必担心(事件处理函数中)取消了默认行为,为监听事件增加 passive:

ele.addEventListener('touchstart', e => {
  // do something
}, { passive: true })

监控网页的崩溃

由于网页崩溃以后,页面上的 js很可能也是无法运行的了,所以无法使用页面上的 js进行网页崩溃的监控,需要另寻他法。

参见 如何监控网页崩溃? 或者 如何监控网页崩溃?

解决堆栈溢出

const nextListItem = () => {
  // 假设 list是一个已经存在的数组
  if (list.pop()) {
    nextListItem();
  }
}

上述代码在 list数组长度较大的时候,由于可能会不断地进行回调自身的操作,不断在申请内存用于存放堆栈信息,一直到函数达到一定条件不再进行持续的回调自身,最初的函数才运行结束,释放掉所申请的所有内存,而如果在此期间所申请的总内存超出了线程的栈空间,则将发生栈溢出。

有两种解决方式:

  • 异步
const nextListItem = () => {
  if (list.pop()) {
    setTimeout(nextListItem, 0)
  }
}

setTimeout的作用就是让代码异步运行,将每次回调自身函数的执行放到事件队列中,上一个函数执行完就可以退出,释放所占用的内存,不会造成内存累计的情况。

  • 闭包
const nextListItem = () => {
  if (list.pop()) {
    return function() {
      return nextListItem()
    }
  }
}
// 为了避免不断调用,例如 nextListItem()()(),增加一个自动调用器
const autoRun = func => {
  const value = func()
  typeof value === 'function' && autoRun(value)
}
// 使用
autoRun(nextListItem)

闭包返回的是一个新函数,之前的函数在运行结束后可以释放内存,所以也不存在内存堆积的情况。

事件循环

macrotasks: setTimeout setInterval setImmediate I/O UI渲染 microtasks: process.nextTick Promise Object.observe MutationObserver

一次事件循环中,先执行 microtasks,再执行 macrotasks microtasks中,process.nextTick最先执行,比 promise 先执行

移动端滚动穿透问题解决方案

弹窗弹出后,整个背景不可滚动,但是弹窗内部可滚动

function getScrollTop () {
 return document.body.scrollTop || document.documentElement.scrollTop
}
const toggleForbidScrollThrough = (function toggleForbidScrollThrough () {
 let scrollTop
 return function toggleForbidScrollThroughInner (isForbide) {
   if (isForbide) {
     scrollTop = getScrollTop()
     // position fixed会使滚动位置丢失,所以利用top定位
     document.body.style.position = 'fixed'
     document.body.style.top = `-${scrollTop}px`
   } else {
     // 恢复时,需要还原之前的滚动位置
     document.body.style.position = 'static'
     document.body.style.top = 'auto'
     window.scrollTo(0, scrollTop)
   }
 }
}())
// 使用
// 当显示弹窗时调用
toggleForbidScrollThrough(true)
// 当隐藏弹窗时调用
toggleForbidScrollThrough(false)

Emoji表情转义处理

编码,用于存入数据库:

// emoji表情需要转义 防止后端报错
const utf16toEntities = (str) => {
 const patt = /[\ud800-\udbff][\udc00-\udfff]/g  // 检测utf16字符正则
 str = str.replace(patt, (char) => {
   if (char.length === 2) {
     const H = char.charCodeAt(0)
     // 取出高位
     const L = char.charCodeAt(1)
     // 取出低位
     const code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00
     // 转换算法
     return '&#' + code + ';'
   }
   return char
 })
 return str
}

解码,用于前端显示:

/**
 * 用于反解开 emoji编码后的字符串
 * @param str emoji编码后的字符串
 */
const uncodeUtf16 = (str) => {
  const reg = /\&#.*?;/g
  const result = str.replace(reg, char => {
    if (char.length === 9) {
      const code = +char.match(/[0-9]+/g)
      const H = Math.floor((code - 0x10000) / 0x400) + 0xD800
      const L = (code - 0x10000) % 0x400 + 0xDC00
      return decodeURI('%u' + H.toString(16) + '%u' + L.toString(16))
    }
    return char
  })
  return result
}

数字取整

对一个数字 num | 0可以取整,负数也同样适用

1.3 | 0         // 1
1.9 | 0         // 1
-1.9 | 0        // -1

惰性载入函数

在某个场景下我们的函数中有判断语句,这个判断依据在整个项目运行期间一般不会变化,所以判断分支在整个项目运行期间只会运行某个特定分支,那么就可以考虑惰性载入函数

function foo() {
  if (a !== b) {
    console.log('aaa')
  } else {
    console.log('bbb')
  }
}
// 优化后
function foo() {
  if (a != b) {
    // 重新赋值,下次就无需进行判断了
    foo = function(){
      console.log('aaa')
    }
  } else {
    // 重新赋值,下次就无需进行判断了
    foo = function() {
      console.log('bbb')
    }
  }
  return foo()
}

数组平铺

const flatten = (arr, depth = 1) =>
  depth != 1
    ? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), [])
    : arr.reduce((a, v) => a.concat(v), []);
// 使用
flatten([1, [2], 3, 4]);                             // [1, 2, 3, 4]
flatten([1, [2, [3, [4, 5], 6], 7], 8], 2);           // [1, 2, 3, [4, 5], 6, 7, 8]

quicklink

quicklink 是一个通过预加载资源来提升后续速度的轻量级工具库。旨在提升浏览过程中,用户访问后续页面的加载速度

GoogleChromeLabs发布了相关开箱即用的库 quicklink

或者也可以自行实现简易的 prefetcher,判断是否支持 Resource Hints 中的 prefetch,支持则使用它,否则回退使用 XHR 加载:

function quicklink(url) {
  const link = document.createElement('link')
  const supports = (link.relList || {}).supports && link.relList.supports('prefetch')
  if (supports) {
    link.rel = 'prefetch'
    link.href = url
    link.as = 'script'
    document.head.appendChild(link)
  } else {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.send()
  }
}