本文主要总结从网上看到的各种 JS 手写题,其中应用题居多。实际应用中,需要结合自己的理解而不是背代码。
防抖的功能类似回城功能,多次调用防抖函数只有最后一次被调用的函数会执行。可以用于防止按钮重复点击导致发起多次网络请求等用途。
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(fn.bind(this, ...args), delay)
}
}
const fn = debounce(f, 1000)
同一个时间段内,最多只会执行一次功能。可以用于减少监听事件触发的次数,提升性能。
function throttle(fn, delay) {
let timer;
return function(...args) {
if(!timer) {
timer = setTimeout(() => {
fn.apply(this, ..args)
timer = null;
}, delay)
}
}
}
试想只是单纯用节流来减少页面滚动事件回调的触发次数,用户可能在某个间隔内将滚动条滑动到底,而此时并不会触发加载数据回调,可能导致用户体验较差的情况。解决方法是在节流的同时,设置一个超时时间,如果超过超时时间则自动调用回调。
function throttle(fn, delay) {
let last = 0, timer = null
return function(...args) {
const now = new Date()
if(now - last < delay) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, ...args)
last = now
})
} else {
last = now
fn.apply(this, ...args)
}
}
}
仅在图片滚动进视口时加载图片,优化性能和体验。
const imgs = document.querySelectorAll('img[data-src]')
window.addEventListener('scroll', throttle(() => {
for(let i=0, len=imgs.length; i<len; i++) {
const { top } = imgs[i].getBoundingClientRect()
if(top < document.documentElement.clientHeight && !imgs[i].src) {
imgs[i].src = imgs[i].getAttribute('data-src')
}
}
}, 100))
let img = document.getElementsByTagName("img");
const observer = new IntersectionObserver(changes => {
//changes 是被观察的元素集合
for(let i = 0, len = changes.length; i < len; i++) {
let change = changes[i];
// 通过这个属性判断是否在视口中
if(change.isIntersecting) {
const imgElement = change.target;
imgElement.src = imgElement.getAttribute("data-src");
observer.unobserve(imgElement);
}
}
})
Array.from(img).forEach(item => observer.observe(item));
// 作者:神三元
// 链接:https://juejin.cn/post/6844904021308735502
最简单的深拷贝方法,无法拷贝/做到:
等数据类型,因为这些数据类型在 JSON 中没有定义
const new_obj = JSON.parse(JSON.stringify(obj))
注意这些边界条件即可
function deepClone(obj, map=new Map()) {
if(typeof obj !== 'object') {
return obj
}
const ref = map.get(obj)
if(ref) {
return ref
}
map.set(obj, obj)
const result = Array.isArray(obj) ? [] : {}
for(const key in obj) {
result[key] = deepClone(obj[key], map)
}
return result
}
有两点可以优化:
const arr = [1, 2, [3, 4, [5, 6]]]
arr.flat(Infinity)
const arr = [1, 2, [3, 4, [5, 6]]]
const jsstr = JSON.stringify(arr).replace(/\[|]/g, '')
const a = JSON.parse('[' + jsstr + ']')
while(arr.some(Array.isArray)) {
arr = [].contact(...arr)
}
const result = []
function flat(arr) {
if(!Array.isArray(arr)) {
result.push(arr)
} else {
for(const item of arr) {
flat(item)
}
}
}
实现形如 sum(1)(2)(3) => 6
的函数,可以看到这种函数调用时能够保存之前传入的参数,且当参数的个数符合调用参数的个数时,就会自动调用该函数。
function curry(fn, ...nargs) {
const len = fn.length // fn 的参数个数
return function(...args) {
const all_args = [...args, ...nargs]
if(all_args.length < len) {
return curry(fn, ...all_args)
} else {
return fn(...all_args)
}
}
}
function sum(a, b, c) {
return a + b + c
}
curry(sum, 1, 2, 3)
curry(sum)(1, 3)(2)
curry(sum, 1)(2, 3)
curry(sum, 1, 2)(3)
const c = curry(sum, 1, 2)
c(3)
c(4)
c(5)
instanceof 的原理:obj instanceof 构造函数,不断向上寻找 obj 的 prototype,看是否和构造函数的相等,如果相等则返回 true。
function isInstanceof(obj, target) {
const targetProto = target.prototype
while(true) {
const proto = Object.getPrototypeOf(obj)
if(proto === null) {
return false
}
if(proto === targetProto) {
return true
}
obj = proto
}
}
// set
new Set(...arr)
// indexOf
arr.filter((item, idx) => arr.indexOf(item) === idx)