一些大部分人不知道的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; }
,会让页面隐藏滚动条,但仍然可以滚动
模态框滚动结束后,页面的滚动条会紧接着被触发导致页面滚动,想要将滚动限制在模态框中,有如下两种解决方案。
- 使用 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新特性兼容性较差,目前只能作为备选方案
现代的浏览器虽然知道如何使得滚动变得平滑,但为确认(滚动)事件处理函数中是否执行了 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表情需要转义 防止后端报错
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
是一个通过预加载资源来提升后续速度的轻量级工具库。旨在提升浏览过程中,用户访问后续页面的加载速度
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()
}
}