面試官刁難系列一:手寫圖片懶加載代碼
作者丨前端發(fā)現(xiàn)者
來源丨前端發(fā)現(xiàn)
前言
今天是跟面試官開撕的第一天,當你過五關(guān)斬六將終于來到面試官面前時,他可能在面試你之前寫過如下的代碼:
<el-image :src="url" lazy></el-image>
沒錯,就是這么輕描淡寫的一句代碼,他心里卻有了不一樣的想法。lazy 這個屬性它是如何實現(xiàn)圖片懶加載的?請說說你的思路。
頭腦頓時一場空。"只怪我平時只復(fù)制粘貼寫業(yè)務(wù)代碼了,草率了!"你可能此時是這樣想的。幸運的是,今天是開撕的一天,今天就甩出你自信的代碼給面試官。
element-plus實現(xiàn)思路
翻看Element-plus源碼得知在packages/comonents/image下的src找到index.vue頁面,lazy屬性是通過子父組件形式的props方式傳遞進來的:
然后在 onMounted 函數(shù)里判斷是否使用了lazy,使用的話執(zhí)行addLazyLoadListener事件,否則直接加載圖片出來。
onMounted(() => {
if (props.lazy) {
nextTick(addLazyLoadListener)
} else {
//后面會講到
loadImage()
}
})
接著就來到了這里:
const { scrollContainer } = props 這句代碼就將父組件傳入的props拿到。接著就是獲取滾動元素的DOM了。來到第235行代碼:
_lazyLoadHandler = throttle(handleLazyLoad, 200)
我們看到這里使用了throttle節(jié)流函數(shù),引入的是throttle-debounce庫,傳入的200作為間歇時間。庫文件地址:(https://www.npmjs.com/package/throttle-debounce)
import throttle from 'lodash/throttle'
接著使用setTimeout函數(shù)在100毫秒后執(zhí)行handleLazyLoad方法。
function handleLazyLoad() {
if (isInContainer(container.value, _scrollContainer)) {
loadImage()
removeLazyLoadListener()
}
}
這里注意isInContainer這個方法,它是從*@element-plus/utils/dom*導(dǎo)入的。痛過引入路徑找到這個方法。
export const isInContainer = (
el: HTMLElement,
container: HTMLElement,
): boolean => {
if (isServer || !el || !container) return false
const elRect = el.getBoundingClientRect()
let containerRect: Partial<DOMRect>
if (
[window, document, document.documentElement, null, undefined].includes(
container,
)
) {
containerRect = {
top: 0,
right: window.innerWidth,
bottom: window.innerHeight,
left: 0,
}
} else {
containerRect = container.getBoundingClientRect()
}
return (
elRect.top < containerRect.bottom &&
elRect.bottom > containerRect.top &&
elRect.right > containerRect.left &&
elRect.left < containerRect.right
)
}
可以看到使用isInContainer方法時傳入的container.value, _scrollContainer使用getBoundingClientRect這個API來比較當前Image組件是否在當前滾動區(qū)域的可視區(qū)。這個API可以獲取:
-
elRect.top:元素上邊到視窗上邊的距離 -
elRect.right:元素右邊到視窗左邊的距離 -
elRect.bottom:元素下邊到視窗上邊的距離 -
elRect.left:元素左邊到視窗左邊的距離
當判斷當前Image組件在滾動可視區(qū)時才執(zhí)行loadImage方法。
const loadImage = () => {
if (isServer) return
const attributes = attrs.value
// reset status
loading.value = true
hasLoadError.value = false
//創(chuàng)建一個img實例
const img = new Image()
//onload 事件在圖片加載完成后立即執(zhí)行
img.onload = (e) => handleLoad(e, img)
img.onerror = handleError
// bind html attrs
// so it can behave consistently
//這句代碼是將設(shè)置在屬性全部設(shè)置在img標簽上
Object.keys(attributes).forEach((key) => {
// avoid onload to be overwritten
if (key.toLowerCase() === 'onload') return
const value = attributes[key]
img.setAttribute(key, value)
})
img.src = props.src
}
這樣就完成了懶加載的大概過程。
自行手寫圖片懶加載實現(xiàn)思路
我們寫一個簡易的圖片懶加載版本。當不使用懶加載直接給src設(shè)置圖片路徑時,倘若圖片太大或者資源還沒加載完成,img標簽就會出現(xiàn)空白情況,為此我們需要利用上面提到的onload事件,等圖片資源加載完成我再設(shè)置src屬性為圖片的地址那就不會出現(xiàn)空白情況啦。
手寫代碼之前,我們需要知道的是:什么是自執(zhí)行函數(shù)?
我們先來看看普通的函數(shù)。
//定義一個函數(shù)
let total =0
function sum (a,b) {
return total = a + b
}
Q:那么我們想執(zhí)行這個函數(shù)需要怎么做呢?A:如果想執(zhí)行它,就必須得調(diào)用它,并且還得給它傳參。
const hh = sum(1,2)
console.log(hh) // =>3
那自執(zhí)行函數(shù)呢?自執(zhí)行函數(shù)就是當它被定義出來,就會自動執(zhí)行的函數(shù)。不需要調(diào)用,傳參也很方便。就上面的函數(shù),用自執(zhí)行函數(shù)定義就是這樣:
let total = 0
(function sum (a,b) {
return total = a + b
})(1,2)
console.log(total) // => 3
所以,這就很有用處了。
const myImage = (function(){
// 創(chuàng)建一個img標簽
let imgNode = document.createElement('img')
// 將創(chuàng)建好的img標簽添加到body上
document.body.appendChild(imgNode)
// new一個Image實例
let img = new Image()
// 圖片資源加載完成后將地址設(shè)置給上面創(chuàng)建好的img標簽的src屬性
img.onload = function() {
imgNode.src = img.src
}
return {
setSrc(src){
// 首先將加載中的圖片設(shè)定給img的src屬性
imgNode.src = '圖片加載中的圖片(可本地地址)'
// 將傳入的地址緩存到上面創(chuàng)建的實例的src中
img.src = src
}
}
})()// 自執(zhí)行函數(shù)
// 使用
myImage.setSrc('https://www.baidu.com/img/bd_logo1.png?where=super')
調(diào)戲面試官的一天就這么輕松愉快地結(jié)束了。當你面試后真的遇到了別忘記回來點個贊哦
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取
