причина
Когда я раньше читал вопросы интервью, я вдруг увидел, как фронтенд обрабатывает 100 000 кусков данных, взаимодействие с пользователем отображается в виде выпадающего списка.
Тестовый сайт интервьюера
Я проанализировал волну, и на тестовой площадке интервьюера могут быть следующие точки:
- Внешний рендеринг массивных узлов dom, созданных с помощью фрагмента?
- При большом количестве узлов dom браузер точно застрянет, как сделать так, чтобы пользователь взаимодействовал без восприятия
- Как связать 100 000 штук данных и поместить их все в память?
виртуальный список
Первое, что приходит на ум, это технология виртуального списка.Предполагая, что мы можем отображать до 20 элементов данных в нашем окне, нам нужно создать всего 20 узлов DOM.Пока пользователь прокручивает мышь, чтобы обновить содержимое DOM, эффект достигнут. ! !
реализация виртуального списка
Этапы реализации
-
указать некоторые переменные
- length = parseInt(scrollTop / itemHeight) Количество столбцов данных, прокрученных вверх, округленных вверх
- высота общая высота
- total Общее количество данных
- itemHeight высота отдельных данных
- num Общее количество данных, отображаемых в окне
- start = длина Начальная позиция данных запроса
-
Полоса прокрутки контейнера должна удерживать пустой элемент, высота = общая * высота элемента
-
Фактическая высота окна = число * высота элемента
-
В это время, пока мы получаем scrollTop в событии прокрутки, мы знаем длину, а затем обновляем marginTop(length * itemHeight) фактического содержимого dom. В это время наш контент всегда находится в окне
-
Вышеупомянутый шаг гарантирует, что фактический контейнер содержимого находится в окне.Далее мы получим данные для отображения, то есть данные до длины до длины + число, а затем обновим их, и все будет в порядке.
Код:
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script src="https://unpkg.com/vue@next"></script>
<title>虚拟列表</title>
<style>
.container {
display: flex;
border: 1px solid;
overflow-y: auto;
height: var(--containerHeight);
}
.scroll-blank {
height: var(--height);
}
.scroll {
margin-top: var(--marginTop);
}
.scroll-item {
height: var(--itemHeight);
/* background-color: pink; */
}
</style>
</head>
<body>
<div id="app">
<div
ref='container'
class="container"
:style="{
'--containerHeight': containerHeight + 'px',
'--height': height + 'px',
'--itemHeight': itemHeight + 'px',
'--marginTop': marginTop + 'px',
}"
>
<div class="scroll-blank"></div>
<div class="scroll">
<div v-for='(item, index) in activeList' :key='item' class="scroll-item">{{ item }}</div>
</div>
</div>
</div>
<script>
const { computed, onMounted, ref } = Vue
const createList = () => {
const list = []
let i = 0
while (i < 1000) {
list.push(i++)
}
return list
}
const App = {
setup() {
const container = ref(null)
const list = createList()
const num = 20
const itemHeight = 20
const containerHeight = num * itemHeight
const height = list.length * itemHeight
let start = ref(0)
let marginTop = ref(0)
const activeList = computed(() => {
const index = start.value
return list.slice(index, index + 20)
})
onMounted(() => {
container.value.addEventListener('scroll', ({ target }) => {
const { scrollTop } = target
const itemNum = scrollTop / itemHeight
start.value = parseInt(itemNum)
marginTop.value = scrollTop
})
})
return {
container,
marginTop,
containerHeight,
height,
itemHeight,
activeList
}
}
};
const app = Vue.createApp(App);
app.mount("#app");
</script>
</body>
</html>
100 000 единиц хранения и поиска данных
Виртуальный список просто реализован с помощью Vue3 выше, а остальное — как хранить и извлекать данные. Если все массивные данные будут помещены в память основного потока, они будут чувствовать себя немного взломанными, и тогда я думаю о webWorker и indexedDB!
webWorker
Запустите поток, отличный от основного потока, чтобы получать массивные данные, не затрагивая основной поток.
Справочная документация:
indexedDB
Может выполнять массовое хранение данных и поддерживать поиск
Справочная документация:
выполнить
Сервис сборки
Прежде всего, чтобы упростить предварительный просмотр, используйте koa для создания службы, а затем отлаживайте ее.
const Koa = require('koa')
const Router = require('koa-router');
const fs = require('fs/promises')
const app = new Koa()
const router = new Router()
// 实现的虚拟列表
router.get('/', async (ctx, next) => {
const content = await fs.readFile('./src/index.html', { encoding: 'utf8' })
ctx.body = content
})
// worker
router.get('/worker.js', async (ctx, next) => {
const content = await fs.readFile('./src/worker.js')
ctx.body = content
})
app.use(router.routes()).use(router.allowedMethods()).listen(9527)
console.log(`预览:`, `\x1B[36mhttp://localhost:9527\x1B[0m`)
рабочая часть кода
// 创建数据库
const createDatabase = async () => {
let version = 1
const databases = await indexedDB.databases()
const preDatabase = databases.find(({ name }) => name === databaseName)
if (preDatabase) version = preDatabase.version + 1
// indexedDB.deleteDatabase(databaseName)
const database = indexedDB.open(databaseName, version)
return new Promise((f, r) => {
database.onsuccess = e => controlDatabase(e.target.result)
database.onupgradeneeded = f
database.onerror = r
})
}
// 创建表
const createTable = db => {
return new Promise((f, r) => {
db.deleteObjectStore('list')
const table = db.createObjectStore('list', { keyPath: 'id' })
table.transaction.oncomplete = () => {
const store = db.transaction('list', 'readwrite').objectStore('list')
let i = 0
while (i < 100000) {
store.add({ id: i++, num: Math.random() })
}
f(i)
isFinished = true
}
})
}
// 检索
const search = ({ size, start }) => {
if (!isFinished) return []
return new Promise((f, r) => {
const store = db.transaction('list', 'readonly').objectStore('list')
const range = IDBKeyRange.bound(start, start + size)
const list = []
store.openCursor(range).onsuccess = ({ target: { result } }) => {
if (!result) {
return f(list)
}
list.push(result.value)
result.continue()
}
})
}