# 1.原生JS實現圖片懶加載(考慮不重復加載以及節流)
知識點:視口位置判斷,懶加載實現(data-set),節流等

1.`Element.getBoundingClientRect()`方法返回元素的大小及其相對于視口的位置。具體解釋及用法參考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect)
通過 Element.getBoundingClientRect().top 與 window.innerHeight(當前視窗的高度)比較就可以判斷圖片是否出現在可視區域。注意這個 top 是相對于當前視窗的頂部的top值而不是一開始的頂部。
2.通過 Element.dataset 可以獲取到為元素節點添加的`data-*`屬性,我們可以通過這個屬性來保存圖片加載時的url。
``` html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
img {
display: inline-block;
width: 100%;
height: 300px;
background: gray;
}
</style>
</head>
<body>
<div class="box-image">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/252339290/TB29PYUXnIlyKJjSZFrXXXn2VXa_!!252339290-0-beehive-scenes.jpg_180x180xzq90.jpg_.webp" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i4/2260152888/O1CN01vw2e251XCkJr0VPZU_!!2260152888-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i3/24687421/TB2Xg1izsyYBuNkSnfoXXcWgVXa_!!24687421-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i4/890151842/TB2II1mnZbI8KJjy1zdXXbe1VXa_!!890151842-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/2096513830/TB2l1W0kRnTBKNjSZPfXXbf1XXa_!!2096513830-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i3/2586222636/TB2RDGrpqAoBKNjSZSyXXaHAVXa_!!2586222636-0-daren.jpg_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/1870112525/TB2Ae.xbOwIL1JjSZFsXXcXFFXa_!!1870112525-2-beehive-scenes.png_250x250xz.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/2194952831/TB2PwWty7qvpuFjSZFhXXaOgXXa_!!2194952831-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
</div>
<script>
var viewHeight = document.documentElement.clientHeight; // = window.innerHeight?
// 節流:加一個300ms的間隔執行
function throttle(fn, wait) {
let canRun = true
return function (...args) {
if (!canRun) return
canRun = false
setTimeout(() => {
fn.apply(this, args)
canRun = true
}, wait)
}
}
function lazyload() {
let imgs = document.querySelectorAll('img[data-original][lazyload]') // 獲取文檔中所有擁有data-original lazyload屬性的<img>節點
imgs.forEach(item => {
if (item.dataset.original == '') {// HTMLElement.dataset屬性允許無論是在讀取模式和寫入模式下訪問在 HTML或 DOM中的元素上設置的所有自定義數據屬性(data-*)集。
return
}
// 返回一個DOMRect對象,包含了一組用于描述邊框的只讀屬性——left、top、right和bottom,
// 單位為像素。除了 width 和 height 外的屬性都是相對于視口的左上角位置而言的。
let rect = item.getBoundingClientRect()
// 其top值是相對于當前視窗的頂部而言的而不是絕對的頂部,所以top值 < window.innerHeight的話圖片就出現在底部了就需要加載
if (rect.bottom >= 0 && rect.top < viewHeight) {
let img = new Image()
img.src = item.dataset.original
// 圖片加載完成觸發load事件?
img.onload = function () {
item.src = img.src
}
// 移除屬性的話就不會重復加載了
item.removeAttribute('data-original')
item.removeAttribute('lazyload')
}
})
}
// 先調用一次加載最初顯示在視窗中的圖片
lazyload();
let throttle_lazyload = throttle(lazyload, 300)
document.addEventListener('scroll', throttle_lazyload)
</script>
</body>
</html>
```
# 2.如何渲染幾萬條數據且不卡住頁面?
① 利用文檔碎片,實現一次性插入多個節點,減少回流
② 分批次地插入節點而不是一次性插入,防止阻塞
③ 使用`requestAnimationFrame`讓瀏覽器選擇最為合適的渲染間隔
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
控件
</ul>
<script>
const total = 100000 // 10萬條數據
const once = 20 // 每輪插入的數據條目
const loopCount = total / once // 渲染總次數
let countOfRender = 0
let ul = document.querySelector('ul')
function add() {
// 使用文檔碎片優化性能
const fragment = document.createDocumentFragment()
for (let i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
countOfRender+=1
loop()
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add) // 使用requestAnimationFrame每隔16ms(瀏覽器自己選擇最佳時間)刷新一次
}
}
</script>
</body>
</html>
```
# 3.寫函數實現任意標簽轉換成 json 形式
```javaScript
/*
<div>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
*/
function DOM2JSON(str) {
let reg = /<(.+)>(.*?)<\/\1>/g
let result = null
let nodes = []
while((result = reg.exec(str))!== null) {
nodes.push({ tag: result[1], children: DOM2JSON(result[2])}) // exec 返回的數組,[0]匹配的字符串 然后依次是捕獲的分組 然后有 index 和 input 屬性
}
return nodes.length > 1 ? nodes : nodes[0]
}
console.log(JSON.stringify(DOM2JSON('<div><span><a></a></span><span><a></a><a></a></span></div>')))
// {"tag":"div","children":[{"tag":"span","children":{"tag":"a"}},{"tag":"span","children":[{"tag":"a"},{"tag":"a"}]}]}
```
這里主要利用了 exec 函數會在上一次匹配的結果之后繼續匹配,且如果未匹配成功會返回 null,然后注意下 exec 和正則表達式分組的使用即可。
# 4.判斷執行順序(事件循環)
```javaScript
console.log('begin'); // 1.begin
setTimeout(() => {
console.log('setTimeout 1'); // 3.setTimeout 1
Promise.resolve() // Promise.resolve().then ?
.then(() => {
console.log('promise 1'); // 5.promise 1
setTimeout(() => {
console.log('setTimeout2'); // 8. setTimeout2
});
})
.then(() => {
console.log('promise 2'); // 7. promise 2 注意 7 比 8 要快,會同時放入宏任務和微任務隊列?
});
new Promise(resolve => {
console.log('a'); // 4. a
resolve();
}).then(() => {
console.log('b'); // 6. b
});
}, 0);
console.log('end'); // 2.end
```
# 5.實現一個 sleep 函數
```js
async function test () {
console.log('start')
await sleep(3000)
console.log('3s has passed')
}
function sleep (ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, ms)
})
}
```
# 6.meta 標簽的作用
meta 標簽分兩大部分:HTTP 標題信息(http-equiv)和頁面描述信息(name)。
1、聲明文檔使用的字符編碼
```html
<meta charset='utf-8'>
```
以下設置更為詳細:
```html
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
```
該 meta 標簽定義了 HTML 頁面所使用的字符集為 utf-8 ,就是萬國碼。它可以在同一頁面顯示中文簡體、繁體及其它語言(如日文,韓文)等
2、聲明文檔的兼容模式
```html
// 指示IE以目前可用的最高模式顯示內容
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=Emulate IE7" />
// 指示IE使用 <!DOCTYPE> 指令確定如何呈現內容。標準模式指令以IE7 標準模式顯示,而 Quirks 模式指令以 IE5 模式顯示
```
3、SEO 優化
```html
<meta name="description" content="不超過150個字符" /> // 頁面描述
<meta name="keywords" content="html5, css3, 關鍵字"/> // 頁面關鍵詞
<meta name="author" content="魔法小棧" /> // 定義網頁作者
// 定義網頁搜索引擎索引方式,robotterms是一組使用英文逗號「,」分割的值,通常有如下幾種取值:none,noindex,nofollow,all,index和follow。
<meta name="robots" content="index,follow" />
```
4、為移動設備添加 viewport
```html
<meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
/*
content 參數解釋:
width viewport 寬度(數值/device-width)
height viewport 高度(數值/device-height)
initial-scale 初始縮放比例
maximum-scale 最大縮放比例
minimum-scale 最小縮放比例
user-scalable 是否允許用戶縮放(yes/no)
minimal-ui iOS 7.1 beta 2 中新增屬性,可以在頁面加載時最小化上下狀態欄。這是一個布爾值,可以直接這樣寫:
*/
<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
```
例如如下設置:
```html
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
```
? 強制讓文檔與設備的寬度保持 1:1 ;
? 文檔最大的寬度比列是1.0( initial-scale 初始刻度值和 maximum-scale 最大刻度值);
? user-scalable 定義用戶是否可以手動縮放( no 為不縮放),使頁面固定設備上面的大小;
更多內容參考 [https://blog.csdn.net/xmtblog/article/details/46226717](https://blog.csdn.net/xmtblog/article/details/46226717)
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs