star
~Node.js
、Electron
、React-native
、 Linux
、Docker
、數據庫、redis
、消息隊列和操做系統等知識.根據經驗,看上篇的人最多,由於畢竟這部分的前端佔比大。有必定技術基礎的,能夠看看後面文章。display
出現「動畫
」APP
,display
爲"none"<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app { width: 200px; height: 200px; background-color: red; display: none; transition: all 1s; } </style> </head> <body> <div id="app"> </div> <button id="test">測試</button> </body> </html>
none
,而且寫入腳本文件<style> #app { width: 200px; height: 200px; background-color: red; display: none; } </style> 。。。 <script> test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.transform = "translateX(200px)" app.style.display = "block" } </script>
test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.display = "block" const height = app.offsetHeight app.style.transform = "translateX(200px)" }
const height = app.offsetHeight
這行代碼的時候,再點擊測試按鈕,display
切換就順帶出來了「動畫」,有了過分效果dom
的這些特殊屬性時,瀏覽器就會強制清空渲染隊列一次,讓我拿到最新的值。也就是說讀取的時候,其實已是display
爲"block"了,所以。咱們出現了過渡動畫<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #app { width: 200px; height: 200px; background-color: red; display: none; transition: all 1s; } </style> </head> <body> <div id="app"> </div> <button id="test">測試</button> </body> <script> test.onclick = function () { const app = document.querySelector('#app') console.log(app, 'app') app.style.display = "block" const height = app.offsetHeight app.style.transform = "translateX(200px)" } </script> </html>
答:html
js
代碼執行層面的webpack
/或其餘工具打包,精細化分割了代碼。將node_modules
(vendor
開頭)和本身寫的src
(default
開頭)源碼分開打包了.這樣最小的限度的縮小了hash
值改變帶來的http
緩存失效問題.在webpack
中配置splitChunks
.splitChunks: { // 表示選擇哪些 chunks 進行分割,可選值有:async,initial和all chunks: "async", // 表示新分離出的chunk必須大於等於minSize,默認爲30000,約30kb。 minSize: 30000, // 表示一個模塊至少應被minChunks個chunk所包含才能分割。默認爲1。 minChunks: 1, // 表示按需加載文件時,並行請求的最大數目。默認爲5。 maxAsyncRequests: 5, // 表示加載入口文件時,並行請求的最大數目。默認爲3。 maxInitialRequests: 3, // 表示拆分出的chunk的名稱鏈接符。默認爲~。如chunk~vendors.js automaticNameDelimiter: '~', // 設置chunk的文件名。默認爲true。當爲true時,splitChunks基於chunk和cacheGroups的key自動命名。 name: true, // cacheGroups 下能夠能夠配置多個組,每一個組根據test設置條件,符合test條件的模塊,就分配到該組。模塊能夠被多個組引用,但最終會根據priority來決定打包到哪一個組中。默認將全部來自 node_modules目錄的模塊打包至vendors組,將兩個以上的chunk所共享的模塊打包至default組。 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, // default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } }
js
文件仍是有幾MB
大小,這個問題能忽略嗎?固然不能夠。咱們通常生產環境部署時候,會藉助CDN
,在各類雲廠商提供/本身搭建的CDN
服務配置相應的解析。掘金如今已由頭條系接手,我看靜態資源方案的域名開頭使用的是s3.pstatp.com
,我查詢大概是跟抖音/相關的域名。或許是本身搭建的CDN
服務(千萬不要忽略CDN,靜態資源服務對於服務器來講是很是佔帶寬的,速度和體驗確定會比CDN差不少,越大越重的系統提高越大
)網絡傳輸層面, 僅僅是靜態資源嗎?前端
ajax
請求APP
改造方案,弱網/無網絡狀況擁有正常的體驗,以爲分塊傳輸就是核心,特別在APP
端)對於ws
/wss
協議、TCP等私有協議雙工通訊的node
hash
算法和接收到的錯誤上報,生成對應的後綴空文件,發佈到CDN。這樣前端每次上報時候,都會用統一hash
算法生成對應後綴,請求那個地址,若是存在,就說明後端已經接受到了這個上報,能夠丟棄這個上報。同理,咱們能夠參考這個,能夠用空的CDN
文件作網絡檢測質量檢測(由於一些跨平臺APP框架沒法精確獲取當前網絡質量),而後再將發送請求隊列持久化便可保證請求不丟失。(對於IM場景的APP,那就要考慮更多,這裏不對IM展開講)Js
代碼執行層面的,先看一段代碼:// 真奧義之阻塞JS主線程之休眠 const sleep = (delay = 500) => { let t = Date.now(); while(Date.now() - t <= delay) { continue; } };
JS
主解析線程,默認是500ms
,這意味着,這500ms
,用戶操做咱們的系統,是不會有任何相應的,由於此時JS主解析線程一直被阻塞。(若是是10秒,那麼會怎樣?若是把這段代碼換成其餘計算的函數/循環,效果也是同樣)。這就是由於JS
代碼致使的性能問題,如何解決?固然是分片分片分片!不要寫太耗時的JS
同步代碼.web worker wasm PWA
等技術能夠解決一些問題啊.可是我勸你在技術選型的時候,要考慮一個複雜度和收益的問題。如今仍是有人用JQ
和原生JS
、Canvas
開發項目,由於開發最複雜的功能,每每都須要他們。(例如富文本編輯器、可視化編輯等)web worker
技術,使用事件通訊,將大量計算工做交給web worker
。Worker 也能夠建立新的 Worker,固然,全部 Worker 必須與其建立者同源(注意:Blink暫時不支持嵌套 Worker)。var myWorker = new Worker('worker.js'); var first = document.querySelector('#number1'); var second = document.querySelector('#number2'); first.onchange = function() { myWorker.postMessage([first.value,second.value]); console.log('Message posted to worker'); }
web worker
兼容性很好,若是是webpack
環境,須要對應作一些工程化環境處理,web worker
在我看來,適用於純粹的大量計算場景。我記得有團隊是用於3D數據可視化處理的,他們使用web worker
後提高就很大webAssembly
技術,更適用於音視頻領域,更底層的功能處理,例如瀏覽器端要音視頻編解碼或者圖片壓縮處理之類就很適合用它.它有點像瀏覽器版的Node.js的c++
插件。Node.js
能夠調用C# Go Object-c
等語言的插件&通訊,同理,webAssembly
也是能夠用各類語言編寫。關於webAssembly
這個技術,我寫過一篇文章方便你們入門。self.importScripts('ffmpeg.js'); onmessage = function(e) { console.log('ffmpeg_run', ffmpeg_run); var files = e.data; console.log(files); ffmpeg_run({ arguments: ['-i', '/input/' + files[0].name, '-b:v', '64k', '-bufsize', '64k', '-vf', 'showinfo', '-strict', '-2', 'out.mp4'], files: files, }, function(results) { console.log('result',results); self.postMessage(results[0].data, [results[0].data]); }); }
V8
,因爲它是自動垃圾回收機制,那麼有一可能下一次回收的內存垃圾達到500MB
,這個時候就會產生卡頓,有人會莫名其妙,爲何會卡?由於v8
垃圾回收也會阻塞主解析線程,因此形成了讓你卡的假象
.這裏留下一個問題,若是使用Node.js c++
插件或者wasm
技術,調用它們模塊暴露的方法,它們的方法很是耗時,此時js
主解析線程掛起嗎?
渲染層面的性能問題:webpack
function defer(fn) { //requestIdleCallback的兼容性很差,對於用戶交互頻繁屢次合併更新來講,requestAnimation更有及時性高優先級,requestIdleCallback則適合處理能夠延遲渲染的任務~ // if (window.requestIdleCallback) { // return requestIdleCallback(fn); // } //高優先級任務 異步的 先掛起 return requestAnimationFrame(fn); } export function enqueueSetState(stateChange, component) { //第一次進來確定會先調用defer函數 if (setStateQueue.length === 0) { //清空隊列的辦法是異步執行 defer(flush); } //向隊列中添加對象 key:stateChange value:component setStateQueue.push({ stateChange, component }); //若是渲染隊列中沒有這個組件 那麼添加進去 if (!renderQueue.some(item => item === component)) { renderQueue.push(component); } }
SSR
,SSR
有成熟的框架和庫,能夠考慮用nuxt,next
等直接生成靜態資源文件性能是一個很是大的學問,每每都看重這方面,可是真正有對性能要求很高的項目,每每都是環境很複雜的,例如使用JS
和C++
插件去作解析播放同一個格式的音頻,解析速度哪一個快,這些都是須要實際項目和團隊支撐你去實踐,這裏我以爲也寫得比較淺,雖然說核心技術不外泄,可是這些大部分人確定都夠用了
Promise
核心原理.then
實現鏈式調用,每次都是返回一個新的Promise
,跟JQuery
的鏈式調用類似(返回this
)MyPromise.prototype.then = function (onResolved, onRejected) { // 定義then const self = this; // 指定回調函數的默認值(必須是函數) onResolved = typeof onResolved==='function' ? onResolved : value => value; onRejected = typeof onRejected==='function' ? onRejected : reason => {throw reason}; return new MyPromise((resolve,reject)=>{ // 返回一個新的MyPromise對象 function handle(callback) { // 返回的MyPromise結果由onResolved/onRejected的結果決定 // 一、拋出異常MyPromise結果爲失敗 reason爲結果 // 二、返回的是MyPromise MyPromise爲當前的結果 // 三、返回的不是MyPromise value爲結果 // 須要經過捕獲獲取才能知道有沒有異常 try{ const result = callback(self.data) // 判斷是否是MyPromise if ( result instanceof MyPromise){ // 只有then才知道結果 result.then(value=>resolve(value),reason=>reject(reason)) }else{ resolve(result) } }catch(error){ reject(error) // 返回的結果爲reject(error) 上面第一點 } } // 判斷當前的status if (self.status === FULFILLED){ // 狀態爲 fulfilled setTimeout(()=>{ // 當即執行異步回調 handle(onResolved); }) } else if (self.status === REJECTED){ // 狀態爲 rejected setTimeout(()=>{ // 當即執行異步回調 handle(onRejected); }) }else{ // pendding將成功和失敗保存到callbacks裏緩存起來 self.callbacks.push({ onResolved(value){ //函數裏面調用回調函數 而且根據回調函數的結果改變MyPromise的結果 handle(onResolved) //爲何這裏沒有setTimeout,由於上面已經寫到過改變狀態後回去callbacks裏循環待執行的回調函數 }, onRejected(reason){ handle(onRejected) } }) } }) }
createStore
裏的實現,根據是否傳入了中間件作處理export default function createStore(reducer, enhancer) { if (typeof enhancer !== 'undefined') { return enhancer(createStore)(reducer) } let state = null const listeners = [] const subscribe = (listener) => { listeners.push(listener) } const getState = () => state const dispatch = (action) => { state = reducer(state, action) listeners.forEach((listener) => listener()) } dispatch({}) return { getState, dispatch, subscribe } }
reduce
,將上次的結果逐個傳入,核心在於compose
,支持了多箇中間件使用.import compose from './compose'; export default function applyMiddleware(...middlewares) { return (createStore) => (reducer) => { const store = createStore(reducer) let dispatch = store.dispatch let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } export default function compose(...funcs) { return funcs.reduce((a, b) => (...args) => a(b(...args))); }
微前端原理:c++
entry
入口,去對應的地址拉取index.html
文件,獲取他們所需的資源和全部標籤、DOM
節點DOM
節點和標籤塞入基座中以及用key-value
形式緩存內存中(避免重複發送請求拉取)async function loadApp() { const shouldMountApp = Apps.filter(shouldBeActive); const App = shouldMountApp.pop(); fetch(App.entry) .then(function (response) { return response.text(); }) .then(async function (text) { const dom = document.createElement('div'); dom.innerHTML = text; const entryPath = App.entry; const subapp = document.querySelector('#subApp-content'); subapp.appendChild(dom); handleScripts(entryPath, subapp, dom); handleStyles(entryPath, subapp, dom); }); }
async function handleScripts(entryPath, subapp, dom) { const scripts = dom.querySelectorAll('script'); const paromiseArr = scripts && Array.from(scripts).map((item) => { if (item.src) { const url = window.location.protocol + '//' + window.location.host; return fetch(`${entryPath}/${item.src}`.replace(url, '')).then( function (response) { return response.text(); } ); } else { return Promise.resolve(item.textContent); } }); const res = await Promise.all(paromiseArr); if (res && res.length > 0) { res.forEach((item) => { const script = document.createElement('script'); script.innerText = item; subapp.appendChild(script); }); } }
webpack
原理webpack打包過程git
webpack打包原理es6
AST
抽象語法樹 ,一個AST
抽象語法樹以下所示:Node { type: 'File', start: 0, end: 32, loc: SourceLocation { start: Position { line: 1, column: 0 }, end: Position { line: 1, column: 32 } }, program: Node { type: 'Program', start: 0, end: 32, loc: SourceLocation { start: [Position], end: [Position] }, sourceType: 'module', interpreter: null, body: [ [Node] ], directives: [] }, comments: [] }
AST
階段中去處理代碼AST
抽象語法樹變成瀏覽器能夠識別的代碼, 而後輸出const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; // traverse 採用的 ES Module 導出,咱們經過 requier 引入的話就加個 .default const babel = require('@babel/core'); const read = fileName => { const buffer = fs.readFileSync(fileName, 'utf-8'); const AST = parser.parse(buffer, { sourceType: 'module' }); console.log(AST); // 依賴收集 const dependencies = {}; // 使用 traverse 來遍歷 AST traverse(AST, { ImportDeclaration({ node }) { // 函數名是 AST 中包含的內容,參數是一些節點,node 表示這些節點下的子內容 const dirname = path.dirname(filename); // 咱們從抽象語法樹裏面拿到的路徑是相對路徑,而後咱們要處理它,在 bundler.js 中才能正確使用 const newDirname = './' + path.join(dirname, node.source.value).replace('\\', '/'); // 將dirname 和 獲取到的依賴聯合生成絕對路徑 dependencies[node.source.value] = newDirname; // 將源路徑和新路徑以 key-value 的形式存儲起來 } }) // 將抽象語法樹轉換成瀏覽器能夠運行的代碼 const { code } = babel.transformFromAst(AST, null, { presets: ['@babel/preset-env'] }) return { filename, dependencies, code } }; read('./test1.js');
// 建立依賴圖譜函數, 遞歸遍歷全部依賴模塊 const makeDependenciesGraph = (entry) => { const entryModule = read(entry) const graghArray = [ entryModule ]; // 首先將咱們分析的入口文件結果放入圖譜數組中 for (let i = 0; i < graghArray.length; i ++) { const item = graghArray[i]; const { dependencies } = item; // 拿到當前模塊所依賴的模塊 if (dependencies) { for ( let j in dependencies ) { // 經過 for-in 遍歷對象 graghArray.push(read(dependencies[j])); // 若是子模塊又依賴其它模塊,就分析子模塊的內容 } } } const gragh = {}; // 將圖譜的數組形式轉換成對象形式 graghArray.forEach( item => { gragh[item.filename] = { dependencies: item.dependencies, code: item.code } }) console.log(gragh) return gragh; }
gragh
獲得的對象:{ './app.js': { dependencies: { './test1.js': './test1.js' }, code: '"use strict";\n\nvar _test = _interopRequireDefault(require("./test1.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(test 1);' }, './test1.js': { dependencies: { './test2.js': './test2.js' }, code: '"use strict";\n\nvar _test = _interopRequireDefault(require("./test2.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(\'th is is test1.js \', _test["default"]);' }, './test2.js': { dependencies: {}, code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports["default"] = void 0;\n\nfunction test2() {\n console.log(\'this is test2 \');\n}\n\nvar _default = tes t2;\nexports["default"] = _default;' } }
const generateCode = (entry) => { // 注意:咱們的 gragh 是一個對象,key是咱們全部模塊的絕對路徑,須要經過 JSON.stringify 來轉換 const gragh = JSON.stringify(makeDependenciesGraph(entry)); // 咱們知道,webpack 是將咱們的全部模塊放在閉包裏面執行的,因此咱們寫一個自執行的函數 // 注意: 咱們生成的代碼裏面,都是使用的 require 和 exports 來引入導出模塊的,而咱們的瀏覽器是不認識的,因此須要構建這樣的函數 return ` (function( gragh ) { function require( module ) { // 相對路徑轉換成絕對路徑的方法 function localRequire(relativePath) { return require(gragh[module].dependencies[relativePath]) } const exports = {}; (function( require, exports, code ) { eval(code) })( localRequire, exports, gragh[module].code ) return exports; } require('${ entry }') })(${ gragh }) `; } const code = generateCode('./app.js'); console.log(code)
code
以下:(function( gragh ) { function require( module ) { // 相對路徑轉換成絕對路徑的方法 function localRequire(relativePath) { return require(gragh[module].dependencies[relativePath]) } const exports = {}; (function( require, exports, code ) { eval(code) })( localRequire, exports, gragh[module].code ) return exports; } require('./app.js') })({"./app.js":{"dependencies":{"./test1.js":"./test1.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test1.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(_test[\"default\"]);"},"./test1.js":{"dependencies":{"./test2.js":"./test2.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test2.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log('this is test1.js ', _test[\"default\"]);"},"./test2.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nfunction test2() {\n console.log('this is test2 ');\n}\n\nvar _default = test2;\nexports[\"default\"] = _default;"}})
贊
,讓更多人看到這篇文章前端巔峯
],個人gitHub
源碼地址是:https://github.com/JinJieTan/Peter-
,記得Star
哦