├── .gitignore ├── InterviewMap.png ├── InterviewMapMind.png ├── InterviewMapMind-en.png ├── image ├── InterviewMap.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f33302f31363237356638396562663933316539.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656332643366396534313f773d38343026683d31303026663d706e6726733d3330313233.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656362333961623230643f773d36373826683d31323026663d706e6726733d3332383132.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31312f313632623261623265633730616335623f773d39303026683d33353226663d706e6726733d3439393833.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31382f313632643764663234376463646130303f773d34343026683d37323726663d706e6726733d3338303032.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663130396462323762653035343f773d35303526683d34363126663d706e6726733d3232373936.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663131636332636238623333323f773d35303526683d35363326663d706e6726733d3236353134.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663134646639386365336438333f773d39353026683d31313826663d706e6726733d3737313531.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32342f313632663631616438653835383862373f773d36383226683d34383626663d706e6726733d3431303237.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633313935623234356365623839633f773d38333126683d31373026663d706e6726733d3232373933.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265343562303834653462633f773d38353826683d33303526663d706e6726733d3632313132.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316266316537396233636434323f773d36363626683d34323626663d706e6726733d3332313231.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633936646638353633643f773d35313826683d36343226663d706e6726733d3732343137.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633964333132386337613f773d39303026683d36313626663d706e6726733d3731303134.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353433633235653565396632333f773d38373426683d34353926663d706e6726733d3236333230.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353434323533316433653565653f773d34393426683d31333826663d706e6726733d39363336.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f322f313633316662383037663263366331623f773d36343026683d35313226663d706e6726733d3331303539.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376237383564326436383733353f773d36343026683d34363026663d706e6726733d36393332.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376362613261363135353739333f773d36343026683d34313926663d706e6726733d3135373337.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834663734633966346534643f773d33323026683d32363726663d706e6726733d3131363232.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383835306261373435383230383f773d35393626683d34383526663d706e6726733d3336373936.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356335383766666435343f773d36363026683d32373026663d706e6726733d3337313039.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356363613939633866343f773d36363026683d32313026663d706e6726733d3234353534.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f31352f313634303039653538613561323166383f773d35333726683d33393426663d706e6726733d3737323232.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32332f313634326363313435613063666232363f773d38303026683d35363626663d706e6726733d3733353733.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538623033313066343736633f773d36383526683d37333926663d706e6726733d3631343632.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653164326636636563333334383f773d36343026683d36303026663d706e6726733d3438333434.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653435623536666432353137323f773d34323126683d32303926663d706e6726733d3236353435.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838313039643537393935663f773d39343226683d34393326663d706e6726733d3339353831.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f31332f313632316538613962636230383732643f773d34383826683d35393026663d706e6726733d313531373232.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306466633466636432363f773d3131313826683d37323826663d706e6726733d3835363130.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306534663866333235373f773d3238313826683d32393826663d706e6726733d3734383333.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356234353262333036633f773d36373026683d35303826663d67696626733d323832333037.gif ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356337653539646364313f773d36373026683d35303826663d67696626733d363039353439.gif ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626338656131343536376532653f773d36373026683d35303826663d67696626733d393635363336.gif ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31362f313632636432336536396361396561333f773d38323426683d35303626663d67696626733d383637373434.gif ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32362f313632666663623766633163613561393f773d38303026683d3133303026663d706e6726733d3833313339.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f392f313632613963353663383338616138383f773d3231303026683d35343026663d706e6726733d313237353036.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265663965336336303033353f773d3132383026683d39363526663d706e6726733d313031343332.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432636136316561666631373f773d39323926683d35313226663d706e6726733d323435363730.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834383737353962313135323f773d3130363026683d31373826663d706e6726733d3230383230.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838343738353834613231373f773d3132343426683d35383526663d706e6726733d3539363337.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f382f313634373833383865373733623136613f773d3135303426683d37363026663d706e6726733d313233323331.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231653865626136386531633f773d3137373026683d37323226663d706e6726733d313932323737.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632366662366633336136663964373f773d3135383826683d37363826663d706e6726733d323633323630.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632373135653865333765363839643f773d3131363426683d36333626663d706e6726733d333030323835.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626531336337653330626438363f773d38393626683d3130303826663d67696626733d393337393532.gif ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538663839353935643536663f773d3131313926683d36303026663d706e6726733d333330383835.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31322f313634386439646637383230316630373f773d3132303026683d3330333926663d706e6726733d3530303231.png ├── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31372f313632643261396666323538646665313f773d3133373226683d33393426663d67696626733d31303138313831.gif └── 68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353236303132366233613130633f773d3135353826683d3130303626663d7765627026733d3539343234.webp ├── log-zh.md ├── LICENSE ├── Git ├── git-zh.md └── git-en.md ├── README.md ├── Career └── How-to-use-your-time-correctly.md ├── README-EN.md ├── Safety ├── safety-cn.md └── safety-en.md ├── Framework ├── vue-en.md ├── vue-br.md ├── react-zh.md ├── framework-zh.md └── react-en.md ├── Performance ├── performance-ch.md └── performance-en.md ├── Browser └── browser-ch.md ├── Network └── Network-zh.md └── Algorithm └── algorithm-ch.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test.js 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /InterviewMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/InterviewMap.png -------------------------------------------------------------------------------- /InterviewMapMind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/InterviewMapMind.png -------------------------------------------------------------------------------- /InterviewMapMind-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/InterviewMapMind-en.png -------------------------------------------------------------------------------- /image/InterviewMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/InterviewMap.png -------------------------------------------------------------------------------- /log-zh.md: -------------------------------------------------------------------------------- 1 | ##2018-07-28 2 | 3 | - flattenDeep 代码修改 4 | - 修改 Node eventloop 中对于 node 环境下 setTimeout 的打印描述 5 | - 增加 VueRouter 源码分析 6 | 7 | ## 2018-07-23 8 | 9 | - 对象转基本类型中增加 `Symbol.toPrimitive` 的描述 10 | - 修正正则简写中对 `\w` 错误的描述 11 | - 修改网络模块中的翻译错误 12 | - 删除判断 `null` 的语句 13 | - 修改服务端推送中的内容,使描述更加严谨 -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f33302f31363237356638396562663933316539.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f33302f31363237356638396562663933316539.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656332643366396534313f773d38343026683d31303026663d706e6726733d3330313233.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656332643366396534313f773d38343026683d31303026663d706e6726733d3330313233.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656362333961623230643f773d36373826683d31323026663d706e6726733d3332383132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231656362333961623230643f773d36373826683d31323026663d706e6726733d3332383132.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31312f313632623261623265633730616335623f773d39303026683d33353226663d706e6726733d3439393833.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31312f313632623261623265633730616335623f773d39303026683d33353226663d706e6726733d3439393833.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31382f313632643764663234376463646130303f773d34343026683d37323726663d706e6726733d3338303032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31382f313632643764663234376463646130303f773d34343026683d37323726663d706e6726733d3338303032.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663130396462323762653035343f773d35303526683d34363126663d706e6726733d3232373936.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663130396462323762653035343f773d35303526683d34363126663d706e6726733d3232373936.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663131636332636238623333323f773d35303526683d35363326663d706e6726733d3236353134.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663131636332636238623333323f773d35303526683d35363326663d706e6726733d3236353134.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663134646639386365336438333f773d39353026683d31313826663d706e6726733d3737313531.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32332f313632663134646639386365336438333f773d39353026683d31313826663d706e6726733d3737313531.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32342f313632663631616438653835383862373f773d36383226683d34383626663d706e6726733d3431303237.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32342f313632663631616438653835383862373f773d36383226683d34383626663d706e6726733d3431303237.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633313935623234356365623839633f773d38333126683d31373026663d706e6726733d3232373933.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633313935623234356365623839633f773d38333126683d31373026663d706e6726733d3232373933.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265343562303834653462633f773d38353826683d33303526663d706e6726733d3632313132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265343562303834653462633f773d38353826683d33303526663d706e6726733d3632313132.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316266316537396233636434323f773d36363626683d34323626663d706e6726733d3332313231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316266316537396233636434323f773d36363626683d34323626663d706e6726733d3332313231.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633936646638353633643f773d35313826683d36343226663d706e6726733d3732343137.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633936646638353633643f773d35313826683d36343226663d706e6726733d3732343137.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633964333132386337613f773d39303026683d36313626663d706e6726733d3731303134.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432633964333132386337613f773d39303026683d36313626663d706e6726733d3731303134.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353433633235653565396632333f773d38373426683d34353926663d706e6726733d3236333230.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353433633235653565396632333f773d38373426683d34353926663d706e6726733d3236333230.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353434323533316433653565653f773d34393426683d31333826663d706e6726733d39363336.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353434323533316433653565653f773d34393426683d31333826663d706e6726733d39363336.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f322f313633316662383037663263366331623f773d36343026683d35313226663d706e6726733d3331303539.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f322f313633316662383037663263366331623f773d36343026683d35313226663d706e6726733d3331303539.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376237383564326436383733353f773d36343026683d34363026663d706e6726733d36393332.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376237383564326436383733353f773d36343026683d34363026663d706e6726733d36393332.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376362613261363135353739333f773d36343026683d34313926663d706e6726733d3135373337.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32302f313633376362613261363135353739333f773d36343026683d34313926663d706e6726733d3135373337.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834663734633966346534643f773d33323026683d32363726663d706e6726733d3131363232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834663734633966346534643f773d33323026683d32363726663d706e6726733d3131363232.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383835306261373435383230383f773d35393626683d34383526663d706e6726733d3336373936.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383835306261373435383230383f773d35393626683d34383526663d706e6726733d3336373936.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356335383766666435343f773d36363026683d32373026663d706e6726733d3337313039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356335383766666435343f773d36363026683d32373026663d706e6726733d3337313039.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356363613939633866343f773d36363026683d32313026663d706e6726733d3234353534.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f352f313633326632356363613939633866343f773d36363026683d32313026663d706e6726733d3234353534.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f31352f313634303039653538613561323166383f773d35333726683d33393426663d706e6726733d3737323232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f31352f313634303039653538613561323166383f773d35333726683d33393426663d706e6726733d3737323232.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32332f313634326363313435613063666232363f773d38303026683d35363626663d706e6726733d3733353733.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32332f313634326363313435613063666232363f773d38303026683d35363626663d706e6726733d3733353733.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538623033313066343736633f773d36383526683d37333926663d706e6726733d3631343632.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538623033313066343736633f773d36383526683d37333926663d706e6726733d3631343632.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653164326636636563333334383f773d36343026683d36303026663d706e6726733d3438333434.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653164326636636563333334383f773d36343026683d36303026663d706e6726733d3438333434.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653435623536666432353137323f773d34323126683d32303926663d706e6726733d3236353435.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f392f313633653435623536666432353137323f773d34323126683d32303926663d706e6726733d3236353435.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838313039643537393935663f773d39343226683d34393326663d706e6726733d3339353831.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838313039643537393935663f773d39343226683d34393326663d706e6726733d3339353831.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f31332f313632316538613962636230383732643f773d34383826683d35393026663d706e6726733d313531373232.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f31332f313632316538613962636230383732643f773d34383826683d35393026663d706e6726733d313531373232.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306466633466636432363f773d3131313826683d37323826663d706e6726733d3835363130.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306466633466636432363f773d3131313826683d37323826663d706e6726733d3835363130.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306534663866333235373f773d3238313826683d32393826663d706e6726733d3734383333.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366232306534663866333235373f773d3238313826683d32393826663d706e6726733d3734383333.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356234353262333036633f773d36373026683d35303826663d67696626733d323832333037.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356234353262333036633f773d36373026683d35303826663d67696626733d323832333037.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356337653539646364313f773d36373026683d35303826663d67696626733d363039353439.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31322f313632623839356337653539646364313f773d36373026683d35303826663d67696626733d363039353439.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626338656131343536376532653f773d36373026683d35303826663d67696626733d393635363336.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626338656131343536376532653f773d36373026683d35303826663d67696626733d393635363336.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31362f313632636432336536396361396561333f773d38323426683d35303626663d67696626733d383637373434.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31362f313632636432336536396361396561333f773d38323426683d35303626663d67696626733d383637373434.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32362f313632666663623766633163613561393f773d38303026683d3133303026663d706e6726733d3833313339.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f32362f313632666663623766633163613561393f773d38303026683d3133303026663d706e6726733d3833313339.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f392f313632613963353663383338616138383f773d3231303026683d35343026663d706e6726733d313237353036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f392f313632613963353663383338616138383f773d3231303026683d35343026663d706e6726733d313237353036.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265663965336336303033353f773d3132383026683d39363526663d706e6726733d313031343332.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f312f313633316265663965336336303033353f773d3132383026683d39363526663d706e6726733d313031343332.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432636136316561666631373f773d39323926683d35313226663d706e6726733d323435363730.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353432636136316561666631373f773d39323926683d35313226663d706e6726733d323435363730.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834383737353962313135323f773d3130363026683d31373826663d706e6726733d3230383230.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f32322f313633383834383737353962313135323f773d3130363026683d31373826663d706e6726733d3230383230.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838343738353834613231373f773d3132343426683d35383526663d706e6726733d3539363337.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31312f313634383838343738353834613231373f773d3132343426683d35383526663d706e6726733d3539363337.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f382f313634373833383865373733623136613f773d3135303426683d37363026663d706e6726733d313233323331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f382f313634373833383865373733623136613f773d3135303426683d37363026663d706e6726733d313233323331.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231653865626136386531633f773d3137373026683d37323226663d706e6726733d313932323737.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32382f313632366231653865626136386531633f773d3137373026683d37323226663d706e6726733d313932323737.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632366662366633336136663964373f773d3135383826683d37363826663d706e6726733d323633323630.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632366662366633336136663964373f773d3135383826683d37363826663d706e6726733d323633323630.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632373135653865333765363839643f773d3131363426683d36333626663d706e6726733d333030323835.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32392f313632373135653865333765363839643f773d3131363426683d36333626663d706e6726733d333030323835.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626531336337653330626438363f773d38393626683d3130303826663d67696626733d393337393532.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31332f313632626531336337653330626438363f773d38393626683d3130303826663d67696626733d393337393532.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538663839353935643536663f773d3131313926683d36303026663d706e6726733d333330383835.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f362f32352f313634333538663839353935643536663f773d3131313926683d36303026663d706e6726733d333330383835.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31322f313634386439646637383230316630373f773d3132303026683d3330333926663d706e6726733d3530303231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f372f31322f313634386439646637383230316630373f773d3132303026683d3330333926663d706e6726733d3530303231.png -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31372f313632643261396666323538646665313f773d3133373226683d33393426663d67696626733d31303138313831.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f342f31372f313632643261396666323538646665313f773d3133373226683d33393426663d67696626733d31303138313831.gif -------------------------------------------------------------------------------- /image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353236303132366233613130633f773d3135353826683d3130303626663d7765627026733d3539343234.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InterviewMap/CS-Interview-Knowledge-Map/HEAD/image/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f352f31322f313633353236303132366233613130633f773d3135353826683d3130303626663d7765627026733d3539343234.webp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 YuChengKai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Git/git-zh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Rebase 合并](#rebase-%E5%90%88%E5%B9%B6) 6 | - [stash](#stash) 7 | - [reflog](#reflog) 8 | - [Reset](#reset) 9 | 10 | 11 | 12 | 本文不会介绍 Git 的基本操作,会对一些高级操作进行说明。 13 | 14 | ## Rebase 合并 15 | 16 | 该命令可以让和 `merge` 命令得到的结果基本是一致的。 17 | 18 | 通常使用 `merge` 操作将分支上的代码合并到 `master` 中,分支样子如下所示 19 | 20 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043128.png) 21 | 22 | 使用 `rebase` 后,会将 `develop` 上的 `commit` 按顺序移到 `master` 的第三个 `commit` 后面,分支样子如下所示 23 | 24 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043129.png) 25 | 26 | Rebase 对比 merge,优势在于合并后的结果很清晰,只有一条线,劣势在于如果一旦出现冲突,解决冲突很麻烦,可能要解决多个冲突,但是 merge 出现冲突只需要解决一次。 27 | 28 | 使用 rebase 应该在需要被 rebase 的分支上操作,并且该分支是本地分支。如果 `develop` 分支需要 rebase 到 `master` 上去,那么应该如下操作 29 | 30 | ```shell 31 | ## branch develop 32 | git rebase master 33 | git checkout master 34 | ## 用于将 `master` 上的 HEAD 移动到最新的 commit 35 | git merge develop 36 | ``` 37 | 38 | ## stash 39 | 40 | `stash` 用于临时保存工作目录的改动。开发中可能会遇到代码写一半需要切分支打包的问题,如果这时候你不想 `commit` 的话,就可以使用该命令。 41 | 42 | ```shell 43 | git stash 44 | ``` 45 | 46 | 使用该命令可以暂存你的工作目录,后面想恢复工作目录,只需要使用 47 | 48 | ```shell 49 | git stash pop 50 | ``` 51 | 52 | 这样你之前临时保存的代码又回来了 53 | 54 | ## reflog 55 | 56 | `reflog` 可以看到 HEAD 的移动记录,假如之前误删了一个分支,可以通过 `git reflog` 看到移动 HEAD 的哈希值 57 | 58 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-43130.png) 59 | 60 | 从图中可以看出,HEAD 的最后一次移动行为是 `merge` 后,接下来分支 `new` 就被删除了,那么我们可以通过以下命令找回 `new` 分支 61 | 62 | ```shell 63 | git checkout 37d9aca 64 | git checkout -b new 65 | ``` 66 | 67 | PS:`reflog` 记录是时效的,只会保存一段时间内的记录。 68 | 69 | ## Reset 70 | 71 | 如果你想删除刚写的 commit,就可以通过以下命令实现 72 | 73 | ```shell 74 | git reset --hard HEAD^ 75 | ``` 76 | 77 | 但是 `reset` 的本质并不是删除了 commit,而是重新设置了 HEAD 和它指向的 branch。 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Interview Map 5 |

6 | 7 |

这是一份能让你更好准备下一次面试的图谱

8 | 9 | [English Version](./README-EN.md) 10 | 11 | ## 阅读 12 | 13 | | 微信扫码关注公众号,订阅更多精彩内容 | 加笔者微信进群与大厂大佬讨论技术 | 14 | | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | 15 | | | | 16 | 17 | [线上版本阅读更清晰](https://yuchengkai.cn/docs/zh/) 18 | 19 | ## 小册 20 | 21 | 很荣幸在「掘金」平台发售了这个开源项目的进阶版面试小册「前端面试之道」。 22 | 23 |

24 | 25 |

26 | 27 | 如果需要用一句话来介绍这本小册的话,「**一年磨一剑**」应该是最好的答案了。 28 | 29 | 为什么这样说呢?在出小册之前,我花了半年的时间做了一个这个开源项目。在半年的时间里,我收集了大量的一线大厂面试题,通过大数据统计出了近百个常考知识点,然后根据这些知识点写成了**近十万字**的内容。 30 | 31 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042717.png) 32 | 33 | 这本小册是基于本开源项目重新写的一份前端面试书籍(目前已经写作三个月,预计成品需要五个月),对原本的内容进行了大幅度的优化,并且新增了很多新的内容。这本小册可以说是一线互联网大厂的面试精华总结,同时还包含了如何写简历和面试技巧的内容,能够帮助你省时省力地准备面试,让找工作不再是一个难题。 34 | 35 | 当然小册面向的群体不单单是求职者,同时也适合初级进阶,中级查漏补缺。如果你是一名面试官的话,说不定这本小册也能给你带来一些灵感。 36 | 37 | 面试是每个程序员都绕不开的坎,虽然这本小册不能帮你一夜之间技术一蹴而就,但是如果你能**细细阅读**的话,绝对能让你醍醐灌顶。 38 | 39 | 如果你对于内容不放心的话,可以看一下这两位业内大佬的评价,他们都是仔细读过小册后才给出的一个推荐。 40 | 41 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042719.png) 42 | 43 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042722.png) 44 | 45 | ## 贡献 46 | 47 | 如果你发现知识点内容有出错或者代码有 Bug,欢迎你提交英文 [issue](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/new),如果你英文不好的话,请在 [该 issue](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/18) 中讨论 48 | 49 | 如果你认为有一个不错的知识点或者也想参与翻译校对,欢迎提交 [PR](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/pulls),或者也可以联系我 50 | 51 | ## 协议 52 | 53 | [MIT](LICENSE). Copyright (c) 54 | -------------------------------------------------------------------------------- /Career/How-to-use-your-time-correctly.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [花时间补基础,读文档](#%E8%8A%B1%E6%97%B6%E9%97%B4%E8%A1%A5%E5%9F%BA%E7%A1%80%E8%AF%BB%E6%96%87%E6%A1%A3) 6 | - [学会搜索](#%E5%AD%A6%E4%BC%9A%E6%90%9C%E7%B4%A2) 7 | - [学点英语](#%E5%AD%A6%E7%82%B9%E8%8B%B1%E8%AF%AD) 8 | - [画个图,想一想再做](#%E7%94%BB%E4%B8%AA%E5%9B%BE%E6%83%B3%E4%B8%80%E6%83%B3%E5%86%8D%E5%81%9A) 9 | - [利用好下班时间学习](#%E5%88%A9%E7%94%A8%E5%A5%BD%E4%B8%8B%E7%8F%AD%E6%97%B6%E9%97%B4%E5%AD%A6%E4%B9%A0) 10 | - [列好 ToDo](#%E5%88%97%E5%A5%BD-todo) 11 | - [反思和整理](#%E5%8F%8D%E6%80%9D%E5%92%8C%E6%95%B4%E7%90%86) 12 | 13 | 14 | 15 | 你是否时常会焦虑时间过的很快,没时间学习,本文将会分享一些个人的见解。 16 | 17 | ### 花时间补基础,读文档 18 | 在工作中我们时常会花很多时间去 debug,但是你是否发现很多问题最终只是你基础不扎实或者文档没有仔细看。 19 | 20 | 基础是你技术的基石,一定要花时间打好基础,而不是追各种新的技术。一旦你的基础扎实,学习各种新的技术也肯定不在话下,因为新的技术,究其根本都是相通的。 21 | 22 | 文档同样也是一门技术的基础。一个优秀的库,开发人员肯定已经把如何使用这个库都写在文档中了,仔细阅读文档一定会是少写 bug 的最省事路子。 23 | 24 | ### 学会搜索 25 | 如果你还在使用百度搜索编程问题,请尽快抛弃这个垃圾搜索引擎。同样一个关键字,使用百度和谷歌,谷歌基本完胜的。即使你使用中文在谷歌中搜索,得到的结果也往往是谷歌占优,所以如果你想迅速的通过搜索引擎来解决问题,那一定是谷歌。 26 | 27 | ### 学点英语 28 | 说到英语,一定是大家所最不想听的。其实我一直认为程序员学习英语是简单的,因为我们工作中是一直接触着英语,并且看懂技术文章,文档所需要的单词量是极少的。我时常在群里看到大家发出一个问题的截图问什么原因,其实在截图中英语已经很明白的说明了问题的所在,如果你的英语过关,完全不需要浪费时间来提问和搜索。所以我认为学点英语也是节省时间中很重要的一点。 29 | 30 | 那么如何去学习呢,chrome 装个翻译插件,直接拿英文文档或文章读,不会的就直接划词翻译,然后记录下这个单词并背诵。每天花半小时看点英文文档和文章,坚持两个月,你的英语水平不说别的,看文档和文章绝对不会有难题了。这一定是一个很划算的个人时间投资,花点时间学习英语,能为你将来的技术之路铺平很多坎。 31 | 32 | ### 画个图,想一想再做 33 | 你是否遇到过这种问题,需求一下来,看一眼,然后马上就按照设计稿开始做了,可能中间出个问题导致你需要返工。 34 | 35 | 如果你存在这样的问题,我很推荐在看到设计稿和需求的时候花点时间想一想,画一画。考虑一下设计稿中是否可以找到可以拆分出来的复用组件,是否存在之前写过的组件。该如何组织这个界面,数据的流转是怎么样的。然后画一下这个页面的需求,最后再动手做。 36 | 37 | ### 利用好下班时间学习 38 | 说到下班时间,那可能就有人说了公司很迟下班,这其实是国内很普遍的情况。但是我认为正常的加班是可以的,但是强制的加班就是在损耗你的身体和前途。 39 | 40 | 可以这么说,大部分的 996 公司,加班的这些时间并不会增加你的技术,无非就是在写一些重复的业务逻辑。也许你可以拿到更多的钱,但是代价是身体还有前途。程序员是靠技术吃饭的,如果你长久呆在一个长时间加班的公司,不能增长你的技术还要吞噬你的下班学习时间,那么你一定会废掉的。如果你遇到了这种情况,只能推荐尽快跳槽到非 996 的公司。 41 | 42 | 那么如果你有足够的下班时间,一定要花上 1, 2 小时去学习,上班大家基本都一样,技术的精进就是看下班以后的那几个小时了。如果你能利用好下班时间来学习,坚持下去,时间一定会给你很好的答复。 43 | 44 | ### 列好 ToDo 45 | 我喜欢规划好一段时间内要做的事情,并且要把事情拆分为小点。给 ToDo 列好优先级,紧急的优先级最高。相同优先级的我喜欢先做简单的,因为这样一旦完成就能划掉一个,提高成就感。 46 | 47 | ### 反思和整理 48 | 每周末都会花上点时间整理下本周记录的笔记和看到的不错文章。然后考虑下本周完成的工作和下周准备要完成的工作。 49 | -------------------------------------------------------------------------------- /Git/git-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Merge with Rebase](#merge-with-rebase) 6 | - [stash](#stash) 7 | - [reflog](#reflog) 8 | - [Reset](#reset) 9 | 10 | 11 | 12 | This is not for rookie, we'll introduce something about more advanced. 13 | ## Merge with Rebase 14 | This command shows no difference with the command `merge`. 15 | 16 | We usually use `merge` to merge the code from one branch to `master` , like this: 17 | 18 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043130.png) 19 | 20 | After using `rebase ` , the commits from `develop` will be moved to the third `commit` of the `master` in order, as follows: 21 | 22 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043133.png) 23 | 24 | Compare with `merge`, the result of `rebase` is very clear with a single flow. But if there is any conflict, you'll be in trouble to solving them. You have to solve them one by one , while you only need to solve them one-time if using `merge`. 25 | 26 | You should use `rebase` on the local branchs which need be rebased. If you need to `rebase` the `develop` to the `master`, you should do as follows: 27 | 28 | ```shell 29 | ## branch develop 30 | git rebase master 31 | git checkout master 32 | ## move HEAD on `master` to the latest commit 33 | git merge develop 34 | ``` 35 | 36 | ## stash 37 | 38 | Use `git stash` to save the current state of the working directory while you want to check out branch, if you don't want to use `commit`. 39 | 40 | ```shell 41 | git stash 42 | ``` 43 | This command can record the current state of the working directory, if you want to recover it, you can do like this: 44 | 45 | ```shell 46 | git stash pop 47 | ``` 48 | then you'll back to the exactly state before. 49 | 50 | ## reflog 51 | 52 | This command will show you the records of HEAD's trace. If you delete a branch by mistake, you can examine the hashs of HEAD by using `reflog`. 53 | 54 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043135.png) 55 | 56 | According to the picture, the last movement of HEAD is just after `merge`, and then the `new` branch was deleted, so we can get the branch back by the following command: 57 | 58 | ```shell 59 | git checkout 37d9aca 60 | git checkout -b new 61 | ``` 62 | 63 | PS:`reflog` is time-bound, it can only record the state over a period of time. 64 | 65 | 66 | ## Reset 67 | 68 | If you want to delete the last commit, you can do like this: 69 | 70 | ```shell 71 | git reset --hard HEAD^ 72 | ``` 73 | But this command doesn't delete the commit, it just reset the HEAD and branch. 74 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Interview Map 5 |

6 | 7 |

This is a map that can help you prepare better for the next interview

8 | 9 |

10 | Gitter 11 | Telegram 12 |

13 | 14 | ## Foreword 15 | 16 | > When you are old, looking back on your life, you will find out: when to study abroad, when to decide on your first occupation, when to choose the other half to fall in love with, when to marry; all of these are great changes in fate. We were just standing at the three-forked intersection and seeing the wind blow the clouds and sails. The day you made your choice was a quite dull and peaceful day in the diary, and it was thought to be an ordinary day in your life. 17 | > A project to change the interview — Interview Map. 18 | 19 | 20 | The best job-hopping months, September and October, are coming(in Chinese, there is an idiom called "Golden September and Silver October”). Most people will be eager to prepare for and to pursue better job opportunities. The interview will be their biggest challenge. 21 | 22 | For the interview, the usual accumulation of learning is necessary, but the preparation before the interview is also crucial. 23 | 24 | A few months ago, I set up a small team. It took us half a year to search for interview questions from big companies, filtering out nearly 100 knowledge points, writing the content and translating it all into English. Today, we finally release the first version and the total number of words has reached more than 100,000 so far. 25 | 26 | We think that rote learning of the interview questions is not very useful. Only when you are familiar with the various knowledge points and are capable of integrating them, can you get through the interviews. This InterviewMap currently contains nearly a hundred high-frequency knowledge points. No matter the preparation before the interview or the study, we believe that it will help you. The current content includes JS, network, browser related, performance optimization, security, framework, Git, data structures, algorithms, etc. Whether it is basic or advanced learning or source code interpretation, you can get a satisfactory answer in this InterviewMap, and we hope that the InterviewMap can help you better prepare for your interview. 27 | 28 | The contents of the repository will update continuously, and more content will be added into the repository later, such as system design, the blockchain, operating and support, backend, etc. Of course, these are not my strengths, and I will invite friends who have good experience in this area to write this content. 29 | 30 | 31 | # Outline 32 | ![mind](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-043136.png) 33 | 34 | 35 | ## Contributing 36 | If you find something wrong with the knowledge point or there’s a bug in the code, you are welcome to submit an English [issue](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/new). If your English is not good, please discuss it in [this issue](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/18). 37 | 38 | If you think you know of a good knowledge point to contribute, or would like to participate in translation and proofreading, you are welcome to submit a [PR](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/pulls), or you can contact me at . 39 | 40 | ## Todo 41 | 42 | * Complete the content about CSS 43 | * Complete the content about Webpack 44 | * Complete the content about the mini program (WeiXin) 45 | * Improve the content of the framework 46 | 47 | The above content is expected to be updated completely in September, and you are welcome to participate in the construction of this interviewmap. 48 | 49 | ## Reading 50 | 51 | [Online version is clearer to read](https://yuchengkai.cn/docs/). 52 | 53 | ## Communicate and share 54 | If you want to communicate and discuss the content of this interviewmap with others, you can join [communicate group](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/19) or [gitter](https://gitter.im/interview-map/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link). 55 | 56 | 57 | ## Support us 58 | If the interviewmap helps you with your interview or study, you can [support our work](https://github.com/InterviewMap/CS-Interview-Knowledge-Map/issues/20). 59 | 60 | 61 | ## License 62 | [MIT](LICENSE). Copyright (c) 63 | -------------------------------------------------------------------------------- /Safety/safety-cn.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [XSS](#xss) 6 | - [如何攻击](#%E5%A6%82%E4%BD%95%E6%94%BB%E5%87%BB) 7 | - [如何防御](#%E5%A6%82%E4%BD%95%E9%98%B2%E5%BE%A1) 8 | - [CSP](#csp) 9 | - [CSRF](#csrf) 10 | - [如何攻击](#%E5%A6%82%E4%BD%95%E6%94%BB%E5%87%BB-1) 11 | - [如何防御](#%E5%A6%82%E4%BD%95%E9%98%B2%E5%BE%A1-1) 12 | - [SameSite](#samesite) 13 | - [验证 Referer](#%E9%AA%8C%E8%AF%81-referer) 14 | - [Token](#token) 15 | - [密码安全](#%E5%AF%86%E7%A0%81%E5%AE%89%E5%85%A8) 16 | - [加盐](#%E5%8A%A0%E7%9B%90) 17 | 18 | 19 | 20 | ## XSS 21 | 22 | > **跨网站指令码**(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程式的安全漏洞攻击,是[代码注入](https://www.wikiwand.com/zh-hans/%E4%BB%A3%E7%A2%BC%E6%B3%A8%E5%85%A5)的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及使用者端脚本语言。 23 | 24 | XSS 分为三种:反射型,存储型和 DOM-based 25 | 26 | ### 如何攻击 27 | 28 | XSS 通过修改 HTML 节点或者执行 JS 代码来攻击网站。 29 | 30 | 例如通过 URL 获取某些参数 31 | 32 | ```html 33 | 34 |
{{name}}
35 | ``` 36 | 37 | 上述 URL 输入可能会将 HTML 改为 `
` ,这样页面中就凭空多了一段可执行脚本。这种攻击类型是反射型攻击,也可以说是 DOM-based 攻击。 38 | 39 | 也有另一种场景,比如写了一篇包含攻击代码 `` 的文章,那么可能浏览文章的用户都会被攻击到。这种攻击类型是存储型攻击,也可以说是 DOM-based 攻击,并且这种攻击打击面更广。 40 | 41 | ### 如何防御 42 | 43 | 最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义 44 | 45 | ```js 46 | function escape(str) { 47 | str = str.replace(/&/g, "&"); 48 | str = str.replace(//g, ">"); 50 | str = str.replace(/"/g, "&quto;"); 51 | str = str.replace(/'/g, "'"); 52 | str = str.replace(/`/g, "`"); 53 | str = str.replace(/\//g, "/"); 54 | return str 55 | } 56 | ``` 57 | 58 | 通过转义可以将攻击代码 `` 变成 59 | 60 | ```js 61 | // -> <script>alert(1)</script> 62 | escape('') 63 | ``` 64 | 65 | 对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。 66 | 67 | ```js 68 | var xss = require("xss"); 69 | var html = xss('

XSS Demo

'); 70 | // ->

XSS Demo

<script>alert("xss");</script> 71 | console.log(html); 72 | ``` 73 | 74 | 以上示例使用了 `js-xss` 来实现。可以看到在输出中保留了 `h1` 标签且过滤了 `script` 标签 75 | 76 | ### CSP 77 | 78 | > 内容安全策略 ([CSP](https://developer.mozilla.org/en-US/docs/Glossary/CSP)) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 ([XSS](https://developer.mozilla.org/en-US/docs/Glossary/XSS)) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。 79 | 80 | 我们可以通过 CSP 来尽量减少 XSS 攻击。CSP 本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码。 81 | 82 | 通常可以通过 HTTP Header 中的 `Content-Security-Policy` 来开启 CSP 83 | 84 | - 只允许加载本站资源 85 | 86 | ```http 87 | Content-Security-Policy: default-src ‘self’ 88 | ``` 89 | 90 | - 只允许加载 HTTPS 协议图片 91 | 92 | ```http 93 | Content-Security-Policy: img-src https://* 94 | ``` 95 | 96 | - 允许加载任何来源框架 97 | 98 | ```http 99 | Content-Security-Policy: child-src 'none' 100 | ``` 101 | 102 | 更多属性可以查看 [这里](https://content-security-policy.com/) 103 | 104 | ## CSRF 105 | 106 | > **跨站请求伪造**(英语:Cross-site request forgery),也被称为 **one-click attack** 或者 **session riding**,通常缩写为 **CSRF** 或者 **XSRF**, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。[[1\]](https://www.wikiwand.com/zh/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0#citenoteRistic1) 跟[跨網站指令碼](https://www.wikiwand.com/zh/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC)(XSS)相比,**XSS** 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。 107 | 108 | 简单点说,CSRF 就是利用用户的登录态发起恶意请求。 109 | 110 | ### 如何攻击 111 | 112 | 假设网站中有一个通过 Get 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口 113 | 114 | ```html 115 | 116 | ``` 117 | 118 | 如果接口是 Post 提交的,就相对麻烦点,需要用表单来提交接口 119 | 120 | ```html 121 |
122 | 123 |
124 | ``` 125 | 126 | ### 如何防御 127 | 128 | 防范 CSRF 可以遵循以下几种规则: 129 | 130 | 1. Get 请求不对数据进行修改 131 | 2. 不让第三方网站访问到用户 Cookie 132 | 3. 阻止第三方网站请求接口 133 | 4. 请求时附带验证信息,比如验证码或者 token 134 | 135 | #### SameSite 136 | 137 | 可以对 Cookie 设置 `SameSite` 属性。该属性设置 Cookie 不随着跨域请求发送,该属性可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。 138 | 139 | #### 验证 Referer 140 | 141 | 对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。 142 | 143 | #### Token 144 | 145 | 服务器下发一个随机 Token(算法不能复杂),每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。 146 | 147 | ## 密码安全 148 | 149 | 密码安全虽然大多是后端的事情,但是作为一名优秀的前端程序员也需要熟悉这方面的知识。 150 | 151 | ### 加盐 152 | 153 | 对于密码存储来说,必然是不能明文存储在数据库中的,否则一旦数据库泄露,会对用户造成很大的损失。并且不建议只对密码单纯通过加密算法加密,因为存在彩虹表的关系。 154 | 155 | 通常需要对密码加盐,然后进行几次不同加密算法的加密。 156 | 157 | ```js 158 | // 加盐也就是给原密码添加字符串,增加原密码长度 159 | sha256(sha1(md5(salt + password + salt))) 160 | ``` 161 | 162 | 但是加盐并不能阻止别人盗取账号,只能确保即使数据库泄露,也不会暴露用户的真实密码。一旦攻击者得到了用户的账号,可以通过暴力破解的方式破解密码。对于这种情况,通常使用验证码增加延时或者限制尝试次数的方式。并且一旦用户输入了错误的密码,也不能直接提示用户输错密码,而应该提示账号或密码错误。 163 | -------------------------------------------------------------------------------- /Framework/vue-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [NextTick principle analysis](#nexttick-principle-analysis) 6 | - [Lifecycle analysis](#lifecycle-analysis) 7 | 8 | 9 | 10 | # NextTick principle analysis 11 | 12 | `nextTick` allows us to defer the callback to be executed after the next DOM update cycle, to get the updated DOM. 13 | 14 | Before version 2.4, Vue used microtasks, but the priority of microtasks is too high, and in some cases, it may faster than event bubbling, but if you use macrotasks, there may be some issues of rendering performance. So in the new version, microtasks will be used by default, but macrotasks will be used in special cases, such as v-on. 15 | 16 | For implementing macrotasks, you will first determine if `setImmediate` can be used, if not, downgrade to `MessageChannel`. If not again, use `setTimeout`. 17 | 18 | ```js 19 | if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { 20 | macroTimerFunc = () => { 21 | setImmediate(flushCallbacks) 22 | } 23 | } else if ( 24 | typeof MessageChannel !== 'undefined' && 25 | (isNative(MessageChannel) || 26 | // PhantomJS 27 | MessageChannel.toString() === '[object MessageChannelConstructor]') 28 | ) { 29 | const channel = new MessageChannel() 30 | const port = channel.port2 31 | channel.port1.onmessage = flushCallbacks 32 | macroTimerFunc = () => { 33 | port.postMessage(1) 34 | } 35 | } else { 36 | /* istanbul ignore next */ 37 | macroTimerFunc = () => { 38 | setTimeout(flushCallbacks, 0) 39 | } 40 | } 41 | ``` 42 | 43 | `nextTick` also supports the use of `Promise`, which will determine whether `Promise` is implemented. 44 | 45 | ```js 46 | export function nextTick(cb?: Function, ctx?: Object) { 47 | let _resolve 48 | // Consolidate callback functions into an array 49 | callbacks.push(() => { 50 | if (cb) { 51 | try { 52 | cb.call(ctx) 53 | } catch (e) { 54 | handleError(e, ctx, 'nextTick') 55 | } 56 | } else if (_resolve) { 57 | _resolve(ctx) 58 | } 59 | }) 60 | if (!pending) { 61 | pending = true 62 | if (useMacroTask) { 63 | macroTimerFunc() 64 | } else { 65 | microTimerFunc() 66 | } 67 | } 68 | // Determine if Promise can be used 69 | // Assign _resolve if possible 70 | // This way the callback function can be called in the way of promise 71 | if (!cb && typeof Promise !== 'undefined') { 72 | return new Promise(resolve => { 73 | _resolve = resolve 74 | }) 75 | } 76 | } 77 | ``` 78 | 79 | # Lifecycle analysis 80 | 81 | The lifecycle function is the hook function that the component will trigger when it initializes or updates the data. 82 | 83 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042527.png) 84 | 85 | The following code will be called at initialization, and lifecycle is called by `callHook` 86 | 87 | ```js 88 | Vue.prototype._init = function(options) { 89 | initLifecycle(vm) 90 | initEvents(vm) 91 | initRender(vm) 92 | callHook(vm, 'beforeCreate') // can not get props data 93 | initInjections(vm) 94 | initState(vm) 95 | initProvide(vm) 96 | callHook(vm, 'created') 97 | } 98 | ``` 99 | 100 | It can be found that in the above code when `beforeCreate` is called, the data in `props` or `data` cannot be obtained because the initialization of these data is in `initState`. 101 | 102 | Next, the mount function will be called 103 | 104 | ```js 105 | export function mountComponent { 106 | callHook(vm, 'beforeMount') 107 | // ... 108 | if (vm.$vnode == null) { 109 | vm._isMounted = true 110 | callHook(vm, 'mounted') 111 | } 112 | } 113 | ``` 114 | 115 | `beforeMount` will be executed before mounting the instance, then starts to create the VDOM and replace it with the real DOM, and finally call the `mounted` hook. And there’s a judgment logic here that if it is an external `new Vue({}) `, `$vnode` doesn’t exist, so the `mounted` hook will be executed directly. If there are child components, they will be mounted recursively, only when all the child components are mounted, the mount hooks of the root components will be executed. 116 | 117 | Next, it comes to the hook function that will be called when the data is updated. 118 | 119 | ```js 120 | function flushSchedulerQueue() { 121 | // ... 122 | for (index = 0; index < queue.length; index++) { 123 | watcher = queue[index] 124 | if (watcher.before) { 125 | watcher.before() // call `beforeUpdate` 126 | } 127 | id = watcher.id 128 | has[id] = null 129 | watcher.run() 130 | // in dev build, check and stop circular updates. 131 | if (process.env.NODE_ENV !== 'production' && has[id] != null) { 132 | circular[id] = (circular[id] || 0) + 1 133 | if (circular[id] > MAX_UPDATE_COUNT) { 134 | warn( 135 | 'You may have an infinite update loop ' + 136 | (watcher.user 137 | ? `in watcher with expression "${watcher.expression}"` 138 | : `in a component render function.`), 139 | watcher.vm 140 | ) 141 | break 142 | } 143 | } 144 | } 145 | callUpdatedHooks(updatedQueue) 146 | } 147 | 148 | function callUpdatedHooks(queue) { 149 | let i = queue.length 150 | while (i--) { 151 | const watcher = queue[i] 152 | const vm = watcher.vm 153 | if (vm._watcher === watcher && vm._isMounted) { 154 | callHook(vm, 'updated') 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | There are two lifecycle functions that aren’t mentioned in the above diagram, `activated` and `deactivated`, and only the `kee-alive` component has these two life cycles. Components wrapped with `keep-alive` will not be destroyed during the switch, but be cached in memory and execute the `deactivated` hook function, and execute the `actived` hook function after matching the cache and rendering. 161 | 162 | Finally, let’s see the hook function that used to destroy the component. 163 | 164 | ```js 165 | Vue.prototype.$destroy = function() { 166 | // ... 167 | callHook(vm, 'beforeDestroy') 168 | vm._isBeingDestroyed = true 169 | // remove self from parent 170 | const parent = vm.$parent 171 | if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { 172 | remove(parent.$children, vm) 173 | } 174 | // teardown watchers 175 | if (vm._watcher) { 176 | vm._watcher.teardown() 177 | } 178 | let i = vm._watchers.length 179 | while (i--) { 180 | vm._watchers[i].teardown() 181 | } 182 | // remove reference from data ob 183 | // frozen object may not have observer. 184 | if (vm._data.__ob__) { 185 | vm._data.__ob__.vmCount-- 186 | } 187 | // call the last hook... 188 | vm._isDestroyed = true 189 | // invoke destroy hooks on current rendered tree 190 | vm.__patch__(vm._vnode, null) 191 | // fire destroyed hook 192 | callHook(vm, 'destroyed') 193 | // turn off all instance listeners. 194 | vm.$off() 195 | // remove __vue__ reference 196 | if (vm.$el) { 197 | vm.$el.__vue__ = null 198 | } 199 | // release circular reference (#6759) 200 | if (vm.$vnode) { 201 | vm.$vnode.parent = null 202 | } 203 | } 204 | ``` 205 | 206 | The `beforeDestroy` hook function will be called before the destroy operation is performed, and then a series of destruction operations are performed. If there are child components, they will be destroyed recursively, and only when all the child components are destroyed, the hook `destroyed` of the root component will be executed. 207 | -------------------------------------------------------------------------------- /Framework/vue-br.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Análises do princípio NextTick](#nexttick-principle-analysis) 6 | - [Análises do ciclo de vid](#lifecycle-analysis) 7 | 8 | 9 | 10 | # Análises do princípio NextTick 11 | 12 | `nextTick` permiti-nos adiar a callback ser executada depois da próxima atualizada do ciclo do DOM, para obter a atualização. 13 | 14 | Antes da versão 2.4, Vue usou micro tarefas, mas prioridade das micro tarefas é bem alta, e em alguns casos, isso deve ser mais rápido que o evento de bubbling, mas se você usar macro tarefas, pode haver alguns problemas de performance na renderização. Então na nova versão, micro tarefas são usadas por padrão, mas macro tarefas serão usadas em casos especiais, como v-on. 15 | 16 | Para implementar macro tarefas, você primeiro deve determinar se o `setImmediate` pode ser usado, se não, abaixe para `MessageChannel`. Se não novamente, use `setTimeout`. 17 | 18 | ```js 19 | if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { 20 | macroTimerFunc = () => { 21 | setImmediate(flushCallbacks) 22 | } 23 | } else if ( 24 | typeof MessageChannel !== 'undefined' && 25 | (isNative(MessageChannel) || 26 | // PhantomJS 27 | MessageChannel.toString() === '[object MessageChannelConstructor]') 28 | ) { 29 | const channel = new MessageChannel() 30 | const port = channel.port2 31 | channel.port1.onmessage = flushCallbacks 32 | macroTimerFunc = () => { 33 | port.postMessage(1) 34 | } 35 | } else { 36 | /* istanbul ignore next */ 37 | macroTimerFunc = () => { 38 | setTimeout(flushCallbacks, 0) 39 | } 40 | } 41 | ``` 42 | 43 | `nextTick` também suporta o uso de `Promise`, do qual ira determinar se a `Promise` está implementada. 44 | 45 | ```js 46 | export function nextTick(cb?: Function, ctx?: Object) { 47 | let _resolve 48 | // Consolide funções de callback dentro do de um array 49 | callbacks.push(() => { 50 | if (cb) { 51 | try { 52 | cb.call(ctx) 53 | } catch (e) { 54 | handleError(e, ctx, 'nextTick') 55 | } 56 | } else if (_resolve) { 57 | _resolve(ctx) 58 | } 59 | }) 60 | if (!pending) { 61 | pending = true 62 | if (useMacroTask) { 63 | macroTimerFunc() 64 | } else { 65 | microTimerFunc() 66 | } 67 | } 68 | // Determina se a Promisse pode ser usada 69 | // Atribuir _resolve se possivel 70 | // Desta maneira a função callback pode ser chamada em forma de promise 71 | if (!cb && typeof Promise !== 'undefined') { 72 | return new Promise(resolve => { 73 | _resolve = resolve 74 | }) 75 | } 76 | } 77 | ``` 78 | 79 | # Análise do Ciclo de Vida 80 | 81 | A função do ciclo de vida é a função gancho que o componente vai disparar quando inicializar ou atualizar os dados. 82 | 83 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042532.png) 84 | 85 | O seguinte código irá ser chamado na inicialização, e o ciclo de vida vai ser chamado pelo `callHook` 86 | 87 | ```js 88 | Vue.prototype._init = function(options) { 89 | initLifecycle(vm) 90 | initEvents(vm) 91 | initRender(vm) 92 | callHook(vm, 'beforeCreate') // não consegue receber dados das props 93 | initInjections(vm) 94 | initState(vm) 95 | initProvide(vm) 96 | callHook(vm, 'created') 97 | } 98 | ``` 99 | 100 | Ele pode ser encontrado no código acima quando `beforeCreate` é chamado, o dado no `props` ou `data` não pode ser obtido porque a inicialização desse dado está no `initState`. 101 | 102 | No próximo, a função montadora vai ser chamada 103 | 104 | ```js 105 | export function mountComponent { 106 | callHook(vm, 'beforeMount') 107 | // ... 108 | if (vm.$vnode == null) { 109 | vm._isMounted = true 110 | callHook(vm, 'mounted') 111 | } 112 | } 113 | ``` 114 | 115 | `beforeMount` vai ser executado antes de montar uma instância, então comece a criar o VDOM e substituir ele com o DOM real, e finalmente chame o gancho `mounted`. E há um julgamente lógico aqui, que se ele for um `new Vue({}) ` externo, `$vnode` não existe, então o gancho `mounted` será executado diretamente. Se existe um componente filho, ele vai ser montado recursivamente, apenas quando todos os componentes filhos forem montados, o gancho de montar o componente raíz vai ser executado. 116 | 117 | Próximo, isso vem para a função gancho que vai ser chamada quando os dados forem atualizados. 118 | 119 | ```js 120 | function flushSchedulerQueue() { 121 | // ... 122 | for (index = 0; index < queue.length; index++) { 123 | watcher = queue[index] 124 | if (watcher.before) { 125 | watcher.before() // chama `beforeUpdate` 126 | } 127 | id = watcher.id 128 | has[id] = null 129 | watcher.run() 130 | // no dev build, verifca e para check and stop circular updates. 131 | if (process.env.NODE_ENV !== 'production' && has[id] != null) { 132 | circular[id] = (circular[id] || 0) + 1 133 | if (circular[id] > MAX_UPDATE_COUNT) { 134 | warn( 135 | 'You may have an infinite update loop ' + 136 | (watcher.user 137 | ? `in watcher with expression "${watcher.expression}"` 138 | : `in a component render function.`), 139 | watcher.vm 140 | ) 141 | break 142 | } 143 | } 144 | } 145 | callUpdatedHooks(updatedQueue) 146 | } 147 | 148 | function callUpdatedHooks(queue) { 149 | let i = queue.length 150 | while (i--) { 151 | const watcher = queue[i] 152 | const vm = watcher.vm 153 | if (vm._watcher === watcher && vm._isMounted) { 154 | callHook(vm, 'updated') 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | Existem duas funções do ciclo de vida que não são mencionada no diagrama acima, `activated` e `deactivated`, e apenas o componente `kee-alive` possui esses dois ciclos. Componente encapsulado com `keep-alive` não serão destruídos durante o switch, mas sera cacheado em memória e executado a função gancho `deactivated`, e executar a função `actived` depois de coincidir o cache e a renderização. 161 | 162 | Finalmente, vamos olhar a função gancho usada para destruir o componente. 163 | 164 | ```js 165 | Vue.prototype.$destroy = function() { 166 | // ... 167 | callHook(vm, 'beforeDestroy') 168 | vm._isBeingDestroyed = true 169 | // remove-se mesmo a partir do pai 170 | const parent = vm.$parent 171 | if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { 172 | remove(parent.$children, vm) 173 | } 174 | // destroi watchers 175 | if (vm._watcher) { 176 | vm._watcher.teardown() 177 | } 178 | let i = vm._watchers.length 179 | while (i--) { 180 | vm._watchers[i].teardown() 181 | } 182 | // remove a referência a partir do dado ob 183 | // objeto congelados não devem ter um observador. 184 | if (vm._data.__ob__) { 185 | vm._data.__ob__.vmCount-- 186 | } 187 | // chame o último gancho... 188 | vm._isDestroyed = true 189 | // invoque ganchos destruídos na árvore atualmente renderizada 190 | // dispare o gancho destruído 191 | callHook(vm, 'destroyed') 192 | // desligo todos os ouvintes da instância. 193 | vm.$off() 194 | // remove __vue__ reference 195 | // remove __vue__ reference 196 | if (vm.$el) { 197 | vm.$el.__vue__ = null 198 | } 199 | // lance uma referência circular (#6759) 200 | if (vm.$vnode) { 201 | vm.$vnode.parent = null 202 | } 203 | } 204 | ``` 205 | 206 | A função `beforeDestroy` será chamada antes da operação de destruir ser desempenhada, e então uma série de operações de destruição são desempenhadas. Se existe um componente filho, eles serão destruidos recursivamente, e apenas quando todos os componente filhos são destruídos, o gancho `destroyed` do componente raíz será executado. 207 | -------------------------------------------------------------------------------- /Performance/performance-ch.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [网络相关](#%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3) 6 | - [DNS 预解析](#dns-%E9%A2%84%E8%A7%A3%E6%9E%90) 7 | - [缓存](#%E7%BC%93%E5%AD%98) 8 | - [强缓存](#%E5%BC%BA%E7%BC%93%E5%AD%98) 9 | - [协商缓存](#%E5%8D%8F%E5%95%86%E7%BC%93%E5%AD%98) 10 | - [Last-Modified 和 If-Modified-Since](#last-modified-%E5%92%8C-if-modified-since) 11 | - [ETag 和 If-None-Match](#etag-%E5%92%8C-if-none-match) 12 | - [选择合适的缓存策略](#%E9%80%89%E6%8B%A9%E5%90%88%E9%80%82%E7%9A%84%E7%BC%93%E5%AD%98%E7%AD%96%E7%95%A5) 13 | - [使用 HTTP / 2.0](#%E4%BD%BF%E7%94%A8-http--20) 14 | - [预加载](#%E9%A2%84%E5%8A%A0%E8%BD%BD) 15 | - [预渲染](#%E9%A2%84%E6%B8%B2%E6%9F%93) 16 | - [优化渲染过程](#%E4%BC%98%E5%8C%96%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B) 17 | - [懒执行](#%E6%87%92%E6%89%A7%E8%A1%8C) 18 | - [懒加载](#%E6%87%92%E5%8A%A0%E8%BD%BD) 19 | - [文件优化](#%E6%96%87%E4%BB%B6%E4%BC%98%E5%8C%96) 20 | - [图片优化](#%E5%9B%BE%E7%89%87%E4%BC%98%E5%8C%96) 21 | - [计算图片大小](#%E8%AE%A1%E7%AE%97%E5%9B%BE%E7%89%87%E5%A4%A7%E5%B0%8F) 22 | - [图片加载优化](#%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E4%BC%98%E5%8C%96) 23 | - [其他文件优化](#%E5%85%B6%E4%BB%96%E6%96%87%E4%BB%B6%E4%BC%98%E5%8C%96) 24 | - [CDN](#cdn) 25 | - [其他](#%E5%85%B6%E4%BB%96) 26 | - [使用 Webpack 优化项目](#%E4%BD%BF%E7%94%A8-webpack-%E4%BC%98%E5%8C%96%E9%A1%B9%E7%9B%AE) 27 | - [监控](#%E7%9B%91%E6%8E%A7) 28 | - [面试题](#%E9%9D%A2%E8%AF%95%E9%A2%98) 29 | 30 | 31 | 32 | # 网络相关 33 | 34 | ## DNS 预解析 35 | 36 | DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | ## 缓存 43 | 44 | 缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度。 45 | 46 | 通常浏览器缓存策略分为两种:强缓存和协商缓存。 47 | 48 | ### 强缓存 49 | 50 | 实现强缓存可以通过两种响应头实现:`Expires` 和 `Cache-Control` 。强缓存表示在缓存期间不需要请求,`state code` 为 200 51 | 52 | ```js 53 | Expires: Wed, 22 Oct 2018 08:41:00 GMT 54 | ``` 55 | 56 | `Expires` 是 HTTP / 1.0 的产物,表示资源会在 `Wed, 22 Oct 2018 08:41:00 GMT` 后过期,需要再次请求。并且 `Expires` 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。 57 | 58 | ```js 59 | Cache-control: max-age=30 60 | ``` 61 | 62 | `Cache-Control` 出现于 HTTP / 1.1,优先级高于 `Expires` 。该属性表示资源会在 30 秒后过期,需要再次请求。 63 | 64 | ### 协商缓存 65 | 66 | 如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。 67 | 68 | 协商缓存需要客户端和服务端共同实现,和强缓存一样,也有两种实现方式。 69 | 70 | #### Last-Modified 和 If-Modified-Since 71 | 72 | `Last-Modified` 表示本地文件最后修改日期,`If-Modified-Since` 会将 `Last-Modified` 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。 73 | 74 | 但是如果在本地打开缓存文件,就会造成 `Last-Modified` 被修改,所以在 HTTP / 1.1 出现了 `ETag` 。 75 | 76 | #### ETag 和 If-None-Match 77 | 78 | `ETag` 类似于文件指纹,`If-None-Match` 会将当前 `ETag` 发送给服务器,询问该资源 `ETag` 是否变动,有变动的话就将新的资源发送回来。并且 `ETag` 优先级比 `Last-Modified` 高。 79 | 80 | ### 选择合适的缓存策略 81 | 82 | 对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略 83 | 84 | - 对于某些不需要缓存的资源,可以使用 `Cache-control: no-store` ,表示该资源不需要缓存 85 | - 对于频繁变动的资源,可以使用 `Cache-Control: no-cache` 并配合 `ETag` 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新。 86 | - 对于代码文件来说,通常使用 `Cache-Control: max-age=31536000` 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件。 87 | 88 | ## 使用 HTTP / 2.0 89 | 90 | 因为浏览器会有并发请求限制,在 HTTP / 1.1 时代,每个请求都需要建立和断开,消耗了好几个 RTT 时间,并且由于 TCP 慢启动的原因,加载体积大的文件会需要更多的时间。 91 | 92 | 在 HTTP / 2.0 中引入了多路复用,能够让多个请求使用同一个 TCP 链接,极大的加快了网页的加载速度。并且还支持 Header 压缩,进一步的减少了请求的数据大小。 93 | 94 | 更详细的内容你可以查看 [该小节](../Network/Network-zh.md##http-20) 95 | 96 | ## 预加载 97 | 98 | 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。 99 | 100 | 预加载其实是声明式的 `fetch` ,强制浏览器请求资源,并且不会阻塞 `onload` 事件,可以使用以下代码开启预加载 101 | 102 | ```html 103 | 104 | ``` 105 | 106 | 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。 107 | 108 | ## 预渲染 109 | 110 | 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染 111 | 112 | ```html 113 | 114 | ``` 115 | 116 | 预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染 117 | 118 | # 优化渲染过程 119 | 120 | 对于代码层面的优化,你可以查阅浏览器系列中的 [相关内容](../Browser/browser-ch.md#渲染机制)。 121 | 122 | ## 懒执行 123 | 124 | 懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒。 125 | 126 | ## 懒加载 127 | 128 | 懒加载就是将不关键的资源延后加载。 129 | 130 | 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 `src` 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 `src` 属性,这样图片就会去下载资源,实现了图片懒加载。 131 | 132 | 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。 133 | 134 | # 文件优化 135 | 136 | ## 图片优化 137 | 138 | ### 计算图片大小 139 | 140 | 对于一张 100 * 100 像素的图片来说,图像上有 10000 个像素点,如果每个像素的值是 RGBA 存储的话,那么也就是说每个像素有 4 个通道,每个通道 1 个字节(8 位 = 1个字节),所以该图片大小大概为 39KB(10000 * 1 * 4 / 1024)。 141 | 142 | 但是在实际项目中,一张图片可能并不需要使用那么多颜色去显示,我们可以通过减少每个像素的调色板来相应缩小图片的大小。 143 | 144 | 了解了如何计算图片大小的知识,那么对于如何优化图片,想必大家已经有 2 个思路了: 145 | 146 | - 减少像素点 147 | - 减少每个像素点能够显示的颜色 148 | 149 | ### 图片加载优化 150 | 151 | 1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。 152 | 2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。 153 | 3. 小图使用 base64 格式 154 | 4. 将多个图标文件整合到一张图片中(雪碧图) 155 | 6. 选择正确的图片格式: 156 | - 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好 157 | - 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替 158 | - 照片使用 JPEG 159 | 160 | ## 其他文件优化 161 | 162 | - CSS 文件放在 `head` 中 163 | - 服务端开启文件压缩功能 164 | - 将 `script` 标签放在 `body` 底部,因为 JS 文件执行会阻塞渲染。当然也可以把 `script` 标签放在任意位置然后加上 `defer` ,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件可以加上 `async` ,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。 165 | - 执行 JS 代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑使用 `Webworker`。`Webworker` 可以让我们另开一个线程执行脚本而不影响渲染。 166 | 167 | ## CDN 168 | 169 | 静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。 170 | 171 | # 其他 172 | 173 | ## 使用 Webpack 优化项目 174 | 175 | - 对于 Webpack4,打包项目使用 production 模式,这样会自动开启代码压缩 176 | - 使用 ES6 模块来开启 tree shaking,这个技术可以移除没有使用的代码 177 | - 优化图片,对于小图可以使用 base64 的方式写入文件中 178 | - 按照路由拆分代码,实现按需加载 179 | - 给打包出来的文件名添加哈希,实现浏览器缓存文件 180 | 181 | ## 监控 182 | 183 | 对于代码运行错误,通常的办法是使用 `window.onerror` 拦截报错。该方法能拦截到大部分的详细报错信息,但是也有例外 184 | 185 | - 对于跨域的代码运行错误会显示 `Script error.` 对于这种情况我们需要给 `script` 标签添加 `crossorigin` 属性 186 | - 对于某些浏览器可能不会显示调用栈信息,这种情况可以通过 `arguments.callee.caller` 来做栈递归 187 | 188 | 对于异步代码来说,可以使用 `catch` 的方式捕获错误。比如 `Promise` 可以直接使用 `catch` 函数,`async await` 可以使用 `try catch` 189 | 190 | 但是要注意线上运行的代码都是压缩过的,需要在打包时生成 sourceMap 文件便于 debug。 191 | 192 | 对于捕获的错误需要上传给服务器,通常可以通过 `img` 标签的 `src` 发起一个请求。 193 | 194 | ## 面试题 195 | 196 | **如何渲染几万条数据并不卡住界面** 197 | 198 | 这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 `requestAnimationFrame` 来每 16 ms 刷新一次。 199 | 200 | ```html 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Document 209 | 210 | 211 | 212 | 242 | 243 | 244 | ``` 245 | 246 | -------------------------------------------------------------------------------- /Safety/safety-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [XSS](#xss) 6 | - [How to attack](#how-to-attack) 7 | - [How to defend](#how-to-defend) 8 | - [CSP](#csp) 9 | - [CSRF](#csrf) 10 | - [How to attack](#how-to-attack-1) 11 | - [How to defend](#how-to-defend-1) 12 | - [SameSite](#samesite) 13 | - [Verify Referer](#verify-referer) 14 | - [Token](#token) 15 | - [Password security](#password-security) 16 | - [Add salt](#add-salt) 17 | 18 | 19 | 20 | ## XSS 21 | 22 | > **Cross-site scripting**(Cross-site scripting in English, often abbreviated as XSS)is one kind of security vulnerabilities attack of web applications , and is a kind of [code input](https://www.wikiwand.com/zh-hans/%E4%BB%A3%E7%A2%BC%E6%B3%A8%E5%85%A5)It allows malicious users to input code into web pages, and other users are affected when they browse web pages. Such attacks often include HTML and consumer-side scripting languages. 23 | 24 | XSS is divided into three types: reflective type, storage type, and DOM-based type 25 | 26 | ### How to attack 27 | 28 | XSS attacks websites by modifying HTML nodes or run JS code. 29 | 30 | For example, get some parameters through the URL 31 | 32 | ```html 33 | 34 |
{{name}}
35 | ``` 36 | 37 | The URL input above may change the HTML into `
` so that there is an extra executable script out of the page. This type of attack is a reflection attack, or DOM-based attack 38 | 39 | There is also another scenario. For example, if you write an article that contains the attack code ``, then users who may be browsing the article will be attacked. This type of attack is a store attack, which can also be called a DOM-based attack. 40 | 41 | ### How to defend 42 | 43 | The most common practice is to escape the input and output, escape the quotes, angle brackets, and slashes. 44 | 45 | ```js 46 | function escape(str) { 47 | str = str.replace(/&/g, "&"); 48 | str = str.replace(//g, ">"); 50 | str = str.replace(/"/g, "&quto;"); 51 | str = str.replace(/'/g, "'"); 52 | str = str.replace(/`/g, "`"); 53 | str = str.replace(/\//g, "/"); 54 | return str 55 | } 56 | ``` 57 | 58 | The attack code `` can be changed by escaping 59 | 60 | ```js 61 | // -> <script>alert(1)</script> 62 | escape('') 63 | ``` 64 | 65 | For displaying rich text, all characters cannot be escaped by the above method, because this will filter out the required format. This kind of situation usually adopts the method of the white list to filter, certainly can also pass the black list to filter, but consider the too many labels and attribute that need to filter, it is more recommended to use the white list way. 66 | 67 | ```js 68 | var xss = require("xss"); 69 | var html = xss('

XSS Demo

'); 70 | // ->

XSS Demo

<script>alert("xss");</script> 71 | console.log(html); 72 | ``` 73 | 74 | The above example uses `js-xss` to implement. You can see that the `h1` tag is preserved in the output and the `script` tag is filtered. 75 | 76 | ### CSP 77 | 78 | The Content Security Policy ([CSP] (https://developer.mozilla.org/en-US/docs/Glossary/CSP)) is an additional layer of security that detects and undermines certain types of attacks, including Cross-site scripting ([XSS] (https://developer.mozilla.org/en-US/docs/Glossary/XSS)) and data injection attacks. Whether it's data theft, website content contamination or malware, these attacks are the primary means. 79 | 80 | We can minimize XSS attacks with CSP. CSP is also essentially whitelisted, which stipulates that browsers can only execute code from a specific source. 81 | 82 | You can usually enable the CSP with the `Content-Security-Policy` in the HTTP Header. 83 | 84 | - Only allow loading of self-site resource 85 | 86 | ```http 87 | Content-Security-Policy: default-src ‘self’ 88 | ``` 89 | 90 | - Only allow loading HTTPS protocol pictures 91 | 92 | ```http 93 | Content-Security-Policy: img-src https://* 94 | ``` 95 | 96 | - Allow loading of any source frame 97 | 98 | ```http 99 | Content-Security-Policy: child-src 'none' 100 | ``` 101 | 102 | More attributes can be viewed at [here] (https://content-security-policy.com/) 103 | 104 | ## CSRF 105 | 106 | > **Cross-site request forgery (English: Cross-site request forgery), also known as **one-click attack** or **session riding**, usually abbreviated as **CSRF** or **XSRF** is an attack method that forces users to perform unintended operations on currently logged-in web applications. [[1\]](https://www.wikiwand.com/zh/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0#citenoteRistic1) Follow [cross-site script](https://www.wikiwand.com/zh/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC) (XSS) Compared to **XSS**, users trust the designated website and CSRF uses the website's trust in the user's web browser. 107 | 108 | To put it simply, CSRF uses the login state of the user to initiate a malicious request. 109 | 110 | ### How to attack 111 | 112 | Assume that there is an interface on the site that submits user comments through a Get request. The attacker can then add a picture to the phishing site. The address of the picture is the comment interface. 113 | 114 | ```html 115 | 116 | ``` 117 | 118 | If the interface is submitted by the Post, it is relatively troublesome, you need to use the form to submit the interface. 119 | 120 | ```html 121 |
122 | 123 |
124 | ``` 125 | 126 | ### How to defend 127 | 128 | There are several rules for defending against CSRF: 129 | 130 | 1. Get request does not modify the data 131 | 2. Do not allow third-party websites to access user cookies 132 | 3. Block third-party website request interfaces 133 | 4. Request verification information, such as verification code or token 134 | 135 | #### SameSite 136 | 137 | The `SameSite` attribute can be set on cookies. This attribute sets the cookie not to be sent along with cross-domain requests. This attribute can greatly reduce the CSRF attack, but this attribute is currently not compatible with all browsers. 138 | 139 | #### Verify Referer 140 | 141 | For requests that need protection against CSRF, we can verify the Referer to determine if the request was initiated by a third-party website. 142 | 143 | #### Token 144 | 145 | The server sends a random Token (the algorithm cannot be complex). The Token is carried on each request, and the server verifies that the Token is valid. 146 | 147 | ## Password security 148 | 149 | Although password security is mostly a back-end thing, as a good front-end programmer, you need to be familiar with this knowledge. 150 | 151 | ### Add salt 152 | 153 | For password storage, it must be stored in the database in the clear, otherwise, once the database is leaked, it will cause great losses to the user. And it is not recommended to encrypt the password only by the encryption algorithm because of the rainbow table relationship. 154 | 155 | It is usually necessary to add salt to the password and then perform several encryptions with different encryption algorithms. 156 | 157 | It is often necessary to add a salt to the password and then encrypt it several times with different encryption algorithms. 158 | 159 | ```js 160 | // Adding salt means adding a string to the original password and increasing the length of the original password. 161 | sha256(sha1(md5(salt + password + salt))) 162 | ``` 163 | 164 | But adding salt does not prevent others from stealing accounts. It only ensures that even if the database is compromised, the user's real password will not be exposed. Once the attacker gets the user's account, the password can be cracked by brute force. In this case, a verification code is usually used to increase the delay or limit the number of attempts. And once the user enters the wrong password, the user cannot directly prompt the user to enter the wrong password, but should prompt the account or password to be incorrect. 165 | -------------------------------------------------------------------------------- /Performance/performance-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Network related](#network-related) 6 | - [DNS Prefetching](#dns-prefetching) 7 | - [Cache](#cache) 8 | - [Strong cache](#strong-cache) 9 | - [Negotiation cache](#negotiation-cache) 10 | - [`Last-Modified` and `If-Modified-Since`](#last-modified--and--if-modified-since) 11 | - [`ETag` and `If-None-Match`](#etag-and-if-none-match) 12 | - [Choosing the suitable caching strategy](#choosing-the-suitable-caching-strategy) 13 | - [Use HTTP / 2.0](#use-http--20) 14 | - [PreLoad](#preload) 15 | - [Pre-render](#pre-render) 16 | - [Optimizing the rendering process](#optimizing-the-rendering-process) 17 | - [Lazy execution](#lazy-execution) 18 | - [Lazy load](#lazy-load) 19 | - [File optimization](#file-optimization) 20 | - [Image optimization](#image-optimization) 21 | - [Calculate the size of image](#calculate-the-size-of-image) 22 | - [Image loading optimization](#image-loading-optimization) 23 | - [Optimization of other files](#optimization-of-other-files) 24 | - [CDN](#cdn) 25 | - [Others](#others) 26 | - [Use Webpack to optimize the project](#use-webpack-to-optimize-the-project) 27 | - [Monitor](#monitor) 28 | - [An interview question](#an-interview-question) 29 | - [How to render tens of thousands of data without blocking the interface](#how-to-render-tens-of-thousands-of-data-without-blocking-the-interface) 30 | 31 | 32 | 33 | # Network related 34 | 35 | ## DNS Prefetching 36 | 37 | It takes time for DNS resolution. We can obtain the IP corresponding to the domain name in advance through DNS prefetching. 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | ## Cache 44 | 45 | Cache is a very important point for front-end performance optimization. A good caching strategy can reduce the repeated loading of resources and increase the overall loading speed of the websites. 46 | 47 | Browser cache strategy is usually divided into two types: strong cache and negotiation cache 48 | 49 | ### Strong cache 50 | 51 | Implementing strong caching can be achieved with two response headers: `Expires` and `Cache-Control`. Strong cache means that no request is required during caching, the `state code` is 200. 52 | 53 | ```js 54 | Expires: Wed, 22 Oct 2018 08:41:00 GMT 55 | ``` 56 | 57 | `Expires` is a product of HTTP / 1.0, indicating that the resource expires after `Wed, 22 Oct 2018 08:41:00 GMT` and needs to be requested again. And `Expires` is limited by the local time, if the local time is modified, the cache may be invalidated. 58 | 59 | ```js 60 | Cache-control: max-age=30 61 | ``` 62 | 63 | `Cache-Control` appears in HTTP/1.1 and takes precedence over `Expires`. This attribute indicates that the resource expires after 30 seconds and needs to be requested again. 64 | 65 | ### Negotiation cache 66 | 67 | If the cache expires, we can use negotiation cache to solve the problem. Negotiation cache requires a request and returns 304 if the cache is valid. 68 | 69 | Negotiation cache needs to be implemented by the client-side and server-side together. Like strong caching, there are two implementations. 70 | 71 | ## `Last-Modified` and `If-Modified-Since` 72 | 73 | `Last-Modified` indicates the last modified date of the local file. `If-Modified-Since` will send the value of `Last-Modified` to the server, asking the server if the resource has been updated after that date, and if there is an update, the new resource will be sent back. 74 | 75 | But if you open the cache file locally, it will cause `Last-Modified` to be modified, so `ETag` appears in HTTP / 1.1 76 | 77 | ## `ETag` and `If-None-Match` 78 | 79 | `ETag` is similar to the fingerprint of a file. `If-None-Match` sends the current `ETag` to the server and asks whether the `ETag` of the resource changes. If there is a change, the new resource will be sent back. And `ETag` has a higher priority than `Last-Modified` 80 | 81 | ### Choosing the suitable caching strategy 82 | 83 | We can use strong cache with negotiation cache to solve most problems, but in some special cases, we may need to choose a special caching strategy. 84 | 85 | - For some resources that do not need to be cached, we can use `Cache-control: no-store` to indicate that the resource does not need to be cached. 86 | - For the resources that will be frequently changed, we can use `Cache-Control: no-cache` with `ETag` to indicate that the resource is cached, but each time it will send a request to ask if the resource is updated. 87 | - For code files, we usually use `Cache-Control: max-age=31536000` with the negotiation cache, and then make the file fingerprinted. Once the name of the file changes, the new file will be downloaded immediately. 88 | 89 | ## Use HTTP / 2.0 90 | 91 | Since browsers have limitations on concurrent requests, each request needs to be established and disconnected in the era of HTTP/1.1, which will consume several `RTT` , and loading large files requires more time because of `TCP Slow Start`. 92 | 93 | Multiplexing was introduced in HTTP/2.0, allowing multiple requests to use the same TCP connect, greatly speeding up the loading of websites. Header compression is also supported, further shortening the size of the request data. 94 | 95 | To know more detailed content, you can view [this section](../Network/Network-zh.md##http-20) TODO 96 | 97 | ## PreLoad 98 | 99 | In development, you may encounter such a situation. Some resources do not need to be used immediately, but you want to get it as soon as possible. At this point, you can use preloading. 100 | 101 | Preloading is actually a declarative `fetch` that forces the browser to request resources and does not block the `onload` event. You can use the following code to enable preloading 102 | 103 | ```html 104 | 105 | ``` 106 | 107 | Preloading can reduce the loading time of home screen to a certain degree because some important files that do not affect the home screen can be delayed for loading. The only disadvantage is that the compatibility is not good. 108 | 109 | ## Pre-render 110 | 111 | The downloaded file can be pre-rendered in the background through pre-rendering. You can use the following code to enable pre-rendering. 112 | 113 | ```html 114 | 115 | ``` 116 | 117 | Although pre-rendering can improve the loading speed of a website, it must be 100 percent ensured that this page will be opened by the user, otherwise, it would waste resources to render. 118 | 119 | # Optimizing the rendering process 120 | 121 | As for the optimization about code, you can refer to [the relevant content](../Browser/browser-en.md#rendering-machanism) in the browser series 122 | 123 | ## Lazy execution 124 | 125 | Lazy execution delays some logic until it is used. This technique can be used for the first screen optimization. Lazy execution can be used in some time-consuming logic that does not need to be used on the first screen. And lazy execution requires a wake-up, which can typically be awakened by a timer or event call. 126 | 127 | ## Lazy load 128 | 129 | Lazy loading is to delay the loading of non-critical resources 130 | 131 | The principle of lazy loading is to only load those that need to be loaded in the custom area (usually the visible area, but it can also be the visible area that will be entered soon). For the image, firstly set the src attribute of the `image` tag to be a placeholder, then put the real url into a custom attribute. When entering the custom area, replace the custom attribute with the src attribute, and the `image` tag will go to download resources, which achieves lazy loading of the image. 132 | 133 | Lazy loading can be used not only for images but also for other resources. For example, start playing video after entering the visible area and so on. 134 | 135 | # File optimization 136 | 137 | ## Image optimization 138 | 139 | ### Calculate the size of image 140 | 141 | There are 10,000 pixels on a 100 * 100-pixel image. If the value of each pixel is stored in the way of RGBA, then there are 4 channels per pixel and 1 byte per channel (8 bits = 1 byte), so the size of the image is about 39KB (10000 * 1 * 4 / 1024) 142 | 143 | But in a real project, it may not need so many colors to display an image, we can reduce the size of the image by reducing the color palette of each pixel. 144 | 145 | After knowing how to calculate the size of an image, I guess that you may have 2 ways to optimize image: 146 | 147 | - Reduce pixels 148 | - Reduce the color that each pixel can display 149 | 150 | ### Image loading optimization 151 | 152 | 1. No image. Sometimes we would use a lot of modified images, those can be completely replaced by CSS. 153 | 2. For the mobile side, since the screen width is small, there is no need to load the original image, which wastes bandwidth. We generally load images from CDN, firstly calculate the suitably width, and then request the corresponding cropped images. 154 | 3. Use base64 for thumbnails 155 | 4. Integrate multiple icon files into one image (Sprite image) 156 | 5. Choose the correct image format 157 | - Use WebP format as much as possible for browsers that can display WebP format. Because the WebP format has a better image data compression algorithm, which can bring a smaller image volume, and there is no difference in image quality with the naked eye, the disadvantage is that the compatibility of WebP format is not good 158 | - The thumbnail uses PNG format. In fact, for most of the icons, they can be completely replaced by SVG. 159 | - Photo use JPEG format. 160 | 161 | ## Optimization of other files 162 | 163 | - Put the CSS file in `head` 164 | - Server opens the function of file compression 165 | - Place the `script` tag at the bottom of the `body`, because the execution of JS file will block the process of rendering. Of course, you can put the `script` tag anywhere and add `defer` to indicate that the file will be downloaded in parallel, but it will be executed sequentially after the HTML parsing is completed. `async` can be used to the JS files that don’t have any dependencies, indicating that the process of loading and rendering subsequent document elements will be performed in parallel with the loading and execution of the JS file. 166 | - The execution of the excessive JS code will block the process of rendering. For codes that take a lot of time to calculate, we can consider using `Webworker`. `Webworker` will not effect the rendering process by allowing developers to open another thread to execute the script. 167 | 168 | ## CDN 169 | 170 | Use CDN to load static resources as far as possible. Since the browser has a limit on concurrent requests for a single domain name, we can consider using multiple CDN domain names. And we should be careful that the CDN domain name must be different from the master station when loading static resources from CDN, otherwise, each request will carry the cookie of the master station. 171 | 172 | # Others 173 | 174 | ## Use Webpack to optimize the project 175 | 176 | - For Webpack4, use `production` mode to packaged projects, which will automatically open code compression. 177 | - Use the ES6 module to open `tree shaking`, which can remove unused code. 178 | - Optimize the image, the thumbnail can be written to the file using base64. 179 | - Split code In accordance with the route, to achieve on-demand loading. 180 | - Add a hash to the name of the packaged file, to implement the browser cache file 181 | 182 | ## Monitor 183 | 184 | For the errors of code execution, the usual way is to use `window.onerror` to intercept the error. This method can intercept most of the detailed error information, but there are exceptions 185 | 186 | - The execution error of cross-domain code will show `script error`. For this case, we need to add the `crossorigin` attribute to the `script` tag. 187 | - Call stack information may not be displayed for some browsers. For this case, we can use `arguments.callee.caller` to implement stack recursion 188 | 189 | For asynchronous code, we can use `catch` to catch errors. For example, `Promise` can use the `catch` function directly, and `async await `can use `try catch`. 190 | 191 | However, it should be noted that the codes which are running online are compressed, and it is necessary to generate a `sourceMap` file to facilitate debugging. 192 | 193 | The captured errors need to be uploaded to the server, usually, we can implement that by sending a network request using the `src` attribute of the `ima` tag. 194 | 195 | ## An interview question 196 | 197 | ### How to render tens of thousands of data without blocking the interface 198 | 199 | The question examines how to render data without blocking the interface. It means that you cannot render tens of thousands at a time. Instead, you should render part of the DOM at once. Then you can use the `requestAnimationFrame` to refresh every 16 milliseconds. 200 | 201 | ```html 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Document 210 | 211 | 212 | 213 | 243 | 244 | 245 | ``` 246 | -------------------------------------------------------------------------------- /Browser/browser-ch.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [事件机制](#%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6) 6 | - [事件触发三阶段](#%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E4%B8%89%E9%98%B6%E6%AE%B5) 7 | - [注册事件](#%E6%B3%A8%E5%86%8C%E4%BA%8B%E4%BB%B6) 8 | - [事件代理](#%E4%BA%8B%E4%BB%B6%E4%BB%A3%E7%90%86) 9 | - [跨域](#%E8%B7%A8%E5%9F%9F) 10 | - [JSONP](#jsonp) 11 | - [CORS](#cors) 12 | - [document.domain](#documentdomain) 13 | - [postMessage](#postmessage) 14 | - [Event loop](#event-loop) 15 | - [Node 中的 Event loop](#node-%E4%B8%AD%E7%9A%84-event-loop) 16 | - [timer](#timer) 17 | - [I/O](#io) 18 | - [idle, prepare](#idle-prepare) 19 | - [poll](#poll) 20 | - [check](#check) 21 | - [close callbacks](#close-callbacks) 22 | - [存储](#%E5%AD%98%E5%82%A8) 23 | - [cookie,localStorage,sessionStorage,indexDB](#cookielocalstoragesessionstorageindexdb) 24 | - [Service Worker](#service-worker) 25 | - [渲染机制](#%E6%B8%B2%E6%9F%93%E6%9C%BA%E5%88%B6) 26 | - [Load 和 DOMContentLoaded 区别](#load-%E5%92%8C-domcontentloaded-%E5%8C%BA%E5%88%AB) 27 | - [图层](#%E5%9B%BE%E5%B1%82) 28 | - [重绘(Repaint)和回流(Reflow)](#%E9%87%8D%E7%BB%98repaint%E5%92%8C%E5%9B%9E%E6%B5%81reflow) 29 | - [减少重绘和回流](#%E5%87%8F%E5%B0%91%E9%87%8D%E7%BB%98%E5%92%8C%E5%9B%9E%E6%B5%81) 30 | 31 | 32 | 33 | # 事件机制 34 | 35 | ## 事件触发三阶段 36 | 37 | 事件触发有三个阶段 38 | 39 | - `window` 往事件触发处传播,遇到注册的捕获事件会触发 40 | - 传播到事件触发处时触发注册的事件 41 | - 从事件触发处往 `window` 传播,遇到注册的冒泡事件会触发 42 | 43 | 事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。 44 | 45 | ```js 46 | // 以下会先打印冒泡然后是捕获 47 | node.addEventListener('click',(event) =>{ 48 | console.log('冒泡') 49 | },false); 50 | node.addEventListener('click',(event) =>{ 51 | console.log('捕获 ') 52 | },true) 53 | ``` 54 | 55 | ## 注册事件 56 | 57 | 通常我们使用 `addEventListener` 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 `useCapture` 参数来说,该参数默认值为 `false` 。`useCapture` 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性 58 | 59 | - `capture`,布尔值,和 `useCapture` 作用一样 60 | - `once`,布尔值,值为 `true` 表示该回调只会调用一次,调用后会移除监听 61 | - `passive`,布尔值,表示永远不会调用 `preventDefault` 62 | 63 | 一般来说,我们只希望事件只触发在目标上,这时候可以使用 `stopPropagation` 来阻止事件的进一步传播。通常我们认为 `stopPropagation` 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。`stopImmediatePropagation` 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。 64 | 65 | ```js 66 | node.addEventListener('click',(event) =>{ 67 | event.stopImmediatePropagation() 68 | console.log('冒泡') 69 | },false); 70 | // 点击 node 只会执行上面的函数,该函数不会执行 71 | node.addEventListener('click',(event) => { 72 | console.log('捕获 ') 73 | },true) 74 | ``` 75 | 76 | ## 事件代理 77 | 78 | 如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上 79 | 80 | ```html 81 | 88 | 94 | ``` 95 | 96 | 事件代理的方式相对于直接给目标注册事件来说,有以下优点 97 | 98 | - 节省内存 99 | - 不需要给子节点注销事件 100 | 101 | # 跨域 102 | 103 | 因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。 104 | 105 | 我们可以通过以下几种常用方法解决跨域的问题 106 | 107 | ## JSONP 108 | 109 | JSONP 的原理很简单,就是利用 ` 113 | 118 | ``` 119 | 120 | JSONP 使用简单且兼容性不错,但是只限于 `get` 请求。 121 | 122 | 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现 123 | 124 | ```js 125 | function jsonp(url, jsonpCallback, success) { 126 | let script = document.createElement("script"); 127 | script.src = url; 128 | script.async = true; 129 | script.type = "text/javascript"; 130 | window[jsonpCallback] = function(data) { 131 | success && success(data); 132 | }; 133 | document.body.appendChild(script); 134 | } 135 | jsonp( 136 | "http://xxx", 137 | "callback", 138 | function(value) { 139 | console.log(value); 140 | } 141 | ); 142 | ``` 143 | 144 | ## CORS 145 | 146 | CORS需要浏览器和后端同时支持。IE 8 和 9 需要通过 `XDomainRequest` 来实现。 147 | 148 | 浏览器会自动进行 CORS 通信,实现CORS通信的关键是后端。只要后端实现了 CORS,就实现了跨域。 149 | 150 | 服务端设置 `Access-Control-Allow-Origin` 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 151 | 152 | ## document.domain 153 | 154 | 该方式只能用于二级域名相同的情况下,比如 `a.test.com` 和 `b.test.com` 适用于该方式。 155 | 156 | 只需要给页面添加 `document.domain = 'test.com'` 表示二级域名都相同就可以实现跨域 157 | 158 | ## postMessage 159 | 160 | 这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息 161 | 162 | ```js 163 | // 发送消息端 164 | window.parent.postMessage('message', 'http://test.com'); 165 | // 接收消息端 166 | var mc = new MessageChannel(); 167 | mc.addEventListener('message', (event) => { 168 | var origin = event.origin || event.originalEvent.origin; 169 | if (origin === 'http://test.com') { 170 | console.log('验证通过') 171 | } 172 | }); 173 | ``` 174 | 175 | # Event loop 176 | 177 | 众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题。 178 | 179 | JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。 180 | 181 | ```js 182 | console.log('script start'); 183 | 184 | setTimeout(function() { 185 | console.log('setTimeout'); 186 | }, 0); 187 | 188 | console.log('script end'); 189 | ``` 190 | 191 | 以上代码虽然 `setTimeout` 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。所以 `setTimeout` 还是会在 `script end` 之后打印。 192 | 193 | 不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 `jobs`,macrotask 称为 `task`。 194 | 195 | ```js 196 | console.log('script start'); 197 | 198 | setTimeout(function() { 199 | console.log('setTimeout'); 200 | }, 0); 201 | 202 | new Promise((resolve) => { 203 | console.log('Promise') 204 | resolve() 205 | }).then(function() { 206 | console.log('promise1'); 207 | }).then(function() { 208 | console.log('promise2'); 209 | }); 210 | 211 | console.log('script end'); 212 | // script start => Promise => script end => promise1 => promise2 => setTimeout 213 | ``` 214 | 215 | 以上代码虽然 `setTimeout` 写在 `Promise` 之前,但是因为 `Promise` 属于微任务而 `setTimeout` 属于宏任务,所以会有以上的打印。 216 | 217 | 微任务包括 `process.nextTick` ,`promise` ,`Object.observe` ,`MutationObserver` 218 | 219 | 宏任务包括 `script` , `setTimeout` ,`setInterval` ,`setImmediate` ,`I/O` ,`UI rendering` 220 | 221 | 很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 `script` ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务。 222 | 223 | 所以正确的一次 Event loop 顺序是这样的 224 | 225 | 1. 执行同步代码,这属于宏任务 226 | 2. 执行栈为空,查询是否有微任务需要执行 227 | 3. 执行所有微任务 228 | 4. 必要的话渲染 UI 229 | 5. 然后开始下一轮 Event loop,执行宏任务中的异步代码 230 | 231 | 通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中。 232 | 233 | ## Node 中的 Event loop 234 | 235 | Node 中的 Event loop 和浏览器中的不相同。 236 | 237 | Node 的 Event loop 分为6个阶段,它们会按照顺序反复运行 238 | 239 | ``` 240 | ┌───────────────────────┐ 241 | ┌─>│ timers │ 242 | │ └──────────┬────────────┘ 243 | │ ┌──────────┴────────────┐ 244 | │ │ I/O callbacks │ 245 | │ └──────────┬────────────┘ 246 | │ ┌──────────┴────────────┐ 247 | │ │ idle, prepare │ 248 | │ └──────────┬────────────┘ ┌───────────────┐ 249 | │ ┌──────────┴────────────┐ │ incoming: │ 250 | │ │ poll │<──connections─── │ 251 | │ └──────────┬────────────┘ │ data, etc. │ 252 | │ ┌──────────┴────────────┐ └───────────────┘ 253 | │ │ check │ 254 | │ └──────────┬────────────┘ 255 | │ ┌──────────┴────────────┐ 256 | └──┤ close callbacks │ 257 | └───────────────────────┘ 258 | ``` 259 | 260 | ### timer 261 | 262 | timers 阶段会执行 `setTimeout` 和 `setInterval` 263 | 264 | 一个 `timer` 指定的时间并不是准确时间,而是在达到这个时间后尽快执行回调,可能会因为系统正在执行别的事务而延迟。 265 | 266 | 下限的时间有一个范围:`[1, 2147483647]` ,如果设定的时间不在这个范围,将被设置为1。 267 | 268 | ### I/O 269 | 270 | I/O 阶段会执行除了 close 事件,定时器和 `setImmediate` 的回调 271 | 272 | ### idle, prepare 273 | 274 | idle, prepare 阶段内部实现 275 | 276 | ### poll 277 | 278 | poll 阶段很重要,这一阶段中,系统会做两件事情 279 | 280 | 1. 执行到点的定时器 281 | 2. 执行 poll 队列中的事件 282 | 283 | 并且当 poll 中没有定时器的情况下,会发现以下两件事情 284 | 285 | - 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制 286 | - 如果 poll 队列为空,会有两件事发生 287 | - 如果有 `setImmediate` 需要执行,poll 阶段会停止并且进入到 check 阶段执行 `setImmediate` 288 | - 如果没有 `setImmediate` 需要执行,会等待回调被加入到队列中并立即执行回调 289 | 290 | 如果有别的定时器需要被执行,会回到 timer 阶段执行回调。 291 | 292 | ### check 293 | 294 | check 阶段执行 `setImmediate` 295 | 296 | ### close callbacks 297 | 298 | close callbacks 阶段执行 close 事件 299 | 300 | 并且在 Node 中,有些情况下的定时器执行顺序是随机的 301 | 302 | ```js 303 | setTimeout(() => { 304 | console.log('setTimeout'); 305 | }, 0); 306 | setImmediate(() => { 307 | console.log('setImmediate'); 308 | }) 309 | // 这里可能会输出 setTimeout,setImmediate 310 | // 可能也会相反的输出,这取决于性能 311 | // 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate 312 | // 否则会执行 setTimeout 313 | ``` 314 | 315 | 当然在这种情况下,执行顺序是相同的 316 | 317 | ```js 318 | var fs = require('fs') 319 | 320 | fs.readFile(__filename, () => { 321 | setTimeout(() => { 322 | console.log('timeout'); 323 | }, 0); 324 | setImmediate(() => { 325 | console.log('immediate'); 326 | }); 327 | }); 328 | // 因为 readFile 的回调在 poll 中执行 329 | // 发现有 setImmediate ,所以会立即跳到 check 阶段执行回调 330 | // 再去 timer 阶段执行 setTimeout 331 | // 所以以上输出一定是 setImmediate,setTimeout 332 | ``` 333 | 334 | 上面介绍的都是 macrotask 的执行情况,microtask 会在以上每个阶段完成后立即执行。 335 | 336 | ```js 337 | setTimeout(()=>{ 338 | console.log('timer1') 339 | 340 | Promise.resolve().then(function() { 341 | console.log('promise1') 342 | }) 343 | }, 0) 344 | 345 | setTimeout(()=>{ 346 | console.log('timer2') 347 | 348 | Promise.resolve().then(function() { 349 | console.log('promise2') 350 | }) 351 | }, 0) 352 | 353 | // 以上代码在浏览器和 node 中打印情况是不同的 354 | // 浏览器中一定打印 timer1, promise1, timer2, promise2 355 | // node 中可能打印 timer1, timer2, promise1, promise2 356 | // 也可能打印 timer1, promise1, timer2, promise2 357 | ``` 358 | 359 | Node 中的 `process.nextTick` 会先于其他 microtask 执行。 360 | 361 | ```js 362 | setTimeout(() => { 363 | console.log("timer1"); 364 | 365 | Promise.resolve().then(function() { 366 | console.log("promise1"); 367 | }); 368 | }, 0); 369 | 370 | process.nextTick(() => { 371 | console.log("nextTick"); 372 | }); 373 | // nextTick, timer1, promise1 374 | ``` 375 | 376 | # 存储 377 | 378 | ## cookie,localStorage,sessionStorage,indexDB 379 | 380 | | 特性 | cookie | localStorage | sessionStorage | indexDB | 381 | | :----------: | :----------------------------------------: | :----------------------: | :------------: | :----------------------: | 382 | | 数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 | 383 | | 数据存储大小 | 4K | 5M | 5M | 无限 | 384 | | 与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 | 385 | 386 | 从上表可以看到,`cookie` 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 `localStorage` 和 `sessionStorage` 。对于不怎么改变的数据尽量使用 `localStorage` 存储,否则可以用 `sessionStorage` 存储。 387 | 388 | 对于 `cookie`,我们还需要注意安全性。 389 | 390 | | 属性 | 作用 | 391 | | :-------: | :----------------------------------------------------------: | 392 | | value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 | 393 | | http-only | 不能通过 JS 访问 Cookie,减少 XSS 攻击 | 394 | | secure | 只能在协议为 HTTPS 的请求中携带 | 395 | | same-site | 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 | 396 | 397 | ## Service Worker 398 | 399 | > Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。 400 | 401 | 目前该技术通常用来做缓存文件,提高首屏速度,可以试着来实现这个功能。 402 | 403 | ```js 404 | // index.js 405 | if (navigator.serviceWorker) { 406 | navigator.serviceWorker 407 | .register("sw.js") 408 | .then(function(registration) { 409 | console.log("service worker 注册成功"); 410 | }) 411 | .catch(function(err) { 412 | console.log("servcie worker 注册失败"); 413 | }); 414 | } 415 | // sw.js 416 | // 监听 `install` 事件,回调中缓存所需文件 417 | self.addEventListener("install", e => { 418 | e.waitUntil( 419 | caches.open("my-cache").then(function(cache) { 420 | return cache.addAll(["./index.html", "./index.js"]); 421 | }) 422 | ); 423 | }); 424 | 425 | // 拦截所有请求事件 426 | // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据 427 | self.addEventListener("fetch", e => { 428 | e.respondWith( 429 | caches.match(e.request).then(function(response) { 430 | if (response) { 431 | return response; 432 | } 433 | console.log("fetch source"); 434 | }) 435 | ); 436 | }); 437 | ``` 438 | 439 | 打开页面,可以在开发者工具中的 `Application` 看到 Service Worker 已经启动了![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042724.png) 440 | 441 | 在 Cache 中也可以发现我们所需的文件已被缓存 442 | 443 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042727.png) 444 | 445 | 当我们重新刷新页面可以发现我们缓存的数据是从 Service Worker 中读取的 446 | 447 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042730.png) 448 | 449 | 450 | 451 | # 渲染机制 452 | 453 | 浏览器的渲染机制一般分为以下几个步骤 454 | 455 | 1. 处理 HTML 并构建 DOM 树。 456 | 2. 处理 CSS 构建 CSSOM 树。 457 | 3. 将 DOM 与 CSSOM 合并成一个渲染树。 458 | 4. 根据渲染树来布局,计算每个节点的位置。 459 | 5. 调用 GPU 绘制,合成图层,显示在屏幕上。 460 | 461 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042733.png) 462 | 463 | 在构建 CSSOM 树时,会阻塞渲染,直至 CSSOM 树构建完成。并且构建 CSSOM 树是一个十分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执行速度越慢。 464 | 465 | 当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM。 466 | 467 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042734.png) 468 | 469 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042735.png) 470 | 471 | ## Load 和 DOMContentLoaded 区别 472 | 473 | Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。 474 | 475 | DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。 476 | 477 | ## 图层 478 | 479 | 一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。**不同的图层渲染互不影响**,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。**但也不能生成过多的图层,会引起反作用。** 480 | 481 | 通过以下几个常用属性可以生成新图层 482 | 483 | - 3D 变换:`translate3d`、`translateZ` 484 | - `will-change` 485 | - `video`、`iframe` 标签 486 | - 通过动画实现的 `opacity` 动画转换 487 | - `position: fixed` 488 | 489 | ## 重绘(Repaint)和回流(Reflow) 490 | 491 | 重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。 492 | 493 | - 重绘是当节点需要更改外观而不会影响布局的,比如改变 `color` 就叫称为重绘 494 | - 回流是布局或者几何属性需要改变就称为回流。 495 | 496 | 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。 497 | 498 | 所以以下几个动作可能会导致性能问题: 499 | 500 | - 改变 window 大小 501 | - 改变字体 502 | - 添加或删除样式 503 | - 文字改变 504 | - 定位或者浮动 505 | - 盒模型 506 | 507 | 很多人不知道的是,重绘和回流其实和 Event loop 有关。 508 | 509 | 1. 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。 510 | 2. 然后判断是否有 `resize` 或者 `scroll` ,有的话会去触发事件,所以 `resize` 和 `scroll` 事件也是至少 16ms 才会触发一次,并且自带节流功能。 511 | 3. 判断是否触发了 media query 512 | 4. 更新动画并且发送事件 513 | 5. 判断是否有全屏操作事件 514 | 6. 执行 `requestAnimationFrame` 回调 515 | 7. 执行 `IntersectionObserver` 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 516 | 8. 更新界面 517 | 9. 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 `requestIdleCallback` 回调。 518 | 519 | 以上内容来自于 [HTML 文档](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model) 520 | 521 | ## 减少重绘和回流 522 | 523 | - 使用 `translate` 替代 `top` 524 | 525 | ```html 526 |
527 | 536 | 542 | ``` 543 | 544 | - 使用 `visibility` 替换 `display: none` ,因为前者只会引起重绘,后者会引发回流(改变了布局) 545 | 546 | - 把 DOM 离线后修改,比如:先把 DOM 给 `display:none` (有一次 Reflow),然后你修改100次,然后再把它显示出来 547 | 548 | - 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量 549 | 550 | ```js 551 | for(let i = 0; i < 1000; i++) { 552 | // 获取 offsetTop 会导致回流,因为需要去获取正确的值 553 | console.log(document.querySelector('.test').style.offsetTop) 554 | } 555 | ``` 556 | 557 | - 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局 558 | 559 | - 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 `requestAnimationFrame` 560 | 561 | - CSS 选择符从右往左匹配查找,避免 DOM 深度过深 562 | 563 | - 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 `video` 标签,浏览器会自动将该节点变为图层。 564 | 565 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042737.png) -------------------------------------------------------------------------------- /Network/Network-zh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [UDP](#udp) 6 | - [面向报文](#%E9%9D%A2%E5%90%91%E6%8A%A5%E6%96%87) 7 | - [不可靠性](#%E4%B8%8D%E5%8F%AF%E9%9D%A0%E6%80%A7) 8 | - [高效](#%E9%AB%98%E6%95%88) 9 | - [传输方式](#%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F) 10 | - [TCP](#tcp) 11 | - [头部](#%E5%A4%B4%E9%83%A8) 12 | - [状态机](#%E7%8A%B6%E6%80%81%E6%9C%BA) 13 | - [建立连接三次握手](#%E5%BB%BA%E7%AB%8B%E8%BF%9E%E6%8E%A5%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B) 14 | - [断开链接四次握手](#%E6%96%AD%E5%BC%80%E9%93%BE%E6%8E%A5%E5%9B%9B%E6%AC%A1%E6%8F%A1%E6%89%8B) 15 | - [ARQ 协议](#arq-%E5%8D%8F%E8%AE%AE) 16 | - [停止等待 ARQ](#%E5%81%9C%E6%AD%A2%E7%AD%89%E5%BE%85-arq) 17 | - [连续 ARQ](#%E8%BF%9E%E7%BB%AD-arq) 18 | - [累计确认](#%E7%B4%AF%E8%AE%A1%E7%A1%AE%E8%AE%A4) 19 | - [滑动窗口](#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3) 20 | - [Zero 窗口](#zero-%E7%AA%97%E5%8F%A3) 21 | - [拥塞处理](#%E6%8B%A5%E5%A1%9E%E5%A4%84%E7%90%86) 22 | - [慢开始算法](#%E6%85%A2%E5%BC%80%E5%A7%8B%E7%AE%97%E6%B3%95) 23 | - [拥塞避免算法](#%E6%8B%A5%E5%A1%9E%E9%81%BF%E5%85%8D%E7%AE%97%E6%B3%95) 24 | - [快速重传](#%E5%BF%AB%E9%80%9F%E9%87%8D%E4%BC%A0) 25 | - [TCP New Ren 改进后的快恢复](#tcp-new-ren-%E6%94%B9%E8%BF%9B%E5%90%8E%E7%9A%84%E5%BF%AB%E6%81%A2%E5%A4%8D) 26 | - [HTTP](#http) 27 | - [Post 和 Get 的区别](#post-%E5%92%8C-get-%E7%9A%84%E5%8C%BA%E5%88%AB) 28 | - [常见状态码](#%E5%B8%B8%E8%A7%81%E7%8A%B6%E6%80%81%E7%A0%81) 29 | - [HTTP 首部](#http-%E9%A6%96%E9%83%A8) 30 | - [HTTPS](#https) 31 | - [TLS](#tls) 32 | - [HTTP 2.0](#http-20) 33 | - [二进制传输](#%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BC%A0%E8%BE%93) 34 | - [多路复用](#%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8) 35 | - [Header 压缩](#header-%E5%8E%8B%E7%BC%A9) 36 | - [服务端 Push](#%E6%9C%8D%E5%8A%A1%E7%AB%AF-push) 37 | - [QUIC](#quic) 38 | - [DNS](#dns) 39 | - [从输入 URL 到页面加载完成的过程](#%E4%BB%8E%E8%BE%93%E5%85%A5-url-%E5%88%B0%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E5%AE%8C%E6%88%90%E7%9A%84%E8%BF%87%E7%A8%8B) 40 | 41 | 42 | 43 | # UDP 44 | 45 | ## 面向报文 46 | 47 | UDP 是一个面向报文(报文可以理解为一段段的数据)的协议。意思就是 UDP 只是报文的搬运工,不会对报文进行任何拆分和拼接操作。 48 | 49 | 具体来说 50 | 51 | - 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了 52 | - 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作 53 | 54 | ## 不可靠性 55 | 56 | 1. UDP 是无连接的,也就是说通信不需要建立和断开连接。 57 | 2. UDP 也是不可靠的。协议收到什么数据就传递什么数据,并且也不会备份数据,对方能不能收到是不关心的 58 | 3. UDP 没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。 59 | 60 | ## 高效 61 | 62 | 因为 UDP 没有 TCP 那么复杂,需要保证数据不丢失且有序到达。所以 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的。 63 | 64 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-42633.png) 65 | 66 | 头部包含了以下几个数据 67 | 68 | - 两个十六位的端口号,分别为源端口(可选字段)和目标端口 69 | - 整个数据报文的长度 70 | - 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误 71 | 72 | ## 传输方式 73 | 74 | UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。 75 | 76 | # TCP 77 | 78 | ## 头部 79 | 80 | TCP 头部比 UDP 头部复杂的多 81 | 82 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042634.png) 83 | 84 | 对于 TCP 头部来说,以下几个字段是很重要的 85 | 86 | - Sequence number,这个序号保证了 TCP 传输的报文都是有序的,对端可以通过序号顺序的拼接报文 87 | - Acknowledgement Number,这个序号表示数据接收端期望接收的下一个字节的编号是多少,同时也表示上一个序号的数据已经收到 88 | - Window Size,窗口大小,表示还能接收多少字节的数据,用于流量控制 89 | - 标识符 90 | - URG=1:该字段为一表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文,此时紧急指针有效。紧急数据一定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。 91 | - ACK=1:该字段为一表示确认号字段有效。此外,TCP 还规定在连接建立后传送的所有报文段都必须把 ACK 置为一。 92 | - PSH=1:该字段为一表示接收端应该立即将数据 push 给应用层,而不是等到缓冲区满后再提交。 93 | - RST=1:该字段为一表示当前 TCP 连接出现严重问题,可能需要重新建立 TCP 连接,也可以用于拒绝非法的报文段和拒绝连接请求。 94 | - SYN=1:当SYN=1,ACK=0时,表示当前报文段是一个连接请求报文。当SYN=1,ACK=1时,表示当前报文段是一个同意建立连接的应答报文。 95 | - FIN=1:该字段为一表示此报文段是一个释放连接的请求报文。 96 | 97 | ## 状态机 98 | 99 | HTTP 是无连接的,所以作为下层的 TCP 协议也是无连接的,虽然看似 TCP 将两端连接了起来,但是其实只是两端共同维护了一个状态 100 | 101 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042638.png) 102 | 103 | TCP 的状态机是很复杂的,并且与建立断开连接时的握手息息相关,接下来就来详细描述下两种握手。 104 | 105 | 在这之前需要了解一个重要的性能指标 RTT。该指标表示发送端发送数据到接收到对端数据所需的往返时间。 106 | 107 | ### 建立连接三次握手 108 | 109 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042641.png) 110 | 111 | 在 TCP 协议中,主动发起请求的一端为客户端,被动连接的一端称为服务端。不管是客户端还是服务端,TCP 连接建立完后都能发送和接收数据,所以 TCP 也是一个全双工的协议。 112 | 113 | 起初,两端都为 CLOSED 状态。在通信开始前,双方都会创建 TCB。 服务器创建完 TCB 后遍进入 LISTEN 状态,此时开始等待客户端发送数据。 114 | 115 | **第一次握手** 116 | 117 | 客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态,`x` 表示客户端的数据通信初始序号。 118 | 119 | **第二次握手** 120 | 121 | 服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。 122 | 123 | **第三次握手** 124 | 125 | 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。 126 | 127 | PS:第三次握手可以包含数据,通过 TCP 快速打开(TFO)技术。其实只要涉及到握手的协议,都可以使用类似 TFO 的方式,客户端和服务端存储相同 cookie,下次握手时发出 cookie 达到减少 RTT 的目的。 128 | 129 | **你是否有疑惑明明两次握手就可以建立起连接,为什么还需要第三次应答?** 130 | 131 | 因为这是为了防止失效的连接请求报文段被服务端接收,从而产生错误。 132 | 133 | 可以想象如下场景。客户端发送了一个连接请求 A,但是因为网络原因造成了超时,这时 TCP 会启动超时重传的机制再次发送一个连接请求 B。此时请求顺利到达服务端,服务端应答完就建立了请求。如果连接请求 A 在两端关闭后终于抵达了服务端,那么这时服务端会认为客户端又需要建立 TCP 连接,从而应答了该请求并进入 ESTABLISHED 状态。此时客户端其实是 CLOSED 状态,那么就会导致服务端一直等待,造成资源的浪费。 134 | 135 | PS:在建立连接中,任意一端掉线,TCP 都会重发 SYN 包,一般会重试五次,在建立连接中可能会遇到 SYN FLOOD 攻击。遇到这种情况你可以选择调低重试次数或者干脆在不能处理的情况下拒绝请求。 136 | 137 | ### 断开链接四次握手 138 | 139 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-42642.png) 140 | 141 | TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。 142 | 143 | **第一次握手** 144 | 145 | 若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。 146 | 147 | **第二次握手** 148 | 149 | B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,表示 A 到 B 的连接已经释放,不接收 A 发的数据了。但是因为 TCP 连接时双向的,所以 B 仍旧可以发送数据给 A。 150 | 151 | **第三次握手** 152 | 153 | B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。 154 | 155 | PS:通过延迟确认的技术(通常有时间限制,否则对方会误认为需要重传),可以将第二次和第三次握手合并,延迟 ACK 包的发送。 156 | 157 | **第四次握手** 158 | 159 | A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。 160 | 161 | **为什么 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态?** 162 | 163 | 为了保证 B 能收到 A 的确认应答。若 A 发完确认应答后直接进入 CLOSED 状态,如果确认应答因为网络问题一直没有到达,那么会造成 B 不能正常关闭。 164 | 165 | ## ARQ 协议 166 | 167 | ARQ 协议也就是超时重传机制。通过确认和超时机制保证了数据的正确送达,ARQ 协议包含停止等待 ARQ 和连续 ARQ 168 | 169 | ### 停止等待 ARQ 170 | 171 | **正常传输过程** 172 | 173 | 只要 A 向 B 发送一段报文,都要停止发送并启动一个定时器,等待对端回应,在定时器时间内接收到对端应答就取消定时器并发送下一段报文。 174 | 175 | **报文丢失或出错** 176 | 177 | 在报文传输的过程中可能会出现丢包。这时候超过定时器设定的时间就会再次发送丢包的数据直到对端响应,所以需要每次都备份发送的数据。 178 | 179 | 即使报文正常的传输到对端,也可能出现在传输过程中报文出错的问题。这时候对端会抛弃该报文并等待 A 端重传。 180 | 181 | PS:一般定时器设定的时间都会大于一个 RTT 的平均时间。 182 | 183 | **ACK 超时或丢失** 184 | 185 | 对端传输的应答也可能出现丢失或超时的情况。那么超过定时器时间 A 端照样会重传报文。这时候 B 端收到相同序号的报文会丢弃该报文并重传应答,直到 A 端发送下一个序号的报文。 186 | 187 | 在超时的情况下也可能出现应答很迟到达,这时 A 端会判断该序号是否已经接收过,如果接收过只需要丢弃应答即可。 188 | 189 | **这个协议的缺点就是传输效率低,在良好的网络环境下每次发送报文都得等待对端的 ACK 。** 190 | 191 | ### 连续 ARQ 192 | 193 | 在连续 ARQ 中,发送端拥有一个发送窗口,可以在没有收到应答的情况下持续发送窗口内的数据,这样相比停止等待 ARQ 协议来说减少了等待时间,提高了效率。 194 | 195 | ### 累计确认 196 | 197 | 连续 ARQ 中,接收端会持续不断收到报文。如果和停止等待 ARQ 中接收一个报文就发送一个应答一样,就太浪费资源了。通过累计确认,可以在收到多个报文以后统一回复一个应答报文。报文中的 ACK 可以用来告诉发送端这个序号之前的数据已经全部接收到了,下次请发送这个序号 + 1的数据。 198 | 199 | 但是累计确认也有一个弊端。在连续接收报文时,可能会遇到接收到序号 5 的报文后,并未接到序号 6 的报文,然而序号 7 以后的报文已经接收。遇到这种情况时,ACK 只能回复 6,这样会造成发送端重复发送数据,这种情况下可以通过 Sack 来解决,这个会在下文说到。 200 | 201 | ## 滑动窗口 202 | 203 | 在上面小节中讲到了发送窗口。在 TCP 中,两端都维护着窗口:分别为发送端窗口和接收端窗口。 204 | 205 | 发送端窗口包含已发送但未收到应答的数据和可以发送但是未发送的数据。 206 | 207 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042642.png) 208 | 209 | 发送端窗口是由接收窗口剩余大小决定的。接收方会把当前接收窗口的剩余大小写入应答报文,发送端收到应答后根据该值和当前网络拥塞情况设置发送窗口的大小,所以发送窗口的大小是不断变化的。 210 | 211 | 当发送端接收到应答报文后,会随之将窗口进行滑动 212 | 213 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042643.png) 214 | 215 | 滑动窗口实现了流量控制。接收方通过报文告知发送方还可以发送多少数据,从而保证接收方能够来得及接收数据。 216 | 217 | ### Zero 窗口 218 | 219 | 在发送报文的过程中,可能会遇到对端出现零窗口的情况。在该情况下,发送端会停止发送数据,并启动 persistent timer 。该定时器会定时发送请求给对端,让对端告知窗口大小。在重试次数超过一定次数后,可能会中断 TCP 链接。 220 | 221 | ## 拥塞处理 222 | 223 | 拥塞处理和流量控制不同,后者是作用于接收方,保证接收方来得及接受数据。而前者是作用于网络,防止过多的数据拥塞网络,避免出现网络负载过大的情况。 224 | 225 | 拥塞处理包括了四个算法,分别为:慢开始,拥塞避免,快速重传,快速恢复。 226 | 227 | ### 慢开始算法 228 | 229 | 慢开始算法,顾名思义,就是在传输开始时将发送窗口慢慢指数级扩大,从而避免一开始就传输大量数据导致网络拥塞。 230 | 231 | 慢开始算法步骤具体如下 232 | 233 | 1. 连接初始设置拥塞窗口(Congestion Window) 为 1 MSS(一个分段的最大数据量) 234 | 2. 每过一个 RTT 就将窗口大小乘二 235 | 3. 指数级增长肯定不能没有限制的,所以有一个阈值限制,当窗口大小大于阈值时就会启动拥塞避免算法。 236 | 237 | ### 拥塞避免算法 238 | 239 | 拥塞避免算法相比简单点,每过一个 RTT 窗口大小只加一,这样能够避免指数级增长导致网络拥塞,慢慢将大小调整到最佳值。 240 | 241 | 在传输过程中可能定时器超时的情况,这时候 TCP 会认为网络拥塞了,会马上进行以下步骤: 242 | 243 | - 将阈值设为当前拥塞窗口的一半 244 | - 将拥塞窗口设为 1 MSS 245 | - 启动拥塞避免算法 246 | 247 | ### 快速重传 248 | 249 | 快速重传一般和快恢复一起出现。一旦接收端收到的报文出现失序的情况,接收端只会回复最后一个顺序正确的报文序号(没有 Sack 的情况下)。如果收到三个重复的 ACK,无需等待定时器超时再重发而是启动快速重传。具体算法分为两种: 250 | 251 | **TCP Taho 实现如下** 252 | 253 | - 将阈值设为当前拥塞窗口的一半 254 | - 将拥塞窗口设为 1 MSS 255 | - 重新开始慢开始算法 256 | 257 | **TCP Reno 实现如下** 258 | 259 | - 拥塞窗口减半 260 | - 将阈值设为当前拥塞窗口 261 | - 进入快恢复阶段(重发对端需要的包,一旦收到一个新的 ACK 答复就退出该阶段) 262 | - 使用拥塞避免算法 263 | 264 | ### TCP New Ren 改进后的快恢复 265 | 266 | **TCP New Reno** 算法改进了之前 **TCP Reno** 算法的缺陷。在之前,快恢复中只要收到一个新的 ACK 包,就会退出快恢复。 267 | 268 | 在 **TCP New Reno** 中,TCP 发送方先记下三个重复 ACK 的分段的最大序号。 269 | 270 | 假如我有一个分段数据是 1 ~ 10 这十个序号的报文,其中丢失了序号为 3 和 7 的报文,那么该分段的最大序号就是 10。发送端只会收到 ACK 序号为 3 的应答。这时候重发序号为 3 的报文,接收方顺利接收并会发送 ACK 序号为 7 的应答。这时候 TCP 知道对端是有多个包未收到,会继续发送序号为 7 的报文,接收方顺利接收并会发送 ACK 序号为 11 的应答,这时发送端认为这个分段接收端已经顺利接收,接下来会退出快恢复阶段。 271 | 272 | # HTTP 273 | 274 | HTTP 协议是个无状态协议,不会保存状态。 275 | 276 | ## Post 和 Get 的区别 277 | 278 | 先引入副作用和幂等的概念。 279 | 280 | 副作用指对服务器上的资源做改变,搜索是无副作用的,注册是副作用的。 281 | 282 | 幂等指发送 M 和 N 次请求(两者不相同且都大于 1),服务器上资源的状态一致,比如注册 10 个和 11 个帐号是不幂等的,对文章进行更改 10 次和 11 次是幂等的。 283 | 284 | 在规范的应用场景上说,Get 多用于无副作用,幂等的场景,例如搜索关键字。Post 多用于副作用,不幂等的场景,例如注册。 285 | 286 | 在技术上说: 287 | * Get 请求能缓存,Post 不能 288 | * Post 相对 Get 安全一点点,因为Get 请求都包含在 URL 里,且会被浏览器保存历史纪录,Post 不会,但是在抓包的情况下都是一样的。 289 | * Post 可以通过 request body来传输比 Get 更多的数据,Get 没有这个技术 290 | * URL有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的 291 | * Post 支持更多的编码类型且不对数据类型限制 292 | 293 | ## 常见状态码 294 | 295 | **2XX 成功** 296 | 297 | * 200 OK,表示从客户端发来的请求在服务器端被正确处理 298 | * 204 No content,表示请求成功,但响应报文不含实体的主体部分 299 | * 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容 300 | * 206 Partial Content,进行范围请求 301 | 302 | **3XX 重定向** 303 | 304 | * 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL 305 | * 302 found,临时性重定向,表示资源临时被分配了新的 URL 306 | * 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源 307 | * 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况 308 | * 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 309 | 310 | **4XX 客户端错误** 311 | 312 | * 400 bad request,请求报文存在语法错误 313 | * 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 314 | * 403 forbidden,表示对请求资源的访问被服务器拒绝 315 | * 404 not found,表示在服务器上没有找到请求的资源 316 | 317 | **5XX 服务器错误** 318 | 319 | * 500 internal sever error,表示服务器端在执行请求时发生了错误 320 | * 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能 321 | * 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 322 | 323 | ## HTTP 首部 324 | 325 | | 通用字段 | 作用 | 326 | | :---------------: | :----------------------------------------------: | 327 | | Cache-Control | 控制缓存的行为 | 328 | | Connection | 浏览器想要优先使用的连接类型,比如 `keep-alive` | 329 | | Date | 创建报文时间 | 330 | | Pragma | 报文指令 | 331 | | Via | 代理服务器相关信息 | 332 | | Transfer-Encoding | 传输编码方式 | 333 | | Upgrade | 要求客户端升级协议 | 334 | | Warning | 在内容中可能存在错误 | 335 | 336 | | 请求字段 | 作用 | 337 | | :-----------------: | :--------------------------------: | 338 | | Accept | 能正确接收的媒体类型 | 339 | | Accept-Charset | 能正确接收的字符集 | 340 | | Accept-Encoding | 能正确接收的编码格式列表 | 341 | | Accept-Language | 能正确接收的语言列表 | 342 | | Expect | 期待服务端的指定行为 | 343 | | From | 请求方邮箱地址 | 344 | | Host | 服务器的域名 | 345 | | If-Match | 两端资源标记比较 | 346 | | If-Modified-Since | 本地资源未修改返回 304(比较时间) | 347 | | If-None-Match | 本地资源未修改返回 304(比较标记) | 348 | | User-Agent | 客户端信息 | 349 | | Max-Forwards | 限制可被代理及网关转发的次数 | 350 | | Proxy-Authorization | 向代理服务器发送验证信息 | 351 | | Range | 请求某个内容的一部分 | 352 | | Referer | 表示浏览器所访问的前一个页面 | 353 | | TE | 传输编码方式 | 354 | 355 | | 响应字段 | 作用 | 356 | | :----------------: | :------------------------: | 357 | | Accept-Ranges | 是否支持某些种类的范围 | 358 | | Age | 资源在代理缓存中存在的时间 | 359 | | ETag | 资源标识 | 360 | | Location | 客户端重定向到某个 URL | 361 | | Proxy-Authenticate | 向代理服务器发送验证信息 | 362 | | Server | 服务器名字 | 363 | | WWW-Authenticate | 获取资源需要的验证信息 | 364 | 365 | | 实体字段 | 作用 | 366 | | :--------------: | :----------------------------: | 367 | | Allow | 资源的正确请求方式 | 368 | | Content-Encoding | 内容的编码格式 | 369 | | Content-Language | 内容使用的语言 | 370 | | Content-Length | request body 长度 | 371 | | Content-Location | 返回数据的备用地址 | 372 | | Content-MD5 | Base64加密格式的内容 MD5检验值 | 373 | | Content-Range | 内容的位置范围 | 374 | | Content-Type | 内容的媒体类型 | 375 | | Expires | 内容的过期时间 | 376 | | Last_modified | 内容的最后修改时间 | 377 | 378 | PS:缓存相关已在别的模块中写完,你可以 [阅读该小节](../Performance/performance-ch.md#缓存) 379 | 380 | # HTTPS 381 | 382 | HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议进行了加密。 383 | 384 | ## TLS 385 | 386 | TLS 协议位于传输层之上,应用层之下。首次进行 TLS 协议传输需要两个 RTT ,接下来可以通过 Session Resumption 减少到一个 RTT。 387 | 388 | 在 TLS 中使用了两种加密技术,分别为:对称加密和非对称加密。 389 | 390 | **对称加密**: 391 | 392 | 对称加密就是两边拥有相同的秘钥,两边都知道如何将密文加密解密。 393 | 394 | **非对称加密**: 395 | 396 | 有公钥私钥之分,公钥所有人都可以知道,可以将数据用公钥加密,但是将数据解密必须使用私钥解密,私钥只有分发公钥的一方才知道。 397 | 398 | **TLS 握手过程如下图:** 399 | 400 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042644.jpg) 401 | 402 | 1. 客户端发送一个随机值,需要的协议和加密方式 403 | 2. 服务端收到客户端的随机值,自己也产生一个随机值,并根据客户端需求的协议和加密方式来使用对应的方式,发送自己的证书(如果需要验证客户端证书需要说明) 404 | 3. 客户端收到服务端的证书并验证是否有效,验证通过会再生成一个随机值,通过服务端证书的公钥去加密这个随机值并发送给服务端,如果服务端需要验证客户端证书的话会附带证书 405 | 4. 服务端收到加密过的随机值并使用私钥解密获得第三个随机值,这时候两端都拥有了三个随机值,可以通过这三个随机值按照之前约定的加密方式生成密钥,接下来的通信就可以通过该密钥来加密解密 406 | 407 | 通过以上步骤可知,在 TLS 握手阶段,两端使用非对称加密的方式来通信,但是因为非对称加密损耗的性能比对称加密大,所以在正式传输数据时,两端使用对称加密的方式通信。 408 | 409 | PS:以上说明的都是 TLS 1.2 协议的握手情况,在 1.3 协议中,首次建立连接只需要一个 RTT,后面恢复连接不需要 RTT 了。 410 | 411 | # HTTP 2.0 412 | 413 | HTTP 2.0 相比于 HTTP 1.X,可以说是大幅度提高了 web 的性能。 414 | 415 | 在 HTTP 1.X 中,为了性能考虑,我们会引入雪碧图、将小图内联、使用多个域名等等的方式。这一切都是因为浏览器限制了同一个域名下的请求数量,当页面中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 416 | 417 | 你可以通过 [该链接](https://http2.akamai.com/demo) 感受下 HTTP 2.0 比 HTTP 1.X 到底快了多少。 418 | 419 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042644.png) 420 | 421 | 在 HTTP 1.X 中,因为队头阻塞的原因,你会发现请求是这样的 422 | 423 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042646.png) 424 | 425 | 在 HTTP 2.0 中,因为引入了多路复用,你会发现请求是这样的 426 | 427 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042647.png) 428 | 429 | ## 二进制传输 430 | 431 | HTTP 2.0 中所有加强性能的核心点在于此。在之前的 HTTP 版本中,我们是通过文本的方式传输数据。在 HTTP 2.0 中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。 432 | 433 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042649.png) 434 | 435 | ## 多路复用 436 | 437 | 在 HTTP 2.0 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 438 | 439 | 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 440 | 441 | 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。 442 | 443 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042650.png) 444 | 445 | ## Header 压缩 446 | 447 | 在 HTTP 1.X 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。 448 | 449 | 在 HTTP 2.0 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。 450 | 451 | ## 服务端 Push 452 | 453 | 在 HTTP 2.0 中,服务端可以在客户端某个请求后,主动推送其他资源。 454 | 455 | 可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch 。 456 | 457 | ## QUIC 458 | 459 | 这是一个谷歌出品的基于 UDP 实现的同为传输层的协议,目标很远大,希望替代 TCP 协议。 460 | 461 | - 该协议支持多路复用,虽然 HTTP 2.0 也支持多路复用,但是下层仍是 TCP,因为 TCP 的重传机制,只要一个包丢失就得判断丢失包并且重传,导致发生队头阻塞的问题,但是 UDP 没有这个机制 462 | - 实现了自己的加密协议,通过类似 TCP 的 TFO 机制可以实现 0-RTT,当然 TLS 1.3 已经实现了 0-RTT 了 463 | - 支持重传和纠错机制(向前恢复),在只丢失一个包的情况下不需要重传,使用纠错机制恢复丢失的包 464 | - 纠错机制:通过异或的方式,算出发出去的数据的异或值并单独发出一个包,服务端在发现有一个包丢失的情况下,通过其他数据包和异或值包算出丢失包 465 | - 在丢失两个包或以上的情况就使用重传机制,因为算不出来了 466 | 467 | # DNS 468 | 469 | DNS 的作用就是通过域名查询到具体的 IP。 470 | 471 | 因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。 472 | 473 | 在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 `www.google.com` 时,会进行一下操作: 474 | 475 | 1. 操作系统会首先在本地缓存中查询 476 | 2. 没有的话会去系统配置的 DNS 服务器中查询 477 | 3. 如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 `com` 这个一级域名的服务器 478 | 4. 然后去该服务器查询 `google` 这个二级域名 479 | 5. 接下来三级域名的查询其实是我们配置的,你可以给 `www` 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP 480 | 481 | 以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。 482 | 483 | PS:DNS 是基于 UDP 做的查询。 484 | 485 | # 从输入 URL 到页面加载完成的过程 486 | 487 | 这是一个很经典的面试题,在这题中可以将本文讲得内容都串联起来。 488 | 489 | 1. 首先做 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来 490 | 2. 接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了 491 | 3. TCP 握手结束后会进行 TLS 握手,然后就开始正式的传输数据 492 | 4. 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件 493 | 5. 首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错 494 | 6. 浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件 495 | 7. 文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 `script` 标签的话,会判断是否存在 `async` 或者 `defer` ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行,如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP 2.0 协议的话会极大的提高多图的下载效率。 496 | 8. 初始的 HTML 被完全加载和解析后会触发 `DOMContentLoaded` 事件 497 | 9. CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西 498 | 10. 在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了 499 | -------------------------------------------------------------------------------- /Framework/react-zh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [React 生命周期分析](#react-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%88%86%E6%9E%90) 6 | - [V16 生命周期函数用法建议](#v16-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%87%BD%E6%95%B0%E7%94%A8%E6%B3%95%E5%BB%BA%E8%AE%AE) 7 | - [setState](#setstate) 8 | - [Redux 源码分析](#redux-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90) 9 | 10 | 11 | 12 | # React 生命周期分析 13 | 14 | 在 V16 版本中引入了 Fiber 机制。这个机制一定程度上的影响了部分生命周期的调用,并且也引入了新的 2 个 API 来解决问题。 15 | 16 | 在之前的版本中,如果你拥有一个很复杂的复合组件,然后改动了最上层组件的 `state`,那么调用栈可能会很长 17 | 18 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042522.png) 19 | 20 | 调用栈过长,再加上中间进行了复杂的操作,就可能导致长时间阻塞主线程,带来不好的用户体验。Fiber 就是为了解决该问题而生。 21 | 22 | Fiber 本质上是一个虚拟的堆栈帧,新的调度器会按照优先级自由调度这些帧,从而将之前的同步渲染改成了异步渲染,在不影响体验的情况下去分段计算更新。 23 | 24 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042523.png) 25 | 26 | 对于如何区别优先级,React 有自己的一套逻辑。对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的情况下,React 会每 16 ms(以内) 暂停一下更新,返回来继续渲染动画。 27 | 28 | 对于异步渲染,现在渲染有两个阶段:`reconciliation` 和 `commit` 。前者过程是可以打断的,后者不能暂停,会一直更新界面直到完成。 29 | 30 | **Reconciliation** 阶段 31 | 32 | - `componentWillMount` 33 | - `componentWillReceiveProps` 34 | - `shouldComponentUpdate` 35 | - `componentWillUpdate` 36 | 37 | **Commit** 阶段 38 | 39 | - `componentDidMount` 40 | - `componentDidUpdate` 41 | - `componentWillUnmount` 42 | 43 | 因为 `reconciliation` 阶段是可以被打断的,所以 `reconciliation` 阶段会执行的生命周期函数就可能会出现调用多次的情况,从而引起 Bug。所以对于 `reconciliation` 阶段调用的几个函数,除了 `shouldComponentUpdate` 以外,其他都应该避免去使用,并且 V16 中也引入了新的 API 来解决这个问题。 44 | 45 | `getDerivedStateFromProps` 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用 46 | 47 | ```js 48 | class ExampleComponent extends React.Component { 49 | // Initialize state in constructor, 50 | // Or with a property initializer. 51 | state = {}; 52 | 53 | static getDerivedStateFromProps(nextProps, prevState) { 54 | if (prevState.someMirroredValue !== nextProps.someValue) { 55 | return { 56 | derivedData: computeDerivedState(nextProps), 57 | someMirroredValue: nextProps.someValue 58 | }; 59 | } 60 | 61 | // Return null to indicate no change to state. 62 | return null; 63 | } 64 | } 65 | ``` 66 | 67 | `getSnapshotBeforeUpdate` 用于替换 `componentWillUpdate` ,该函数会在 `update` 后 DOM 更新前被调用,用于读取最新的 DOM 数据。 68 | 69 | ## V16 生命周期函数用法建议 70 | 71 | ```js 72 | class ExampleComponent extends React.Component { 73 | // 用于初始化 state 74 | constructor() {} 75 | // 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用 76 | // 因为该函数是静态函数,所以取不到 `this` 77 | // 如果需要对比 `prevProps` 需要单独在 `state` 中维护 78 | static getDerivedStateFromProps(nextProps, prevState) {} 79 | // 判断是否需要更新组件,多用于组件性能优化 80 | shouldComponentUpdate(nextProps, nextState) {} 81 | // 组件挂载后调用 82 | // 可以在该函数中进行请求或者订阅 83 | componentDidMount() {} 84 | // 用于获得最新的 DOM 数据 85 | getSnapshotBeforeUpdate() {} 86 | // 组件即将销毁 87 | // 可以在此处移除订阅,定时器等等 88 | componentWillUnmount() {} 89 | // 组件销毁后调用 90 | componentDidUnMount() {} 91 | // 组件更新后调用 92 | componentDidUpdate() {} 93 | // 渲染组件函数 94 | render() {} 95 | // 以下函数不建议使用 96 | UNSAFE_componentWillMount() {} 97 | UNSAFE_componentWillUpdate(nextProps, nextState) {} 98 | UNSAFE_componentWillReceiveProps(nextProps) {} 99 | } 100 | ``` 101 | 102 | # setState 103 | 104 | `setState` 在 React 中是经常使用的一个 API,但是它存在一些问题,可能会导致犯错,核心原因就是因为这个 API 是异步的。 105 | 106 | 首先 `setState` 的调用并不会马上引起 `state` 的改变,并且如果你一次调用了多个 `setState` ,那么结果可能并不如你期待的一样。 107 | 108 | ```js 109 | handle() { 110 | // 初始化 `count` 为 0 111 | console.log(this.state.count) // -> 0 112 | this.setState({ count: this.state.count + 1 }) 113 | this.setState({ count: this.state.count + 1 }) 114 | this.setState({ count: this.state.count + 1 }) 115 | console.log(this.state.count) // -> 0 116 | } 117 | ``` 118 | 119 | 第一,两次的打印都为 0,因为 `setState` 是个异步 API,只有同步代码运行完毕才会执行。`setState` 异步的原因我认为在于,`setState` 可能会导致 DOM 的重绘,如果调用一次就马上去进行重绘,那么调用多次就会造成不必要的性能损失。设计成异步的话,就可以将多次调用放入一个队列中,在恰当的时候统一进行更新过程。 120 | 121 | 第二,虽然调用了三次 `setState` ,但是 `count` 的值还是为 1。因为多次调用会合并为一次,只有当更新结束后 `state` 才会改变,三次调用等同于如下代码 122 | 123 | ```js 124 | Object.assign( 125 | {}, 126 | { count: this.state.count + 1 }, 127 | { count: this.state.count + 1 }, 128 | { count: this.state.count + 1 }, 129 | ) 130 | ``` 131 | 132 | 当然你也可以通过以下方式来实现调用三次 `setState` 使得 `count` 为 3 133 | 134 | ```js 135 | handle() { 136 | this.setState((prevState) => ({ count: prevState.count + 1 })) 137 | this.setState((prevState) => ({ count: prevState.count + 1 })) 138 | this.setState((prevState) => ({ count: prevState.count + 1 })) 139 | } 140 | ``` 141 | 142 | 如果你想在每次调用 `setState` 后获得正确的 `state` ,可以通过如下代码实现 143 | 144 | ```js 145 | handle() { 146 | this.setState((prevState) => ({ count: prevState.count + 1 }), () => { 147 | console.log(this.state) 148 | }) 149 | } 150 | ``` 151 | 152 | # Redux 源码分析 153 | 154 | 首先让我们来看下 `combineReducers` 函数 155 | 156 | ```js 157 | // 传入一个 object 158 | export default function combineReducers(reducers) { 159 | // 获取该 Object 的 key 值 160 | const reducerKeys = Object.keys(reducers) 161 | // 过滤后的 reducers 162 | const finalReducers = {} 163 | // 获取每一个 key 对应的 value 164 | // 在开发环境下判断值是否为 undefined 165 | // 然后将值类型是函数的值放入 finalReducers 166 | for (let i = 0; i < reducerKeys.length; i++) { 167 | const key = reducerKeys[i] 168 | 169 | if (process.env.NODE_ENV !== 'production') { 170 | if (typeof reducers[key] === 'undefined') { 171 | warning(`No reducer provided for key "${key}"`) 172 | } 173 | } 174 | 175 | if (typeof reducers[key] === 'function') { 176 | finalReducers[key] = reducers[key] 177 | } 178 | } 179 | // 拿到过滤后的 reducers 的 key 值 180 | const finalReducerKeys = Object.keys(finalReducers) 181 | 182 | // 在开发环境下判断,保存不期望 key 的缓存用以下面做警告 183 | let unexpectedKeyCache 184 | if (process.env.NODE_ENV !== 'production') { 185 | unexpectedKeyCache = {} 186 | } 187 | 188 | let shapeAssertionError 189 | try { 190 | // 该函数解析在下面 191 | assertReducerShape(finalReducers) 192 | } catch (e) { 193 | shapeAssertionError = e 194 | } 195 | // combineReducers 函数返回一个函数,也就是合并后的 reducer 函数 196 | // 该函数返回总的 state 197 | // 并且你也可以发现这里使用了闭包,函数里面使用到了外面的一些属性 198 | return function combination(state = {}, action) { 199 | if (shapeAssertionError) { 200 | throw shapeAssertionError 201 | } 202 | // 该函数解析在下面 203 | if (process.env.NODE_ENV !== 'production') { 204 | const warningMessage = getUnexpectedStateShapeWarningMessage( 205 | state, 206 | finalReducers, 207 | action, 208 | unexpectedKeyCache 209 | ) 210 | if (warningMessage) { 211 | warning(warningMessage) 212 | } 213 | } 214 | // state 是否改变 215 | let hasChanged = false 216 | // 改变后的 state 217 | const nextState = {} 218 | for (let i = 0; i < finalReducerKeys.length; i++) { 219 | // 拿到相应的 key 220 | const key = finalReducerKeys[i] 221 | // 获得 key 对应的 reducer 函数 222 | const reducer = finalReducers[key] 223 | // state 树下的 key 是与 finalReducers 下的 key 相同的 224 | // 所以你在 combineReducers 中传入的参数的 key 即代表了 各个 reducer 也代表了各个 state 225 | const previousStateForKey = state[key] 226 | // 然后执行 reducer 函数获得该 key 值对应的 state 227 | const nextStateForKey = reducer(previousStateForKey, action) 228 | // 判断 state 的值,undefined 的话就报错 229 | if (typeof nextStateForKey === 'undefined') { 230 | const errorMessage = getUndefinedStateErrorMessage(key, action) 231 | throw new Error(errorMessage) 232 | } 233 | // 然后将 value 塞进去 234 | nextState[key] = nextStateForKey 235 | // 如果 state 改变 236 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey 237 | } 238 | // state 只要改变过,就返回新的 state 239 | return hasChanged ? nextState : state 240 | } 241 | } 242 | ``` 243 | 244 | `combineReducers` 函数总的来说很简单,总结来说就是接收一个对象,将参数过滤后返回一个函数。该函数里有一个过滤参数后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。 245 | 246 | 接下来让我们来看看 combinrReducers 中用到的两个函数 247 | 248 | ```js 249 | // 这是执行的第一个用于抛错的函数 250 | function assertReducerShape(reducers) { 251 | // 将 combineReducers 中的参数遍历 252 | Object.keys(reducers).forEach(key => { 253 | const reducer = reducers[key] 254 | // 给他传入一个 action 255 | const initialState = reducer(undefined, { type: ActionTypes.INIT }) 256 | // 如果得到的 state 为 undefined 就抛错 257 | if (typeof initialState === 'undefined') { 258 | throw new Error( 259 | `Reducer "${key}" returned undefined during initialization. ` + 260 | `If the state passed to the reducer is undefined, you must ` + 261 | `explicitly return the initial state. The initial state may ` + 262 | `not be undefined. If you don't want to set a value for this reducer, ` + 263 | `you can use null instead of undefined.` 264 | ) 265 | } 266 | // 再过滤一次,考虑到万一你在 reducer 中给 ActionTypes.INIT 返回了值 267 | // 传入一个随机的 action 判断值是否为 undefined 268 | const type = 269 | '@@redux/PROBE_UNKNOWN_ACTION_' + 270 | Math.random() 271 | .toString(36) 272 | .substring(7) 273 | .split('') 274 | .join('.') 275 | if (typeof reducer(undefined, { type }) === 'undefined') { 276 | throw new Error( 277 | `Reducer "${key}" returned undefined when probed with a random type. ` + 278 | `Don't try to handle ${ 279 | ActionTypes.INIT 280 | } or other actions in "redux/*" ` + 281 | `namespace. They are considered private. Instead, you must return the ` + 282 | `current state for any unknown actions, unless it is undefined, ` + 283 | `in which case you must return the initial state, regardless of the ` + 284 | `action type. The initial state may not be undefined, but can be null.` 285 | ) 286 | } 287 | }) 288 | } 289 | 290 | function getUnexpectedStateShapeWarningMessage( 291 | inputState, 292 | reducers, 293 | action, 294 | unexpectedKeyCache 295 | ) { 296 | // 这里的 reducers 已经是 finalReducers 297 | const reducerKeys = Object.keys(reducers) 298 | const argumentName = 299 | action && action.type === ActionTypes.INIT 300 | ? 'preloadedState argument passed to createStore' 301 | : 'previous state received by the reducer' 302 | 303 | // 如果 finalReducers 为空 304 | if (reducerKeys.length === 0) { 305 | return ( 306 | 'Store does not have a valid reducer. Make sure the argument passed ' + 307 | 'to combineReducers is an object whose values are reducers.' 308 | ) 309 | } 310 | // 如果你传入的 state 不是对象 311 | if (!isPlainObject(inputState)) { 312 | return ( 313 | `The ${argumentName} has unexpected type of "` + 314 | {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + 315 | `". Expected argument to be an object with the following ` + 316 | `keys: "${reducerKeys.join('", "')}"` 317 | ) 318 | } 319 | // 将参入的 state 于 finalReducers 下的 key 做比较,过滤出多余的 key 320 | const unexpectedKeys = Object.keys(inputState).filter( 321 | key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] 322 | ) 323 | 324 | unexpectedKeys.forEach(key => { 325 | unexpectedKeyCache[key] = true 326 | }) 327 | 328 | if (action && action.type === ActionTypes.REPLACE) return 329 | 330 | // 如果 unexpectedKeys 有值的话 331 | if (unexpectedKeys.length > 0) { 332 | return ( 333 | `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + 334 | `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + 335 | `Expected to find one of the known reducer keys instead: ` + 336 | `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` 337 | ) 338 | } 339 | } 340 | ``` 341 | 342 | 接下来让我们先来看看 `compose` 函数 343 | 344 | ```js 345 | // 这个函数设计的很巧妙,通过传入函数引用的方式让我们完成多个函数的嵌套使用,术语叫做高阶函数 346 | // 通过使用 reduce 函数做到从右至左调用函数 347 | // 对于上面项目中的例子 348 | compose( 349 | applyMiddleware(thunkMiddleware), 350 | window.devToolsExtension ? window.devToolsExtension() : f => f 351 | ) 352 | // 经过 compose 函数变成了 applyMiddleware(thunkMiddleware)(window.devToolsExtension()()) 353 | // 所以在找不到 window.devToolsExtension 时你应该返回一个函数 354 | export default function compose(...funcs) { 355 | if (funcs.length === 0) { 356 | return arg => arg 357 | } 358 | 359 | if (funcs.length === 1) { 360 | return funcs[0] 361 | } 362 | 363 | return funcs.reduce((a, b) => (...args) => a(b(...args))) 364 | } 365 | ``` 366 | 367 | 然后我们来解析 `createStore` 函数的部分代码 368 | 369 | ```js 370 | export default function createStore(reducer, preloadedState, enhancer) { 371 | // 一般 preloadedState 用的少,判断类型,如果第二个参数是函数且没有第三个参数,就调换位置 372 | if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { 373 | enhancer = preloadedState 374 | preloadedState = undefined 375 | } 376 | // 判断 enhancer 是否是函数 377 | if (typeof enhancer !== 'undefined') { 378 | if (typeof enhancer !== 'function') { 379 | throw new Error('Expected the enhancer to be a function.') 380 | } 381 | // 类型没错的话,先执行 enhancer,然后再执行 createStore 函数 382 | return enhancer(createStore)(reducer, preloadedState) 383 | } 384 | // 判断 reducer 是否是函数 385 | if (typeof reducer !== 'function') { 386 | throw new Error('Expected the reducer to be a function.') 387 | } 388 | // 当前 reducer 389 | let currentReducer = reducer 390 | // 当前状态 391 | let currentState = preloadedState 392 | // 当前监听函数数组 393 | let currentListeners = [] 394 | // 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证 currentListeners 数组不变 395 | // 可以考虑下只存在 currentListeners 的情况,如果我在某个 subscribe 中再次执行 subscribe 396 | // 或者 unsubscribe,这样会导致当前的 currentListeners 数组大小发生改变,从而可能导致 397 | // 索引出错 398 | let nextListeners = currentListeners 399 | // reducer 是否正在执行 400 | let isDispatching = false 401 | // 如果 currentListeners 和 nextListeners 相同,就赋值回去 402 | function ensureCanMutateNextListeners() { 403 | if (nextListeners === currentListeners) { 404 | nextListeners = currentListeners.slice() 405 | } 406 | } 407 | // ...... 408 | } 409 | ``` 410 | 411 | 接下来先来介绍 `applyMiddleware` 函数 412 | 413 | 在这之前我需要先来介绍一下函数柯里化,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 414 | 415 | ```js 416 | function add(a,b) { return a + b } 417 | add(1, 2) => 3 418 | // 对于以上函数如果使用柯里化可以这样改造 419 | function add(a) { 420 | return b => { 421 | return a + b 422 | } 423 | } 424 | add(1)(2) => 3 425 | // 你可以这样理解函数柯里化,通过闭包保存了外部的一个变量,然后返回一个接收参数的函数,在该函数中使用了保存的变量,然后再返回值。 426 | ``` 427 | 428 | ```js 429 | // 这个函数应该是整个源码中最难理解的一块了 430 | // 该函数返回一个柯里化的函数 431 | // 所以调用这个函数应该这样写 applyMiddleware(...middlewares)(createStore)(...args) 432 | export default function applyMiddleware(...middlewares) { 433 | return createStore => (...args) => { 434 | // 这里执行 createStore 函数,把 applyMiddleware 函数最后次调用的参数传进来 435 | const store = createStore(...args) 436 | let dispatch = () => { 437 | throw new Error( 438 | `Dispatching while constructing your middleware is not allowed. ` + 439 | `Other middleware would not be applied to this dispatch.` 440 | ) 441 | } 442 | let chain = [] 443 | // 每个中间件都应该有这两个函数 444 | const middlewareAPI = { 445 | getState: store.getState, 446 | dispatch: (...args) => dispatch(...args) 447 | } 448 | // 把 middlewares 中的每个中间件都传入 middlewareAPI 449 | chain = middlewares.map(middleware => middleware(middlewareAPI)) 450 | // 和之前一样,从右至左调用每个中间件,然后传入 store.dispatch 451 | dispatch = compose(...chain)(store.dispatch) 452 | // 这里只看这部分代码有点抽象,我这里放入 redux-thunk 的代码来结合分析 453 | // createThunkMiddleware返回了3层函数,第一层函数接收 middlewareAPI 参数 454 | // 第二次函数接收 store.dispatch 455 | // 第三层函数接收 dispatch 中的参数 456 | {function createThunkMiddleware(extraArgument) { 457 | return ({ dispatch, getState }) => next => action => { 458 | // 判断 dispatch 中的参数是否为函数 459 | if (typeof action === 'function') { 460 | // 是函数的话再把这些参数传进去,直到 action 不为函数,执行 dispatch({tyep: 'XXX'}) 461 | return action(dispatch, getState, extraArgument); 462 | } 463 | 464 | return next(action); 465 | }; 466 | } 467 | const thunk = createThunkMiddleware(); 468 | 469 | export default thunk;} 470 | // 最后把经过中间件加强后的 dispatch 于剩余 store 中的属性返回,这样你的 dispatch 471 | return { 472 | ...store, 473 | dispatch 474 | } 475 | } 476 | } 477 | ``` 478 | 479 | 好了,我们现在将困难的部分都攻克了,来看一些简单的代码 480 | 481 | ```js 482 | // 这个没啥好说的,就是把当前的 state 返回,但是当正在执行 reducer 时不能执行该方法 483 | function getState() { 484 | if (isDispatching) { 485 | throw new Error( 486 | 'You may not call store.getState() while the reducer is executing. ' + 487 | 'The reducer has already received the state as an argument. ' + 488 | 'Pass it down from the top reducer instead of reading it from the store.' 489 | ) 490 | } 491 | 492 | return currentState 493 | } 494 | // 接收一个函数参数 495 | function subscribe(listener) { 496 | if (typeof listener !== 'function') { 497 | throw new Error('Expected listener to be a function.') 498 | } 499 | // 这部分最主要的设计 nextListeners 已经讲过,其他基本没什么好说的 500 | if (isDispatching) { 501 | throw new Error( 502 | 'You may not call store.subscribe() while the reducer is executing. ' + 503 | 'If you would like to be notified after the store has been updated, subscribe from a ' + 504 | 'component and invoke store.getState() in the callback to access the latest state. ' + 505 | 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.' 506 | ) 507 | } 508 | 509 | let isSubscribed = true 510 | 511 | ensureCanMutateNextListeners() 512 | nextListeners.push(listener) 513 | 514 | // 返回一个取消订阅函数 515 | return function unsubscribe() { 516 | if (!isSubscribed) { 517 | return 518 | } 519 | 520 | if (isDispatching) { 521 | throw new Error( 522 | 'You may not unsubscribe from a store listener while the reducer is executing. ' + 523 | 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.' 524 | ) 525 | } 526 | 527 | isSubscribed = false 528 | 529 | ensureCanMutateNextListeners() 530 | const index = nextListeners.indexOf(listener) 531 | nextListeners.splice(index, 1) 532 | } 533 | } 534 | 535 | function dispatch(action) { 536 | // 原生的 dispatch 会判断 action 是否为对象 537 | if (!isPlainObject(action)) { 538 | throw new Error( 539 | 'Actions must be plain objects. ' + 540 | 'Use custom middleware for async actions.' 541 | ) 542 | } 543 | 544 | if (typeof action.type === 'undefined') { 545 | throw new Error( 546 | 'Actions may not have an undefined "type" property. ' + 547 | 'Have you misspelled a constant?' 548 | ) 549 | } 550 | // 注意在 Reducers 中是不能执行 dispatch 函数的 551 | // 因为你一旦在 reducer 函数中执行 dispatch,会引发死循环 552 | if (isDispatching) { 553 | throw new Error('Reducers may not dispatch actions.') 554 | } 555 | // 执行 combineReducers 组合后的函数 556 | try { 557 | isDispatching = true 558 | currentState = currentReducer(currentState, action) 559 | } finally { 560 | isDispatching = false 561 | } 562 | // 然后遍历 currentListeners,执行数组中保存的函数 563 | const listeners = (currentListeners = nextListeners) 564 | for (let i = 0; i < listeners.length; i++) { 565 | const listener = listeners[i] 566 | listener() 567 | } 568 | 569 | return action 570 | } 571 | // 然后在 createStore 末尾会发起一个 action dispatch({ type: ActionTypes.INIT }); 572 | // 用以初始化 state 573 | ``` 574 | 575 | 576 | -------------------------------------------------------------------------------- /Framework/framework-zh.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [MVVM](#mvvm) 6 | - [脏数据检测](#%E8%84%8F%E6%95%B0%E6%8D%AE%E6%A3%80%E6%B5%8B) 7 | - [数据劫持](#%E6%95%B0%E6%8D%AE%E5%8A%AB%E6%8C%81) 8 | - [Proxy 与 Object.defineProperty 对比](#proxy-%E4%B8%8E-objectdefineproperty-%E5%AF%B9%E6%AF%94) 9 | - [路由原理](#%E8%B7%AF%E7%94%B1%E5%8E%9F%E7%90%86) 10 | - [Virtual Dom](#virtual-dom) 11 | - [为什么需要 Virtual Dom](#%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-virtual-dom) 12 | - [Virtual Dom 算法简述](#virtual-dom-%E7%AE%97%E6%B3%95%E7%AE%80%E8%BF%B0) 13 | - [Virtual Dom 算法实现](#virtual-dom-%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0) 14 | - [树的递归](#%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92) 15 | - [判断属性的更改](#%E5%88%A4%E6%96%AD%E5%B1%9E%E6%80%A7%E7%9A%84%E6%9B%B4%E6%94%B9) 16 | - [判断列表差异算法实现](#%E5%88%A4%E6%96%AD%E5%88%97%E8%A1%A8%E5%B7%AE%E5%BC%82%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0) 17 | - [遍历子元素打标识](#%E9%81%8D%E5%8E%86%E5%AD%90%E5%85%83%E7%B4%A0%E6%89%93%E6%A0%87%E8%AF%86) 18 | - [渲染差异](#%E6%B8%B2%E6%9F%93%E5%B7%AE%E5%BC%82) 19 | - [最后](#%E6%9C%80%E5%90%8E) 20 | 21 | 22 | 23 | # MVVM 24 | 25 | MVVM 由以下三个内容组成 26 | 27 | - View:界面 28 | - Model:数据模型 29 | - ViewModel:作为桥梁负责沟通 View 和 Model 30 | 31 | 在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合。 32 | 33 | 在 MVVM 中,UI 是通过数据驱动的,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。 34 | 35 | 在 MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持。 36 | 37 | ## 脏数据检测 38 | 39 | 当触发了指定事件后会进入脏数据检测,这时会调用 `$digest` 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 `$watch` 函数,然后再次调用 `$digest` 循环直到发现没有变化。循环至少为二次 ,至多为十次。 40 | 41 | 脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数。所以低效也是相对的,这就仁者见仁智者见智了。 42 | 43 | ## 数据劫持 44 | 45 | Vue 内部使用了 `Object.defineProperty()` 来实现双向绑定,通过这个函数可以监听到 `set` 和 `get` 的事件。 46 | 47 | ```js 48 | var data = { name: 'yck' } 49 | observe(data) 50 | let name = data.name // -> get value 51 | data.name = 'yyy' // -> change value 52 | 53 | function observe(obj) { 54 | // 判断类型 55 | if (!obj || typeof obj !== 'object') { 56 | return 57 | } 58 | Object.keys(obj).forEach(key => { 59 | defineReactive(obj, key, obj[key]) 60 | }) 61 | } 62 | 63 | function defineReactive(obj, key, val) { 64 | // 递归子属性 65 | observe(val) 66 | Object.defineProperty(obj, key, { 67 | enumerable: true, 68 | configurable: true, 69 | get: function reactiveGetter() { 70 | console.log('get value') 71 | return val 72 | }, 73 | set: function reactiveSetter(newVal) { 74 | console.log('change value') 75 | val = newVal 76 | } 77 | }) 78 | } 79 | ``` 80 | 81 | 以上代码简单的实现了如何监听数据的 `set` 和 `get` 的事件,但是仅仅如此是不够的,还需要在适当的时候给属性添加发布订阅 82 | 83 | ```html 84 |
85 | {{name}} 86 |
87 | ``` 88 | ::: v-pre 89 | 在解析如上模板代码时,遇到 `{{name}}` 就会给属性 `name` 添加发布订阅。 90 | ::: 91 | 92 | ```js 93 | // 通过 Dep 解耦 94 | class Dep { 95 | constructor() { 96 | this.subs = [] 97 | } 98 | addSub(sub) { 99 | // sub 是 Watcher 实例 100 | this.subs.push(sub) 101 | } 102 | notify() { 103 | this.subs.forEach(sub => { 104 | sub.update() 105 | }) 106 | } 107 | } 108 | // 全局属性,通过该属性配置 Watcher 109 | Dep.target = null 110 | 111 | function update(value) { 112 | document.querySelector('div').innerText = value 113 | } 114 | 115 | class Watcher { 116 | constructor(obj, key, cb) { 117 | // 将 Dep.target 指向自己 118 | // 然后触发属性的 getter 添加监听 119 | // 最后将 Dep.target 置空 120 | Dep.target = this 121 | this.cb = cb 122 | this.obj = obj 123 | this.key = key 124 | this.value = obj[key] 125 | Dep.target = null 126 | } 127 | update() { 128 | // 获得新值 129 | this.value = this.obj[this.key] 130 | // 调用 update 方法更新 Dom 131 | this.cb(this.value) 132 | } 133 | } 134 | var data = { name: 'yck' } 135 | observe(data) 136 | // 模拟解析到 `{{name}}` 触发的操作 137 | new Watcher(data, 'name', update) 138 | // update Dom innerText 139 | data.name = 'yyy' 140 | ``` 141 | 142 | 接下来,对 `defineReactive` 函数进行改造 143 | 144 | ```js 145 | function defineReactive(obj, key, val) { 146 | // 递归子属性 147 | observe(val) 148 | let dp = new Dep() 149 | Object.defineProperty(obj, key, { 150 | enumerable: true, 151 | configurable: true, 152 | get: function reactiveGetter() { 153 | console.log('get value') 154 | // 将 Watcher 添加到订阅 155 | if (Dep.target) { 156 | dp.addSub(Dep.target) 157 | } 158 | return val 159 | }, 160 | set: function reactiveSetter(newVal) { 161 | console.log('change value') 162 | val = newVal 163 | // 执行 watcher 的 update 方法 164 | dp.notify() 165 | } 166 | }) 167 | } 168 | ``` 169 | 170 | 以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加。 171 | 172 | ## Proxy 与 Object.defineProperty 对比 173 | 174 | `Object.defineProperty` 虽然已经能够实现双向绑定了,但是他还是有缺陷的。 175 | 176 | 1. 只能对属性进行数据劫持,所以需要深度遍历整个对象 177 | 2. 对于数组不能监听到数据的变化 178 | 179 | 虽然 Vue 中确实能检测到数组数据的变化,但是其实是使用了 hack 的办法,并且也是有缺陷的。 180 | 181 | ```js 182 | const arrayProto = Array.prototype 183 | export const arrayMethods = Object.create(arrayProto) 184 | // hack 以下几个函数 185 | const methodsToPatch = [ 186 | 'push', 187 | 'pop', 188 | 'shift', 189 | 'unshift', 190 | 'splice', 191 | 'sort', 192 | 'reverse' 193 | ] 194 | methodsToPatch.forEach(function (method) { 195 | // 获得原生函数 196 | const original = arrayProto[method] 197 | def(arrayMethods, method, function mutator (...args) { 198 | // 调用原生函数 199 | const result = original.apply(this, args) 200 | const ob = this.__ob__ 201 | let inserted 202 | switch (method) { 203 | case 'push': 204 | case 'unshift': 205 | inserted = args 206 | break 207 | case 'splice': 208 | inserted = args.slice(2) 209 | break 210 | } 211 | if (inserted) ob.observeArray(inserted) 212 | // 触发更新 213 | ob.dep.notify() 214 | return result 215 | }) 216 | }) 217 | ``` 218 | 219 | 反观 Proxy 就没以上的问题,原生支持监听数组变化,并且可以直接对整个对象进行拦截,所以 Vue 也将在下个大版本中使用 Proxy 替换 Object.defineProperty 220 | 221 | ```js 222 | let onWatch = (obj, setBind, getLogger) => { 223 | let handler = { 224 | get(target, property, receiver) { 225 | getLogger(target, property) 226 | return Reflect.get(target, property, receiver); 227 | }, 228 | set(target, property, value, receiver) { 229 | setBind(value); 230 | return Reflect.set(target, property, value); 231 | } 232 | }; 233 | return new Proxy(obj, handler); 234 | }; 235 | 236 | let obj = { a: 1 } 237 | let value 238 | let p = onWatch(obj, (v) => { 239 | value = v 240 | }, (target, property) => { 241 | console.log(`Get '${property}' = ${target[property]}`); 242 | }) 243 | p.a = 2 // bind `value` to `2` 244 | p.a // -> Get 'a' = 2 245 | ``` 246 | 247 | # 路由原理 248 | 249 | 前端路由实现起来其实很简单,本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式 250 | 251 | - hash 模式 252 | - history 模式 253 | 254 | `www.test.com/#/` 就是 Hash URL,当 `#` 后面的哈希值发生变化时,不会向服务器请求数据,可以通过 `hashchange` 事件来监听到 URL 的变化,从而进行跳转页面。 255 | 256 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042512.png) 257 | 258 | History 模式是 HTML5 新推出的功能,比之 Hash URL 更加美观 259 | 260 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042514.png) 261 | 262 | # Virtual Dom 263 | 264 | [代码地址](https://github.com/KieSun/My-wheels/tree/master/Virtual%20Dom) 265 | 266 | ## 为什么需要 Virtual Dom 267 | 268 | 众所周知,操作 DOM 是很耗费性能的一件事情,既然如此,我们可以考虑通过 JS 对象来模拟 DOM 对象,毕竟操作 JS 对象比操作 DOM 省时的多。 269 | 270 | 举个例子 271 | 272 | ```js 273 | // 假设这里模拟一个 ul,其中包含了 5 个 li 274 | [1, 2, 3, 4, 5] 275 | // 这里替换上面的 li 276 | [1, 2, 5, 4] 277 | ``` 278 | 279 | 从上述例子中,我们一眼就可以看出先前的 ul 中的第三个 li 被移除了,四五替换了位置。 280 | 281 | 如果以上操作对应到 DOM 中,那么就是以下代码 282 | 283 | ```js 284 | // 删除第三个 li 285 | ul.childNodes[2].remove() 286 | // 将第四个 li 和第五个交换位置 287 | let fromNode = ul.childNodes[4] 288 | let toNode = node.childNodes[3] 289 | let cloneFromNode = fromNode.cloneNode(true) 290 | let cloenToNode = toNode.cloneNode(true) 291 | ul.replaceChild(cloneFromNode, toNode) 292 | ul.replaceChild(cloenToNode, fromNode) 293 | ``` 294 | 295 | 当然在实际操作中,我们还需要给每个节点一个标识,作为判断是同一个节点的依据。所以这也是 Vue 和 React 中官方推荐列表里的节点使用唯一的 `key` 来保证性能。 296 | 297 | 那么既然 DOM 对象可以通过 JS 对象来模拟,反之也可以通过 JS 对象来渲染出对应的 DOM 298 | 299 | 以下是一个 JS 对象模拟 DOM 对象的简单实现 300 | 301 | ```js 302 | export default class Element { 303 | /** 304 | * @param {String} tag 'div' 305 | * @param {Object} props { class: 'item' } 306 | * @param {Array} children [ Element1, 'text'] 307 | * @param {String} key option 308 | */ 309 | constructor(tag, props, children, key) { 310 | this.tag = tag 311 | this.props = props 312 | if (Array.isArray(children)) { 313 | this.children = children 314 | } else if (isString(children)) { 315 | this.key = children 316 | this.children = null 317 | } 318 | if (key) this.key = key 319 | } 320 | // 渲染 321 | render() { 322 | let root = this._createElement( 323 | this.tag, 324 | this.props, 325 | this.children, 326 | this.key 327 | ) 328 | document.body.appendChild(root) 329 | return root 330 | } 331 | create() { 332 | return this._createElement(this.tag, this.props, this.children, this.key) 333 | } 334 | // 创建节点 335 | _createElement(tag, props, child, key) { 336 | // 通过 tag 创建节点 337 | let el = document.createElement(tag) 338 | // 设置节点属性 339 | for (const key in props) { 340 | if (props.hasOwnProperty(key)) { 341 | const value = props[key] 342 | el.setAttribute(key, value) 343 | } 344 | } 345 | if (key) { 346 | el.setAttribute('key', key) 347 | } 348 | // 递归添加子节点 349 | if (child) { 350 | child.forEach(element => { 351 | let child 352 | if (element instanceof Element) { 353 | child = this._createElement( 354 | element.tag, 355 | element.props, 356 | element.children, 357 | element.key 358 | ) 359 | } else { 360 | child = document.createTextNode(element) 361 | } 362 | el.appendChild(child) 363 | }) 364 | } 365 | return el 366 | } 367 | } 368 | ``` 369 | 370 | ## Virtual Dom 算法简述 371 | 372 | 既然我们已经通过 JS 来模拟实现了 DOM,那么接下来的难点就在于如何判断旧的对象和新的对象之间的差异。 373 | 374 | DOM 是多叉树的结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),这个复杂度肯定是不能接受的。于是 React 团队优化了算法,实现了 O(n) 的复杂度来对比差异。 375 | 376 | 实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动 DOM 元素。 377 | 378 | 所以判断差异的算法就分为了两步 379 | 380 | - 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异 381 | - 一旦节点有子元素,就去判断子元素是否有不同 382 | 383 | ## Virtual Dom 算法实现 384 | 385 | ### 树的递归 386 | 387 | 首先我们来实现树的递归算法,在实现该算法前,先来考虑下两个节点对比会有几种情况 388 | 389 | 1. 新的节点的 `tagName` 或者 `key` 和旧的不同,这种情况代表需要替换旧的节点,并且也不再需要遍历新旧节点的子元素了,因为整个旧节点都被删掉了 390 | 2. 新的节点的 `tagName` 和 `key`(可能都没有)和旧的相同,开始遍历子树 391 | 3. 没有新的节点,那么什么都不用做 392 | 393 | ```js 394 | import { StateEnums, isString, move } from './util' 395 | import Element from './element' 396 | 397 | export default function diff(oldDomTree, newDomTree) { 398 | // 用于记录差异 399 | let pathchs = {} 400 | // 一开始的索引为 0 401 | dfs(oldDomTree, newDomTree, 0, pathchs) 402 | return pathchs 403 | } 404 | 405 | function dfs(oldNode, newNode, index, patches) { 406 | // 用于保存子树的更改 407 | let curPatches = [] 408 | // 需要判断三种情况 409 | // 1.没有新的节点,那么什么都不用做 410 | // 2.新的节点的 tagName 和 `key` 和旧的不同,就替换 411 | // 3.新的节点的 tagName 和 key(可能都没有) 和旧的相同,开始遍历子树 412 | if (!newNode) { 413 | } else if (newNode.tag === oldNode.tag && newNode.key === oldNode.key) { 414 | // 判断属性是否变更 415 | let props = diffProps(oldNode.props, newNode.props) 416 | if (props.length) curPatches.push({ type: StateEnums.ChangeProps, props }) 417 | // 遍历子树 418 | diffChildren(oldNode.children, newNode.children, index, patches) 419 | } else { 420 | // 节点不同,需要替换 421 | curPatches.push({ type: StateEnums.Replace, node: newNode }) 422 | } 423 | 424 | if (curPatches.length) { 425 | if (patches[index]) { 426 | patches[index] = patches[index].concat(curPatches) 427 | } else { 428 | patches[index] = curPatches 429 | } 430 | } 431 | } 432 | ``` 433 | 434 | ### 判断属性的更改 435 | 436 | 判断属性的更改也分三个步骤 437 | 438 | 1. 遍历旧的属性列表,查看每个属性是否还存在于新的属性列表中 439 | 2. 遍历新的属性列表,判断两个列表中都存在的属性的值是否有变化 440 | 3. 在第二步中同时查看是否有属性不存在与旧的属性列列表中 441 | 442 | ```js 443 | function diffProps(oldProps, newProps) { 444 | // 判断 Props 分以下三步骤 445 | // 先遍历 oldProps 查看是否存在删除的属性 446 | // 然后遍历 newProps 查看是否有属性值被修改 447 | // 最后查看是否有属性新增 448 | let change = [] 449 | for (const key in oldProps) { 450 | if (oldProps.hasOwnProperty(key) && !newProps[key]) { 451 | change.push({ 452 | prop: key 453 | }) 454 | } 455 | } 456 | for (const key in newProps) { 457 | if (newProps.hasOwnProperty(key)) { 458 | const prop = newProps[key] 459 | if (oldProps[key] && oldProps[key] !== newProps[key]) { 460 | change.push({ 461 | prop: key, 462 | value: newProps[key] 463 | }) 464 | } else if (!oldProps[key]) { 465 | change.push({ 466 | prop: key, 467 | value: newProps[key] 468 | }) 469 | } 470 | } 471 | } 472 | return change 473 | } 474 | 475 | ``` 476 | 477 | ### 判断列表差异算法实现 478 | 479 | 这个算法是整个 Virtual Dom 中最核心的算法,且让我一一为你道来。 480 | 这里的主要步骤其实和判断属性差异是类似的,也是分为三步 481 | 482 | 1. 遍历旧的节点列表,查看每个节点是否还存在于新的节点列表中 483 | 2. 遍历新的节点列表,判断是否有新的节点 484 | 3. 在第二步中同时判断节点是否有移动 485 | 486 | PS:该算法只对有 `key` 的节点做处理 487 | 488 | ```js 489 | function listDiff(oldList, newList, index, patches) { 490 | // 为了遍历方便,先取出两个 list 的所有 keys 491 | let oldKeys = getKeys(oldList) 492 | let newKeys = getKeys(newList) 493 | let changes = [] 494 | 495 | // 用于保存变更后的节点数据 496 | // 使用该数组保存有以下好处 497 | // 1.可以正确获得被删除节点索引 498 | // 2.交换节点位置只需要操作一遍 DOM 499 | // 3.用于 `diffChildren` 函数中的判断,只需要遍历 500 | // 两个树中都存在的节点,而对于新增或者删除的节点来说,完全没必要 501 | // 再去判断一遍 502 | let list = [] 503 | oldList && 504 | oldList.forEach(item => { 505 | let key = item.key 506 | if (isString(item)) { 507 | key = item 508 | } 509 | // 寻找新的 children 中是否含有当前节点 510 | // 没有的话需要删除 511 | let index = newKeys.indexOf(key) 512 | if (index === -1) { 513 | list.push(null) 514 | } else list.push(key) 515 | }) 516 | // 遍历变更后的数组 517 | let length = list.length 518 | // 因为删除数组元素是会更改索引的 519 | // 所有从后往前删可以保证索引不变 520 | for (let i = length - 1; i >= 0; i--) { 521 | // 判断当前元素是否为空,为空表示需要删除 522 | if (!list[i]) { 523 | list.splice(i, 1) 524 | changes.push({ 525 | type: StateEnums.Remove, 526 | index: i 527 | }) 528 | } 529 | } 530 | // 遍历新的 list,判断是否有节点新增或移动 531 | // 同时也对 `list` 做节点新增和移动节点的操作 532 | newList && 533 | newList.forEach((item, i) => { 534 | let key = item.key 535 | if (isString(item)) { 536 | key = item 537 | } 538 | // 寻找旧的 children 中是否含有当前节点 539 | let index = list.indexOf(key) 540 | // 没找到代表新节点,需要插入 541 | if (index === -1 || key == null) { 542 | changes.push({ 543 | type: StateEnums.Insert, 544 | node: item, 545 | index: i 546 | }) 547 | list.splice(i, 0, key) 548 | } else { 549 | // 找到了,需要判断是否需要移动 550 | if (index !== i) { 551 | changes.push({ 552 | type: StateEnums.Move, 553 | from: index, 554 | to: i 555 | }) 556 | move(list, index, i) 557 | } 558 | } 559 | }) 560 | return { changes, list } 561 | } 562 | 563 | function getKeys(list) { 564 | let keys = [] 565 | let text 566 | list && 567 | list.forEach(item => { 568 | let key 569 | if (isString(item)) { 570 | key = [item] 571 | } else if (item instanceof Element) { 572 | key = item.key 573 | } 574 | keys.push(key) 575 | }) 576 | return keys 577 | } 578 | ``` 579 | 580 | ### 遍历子元素打标识 581 | 582 | 对于这个函数来说,主要功能就两个 583 | 584 | 1. 判断两个列表差异 585 | 2. 给节点打上标记 586 | 587 | 总体来说,该函数实现的功能很简单 588 | 589 | ```js 590 | function diffChildren(oldChild, newChild, index, patches) { 591 | let { changes, list } = listDiff(oldChild, newChild, index, patches) 592 | if (changes.length) { 593 | if (patches[index]) { 594 | patches[index] = patches[index].concat(changes) 595 | } else { 596 | patches[index] = changes 597 | } 598 | } 599 | // 记录上一个遍历过的节点 600 | let last = null 601 | oldChild && 602 | oldChild.forEach((item, i) => { 603 | let child = item && item.children 604 | if (child) { 605 | index = 606 | last && last.children ? index + last.children.length + 1 : index + 1 607 | let keyIndex = list.indexOf(item.key) 608 | let node = newChild[keyIndex] 609 | // 只遍历新旧中都存在的节点,其他新增或者删除的没必要遍历 610 | if (node) { 611 | dfs(item, node, index, patches) 612 | } 613 | } else index += 1 614 | last = item 615 | }) 616 | } 617 | ``` 618 | 619 | ### 渲染差异 620 | 621 | 通过之前的算法,我们已经可以得出两个树的差异了。既然知道了差异,就需要局部去更新 DOM 了,下面就让我们来看看 Virtual Dom 算法的最后一步骤 622 | 623 | 这个函数主要两个功能 624 | 625 | 1. 深度遍历树,将需要做变更操作的取出来 626 | 2. 局部更新 DOM 627 | 628 | 整体来说这部分代码还是很好理解的 629 | 630 | ```js 631 | let index = 0 632 | export default function patch(node, patchs) { 633 | let changes = patchs[index] 634 | let childNodes = node && node.childNodes 635 | // 这里的深度遍历和 diff 中是一样的 636 | if (!childNodes) index += 1 637 | if (changes && changes.length && patchs[index]) { 638 | changeDom(node, changes) 639 | } 640 | let last = null 641 | if (childNodes && childNodes.length) { 642 | childNodes.forEach((item, i) => { 643 | index = 644 | last && last.children ? index + last.children.length + 1 : index + 1 645 | patch(item, patchs) 646 | last = item 647 | }) 648 | } 649 | } 650 | 651 | function changeDom(node, changes, noChild) { 652 | changes && 653 | changes.forEach(change => { 654 | let { type } = change 655 | switch (type) { 656 | case StateEnums.ChangeProps: 657 | let { props } = change 658 | props.forEach(item => { 659 | if (item.value) { 660 | node.setAttribute(item.prop, item.value) 661 | } else { 662 | node.removeAttribute(item.prop) 663 | } 664 | }) 665 | break 666 | case StateEnums.Remove: 667 | node.childNodes[change.index].remove() 668 | break 669 | case StateEnums.Insert: 670 | let dom 671 | if (isString(change.node)) { 672 | dom = document.createTextNode(change.node) 673 | } else if (change.node instanceof Element) { 674 | dom = change.node.create() 675 | } 676 | node.insertBefore(dom, node.childNodes[change.index]) 677 | break 678 | case StateEnums.Replace: 679 | node.parentNode.replaceChild(change.node.create(), node) 680 | break 681 | case StateEnums.Move: 682 | let fromNode = node.childNodes[change.from] 683 | let toNode = node.childNodes[change.to] 684 | let cloneFromNode = fromNode.cloneNode(true) 685 | let cloenToNode = toNode.cloneNode(true) 686 | node.replaceChild(cloneFromNode, toNode) 687 | node.replaceChild(cloenToNode, fromNode) 688 | break 689 | default: 690 | break 691 | } 692 | }) 693 | } 694 | ``` 695 | 696 | ## 最后 697 | 698 | Virtual Dom 算法的实现也就是以下三步 699 | 700 | 1. 通过 JS 来模拟创建 DOM 对象 701 | 2. 判断两个对象的差异 702 | 3. 渲染差异 703 | 704 | ```js 705 | let test4 = new Element('div', { class: 'my-div' }, ['test4']) 706 | let test5 = new Element('ul', { class: 'my-div' }, ['test5']) 707 | 708 | let test1 = new Element('div', { class: 'my-div' }, [test4]) 709 | 710 | let test2 = new Element('div', { id: '11' }, [test5, test4]) 711 | 712 | let root = test1.render() 713 | 714 | let pathchs = diff(test1, test2) 715 | console.log(pathchs) 716 | 717 | setTimeout(() => { 718 | console.log('开始更新') 719 | patch(root, pathchs) 720 | console.log('结束更新') 721 | }, 1000) 722 | ``` 723 | 724 | 当然目前的实现还略显粗糙,但是对于理解 Virtual Dom 算法来说已经是完全足够的了。 -------------------------------------------------------------------------------- /Framework/react-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [React Lifecycle analysis](#react-lifecycle-analysis) 6 | - [The usage advice of Lifecycle methods in React V16](#the-usage-advice-of--lifecycle-methods-in-react-v16) 7 | - [setState](#setstate) 8 | - [Redux Source Code Analysis](#redux-source-code-analysis) 9 | 10 | 11 | 12 | # React Lifecycle analysis 13 | 14 | The Fiber mechanism was introduced in the V16 release. The mechanism affects some of the lifecycle calls to a certain extent and introduces two new APIs to solve the problems. 15 | 16 | In previous versions, if you had a very complex composite component and then changed the `state` of the topmost component, the call stack might be long. 17 | 18 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042525.png) 19 | 20 | If the call stack is too long, and complicated operations are performed in the middle, it may cause the main thread to be blocked for a long time, resulting in a bad user experience. Fiber is born to solve this problem. 21 | 22 | Fiber is essentially a virtual stack frame, and the new scheduler freely schedules these frames according to their priority, thereby changing the previous synchronous rendering to asynchronous rendering, and segmenting the update without affecting the experience. 23 | 24 | ![](https://yck-1254263422.cos.ap-shanghai.myqcloud.com/blog/2019-06-01-042526.png) 25 | 26 | React has its own set of logic on how to prioritize. For things that require high real-time performance, such as animation, which means it must be rendered once within 16 ms to ensure that it is not stuck, React pauses the update every 16 ms (within 16ms) and returns to continue rendering the animation. 27 | 28 | For asynchronous rendering, there are now two stages of rendering: `reconciliation` and `commit`. The former process can be interrupted, while the latter cannot be suspended, and the interface will be updated until it is completed. 29 | 30 | **Reconciliation** stage 31 | 32 | - `componentWillMount` 33 | - `componentWillReceiveProps` 34 | - `shouldComponentUpdate` 35 | - `componentWillUpdate` 36 | 37 | **Commit** stage 38 | 39 | - `componentDidMount` 40 | - `componentDidUpdate` 41 | - `componentWillUnmount` 42 | 43 | Because the `reconciliation` phase can be interrupted, the lifecycle functions that will be executed in the `reconciliation` phase may be called multiple times, which may cause bugs. So for these functions, except for `shouldComponentUpdate`, should be avoided as much as possible, and a new API is introduced in V16 to solve this problem. 44 | 45 | `getDerivedStateFromProps` is used to replace `componentWillReceiveProps` , which is called during initialization and update 46 | 47 | ```js 48 | class ExampleComponent extends React.Component { 49 | // Initialize state in constructor, 50 | // Or with a property initializer. 51 | state = {}; 52 | 53 | static getDerivedStateFromProps(nextProps, prevState) { 54 | if (prevState.someMirroredValue !== nextProps.someValue) { 55 | return { 56 | derivedData: computeDerivedState(nextProps), 57 | someMirroredValue: nextProps.someValue 58 | }; 59 | } 60 | 61 | // Return null to indicate no change to state. 62 | return null; 63 | } 64 | } 65 | ``` 66 | 67 | `getSnapshotBeforeUpdate` is used to replace `componentWillUpdate`, which is called after the `update` but before the DOM update to read the latest DOM data. 68 | 69 | ## The usage advice of Lifecycle methods in React V16 70 | 71 | ```js 72 | class ExampleComponent extends React.Component { 73 | // Used to initialize the state 74 | constructor() {} 75 | // Used to replace `componentWillReceiveProps` , which will be called when initializing and `update` 76 | // Because the function is static, you can't get `this` 77 | // If need to compare `prevProps`, you need to maintain it separately in `state` 78 | static getDerivedStateFromProps(nextProps, prevState) {} 79 | // Determine whether you need to update components, mostly for component performance optimization 80 | shouldComponentUpdate(nextProps, nextState) {} 81 | // Called after the component is mounted 82 | // Can request or subscribe in this function 83 | componentDidMount() {} 84 | // Used to get the latest DOM data 85 | getSnapshotBeforeUpdate() {} 86 | // Component is about to be destroyed 87 | // Can remove subscriptions, timers, etc. here 88 | componentWillUnmount() {} 89 | // Called after the component is destroyed 90 | componentDidUnMount() {} 91 | // Called after component update 92 | componentDidUpdate() {} 93 | // render component 94 | render() {} 95 | // The following functions are not recommended 96 | UNSAFE_componentWillMount() {} 97 | UNSAFE_componentWillUpdate(nextProps, nextState) {} 98 | UNSAFE_componentWillReceiveProps(nextProps) {} 99 | } 100 | ``` 101 | 102 | # setState 103 | 104 | `setState` is an API that is often used in React, but it has some problems that can lead to mistakes. The core reason is that the API is asynchronous. 105 | 106 | First, calling `setState` does not immediately cause a change to `state`, and if you call multiple `setState` at a time, the result may not be as you expect. 107 | 108 | ```js 109 | handle() { 110 | // Initialize `count` to 0 111 | console.log(this.state.count) // -> 0 112 | this.setState({ count: this.state.count + 1 }) 113 | this.setState({ count: this.state.count + 1 }) 114 | this.setState({ count: this.state.count + 1 }) 115 | console.log(this.state.count) // -> 0 116 | } 117 | ``` 118 | 119 | First, both prints are 0, because `setState` is an asynchronous API and will only execute after the sync code has finished running. The reason for `setState` is asynchronous is that `setState` may cause repainting of the DOM. If the call is repainted immediately after the call, the call will cause unnecessary performance loss. Designed to be asynchronous, you can put multiple calls into a queue and unify the update process when appropriate. 120 | 121 | Second, although `setState` is called three times, the value of `count` is still 1. Because multiple calls are merged into one, only `state` will change when the update ends, and three calls are equivalent to the following code. 122 | 123 | ```js 124 | Object.assign( 125 | {}, 126 | { count: this.state.count + 1 }, 127 | { count: this.state.count + 1 }, 128 | { count: this.state.count + 1 }, 129 | ) 130 | ``` 131 | 132 | Of course, you can also call `setState` three times by the following way to make `count` 3 133 | 134 | ```js 135 | handle() { 136 | this.setState((prevState) => ({ count: prevState.count + 1 })) 137 | this.setState((prevState) => ({ count: prevState.count + 1 })) 138 | this.setState((prevState) => ({ count: prevState.count + 1 })) 139 | } 140 | ``` 141 | 142 | If you want to get the correct `state` after each call to `setState`, you can do it with the following code: 143 | 144 | ```js 145 | handle() { 146 | this.setState((prevState) => ({ count: prevState.count + 1 }), () => { 147 | console.log(this.state) 148 | }) 149 | } 150 | ``` 151 | # Redux Source Code Analysis 152 | 153 | Let's take a look at the `combineReducers` function first. 154 | 155 | ```js 156 | // pass an object 157 | export default function combineReducers(reducers) { 158 | // get this object's keys 159 | const reducerKeys = Object.keys(reducers) 160 | // reducers after filtering 161 | const finalReducers = {} 162 | // get the values corresponding to every key 163 | // in dev environment, check if the value is undefined 164 | // then put function type values into finalReducers 165 | for (let i = 0; i < reducerKeys.length; i++) { 166 | const key = reducerKeys[i] 167 | 168 | if (process.env.NODE_ENV !== 'production') { 169 | if (typeof reducers[key] === 'undefined') { 170 | warning(`No reducer provided for key "${key}"`) 171 | } 172 | } 173 | 174 | if (typeof reducers[key] === 'function') { 175 | finalReducers[key] = reducers[key] 176 | } 177 | } 178 | // get the keys of the reducers after filtering 179 | const finalReducerKeys = Object.keys(finalReducers) 180 | 181 | // in dev environment check and save unexpected key to cache for warnings later 182 | let unexpectedKeyCache 183 | if (process.env.NODE_ENV !== 'production') { 184 | unexpectedKeyCache = {} 185 | } 186 | 187 | let shapeAssertionError 188 | try { 189 | // explanations of the function is below 190 | assertReducerShape(finalReducers) 191 | } catch (e) { 192 | shapeAssertionError = e 193 | } 194 | // combineReducers returns another function, which is reducer after merging 195 | // this function returns the root state 196 | // also notice a closure is used here. The function uses some outside properties 197 | return function combination(state = {}, action) { 198 | if (shapeAssertionError) { 199 | throw shapeAssertionError 200 | } 201 | // explanations of the function is below 202 | if (process.env.NODE_ENV !== 'production') { 203 | const warningMessage = getUnexpectedStateShapeWarningMessage( 204 | state, 205 | finalReducers, 206 | action, 207 | unexpectedKeyCache 208 | ) 209 | if (warningMessage) { 210 | warning(warningMessage) 211 | } 212 | } 213 | // if state changed 214 | let hasChanged = false 215 | // state after changes 216 | const nextState = {} 217 | for (let i = 0; i < finalReducerKeys.length; i++) { 218 | // get the key with index 219 | const key = finalReducerKeys[i] 220 | // get the corresponding reducer function with key 221 | const reducer = finalReducers[key] 222 | // the key in state tree is the same as the key in finalReducers 223 | // so the key of the parameter passed to combineReducers represents each reducer as well as each state 224 | const previousStateForKey = state[key] 225 | // execute reducer function to get the state corresponding to the key 226 | const nextStateForKey = reducer(previousStateForKey, action) 227 | // check the value of state, report error if it's undefined 228 | if (typeof nextStateForKey === 'undefined') { 229 | const errorMessage = getUndefinedStateErrorMessage(key, action) 230 | throw new Error(errorMessage) 231 | } 232 | // put the value into nextState 233 | nextState[key] = nextStateForKey 234 | // if state changed 235 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey 236 | } 237 | // as long as state changed, return the new state 238 | return hasChanged ? nextState : state 239 | } 240 | } 241 | ``` 242 | 243 | `combineReducers` is simple in general. In short, it accepts an object and return a function after processing the parameters. This function has an object finalReducers that stores the processed parameters. The object is then itereated on, each reducer function in it is executed, and the new state is returned. 244 | 245 | Let's then take a look at the two functions used in combineReducers. 246 | 247 | ```js 248 | // the first function used to throw errors 249 | function assertReducerShape(reducers) { 250 | // iterate on the parameters in combineReducers 251 | Object.keys(reducers).forEach(key => { 252 | const reducer = reducers[key] 253 | // pass an action 254 | const initialState = reducer(undefined, { type: ActionTypes.INIT }) 255 | // throw an error if the state is undefined 256 | if (typeof initialState === 'undefined') { 257 | throw new Error( 258 | `Reducer "${key}" returned undefined during initialization. ` + 259 | `If the state passed to the reducer is undefined, you must ` + 260 | `explicitly return the initial state. The initial state may ` + 261 | `not be undefined. If you don't want to set a value for this reducer, ` + 262 | `you can use null instead of undefined.` 263 | ) 264 | } 265 | // process again, considering the case that the user returned a value for ActionTypes.INIT in the reducer 266 | // pass a random action and check if the value is undefined 267 | const type = 268 | '@@redux/PROBE_UNKNOWN_ACTION_' + 269 | Math.random() 270 | .toString(36) 271 | .substring(7) 272 | .split('') 273 | .join('.') 274 | if (typeof reducer(undefined, { type }) === 'undefined') { 275 | throw new Error( 276 | `Reducer "${key}" returned undefined when probed with a random type. ` + 277 | `Don't try to handle ${ 278 | ActionTypes.INIT 279 | } or other actions in "redux/*" ` + 280 | `namespace. They are considered private. Instead, you must return the ` + 281 | `current state for any unknown actions, unless it is undefined, ` + 282 | `in which case you must return the initial state, regardless of the ` + 283 | `action type. The initial state may not be undefined, but can be null.` 284 | ) 285 | } 286 | }) 287 | } 288 | 289 | function getUnexpectedStateShapeWarningMessage( 290 | inputState, 291 | reducers, 292 | action, 293 | unexpectedKeyCache 294 | ) { 295 | // here the reducers is already finalReducers 296 | const reducerKeys = Object.keys(reducers) 297 | const argumentName = 298 | action && action.type === ActionTypes.INIT 299 | ? 'preloadedState argument passed to createStore' 300 | : 'previous state received by the reducer' 301 | 302 | // if finalReducers is empty 303 | if (reducerKeys.length === 0) { 304 | return ( 305 | 'Store does not have a valid reducer. Make sure the argument passed ' + 306 | 'to combineReducers is an object whose values are reducers.' 307 | ) 308 | } 309 | // if the state passed is not an object 310 | if (!isPlainObject(inputState)) { 311 | return ( 312 | `The ${argumentName} has unexpected type of "` + 313 | {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + 314 | `". Expected argument to be an object with the following ` + 315 | `keys: "${reducerKeys.join('", "')}"` 316 | ) 317 | } 318 | // compare the keys of the state and of finalReducers and filter out the extra keys 319 | const unexpectedKeys = Object.keys(inputState).filter( 320 | key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] 321 | ) 322 | 323 | unexpectedKeys.forEach(key => { 324 | unexpectedKeyCache[key] = true 325 | }) 326 | 327 | if (action && action.type === ActionTypes.REPLACE) return 328 | 329 | // if unexpectedKeys is not empty 330 | if (unexpectedKeys.length > 0) { 331 | return ( 332 | `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + 333 | `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + 334 | `Expected to find one of the known reducer keys instead: ` + 335 | `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` 336 | ) 337 | } 338 | } 339 | ``` 340 | 341 | Let's then take a look at `compose` function 342 | 343 | ```js 344 | // This function is quite elegant. It let us stack several functions via passing the references of functions. The term is called Higher-order function. 345 | // call functions from the right to the left with reduce function 346 | // for the example in the project above 347 | compose( 348 | applyMiddleware(thunkMiddleware), 349 | window.devToolsExtension ? window.devToolsExtension() : f => f 350 | ) 351 | // with compose it turns into applyMiddleware(thunkMiddleware)(window.devToolsExtension()()) 352 | // so you should return a function when window.devToolsExtension is not found 353 | export default function compose(...funcs) { 354 | if (funcs.length === 0) { 355 | return arg => arg 356 | } 357 | 358 | if (funcs.length === 1) { 359 | return funcs[0] 360 | } 361 | 362 | return funcs.reduce((a, b) => (...args) => a(b(...args))) 363 | } 364 | ``` 365 | 366 | Let's then analyze part of the source code of `createStore` function 367 | 368 | ```js 369 | export default function createStore(reducer, preloadedState, enhancer) { 370 | // normally preloadedState is rarely used 371 | // check type, is the second parameter is a function and there is no third parameter, then exchange positions 372 | if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { 373 | enhancer = preloadedState 374 | preloadedState = undefined 375 | } 376 | // check if enhancer is a function 377 | if (typeof enhancer !== 'undefined') { 378 | if (typeof enhancer !== 'function') { 379 | throw new Error('Expected the enhancer to be a function.') 380 | } 381 | // if there is no type error, first execute enhancer, then execute createStore 382 | return enhancer(createStore)(reducer, preloadedState) 383 | } 384 | // check if reducer is a function 385 | if (typeof reducer !== 'function') { 386 | throw new Error('Expected the reducer to be a function.') 387 | } 388 | // current reducer 389 | let currentReducer = reducer 390 | // current state 391 | let currentState = preloadedState 392 | // current listener array 393 | let currentListeners = [] 394 | // this is a very important design. The purpose is that currentListeners array is an invariant when the listeners are iterated every time 395 | // we can consider if only currentListeners exists. If we execute subscribe again in some subscribe execution, or unsubscribe, it would change the length of the currentListeners array, so there might be an index error 396 | let nextListeners = currentListeners 397 | // if reducer is executing 398 | let isDispatching = false 399 | // if currentListeners is the same as nextListeners, assign the value back 400 | function ensureCanMutateNextListeners() { 401 | if (nextListeners === currentListeners) { 402 | nextListeners = currentListeners.slice() 403 | } 404 | } 405 | // ...... 406 | } 407 | ``` 408 | 409 | We look at `applyMiddleware` function next 410 | 411 | Before that I need to introduce a concept called function Currying. Currying is a technology for changing a function with multiple parameters to a series of functions with a single parameter. 412 | 413 | ```js 414 | function add(a,b) { return a + b } 415 | add(1, 2) => 3 416 | // for the above function, we can use Currying like so 417 | function add(a) { 418 | return b => { 419 | return a + b 420 | } 421 | } 422 | add(1)(2) => 3 423 | // you can understand Currying like this: 424 | // we store an outside variable with a closure, and return a function that takes a parameter. In this function, we use the stored variable and return the value. 425 | ``` 426 | 427 | ```js 428 | // this function should be the most abstruse part of the whole source code 429 | // this function returns a function Curried 430 | // therefore the funciton should be called like so: applyMiddleware(...middlewares)(createStore)(...args) 431 | export default function applyMiddleware(...middlewares) { 432 | return createStore => (...args) => { 433 | // here we execute createStore, and pass the parameters passed lastly to the applyMiddleware function 434 | const store = createStore(...args) 435 | let dispatch = () => { 436 | throw new Error( 437 | `Dispatching while constructing your middleware is not allowed. ` + 438 | `Other middleware would not be applied to this dispatch.` 439 | ) 440 | } 441 | let chain = [] 442 | // every middleware should have these two functions 443 | const middlewareAPI = { 444 | getState: store.getState, 445 | dispatch: (...args) => dispatch(...args) 446 | } 447 | // pass every middleware in middlewares to middlewareAPI 448 | chain = middlewares.map(middleware => middleware(middlewareAPI)) 449 | // same as before, calle very middleWare from right to left, and pass to store.dispatch 450 | dispatch = compose(...chain)(store.dispatch) 451 | // this piece is a little abstract, we'll analyze together with the code of redux-thunk 452 | // createThunkMiddleware returns a 3-level function, the first level accepts a middlewareAPI parameter 453 | // the second level accepts store.dispatch 454 | // the third level accepts parameters in dispatch 455 | {function createThunkMiddleware(extraArgument) { 456 | return ({ dispatch, getState }) => next => action => { 457 | // check if the parameters in dispatch is a function 458 | if (typeof action === 'function') { 459 | // if so, pass those parameters, until action is no longer a function, then execute dispatch({type: 'XXX'}) 460 | return action(dispatch, getState, extraArgument); 461 | } 462 | 463 | return next(action); 464 | }; 465 | } 466 | const thunk = createThunkMiddleware(); 467 | 468 | export default thunk;} 469 | // return the middleware-empowered dispatch and the rest of the properties in store. 470 | return { 471 | ...store, 472 | dispatch 473 | } 474 | } 475 | } 476 | ``` 477 | 478 | Now we've passed the hardest part. Let's take a look at some easier pieces. 479 | 480 | ```js 481 | // Not much to say here, return the current state, but we can't call this function when reducer is running 482 | function getState() { 483 | if (isDispatching) { 484 | throw new Error( 485 | 'You may not call store.getState() while the reducer is executing. ' + 486 | 'The reducer has already received the state as an argument. ' + 487 | 'Pass it down from the top reducer instead of reading it from the store.' 488 | ) 489 | } 490 | 491 | return currentState 492 | } 493 | // accept a function parameter 494 | function subscribe(listener) { 495 | if (typeof listener !== 'function') { 496 | throw new Error('Expected listener to be a function.') 497 | } 498 | // the major design of this part is already covered in the description of nextListeners. Not much to talk about otherwise 499 | if (isDispatching) { 500 | throw new Error( 501 | 'You may not call store.subscribe() while the reducer is executing. ' + 502 | 'If you would like to be notified after the store has been updated, subscribe from a ' + 503 | 'component and invoke store.getState() in the callback to access the latest state. ' + 504 | 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.' 505 | ) 506 | } 507 | 508 | let isSubscribed = true 509 | 510 | ensureCanMutateNextListeners() 511 | nextListeners.push(listener) 512 | 513 | // return a cancel subscription function 514 | return function unsubscribe() { 515 | if (!isSubscribed) { 516 | return 517 | } 518 | 519 | if (isDispatching) { 520 | throw new Error( 521 | 'You may not unsubscribe from a store listener while the reducer is executing. ' + 522 | 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.' 523 | ) 524 | } 525 | 526 | isSubscribed = false 527 | 528 | ensureCanMutateNextListeners() 529 | const index = nextListeners.indexOf(listener) 530 | nextListeners.splice(index, 1) 531 | } 532 | } 533 | 534 | function dispatch(action) { 535 | // the prototype dispatch will check if action is an object 536 | if (!isPlainObject(action)) { 537 | throw new Error( 538 | 'Actions must be plain objects. ' + 539 | 'Use custom middleware for async actions.' 540 | ) 541 | } 542 | 543 | if (typeof action.type === 'undefined') { 544 | throw new Error( 545 | 'Actions may not have an undefined "type" property. ' + 546 | 'Have you misspelled a constant?' 547 | ) 548 | } 549 | // note that you can't call dispatch function in reducers 550 | // it would cause a stack overflow 551 | if (isDispatching) { 552 | throw new Error('Reducers may not dispatch actions.') 553 | } 554 | // execute the composed function after combineReducers 555 | try { 556 | isDispatching = true 557 | currentState = currentReducer(currentState, action) 558 | } finally { 559 | isDispatching = false 560 | } 561 | // iterate on currentListeners and execute saved functions in the array 562 | const listeners = (currentListeners = nextListeners) 563 | for (let i = 0; i < listeners.length; i++) { 564 | const listener = listeners[i] 565 | listener() 566 | } 567 | 568 | return action 569 | } 570 | // at the end of createStore, invoke an action dispatch({ type: ActionTypes.INIT }); 571 | // to initialize state 572 | ``` -------------------------------------------------------------------------------- /Algorithm/algorithm-ch.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [时间复杂度](#%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6) 6 | - [位运算](#%E4%BD%8D%E8%BF%90%E7%AE%97) 7 | - [左移 <<](#%E5%B7%A6%E7%A7%BB-) 8 | - [算数右移 >>](#%E7%AE%97%E6%95%B0%E5%8F%B3%E7%A7%BB-) 9 | - [按位操作](#%E6%8C%89%E4%BD%8D%E6%93%8D%E4%BD%9C) 10 | - [排序](#%E6%8E%92%E5%BA%8F) 11 | - [冒泡排序](#%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) 12 | - [插入排序](#%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F) 13 | - [选择排序](#%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) 14 | - [归并排序](#%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F) 15 | - [快排](#%E5%BF%AB%E6%8E%92) 16 | - [面试题](#%E9%9D%A2%E8%AF%95%E9%A2%98) 17 | - [堆排序](#%E5%A0%86%E6%8E%92%E5%BA%8F) 18 | - [系统自带排序实现](#%E7%B3%BB%E7%BB%9F%E8%87%AA%E5%B8%A6%E6%8E%92%E5%BA%8F%E5%AE%9E%E7%8E%B0) 19 | - [链表](#%E9%93%BE%E8%A1%A8) 20 | - [反转单向链表](#%E5%8F%8D%E8%BD%AC%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8) 21 | - [树](#%E6%A0%91) 22 | - [二叉树的先序,中序,后序遍历](#%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%85%88%E5%BA%8F%E4%B8%AD%E5%BA%8F%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86) 23 | - [递归实现](#%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0) 24 | - [非递归实现](#%E9%9D%9E%E9%80%92%E5%BD%92%E5%AE%9E%E7%8E%B0) 25 | - [中序遍历的前驱后继节点](#%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%E7%9A%84%E5%89%8D%E9%A9%B1%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9) 26 | - [前驱节点](#%E5%89%8D%E9%A9%B1%E8%8A%82%E7%82%B9) 27 | - [后继节点](#%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9) 28 | - [树的深度](#%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6) 29 | - [动态规划](#%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92) 30 | - [斐波那契数列](#%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97) 31 | - [0 - 1背包问题](#0---1%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98) 32 | - [最长递增子序列](#%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97) 33 | - [字符串相关](#%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9B%B8%E5%85%B3) 34 | 35 | 36 | 37 | # 时间复杂度 38 | 39 | 通常使用最差的时间复杂度来衡量一个算法的好坏。 40 | 41 | 常数时间 O(1) 代表这个操作和数据量没关系,是一个固定时间的操作,比如说四则运算。 42 | 43 | 对于一个算法来说,可能会计算出如下操作次数 `aN + 1`,`N` 代表数据量。那么该算法的时间复杂度就是 O(N)。因为我们在计算时间复杂度的时候,数据量通常是非常大的,这时候低阶项和常数项可以忽略不计。 44 | 45 | 当然可能会出现两个算法都是 O(N) 的时间复杂度,那么对比两个算法的好坏就要通过对比低阶项和常数项了。 46 | 47 | # 位运算 48 | 49 | 位运算在算法中很有用,速度可以比四则运算快很多。 50 | 51 | 在学习位运算之前应该知道十进制如何转二进制,二进制如何转十进制。这里说明下简单的计算方式 52 | 53 | - 十进制 `33` 可以看成是 `32 + 1` ,并且 `33` 应该是六位二进制的(因为 `33` 近似 `32`,而 `32` 是 2 的五次方,所以是六位),那么 十进制 `33` 就是 `100001` ,只要是 2 的次方,那么就是 1否则都为 0 54 | - 那么二进制 `100001` 同理,首位是 `2^5` ,末位是 `2^0` ,相加得出 33 55 | 56 | ## 左移 << 57 | 58 | ```js 59 | 10 << 1 // -> 20 60 | ``` 61 | 62 | 左移就是将二进制全部往左移动,`10` 在二进制中表示为 `1010` ,左移一位后变成 `10100` ,转换为十进制也就是 20,所以基本可以把左移看成以下公式 `a * (2 ^ b)` 63 | 64 | ## 算数右移 >> 65 | 66 | ```js 67 | 10 >> 1 // -> 5 68 | ``` 69 | 70 | 算数右移就是将二进制全部往右移动并去除多余的右边,`10` 在二进制中表示为 `1010` ,右移一位后变成 `101` ,转换为十进制也就是 5,所以基本可以把右移看成以下公式 `int v = a / (2 ^ b)` 71 | 72 | 右移很好用,比如可以用在二分算法中取中间值 73 | 74 | ```js 75 | 13 >> 1 // -> 6 76 | ``` 77 | 78 | ## 按位操作 79 | 80 | **按位与** 81 | 82 | 每一位都为 1,结果才为 1 83 | 84 | ```js 85 | 8 & 7 // -> 0 86 | // 1000 & 0111 -> 0000 -> 0 87 | ``` 88 | 89 | **按位或** 90 | 91 | 其中一位为 1,结果就是 1 92 | 93 | ```js 94 | 8 | 7 // -> 15 95 | // 1000 | 0111 -> 1111 -> 15 96 | ``` 97 | 98 | **按位异或** 99 | 100 | 每一位都不同,结果才为 1 101 | 102 | ```js 103 | 8 ^ 7 // -> 15 104 | 8 ^ 8 // -> 0 105 | // 1000 ^ 0111 -> 1111 -> 15 106 | // 1000 ^ 1000 -> 0000 -> 0 107 | ``` 108 | 109 | 从以上代码中可以发现按位异或就是不进位加法 110 | 111 | **面试题**:两个数不使用四则运算得出和 112 | 113 | 这道题中可以按位异或,因为按位异或就是不进位加法,`8 ^ 8 = 0` 如果进位了,就是 16 了,所以我们只需要将两个数进行异或操作,然后进位。那么也就是说两个二进制都是 1 的位置,左边应该有一个进位 1,所以可以得出以下公式 `a + b = (a ^ b) + ((a & b) << 1)` ,然后通过迭代的方式模拟加法 114 | 115 | ```js 116 | function sum(a, b) { 117 | if (a == 0) return b 118 | if (b == 0) return a 119 | let newA = a ^ b 120 | let newB = (a & b) << 1 121 | return sum(newA, newB) 122 | } 123 | ``` 124 | 125 | # 排序 126 | 127 | 以下两个函数是排序中会用到的通用函数,就不一一写了 128 | 129 | ```js 130 | function checkArray(array) { 131 | if (!array || array.length <= 2) return 132 | } 133 | function swap(array, left, right) { 134 | let rightValue = array[right] 135 | array[right] = array[left] 136 | array[left] = rightValue 137 | } 138 | ``` 139 | 140 | ## 冒泡排序 141 | 142 | 冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到 `length - 1` 的位置。 143 | 144 |
145 | 146 |
147 | 148 | 以下是实现该算法的代码 149 | 150 | ```js 151 | function bubble(array) { 152 | checkArray(array); 153 | for (let i = array.length - 1; i > 0; i--) { 154 | // 从 0 到 `length - 1` 遍历 155 | for (let j = 0; j < i; j++) { 156 | if (array[j] > array[j + 1]) swap(array, j, j + 1) 157 | } 158 | } 159 | return array; 160 | } 161 | ``` 162 | 163 | 该算法的操作次数是一个等差数列 `n + (n - 1) + (n - 2) + 1` ,去掉常数项以后得出时间复杂度是 O(n * n) 164 | 165 | ## 插入排序 166 | 167 | 插入排序的原理如下。第一个元素默认是已排序元素,取出下一个元素和当前元素比较,如果当前元素大就交换位置。那么此时第一个元素就是当前的最小数,所以下次取出操作从第三个元素开始,向前对比,重复之前的操作。 168 | 169 |
170 | 171 | 以下是实现该算法的代码 172 | 173 | ```js 174 | function insertion(array) { 175 | checkArray(array); 176 | for (let i = 1; i < array.length; i++) { 177 | for (let j = i - 1; j >= 0 && array[j] > array[j + 1]; j--) 178 | swap(array, j, j + 1); 179 | } 180 | return array; 181 | } 182 | ``` 183 | 184 | 该算法的操作次数是一个等差数列 `n + (n - 1) + (n - 2) + 1` ,去掉常数项以后得出时间复杂度是 O(n * n) 185 | 186 | ## 选择排序 187 | 188 | 选择排序的原理如下。遍历数组,设置最小值的索引为 0,如果取出的值比当前最小值小,就替换最小值索引,遍历完成后,将第一个元素和最小值索引上的值交换。如上操作后,第一个元素就是数组中的最小值,下次遍历就可以从索引 1 开始重复上述操作。 189 | 190 |
191 | 192 | 以下是实现该算法的代码 193 | 194 | ```js 195 | function selection(array) { 196 | checkArray(array); 197 | for (let i = 0; i < array.length - 1; i++) { 198 | let minIndex = i; 199 | for (let j = i + 1; j < array.length; j++) { 200 | minIndex = array[j] < array[minIndex] ? j : minIndex; 201 | } 202 | swap(array, i, minIndex); 203 | } 204 | return array; 205 | } 206 | ``` 207 | 208 | 该算法的操作次数是一个等差数列 `n + (n - 1) + (n - 2) + 1` ,去掉常数项以后得出时间复杂度是 O(n * n) 209 | 210 | ## 归并排序 211 | 212 | 归并排序的原理如下。递归的将数组两两分开直到最多包含两个元素,然后将数组排序合并,最终合并为排序好的数组。假设我有一组数组 `[3, 1, 2, 8, 9, 7, 6]`,中间数索引是 3,先排序数组 `[3, 1, 2, 8]` 。在这个左边数组上,继续拆分直到变成数组包含两个元素(如果数组长度是奇数的话,会有一个拆分数组只包含一个元素)。然后排序数组 `[3, 1]` 和 `[2, 8]` ,然后再排序数组 `[1, 3, 2, 8]` ,这样左边数组就排序完成,然后按照以上思路排序右边数组,最后将数组 `[1, 2, 3, 8]` 和 `[6, 7, 9]` 排序。 213 | 214 |
215 | 216 | 以下是实现该算法的代码 217 | 218 | ```js 219 | function sort(array) { 220 | checkArray(array); 221 | mergeSort(array, 0, array.length - 1); 222 | return array; 223 | } 224 | 225 | function mergeSort(array, left, right) { 226 | // 左右索引相同说明已经只有一个数 227 | if (left === right) return; 228 | // 等同于 `left + (right - left) / 2` 229 | // 相比 `(left + right) / 2` 来说更加安全,不会溢出 230 | // 使用位运算是因为位运算比四则运算快 231 | let mid = parseInt(left + ((right - left) >> 1)); 232 | mergeSort(array, left, mid); 233 | mergeSort(array, mid + 1, right); 234 | 235 | let help = []; 236 | let i = 0; 237 | let p1 = left; 238 | let p2 = mid + 1; 239 | while (p1 <= mid && p2 <= right) { 240 | help[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++]; 241 | } 242 | while (p1 <= mid) { 243 | help[i++] = array[p1++]; 244 | } 245 | while (p2 <= right) { 246 | help[i++] = array[p2++]; 247 | } 248 | for (let i = 0; i < help.length; i++) { 249 | array[left + i] = help[i]; 250 | } 251 | return array; 252 | } 253 | ``` 254 | 255 | 以上算法使用了递归的思想。递归的本质就是压栈,每递归执行一次函数,就将该函数的信息(比如参数,内部的变量,执行到的行数)压栈,直到遇到终止条件,然后出栈并继续执行函数。对于以上递归函数的调用轨迹如下 256 | 257 | ```js 258 | mergeSort(data, 0, 6) // mid = 3 259 | mergeSort(data, 0, 3) // mid = 1 260 | mergeSort(data, 0, 1) // mid = 0 261 | mergeSort(data, 0, 0) // 遇到终止,回退到上一步 262 | mergeSort(data, 1, 1) // 遇到终止,回退到上一步 263 | // 排序 p1 = 0, p2 = mid + 1 = 1 264 | // 回退到 `mergeSort(data, 0, 3)` 执行下一个递归 265 | mergeSort(2, 3) // mid = 2 266 | mergeSort(3, 3) // 遇到终止,回退到上一步 267 | // 排序 p1 = 2, p2 = mid + 1 = 3 268 | // 回退到 `mergeSort(data, 0, 3)` 执行合并逻辑 269 | // 排序 p1 = 0, p2 = mid + 1 = 2 270 | // 执行完毕回退 271 | // 左边数组排序完毕,右边也是如上轨迹 272 | ``` 273 | 274 | 275 | 276 | 该算法的操作次数是可以这样计算:递归了两次,每次数据量是数组的一半,并且最后把整个数组迭代了一次,所以得出表达式 `2T(N / 2) + T(N)` (T 代表时间,N 代表数据量)。根据该表达式可以套用 [该公式](https://www.wikiwand.com/zh-hans/%E4%B8%BB%E5%AE%9A%E7%90%86) 得出时间复杂度为 `O(N * logN)` 277 | 278 | ## 快排 279 | 280 | 快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作。 281 | 282 |
283 | 284 | 以下是实现该算法的代码 285 | 286 | ```js 287 | function sort(array) { 288 | checkArray(array); 289 | quickSort(array, 0, array.length - 1); 290 | return array; 291 | } 292 | 293 | function quickSort(array, left, right) { 294 | if (left < right) { 295 | swap(array, , right) 296 | // 随机取值,然后和末尾交换,这样做比固定取一个位置的复杂度略低 297 | let indexs = part(array, parseInt(Math.random() * (right - left + 1)) + left, right); 298 | quickSort(array, left, indexs[0]); 299 | quickSort(array, indexs[1] + 1, right); 300 | } 301 | } 302 | function part(array, left, right) { 303 | let less = left - 1; 304 | let more = right; 305 | while (left < more) { 306 | if (array[left] < array[right]) { 307 | // 当前值比基准值小,`less` 和 `left` 都加一 308 | ++less; 309 | ++left; 310 | } else if (array[left] > array[right]) { 311 | // 当前值比基准值大,将当前值和右边的值交换 312 | // 并且不改变 `left`,因为当前换过来的值还没有判断过大小 313 | swap(array, --more, left); 314 | } else { 315 | // 和基准值相同,只移动下标 316 | left++; 317 | } 318 | } 319 | // 将基准值和比基准值大的第一个值交换位置 320 | // 这样数组就变成 `[比基准值小, 基准值, 比基准值大]` 321 | swap(array, right, more); 322 | return [less, more]; 323 | } 324 | ``` 325 | 326 | 该算法的复杂度和归并排序是相同的,但是额外空间复杂度比归并排序少,只需 O(logN),并且相比归并排序来说,所需的常数时间也更少。 327 | 328 | ### 面试题 329 | 330 | **Sort Colors**:该题目来自 [LeetCode](https://leetcode.com/problems/sort-colors/description/),题目需要我们将 `[2,0,2,1,1,0]` 排序成 `[0,0,1,1,2,2]` ,这个问题就可以使用三路快排的思想。 331 | 332 | 以下是代码实现 333 | 334 | ```js 335 | var sortColors = function(nums) { 336 | let left = -1; 337 | let right = nums.length; 338 | let i = 0; 339 | // 下标如果遇到 right,说明已经排序完成 340 | while (i < right) { 341 | if (nums[i] == 0) { 342 | swap(nums, i++, ++left); 343 | } else if (nums[i] == 1) { 344 | i++; 345 | } else { 346 | swap(nums, i, --right); 347 | } 348 | } 349 | }; 350 | ``` 351 | 352 | **Kth Largest Element in an Array**:该题目来自 [LeetCode](https://leetcode.com/problems/kth-largest-element-in-an-array/description/),题目需要找出数组中第 K 大的元素,这问题也可以使用快排的思路。并且因为是找出第 K 大元素,所以在分离数组的过程中,可以找出需要的元素在哪边,然后只需要排序相应的一边数组就好。 353 | 354 | 以下是代码实现 355 | 356 | ```js 357 | var findKthLargest = function(nums, k) { 358 | let l = 0 359 | let r = nums.length - 1 360 | // 得出第 K 大元素的索引位置 361 | k = nums.length - k 362 | while (l < r) { 363 | // 分离数组后获得比基准树大的第一个元素索引 364 | let index = part(nums, l, r) 365 | // 判断该索引和 k 的大小 366 | if (index < k) { 367 | l = index + 1 368 | } else if (index > k) { 369 | r = index - 1 370 | } else { 371 | break 372 | } 373 | } 374 | return nums[k] 375 | }; 376 | function part(array, left, right) { 377 | let less = left - 1; 378 | let more = right; 379 | while (left < more) { 380 | if (array[left] < array[right]) { 381 | ++less; 382 | ++left; 383 | } else if (array[left] > array[right]) { 384 | swap(array, --more, left); 385 | } else { 386 | left++; 387 | } 388 | } 389 | swap(array, right, more); 390 | return more; 391 | } 392 | ``` 393 | 394 | 395 | 396 | ## 堆排序 397 | 398 | 堆排序利用了二叉堆的特性来做,二叉堆通常用数组表示,并且二叉堆是一颗完全二叉树(所有叶节点(最底层的节点)都是从左往右顺序排序,并且其他层的节点都是满的)。二叉堆又分为大根堆与小根堆。 399 | 400 | - 大根堆是某个节点的所有子节点的值都比他小 401 | - 小根堆是某个节点的所有子节点的值都比他大 402 | 403 | 堆排序的原理就是组成一个大根堆或者小根堆。以小根堆为例,某个节点的左边子节点索引是 `i * 2 + 1`,右边是 `i * 2 + 2`,父节点是 `(i - 1) /2`。 404 | 405 | 1. 首先遍历数组,判断该节点的父节点是否比他小,如果小就交换位置并继续判断,直到他的父节点比他大 406 | 2. 重新以上操作 1,直到数组首位是最大值 407 | 3. 然后将首位和末尾交换位置并将数组长度减一,表示数组末尾已是最大值,不需要再比较大小 408 | 4. 对比左右节点哪个大,然后记住大的节点的索引并且和父节点对比大小,如果子节点大就交换位置 409 | 5. 重复以上操作 3 - 4 直到整个数组都是大根堆。 410 | 411 |
412 | 413 | 以下是实现该算法的代码 414 | 415 | ```js 416 | function heap(array) { 417 | checkArray(array); 418 | // 将最大值交换到首位 419 | for (let i = 0; i < array.length; i++) { 420 | heapInsert(array, i); 421 | } 422 | let size = array.length; 423 | // 交换首位和末尾 424 | swap(array, 0, --size); 425 | while (size > 0) { 426 | heapify(array, 0, size); 427 | swap(array, 0, --size); 428 | } 429 | return array; 430 | } 431 | 432 | function heapInsert(array, index) { 433 | // 如果当前节点比父节点大,就交换 434 | while (array[index] > array[parseInt((index - 1) / 2)]) { 435 | swap(array, index, parseInt((index - 1) / 2)); 436 | // 将索引变成父节点 437 | index = parseInt((index - 1) / 2); 438 | } 439 | } 440 | function heapify(array, index, size) { 441 | let left = index * 2 + 1; 442 | while (left < size) { 443 | // 判断左右节点大小 444 | let largest = 445 | left + 1 < size && array[left] < array[left + 1] ? left + 1 : left; 446 | // 判断子节点和父节点大小 447 | largest = array[index] < array[largest] ? largest : index; 448 | if (largest === index) break; 449 | swap(array, index, largest); 450 | index = largest; 451 | left = index * 2 + 1; 452 | } 453 | } 454 | ``` 455 | 456 | 以上代码实现了小根堆,如果需要实现大根堆,只需要把节点对比反一下就好。 457 | 458 | 该算法的复杂度是 O(logN) 459 | 460 | ## 系统自带排序实现 461 | 462 | 每个语言的排序内部实现都是不同的。 463 | 464 | 对于 JS 来说,数组长度大于 10 会采用快排,否则使用插入排序 [源码实现](https://github.com/v8/v8/blob/ad82a40509c5b5b4680d4299c8f08d6c6d31af3c/src/js/array.js#L760:7) 。选择插入排序是因为虽然时间复杂度很差,但是在数据量很小的情况下和 `O(N * logN) `相差无几,然而插入排序需要的常数时间很小,所以相对别的排序来说更快。 465 | 466 | 对于 Java 来说,还会考虑内部的元素的类型。对于存储对象的数组来说,会采用稳定性好的算法。稳定性的意思就是对于相同值来说,相对顺序不能改变。 467 | 468 |
469 | 470 | # 链表 471 | 472 | ## 反转单向链表 473 | 474 | 该题目来自 [LeetCode](https://leetcode.com/problems/reverse-linked-list/description/),题目需要将一个单向链表反转。思路很简单,使用三个变量分别表示当前节点和当前节点的前后节点,虽然这题很简单,但是却是一道面试常考题 475 | 476 | 以下是实现该算法的代码 477 | 478 | ```js 479 | var reverseList = function(head) { 480 | // 判断下变量边界问题 481 | if (!head || !head.next) return head 482 | // 初始设置为空,因为第一个节点反转后就是尾部,尾部节点指向 null 483 | let pre = null 484 | let current = head 485 | let next 486 | // 判断当前节点是否为空 487 | // 不为空就先获取当前节点的下一节点 488 | // 然后把当前节点的 next 设为上一个节点 489 | // 然后把 current 设为下一个节点,pre 设为当前节点 490 | while(current) { 491 | next = current.next 492 | current.next = pre 493 | pre = current 494 | current = next 495 | } 496 | return pre 497 | }; 498 | ``` 499 | 500 | 501 | 502 | # 树 503 | 504 | ## 二叉树的先序,中序,后序遍历 505 | 506 | 先序遍历表示先访问根节点,然后访问左节点,最后访问右节点。 507 | 508 | 中序遍历表示先访问左节点,然后访问根节点,最后访问右节点。 509 | 510 | 后序遍历表示先访问左节点,然后访问右节点,最后访问根节点。 511 | 512 | ### 递归实现 513 | 514 | 递归实现相当简单,代码如下 515 | 516 | ```js 517 | function TreeNode(val) { 518 | this.val = val; 519 | this.left = this.right = null; 520 | } 521 | var traversal = function(root) { 522 | if (root) { 523 | // 先序 524 | console.log(root); 525 | traversal(root.left); 526 | // 中序 527 | // console.log(root); 528 | traversal(root.right); 529 | // 后序 530 | // console.log(root); 531 | } 532 | }; 533 | ``` 534 | 535 | 对于递归的实现来说,只需要理解每个节点都会被访问三次就明白为什么这样实现了。 536 | 537 | ### 非递归实现 538 | 539 | 非递归实现使用了栈的结构,通过栈的先进后出模拟递归实现。 540 | 541 | 以下是先序遍历代码实现 542 | 543 | ```js 544 | function pre(root) { 545 | if (root) { 546 | let stack = []; 547 | // 先将根节点 push 548 | stack.push(root); 549 | // 判断栈中是否为空 550 | while (stack.length > 0) { 551 | // 弹出栈顶元素 552 | root = stack.pop(); 553 | console.log(root); 554 | // 因为先序遍历是先左后右,栈是先进后出结构 555 | // 所以先 push 右边再 push 左边 556 | if (root.right) { 557 | stack.push(root.right); 558 | } 559 | if (root.left) { 560 | stack.push(root.left); 561 | } 562 | } 563 | } 564 | } 565 | ``` 566 | 567 | 以下是中序遍历代码实现 568 | 569 | ```js 570 | function mid(root) { 571 | if (root) { 572 | let stack = []; 573 | // 中序遍历是先左再根最后右 574 | // 所以首先应该先把最左边节点遍历到底依次 push 进栈 575 | // 当左边没有节点时,就打印栈顶元素,然后寻找右节点 576 | // 对于最左边的叶节点来说,可以把它看成是两个 null 节点的父节点 577 | // 左边打印不出东西就把父节点拿出来打印,然后再看右节点 578 | while (stack.length > 0 || root) { 579 | if (root) { 580 | stack.push(root); 581 | root = root.left; 582 | } else { 583 | root = stack.pop(); 584 | console.log(root); 585 | root = root.right; 586 | } 587 | } 588 | } 589 | } 590 | ``` 591 | 592 | 以下是后序遍历代码实现,该代码使用了两个栈来实现遍历,相比一个栈的遍历来说要容易理解很多 593 | 594 | ```js 595 | function pos(root) { 596 | if (root) { 597 | let stack1 = []; 598 | let stack2 = []; 599 | // 后序遍历是先左再右最后根 600 | // 所以对于一个栈来说,应该先 push 根节点 601 | // 然后 push 右节点,最后 push 左节点 602 | stack1.push(root); 603 | while (stack1.length > 0) { 604 | root = stack1.pop(); 605 | stack2.push(root); 606 | if (root.left) { 607 | stack1.push(root.left); 608 | } 609 | if (root.right) { 610 | stack1.push(root.right); 611 | } 612 | } 613 | while (stack2.length > 0) { 614 | console.log(s2.pop()); 615 | } 616 | } 617 | } 618 | ``` 619 | 620 | ## 中序遍历的前驱后继节点 621 | 622 | 实现这个算法的前提是节点有一个 `parent` 的指针指向父节点,根节点指向 `null` 。 623 | 624 |
625 | 626 | 如图所示,该树的中序遍历结果是 `4, 2, 5, 1, 6, 3, 7` 627 | 628 | ### 前驱节点 629 | 630 | 对于节点 `2` 来说,他的前驱节点就是 `4` ,按照中序遍历原则,可以得出以下结论 631 | 632 | 1. 如果选取的节点的左节点不为空,就找该左节点最右的节点。对于节点 `1` 来说,他有左节点 `2` ,那么节点 `2` 的最右节点就是 `5` 633 | 2. 如果左节点为空,且目标节点是父节点的右节点,那么前驱节点为父节点。对于节点 `5` 来说,没有左节点,且是节点 `2` 的右节点,所以节点 `2` 是前驱节点 634 | 3. 如果左节点为空,且目标节点是父节点的左节点,向上寻找到第一个是父节点的右节点的节点。对于节点 `6` 来说,没有左节点,且是节点 `3` 的左节点,所以向上寻找到节点 `1` ,发现节点 `3` 是节点 `1` 的右节点,所以节点 `1` 是节点 `6` 的前驱节点 635 | 636 | 以下是算法实现 637 | 638 | ```js 639 | function predecessor(node) { 640 | if (!node) return 641 | // 结论 1 642 | if (node.left) { 643 | return getRight(node.left) 644 | } else { 645 | let parent = node.parent 646 | // 结论 2 3 的判断 647 | while(parent && parent.right === node) { 648 | node = parent 649 | parent = node.parent 650 | } 651 | return parent 652 | } 653 | } 654 | function getRight(node) { 655 | if (!node) return 656 | node = node.right 657 | while(node) node = node.right 658 | return node 659 | } 660 | ``` 661 | 662 | ### 后继节点 663 | 664 | 对于节点 `2` 来说,他的后继节点就是 `5` ,按照中序遍历原则,可以得出以下结论 665 | 666 | 1. 如果有右节点,就找到该右节点的最左节点。对于节点 `1` 来说,他有右节点 `3` ,那么节点 `3` 的最左节点就是 `6` 667 | 2. 如果没有右节点,就向上遍历直到找到一个节点是父节点的左节点。对于节点 `5` 来说,没有右节点,就向上寻找到节点 `2` ,该节点是父节点 `1` 的左节点,所以节点 `1` 是后继节点 668 | 669 | 以下是算法实现 670 | 671 | ```js 672 | function successor(node) { 673 | if (!node) return 674 | // 结论 1 675 | if (node.right) { 676 | return getLeft(node.right) 677 | } else { 678 | // 结论 2 679 | let parent = node.parent 680 | // 判断 parent 为空 681 | while(parent && parent.left === node) { 682 | node = parent 683 | parent = node.parent 684 | } 685 | return parent 686 | } 687 | } 688 | function getLeft(node) { 689 | if (!node) return 690 | node = node.left 691 | while(node) node = node.left 692 | return node 693 | } 694 | ``` 695 | 696 | ## 树的深度 697 | 698 | **树的最大深度**:该题目来自 [Leetcode](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/),题目需要求出一颗二叉树的最大深度 699 | 700 | 以下是算法实现 701 | 702 | ```js 703 | var maxDepth = function(root) { 704 | if (!root) return 0 705 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 706 | }; 707 | ``` 708 | 709 | 对于该递归函数可以这样理解:一旦没有找到节点就会返回 0,每弹出一次递归函数就会加一,树有三层就会得到3。 710 | 711 | # 动态规划 712 | 713 | 动态规划背后的基本思想非常简单。就是将一个问题拆分为子问题,一般来说这些子问题都是非常相似的,那么我们可以通过只解决一次每个子问题来达到减少计算量的目的。 714 | 715 | 一旦得出每个子问题的解,就存储该结果以便下次使用。 716 | 717 | ## 斐波那契数列 718 | 719 | 斐波那契数列就是从 0 和 1 开始,后面的数都是前两个数之和 720 | 721 | 0,1,1,2,3,5,8,13,21,34,55,89.... 722 | 723 | 那么显然易见,我们可以通过递归的方式来完成求解斐波那契数列 724 | 725 | ```js 726 | function fib(n) { 727 | if (n < 2 && n >= 0) return n 728 | return fib(n - 1) + fib(n - 2) 729 | } 730 | fib(10) 731 | ``` 732 | 733 | 以上代码已经可以完美的解决问题。但是以上解法却存在很严重的性能问题,当 n 越大的时候,需要的时间是指数增长的,这时候就可以通过动态规划来解决这个问题。 734 | 735 | 动态规划的本质其实就是两点 736 | 737 | 1. 自底向上分解子问题 738 | 2. 通过变量存储已经计算过的解 739 | 740 | 根据上面两点,我们的斐波那契数列的动态规划思路也就出来了 741 | 742 | 1. 斐波那契数列从 0 和 1 开始,那么这就是这个子问题的最底层 743 | 2. 通过数组来存储每一位所对应的斐波那契数列的值 744 | 745 | ```js 746 | function fib(n) { 747 | let array = new Array(n + 1).fill(null) 748 | array[0] = 0 749 | array[1] = 1 750 | for (let i = 2; i <= n; i++) { 751 | array[i] = array[i - 1] + array[i - 2] 752 | } 753 | return array[n] 754 | } 755 | fib(10) 756 | ``` 757 | 758 | ## 0 - 1背包问题 759 | 760 | 该问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。每个问题只能放入至多一次。 761 | 762 | 假设我们有以下物品 763 | 764 | | 物品 ID / 重量 | 价值 | 765 | | :------------: | :--: | 766 | | 1 | 3 | 767 | | 2 | 7 | 768 | | 3 | 12 | 769 | 770 | 对于一个总容量为 5 的背包来说,我们可以放入重量 2 和 3 的物品来达到背包内的物品总价值最高。 771 | 772 | 对于这个问题来说,子问题就两个,分别是放物品和不放物品,可以通过以下表格来理解子问题 773 | 774 | | 物品 ID / 剩余容量 | 0 | 1 | 2 | 3 | 4 | 5 | 775 | | :----------------: | :--: | :--: | :--: | :--: | :--: | :--: | 776 | | 1 | 0 | 3 | 3 | 3 | 3 | 3 | 777 | | 2 | 0 | 3 | 7 | 10 | 10 | 10 | 778 | | 3 | 0 | 3 | 7 | 12 | 15 | 19 | 779 | 780 | 直接来分析能放三种物品的情况,也就是最后一行 781 | 782 | - 当容量少于 3 时,只取上一行对应的数据,因为当前容量不能容纳物品 3 783 | - 当容量 为 3 时,考虑两种情况,分别为放入物品 3 和不放物品 3 784 | - 不放物品 3 的情况下,总价值为 10 785 | - 放入物品 3 的情况下,总价值为 12,所以应该放入物品 3 786 | - 当容量 为 4 时,考虑两种情况,分别为放入物品 3 和不放物品 3 787 | - 不放物品 3 的情况下,总价值为 10 788 | - 放入物品 3 的情况下,和放入物品 1 的价值相加,得出总价值为 15,所以应该放入物品 3 789 | - 当容量 为 5 时,考虑两种情况,分别为放入物品 3 和不放物品 3 790 | - 不放物品 3 的情况下,总价值为 10 791 | - 放入物品 3 的情况下,和放入物品 2 的价值相加,得出总价值为 19,所以应该放入物品 3 792 | 793 | 以下代码对照上表更容易理解 794 | 795 | ```js 796 | /** 797 | * @param {*} w 物品重量 798 | * @param {*} v 物品价值 799 | * @param {*} C 总容量 800 | * @returns 801 | */ 802 | function knapsack(w, v, C) { 803 | let length = w.length 804 | if (length === 0) return 0 805 | 806 | // 对照表格,生成的二维数组,第一维代表物品,第二维代表背包剩余容量 807 | // 第二维中的元素代表背包物品总价值 808 | let array = new Array(length).fill(new Array(C + 1).fill(null)) 809 | 810 | // 完成底部子问题的解 811 | for (let i = 0; i <= C; i++) { 812 | // 对照表格第一行, array[0] 代表物品 1 813 | // i 代表剩余总容量 814 | // 当剩余总容量大于物品 1 的重量时,记录下背包物品总价值,否则价值为 0 815 | array[0][i] = i >= w[0] ? v[0] : 0 816 | } 817 | 818 | // 自底向上开始解决子问题,从物品 2 开始 819 | for (let i = 1; i < length; i++) { 820 | for (let j = 0; j <= C; j++) { 821 | // 这里求解子问题,分别为不放当前物品和放当前物品 822 | // 先求不放当前物品的背包总价值,这里的值也就是对应表格中上一行对应的值 823 | array[i][j] = array[i - 1][j] 824 | // 判断当前剩余容量是否可以放入当前物品 825 | if (j >= w[i]) { 826 | // 可以放入的话,就比大小 827 | // 放入当前物品和不放入当前物品,哪个背包总价值大 828 | array[i][j] = Math.max(array[i][j], v[i] + array[i - 1][j - w[i]]) 829 | } 830 | } 831 | } 832 | return array[length - 1][C] 833 | } 834 | ``` 835 | 836 | ## 最长递增子序列 837 | 838 | 最长递增子序列意思是在一组数字中,找出最长一串递增的数字,比如 839 | 840 | 0, 3, 4, 17, 2, 8, 6, 10 841 | 842 | 对于以上这串数字来说,最长递增子序列就是 0, 3, 4, 8, 10,可以通过以下表格更清晰的理解 843 | 844 | | 数字 | 0 | 3 | 4 | 17 | 2 | 8 | 6 | 10 | 845 | | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 846 | | 长度 | 1 | 2 | 3 | 4 | 2 | 4 | 4 | 5 | 847 | 848 | 通过以上表格可以很清晰的发现一个规律,找出刚好比当前数字小的数,并且在小的数组成的长度基础上加一。 849 | 850 | 这个问题的动态思路解法很简单,直接上代码 851 | 852 | ```js 853 | function lis(n) { 854 | if (n.length === 0) return 0 855 | // 创建一个和参数相同大小的数组,并填充值为 1 856 | let array = new Array(n.length).fill(1) 857 | // 从索引 1 开始遍历,因为数组已经所有都填充为 1 了 858 | for (let i = 1; i < n.length; i++) { 859 | // 从索引 0 遍历到 i 860 | // 判断索引 i 上的值是否大于之前的值 861 | for (let j = 0; j < i; j++) { 862 | if (n[i] > n[j]) { 863 | array[i] = Math.max(array[i], 1 + array[j]) 864 | } 865 | } 866 | } 867 | let res = 1 868 | for (let i = 0; i < array.length; i++) { 869 | res = Math.max(res, array[i]) 870 | } 871 | return res 872 | } 873 | ``` 874 | 875 | # 字符串相关 876 | 877 | 在字符串相关算法中,Trie 树可以解决解决很多问题,同时具备良好的空间和时间复杂度,比如以下问题 878 | 879 | - 词频统计 880 | - 前缀匹配 881 | 882 | 如果你对于 Trie 树还不怎么了解,可以前往 [这里](../DataStruct/dataStruct-zh.md#trie) 阅读 883 | 884 | --------------------------------------------------------------------------------