ElasticSearch(簡稱 ES)是一款基于 Lucene 的分布式、可擴展、RESTful 風格的全文檢索和數據分析引擎,擅長實時處理 PB 級別的數據。
## 一、基本概念
**1)Lucene**
  Lucene 是一款開源免費、成熟權威、高性能的全文檢索庫,是 ES 實現全文檢索的核心基礎,而檢索的關鍵正是倒排索引。
**2)倒排索引**
  索引的目的是加快查詢速度,盡快查出符合條件的數據。
  正排索引就像翻書一樣,先查目錄,然后鎖定頁碼,再去看內容。而倒排索引正好與其相反,通過對內容的分詞,建立內容到文檔 ID 之間的映射關系,如下圖所示(來源于[elasticsearch 原理及入門](https://kiosk007.top/post/elasticsearch/))。
:-: 
  倒排索引包括兩部分: Term Dictionary(單詞詞典)和 Posting List(倒排列表)。
  Term Dictionary 記錄了文檔單詞,以及單詞和倒排列表的關系。Posting List 則是記錄了 Term 在文檔中的位置以及其他信息,主要包括文檔 ID、詞頻(Term 在文檔中出現的次數,用來計算相關性評分),位置以及偏移(實現搜索高亮)。
**3)壓縮算法**
  為了搜索能高性能,需要將倒排列表放入內存中,但是海量的文檔必然會增加表的尺寸,為了節約空間,Lucene 使用了兩種壓縮算法:FOR(Frame Of Reference)和 RBM(RoaringBitmap)。
  FOR 算法的原理就是通過增量,將原來的大數變成小數,僅存儲增量值,最后通過字節存儲,具體分為 3 步:
1. 將排序的整數列表轉換成 Delta 列表,第二排的 227 是增量值(300 - 73),其余值依次計算。
2. 切分成 blocks,每個 block 是 256 個 Delta,這里為了簡化一下,搞成 3 個 Delta。
3. 看下每個 block 最大的 Delta 是多少。下圖的第一個,最大是 227,最接近的 2 次冪是 256(8bits),于是規定這個 block 里都用 8bits 來編碼(綠色的 header 就是 8);第二個最大的是 30,最接近的 2 次冪是 32(5bits),于是規定這個 block 里都用 5bits 來編碼。
:-: 
  FOR 壓縮算法適用于間隔比較小稠密的文檔 ID 列表,如1、2、3、5、8.......。假如遇到間隔較大稀疏的文檔 ID 列表,如 1000、62101、131385、132052、191173、196658,就更適合通過 RBM 算法來壓縮。
  RBM 算法的核心就是把數據表示成 32 位的二進制,分為高 16 和低 16 進行分別存儲,最大值就是 2 的 16 次方(即 65536)。下圖描述了具體的壓縮步驟(來源于[elasticsearch 原理及入門](https://kiosk007.top/post/elasticsearch/)):
1. 每個數字除以 65536 會得到一個商和余數。
2. 用(商,余數)的組合表示每一組 ID,范圍都在 0 ~ 65535 之內。
3. 其中商為該數字(以 196658 為例)的二進制的前 16 位,余數為該數字的二進制的后 16 位。
4. 再將商提取出來作為 short key,將關聯的余數整合在一起,例如商是 0,則 1000 和 62101 重新組合。
:-: 
**4)FST**
  在數據寫入的時候,Lucene 會為原始數據中的每個 Term 生成對應的倒排索引,這就會讓倒排索引的數據量變得很大。而倒排索引對應的倒排列表文件又是存儲在硬盤上的,如果每次查詢都直接去磁盤中讀取,那就會嚴重影響全文檢索的效率。
  因此需要一種方式可以快速定位到倒排索引中的 Term,Lucene 使用了 FST(Finite State Transducer)有限狀態轉換器來實現二級索引的設計,這是一種類似 Trie 樹的算法。
  Trie 樹是一種樹形結構,哈希樹的變種,經常被搜索引擎系統用于文本詞頻統計。可利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。它有 3 個基本性質:
1. 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
2. 從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串。
3. 每個節點的所有子節點包含的字符都不相同。
  假設有兩個 Term:school 和 cool,它們后面的字符一致,可以通過將原先的 Trie 樹中的后綴字符進行合并來進一步的壓縮空間。優化后的 trie 樹就是 FST,如下圖所示(來源于[Elasticsearch 核心概念](https://xie.infoq.cn/article/73c7bc776a8ab2a0d7a173472)):
:-: 
**5)術語**
  ES 是分布式數據庫,允許多臺服務器協同工作,每臺服務器可以運行多個實例。單個實例稱為一個節點(node),一組節點構成一個集群(cluster)。
:-: 
在上圖中,包含三類節點:
1. 主節點(Master Node),為確保一個集群的穩定,分離主節點和數據節點,主要職責是和集群操作相關的內容,如創建或刪除索引,跟蹤哪些節點是集群的一部分,并決定哪些分片分配給相關的節點。
2. 數據節點(Data Node),存儲索引數據的節點,主要對文檔進行增刪改查、聚合等操作。
3. 協調節點(Coordinator Node),該節點只處理路由請求、分發索引等操作,相當于一個智能負載平衡器,協調節點將請求轉發給存儲數據的 Data Node。每個 Data Node 會將結果返回協調節點,協調節點收集完數據后,將每個 Data Node 的結果合并為單個全局結果。
  分片(shared)是底層的工作單元,文檔(document)保存在分片內,分片又被分配到集群內的各個節點里,每個分片僅保存全部數據的一部分。注意,分片不是隨意進行設定的,而是需要根據實際的生產環境提前進行數據存儲的容量規劃,若設置的過大或過小都會影響 ES 集群的整體性能。
  索引(index)是一類文檔的集合,而文檔是具體的一條數據,注意,從 ElasticSearch 8 開始,徹底移除了 Type 的概念。
  為了便于理解,相關概念與關系型數據庫(MySQL)的對比如下:
| MySQL | ElasticSearch |
| --- | --- |
| Table | Index |
| Row | Doucment |
| Column | Field |
| Schema | Mapping |
| SQL | DSL |
## 二、實戰應用
**1)安裝**
  在官網可以下載各種操作系統版本的 [ES](https://www.elastic.co/cn/downloads/elasticsearch),當進入下載頁面時會自動切換成當前電腦的系統。
:-: 
  下載完成后,就可以執行第二步,運行 bin 目錄中 elasticsearch 可執行文件,簡單點就是將其拖到命令行窗口中。
  在安裝成功后,保存給出的密碼和 token。

**2)Kibana**
  官方提供了一套可視化操作 ES 的系統:Kibana,在[下載](https://www.elastic.co/cn/downloads/kibana)完成后,運行 bin 目錄中的 kibana 文件。
  耐心等待,安裝成功后,在命令窗口會給出一條地址。
:-: 
  在初始化時會要求填入之前保存的 token,點擊 Configure 按鈕,若彈出驗證碼,則將上圖中的 code 參數復制過來,配置完成后進入登錄頁面。
:-: 
  在登錄時會用到默認賬號 elastic,上一節保存的密碼,點擊確定進入主頁,在左側菜單中找到 Dev Tools。
:-: 
  點擊后就能進入可運行 ES RESTful API 的操作界面。
:-: 
  若 Kibana 啟動不了,報錯如下:
~~~
Kibana server is not ready yet.
~~~
  此時可以打開 config/kibana.yml 中的配置文件,翻到最后,很可能是 hosts 中的 IP 地址有問題,因為電腦重新聯網時,IP 地址很有可能變換了,將其改成 localhost 問題就能迎刃而解。
~~~
elasticsearch.hosts: ['https://172.21.10.10:9200']
elasticsearch.serviceAccountToken: AAEAAWVsYXN0aWMva2liYW5
elasticsearch.ssl.certificateAuthorities: [/Users/pwstrick/code/kibana/data/ca_1699243503862.crt]
xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: ['https://172.21.10.10:9200'], ca_trusted_fingerprint: 1b6c0b97e18f22efdd4925a95a4a0dc898de5072e3d6c45938b8d2f0a7f920fb}]
~~~
**3)RESTful API**
  ES 提供了對 Document 進行增刪改查的[常規接口](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html),例如使用 [Bulk](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html) 接口插入一條數據,\_index 就相當于數據庫表,第三行就是具體的字段名稱和值。
~~~
POST _bulk
{"index": {"_id": 862024079,"_index": "web_monitor_2023.11"}}
{"id":862024079,"project":"game","project_subdir":"chat","category":"ajax","message":"{\"type\":\"GET\",\"url\":\"https://static.xxx.me/xxx.json\",\"status\":200,\"endBytes\":\"80.43KB\",\"interval\":\"9ms\"}","key":"80c89d32b27f8f7d43fa8470aeba3f3a","source":"","identity":"xe990bhs4j","referer":"https://www.xxx.me/chat.html","message_type":"get","message_status":200,"message_path":"xxx.json","day":"20231103","hour":15,"minute":29,"ctime":1698996585,"ip":"0.0.0.0","os_name":"iOS","os_version":"15.4.1","app_version":"5.36.1","author":"張三","fingerprint":"38eab40b373220bea1bab2933649c","country":"中國","province":"廣東省","city":"佛山市","isp":"電信","digit":1}
~~~
  若要更新或刪除一條記錄,也可以在 Bulk 接口完成,格式參考如下,更新語句需要包含待更新的數據。
~~~
POST _bulk
{ "delete" : {"_id" : "2", "_index" : "web_monitor_2023.11" } }
{ "update" : {"_id" : "1", "_index" : "web_monitor_2023.11"} }
{ "doc" : {"field" : "value"} }
~~~
  使用 [Search](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) 接口做查詢,格式參考 GET //\_search,其中 target 可以理解為 Index(相當于數據庫表的名稱)。
~~~
GET web_monitor_2023.11/_search
~~~
  響應的 JSON 結構字段包含眾多(如下所示),took 是搜索耗費的毫秒數;\_shards 中的 total 代表本次搜索一共使用的分片數量;hits 中的 total 代表本次搜索得到的結果數,默認最大值為 1W,max\_score 指搜索結果中相關度得分的最大值,默認搜索結果會按照相關度得分降序排列,hits 就是命中的數據列表,而其中的 \_score 是單個文檔的相關度得分,\_source 就是原始數據的 JSON 內容。
~~~
{
"took": 6, // 搜索耗費的毫秒數
"timed_out": false,
"_shards": {
"total": 1, // 本次搜索一共使用的分片數量
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1, // 本次搜索得到的結果數,默認最大值為 1W
"relation": "eq"
},
"max_score": 1, // 搜索結果中相關度得分的最大值
"hits": [
{
"_index": "web_monitor_2023.11",
"_id": "862024079",
"_score": 1, // 單個文檔的相關度得分
"_source": { // 原始數據的 JSON 內容
"id": 862024079,
"project": "game",
"project_subdir": "chat",
"category": "ajax",
"fingerprint": "38eab40b373220bea1baee7b2933649c",
"country": "中國",
"province": "廣東省",
"city": "佛山市",
"isp": "電信",
"digit": 1
}
}
]
}
}
~~~
  如果要計算搜索結果真實的數據量,可以參考 [Count](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html) 接口,格式為 GET //\_count。
**4)索引模板**
  索引模板(Index Template)允許用戶在創建索引時,引用已保存的模板來減少配置項,在 MySQL 中就相當于創建表結構。
  Elasticsearch 的索引模板功能以 7.8 版本為界,兩個版本的主要區別是模板之間復用方式。
* 老版本:使用優先級(order)關鍵字實現,當創建索引匹配到多個索引模板時,高優先級會繼承并覆蓋低優先級的模板配置,最終多個模板共同起作用。
* 新版本:刪除了 order 關鍵字,引入了[組件模板](https://www.elastic.co/guide/en/elasticsearch/reference/current/_usage_example.html)(Component Template)的概念。在聲明[索引模板](https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-multi-component-templates.html)時可以引用多個組件模板,當創建索引匹配到多個索引模板時,選最高權重的那個。
  老版本會造成用戶在創建索引時,不能明確知道自己到底用了多少模板,索引配置在繼承覆蓋的過程中容易出錯。
  創建或更新一個老版索引模板,需要向 /\_template 發送 PUT 請求,配置包括 aliases、settings、[mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html)、order 等字段。
~~~
PUT _template/web_monitor
{
order: 0,
index_patterns: ["web_monitor_*"],
settings: {
index: {
number_of_shards: 1
}
},
mappings: {
dynamic: "strict",
properties: {
app_version: {
type: "keyword"
},
ctime: {
format: "strict_date_optional_time||epoch_second",
type: "date"
},
digit: {
type: "keyword",
fields: {
num: {
type: "integer"
}
}
},
author: {
type: "keyword"
},
ip: {
type: "ip"
}
}
},
aliases: {
web_monitor: {}
}
}
~~~
  新版本索引自動配置功能,需要通過組件模板和索引模板來完成。
  在組件模板中可配置的字段包括:aliases、settings 和 mappings,組件模板只有在被索引模板引用時,才會發揮作用。當需要創建或更新一個組件模板時,向 /\_component\_template 發送 PUT 請求即可。
~~~
PUT /_component_template/ct1
{
"template": {
"settings": {
"index.number_of_shards": 2
}
}
}
PUT /_component_template/ct2
{
"template": {
"settings": {
"index.number_of_replicas": 0
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
}
}
}
}
}
~~~
  創建或更新一個索引模板的方式都是向 /\_index\_template 發送 1 個 POST 請求。
~~~
POST /_index_template/_simulate
{
"index_patterns": ["my*"],
"template": {
"settings" : {
"index.number_of_shards" : 3
}
},
"composed_of": ["ct1", "ct2"]
}
~~~
**5)搜索**
  下面是一組查詢條件,[query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html)、from、size 和 sort 平級,分別表示查詢條件、頁碼、頁數和排序規則。
~~~
{
query: {
bool: { // 布爾查詢
must: [
[
{
multi_match: {
query: "精確",
fields: ["message", "title"],
type: "best_fields"
}
}
]
],
filter: [
{
term: {
category: "error"
}
},
{
term: {
project: "backend-app"
}
},
{
term: {
message_type: "runtime"
}
},
{
range: {
ctime: {
gte: 1699286400,
lt: 1699372800
}
}
}
]
}
},
from: 0,
size: 10,
sort: [
{
id: {
order: "DESC"
}
}
]
}
~~~
  布爾查詢([bool](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)),只有符合整個布爾條件的文檔才會被搜索出來,支持 4 種組合類型:
1. must:可包含多個查詢條件,每個條件都被滿足才能命中,每次查詢需要計算相關度得分。
2. should:可包含多個查詢條件,只要滿足一個條件就能命中,匹配到結果越多,相關度得分也越高。
3. filter:與 must 作用類似,但是不計算相關度得分,結果在一定條件下會被緩存。
4. must\_not:與 must 作用相反,并且也不計算相關度得分,結果在一定條件下會被緩存。
  多字段匹配([multi\_match](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html))允許用同一段文本檢索多個字段,其中 best\_fields 是默認的搜索方式,搜索文本與哪個字段相關度最高,就使用最佳字段中的 \_score。
  ES 內置了 8 種[文本分析器](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html),但對于中文的支持并不友好,無法準確的反映中文文本的語義,所以對于中文需要安裝另一款分析器:[ik](https://github.com/medcl/elasticsearch-analysis-ik/)。
  除了常規的全文檢索和精準查詢之外,ES 還支持經緯度搜索,包括圓形、矩形和多邊形范圍內的搜索。
**6)聚合**
  當需要對數據做分析時,就需要對數據進行聚合。在 MySQL 中常用的就是 sum()、group by 等語法。
  ES 提供的[聚合](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)分為 3 大類:
1. 度量聚合:計算搜索結果在某個字段上的數量統計指標,包括平均值、最大值、最小值、求和、基數(唯一值)、百分比、頭部命中等。
2. 桶聚合:在某個字段上劃定一些區間,每個區間是一個桶,統計結果能明確每個桶中的文檔數量。桶聚合還能嵌套其他的桶聚合或度量聚合來進行更為復雜的指標計算,例如詞條、直方圖、缺失等聚合。
3. 管道聚合:把桶聚合統計的結果作為輸入來繼續做聚合統計,在結果中追加一些額外的統計數據。
  下面是一個桶聚合的例子,在查詢條件中使用了 ES 特有的時間范圍語法糖(now-7d/d)。聚合部分要使用 aggs 屬性包裹,其子屬性 date 自定義的聚合名稱(在搜索結果中也會包含這個自定義的名稱),[date\_histogram](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html) 是聚合類型,以天為間隔,計算每天符合條件的數量。
~~~
{
query: {
bool: {
filter: [
{
term: {
category: "error"
}
},
{
range: {
ctime: {
gt: "now-7d/d", // 當前時間減去 7 天
lte: "now/d"
}
}
}
]
}
},
aggs: {
date: {
date_histogram: {
field: "ctime", // 字段名稱
interval: "day", // 以天為間隔
time_zone: "+08:00"
}
}
}
}
~~~
  聚合結果與查詢結果類似,也會包含符合查詢條件的文檔列表,但是還會多一個 aggregations 屬性。其 date 屬性就是之前自定義的聚合名稱,buckets 中就是聚合結果,key 是聚合的字段值,doc\_count 是計算的結果值,key\_as\_string 是格式化后的日期值,可在查詢時指定格式。
~~~
{
took: 245,
timed_out: false,
_shards: {
total: 2,
successful: 2,
skipped: 0,
failed: 0
},
hits: {
total: {
value: 3799,
relation: "eq"
},
max_score: 0,
hits: [{}, {}]
},
aggregations: {
date: {
buckets: [
{
key_as_string: "2023-11-02T00:00:00.000+08:00",
key: 1698854400000,
doc_count: 451
},
{
key_as_string: "2023-11-03T00:00:00.000+08:00",
key: 1698940800000,
doc_count: 594
},
{
key_as_string: "2023-11-04T00:00:00.000+08:00",
key: 1699027200000,
doc_count: 612
}
]
}
}
}
~~~
參考資料:
[elasticsearch 原理及入門](https://kiosk007.top/post/elasticsearch/)
[Elasticsearch-基礎介紹及索引原理分析](https://www.cnblogs.com/dreamroute/p/8484457.html)
[10 張圖理解 Elasticsearch 核心概念](https://xie.infoq.cn/article/73c7bc776a8ab2a0d7a173472)
[Frame of Reference 和 Roaring Bitmaps](https://blog.csdn.net/waltonhuang/article/details/107397028)
[RBM壓縮算法](https://www.cnblogs.com/lyc-code/p/15872505.html)
[全文搜索引擎 Elasticsearch 入門教程](https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html)
[elasticsearch-Index template 索引模板](https://blog.gaiaproject.club/es-index-template/)
*****
> 原文出處:
[博客園-Node.js精進](https://www.cnblogs.com/strick/category/2154090.html)
[知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1611672656142725120)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020