自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我是如何把性能優(yōu)化的顆粒度做的更細(xì)

開發(fā) 前端
大概的實(shí)現(xiàn)思路和思考的過程,基本上我都描述的差不多了,過程當(dāng)中有很多次想過放棄,但是又不忍心拋棄自己之前的付出所以就堅(jiān)持下來了,也算是做出來的了,但是 elementtiming api 那里那個(gè)問題,還是需要我繼續(xù)研究和解決的,我會(huì)繼續(xù)和 WICG 那邊溝通,爭取可以讓它變得更好。

[[404660]]

本文轉(zhuǎn)載自微信公眾號(hào)「前端壹棧」,作者花開富貴。轉(zhuǎn)載本文請聯(lián)系前端壹棧公眾號(hào)。

前言

之前我也研究過很多性能相關(guān)的文檔和博客,發(fā)現(xiàn)現(xiàn)在的性能相關(guān)的文章 90% 都是之前有過的東西,但是目前的性能優(yōu)化只能做到如今的樣子了嗎?

很顯然,肯定不是的,技術(shù)本來就是個(gè)逐漸進(jìn)步的過程,但是現(xiàn)在更多的是把當(dāng)前的內(nèi)容去翻來覆去的卷,我表示卷不動(dòng)了,所以我準(zhǔn)備尋找新的出路了

想法的誕生

其實(shí)我們現(xiàn)在的性能優(yōu)化的檢測及性能優(yōu)化的方案已經(jīng)有了很多了,從開發(fā)到用戶體驗(yàn)的各個(gè)角度來說,都有不同的檢測和處理方案,目前市面上流傳最多的就是以下這些:

  • 開發(fā)階段(公共變量、公共樣式、組件提取、數(shù)據(jù)處理算法、影響頁面渲染速度和用戶響應(yīng)的使用worker(元素除外)等)
  • 打包構(gòu)建(gzip 壓縮、去log、去 sourcemap、按需引入、按需加載、圖片樣式合并、減少打包時(shí)間和打包體積、添加緩存等)
  • 發(fā)布階段(CI、CD)
  • 資源優(yōu)化(強(qiáng)緩存、協(xié)商緩存、資源預(yù)加載、異步加載、service-worker等)

當(dāng)然了不止這么多東西,我只是把常用的一些東西列了一下,比如我之前寫過的一個(gè)實(shí)戰(zhàn)篇 - 如何實(shí)現(xiàn)和淘寶移動(dòng)端一樣的模塊化加載 (task-silce)和 解析篇 - Task-slice實(shí)現(xiàn)淘寶移動(dòng)端方式加載這就是在開發(fā)階段比較細(xì)節(jié)的用戶體驗(yàn)方面的性能優(yōu)化,當(dāng)然我們還可以基于 performance api 來做性能優(yōu)化前的檢測,這方面正好之前我也整理過部分內(nèi)容性能優(yōu)化篇 - Performance(工具 & api)

基于這些東西我想了想,我還是覺得性能優(yōu)化做的不夠細(xì)不夠具體,這樣有很多的弊端:

  • 偽性能優(yōu)化(這樣就代表著性能優(yōu)化做的不夠徹底)
  • 不能完全的掌握頁面dom渲染相關(guān)的數(shù)據(jù)(火焰圖看的太復(fù)雜,沒有數(shù)據(jù)化)
  • 通過 performance.mark 植入的方式,可能對于項(xiàng)目來說是個(gè)很大的成本,會(huì)在業(yè)務(wù)里面植入很多無效代碼來做用戶體驗(yàn)的檢測,而且可能在某些情況下會(huì)影響到業(yè)務(wù),或者業(yè)務(wù)的某些條件導(dǎo)致 performance.mark 無法準(zhǔn)確抓取,這樣整體來說就無法真正達(dá)到完美的目的了

這時(shí)候我就考慮要如何可以規(guī)避這些問題,還能準(zhǔn)確的捕捉到有關(guān)當(dāng)前元素的渲染時(shí)間呢,baidu、google 查了一段時(shí)間后發(fā)現(xiàn)了一個(gè)api好像可以解決這個(gè)問題,于是我開始入手了

想法的實(shí)現(xiàn)

實(shí)現(xiàn)上述想法時(shí),我們需要梳理一下我們的需求:

捕捉當(dāng)前元素的渲染時(shí)間(何時(shí)開始、渲染多久、渲染位置)

不把性能檢測相關(guān)的代碼植入到業(yè)務(wù)當(dāng)中,實(shí)現(xiàn)上述需求

捕捉到的這些信息在何處預(yù)覽(在公司沒有性能檢測平臺(tái)的情況下,我們是否要為了這種做優(yōu)化相關(guān)的需求去在搭建一個(gè)性能檢測平臺(tái))

是否可以通過瀏覽器插件來展示這些數(shù)據(jù)(這樣方便預(yù)覽,還不影響各個(gè)方向的業(yè)務(wù))

有了想法,剩下的就是實(shí)現(xiàn)即可了

捕捉當(dāng)前元素的渲染時(shí)間

其實(shí)本文所述的功能,最主要就是基于這個(gè) api 來實(shí)現(xiàn)的,它就是元素的 elementtiming 屬性

使用方法也很簡單就是給當(dāng)前要檢測的元素添加該屬性:

  1. <div elementtiming="text"
  2.      測試text 
  3.  </div> 

 

 

然后在通過 PerformanceObserver對象獲取相應(yīng)的數(shù)據(jù):

  1. const observer = new PerformanceObserver((list) => { 
  2.     console.log(list.getEntries()) 
  3.   }); 
  4.   observer.observe({ entryTypes: ["element"] }); 

log 里面就可以獲取到 elementtiming 值為 text 的元素的相關(guān)信息:loadTime(加載時(shí)間)、 renderTime(渲染時(shí)間)等,這里簡單介紹一下不做過多的詳解,大家知道我用它做了什么就好

當(dāng)然,這個(gè) api 在該元素只包含其他元素(無文本),就不會(huì)生成 PerformanceEntry,這個(gè)問題是我在網(wǎng)上百度不到,但是看了 MDN 的案例發(fā)現(xiàn)效果不準(zhǔn)確,在給 chromium 提了 issue后,官方回復(fù)給的答案

issue 鏈接:vue or react local server, new PerformanceObserver().obserbe({ entryTypes: ['element'] }) Incomplete acquisition, but build after the project unstable

這個(gè)過程是很復(fù)雜的,在了解到官方的答復(fù)后,我覺得這樣的 api 它是不完善的,本來還想繼續(xù)在上面鏈接的評論區(qū)繼續(xù)討論,但是抵不住老外手快直接把 bug 給關(guān)了

好吧,那我只能重新起一個(gè)需求出來,和他們討論了:

issue 鏈接:PerformanceObserver api result not what i expected

提了這個(gè)需求后,我還等著討論一下我的這個(gè)需求呢,但是還是很利索的告訴我這里不負(fù)責(zé)這個(gè),讓我去 WICG 那邊提需求。。。

然后我就過去了:

大致的意思就是我想要的是一個(gè)完整的樹狀數(shù)據(jù)表,這樣我可以知道我每一層數(shù)據(jù)的渲染時(shí)間和對應(yīng)子級(jí)的渲染,但是老外沒明白我的意思,跟我說直接獲取到目標(biāo) img 或者含有文本的元素不好嗎,這樣還節(jié)省性能:

這明顯是無法滿足我的需求的,我也只能給他在詳細(xì)的解釋一遍了:

不知道我解釋的清楚不,或者是我的需求是否也是大家需要的歡迎討論,底部會(huì)留聯(lián)系方式或者在該 issue 中討論也行

issue 鏈接:PerformanceObserver api return result not what i need

好了,有關(guān)該 api 在調(diào)研和使用階段出現(xiàn)的問題及我的解決辦法表述先到此為止,重點(diǎn)是整體功能,大家會(huì)用就夠

不把性能檢測相關(guān)的代碼植入到業(yè)務(wù)當(dāng)中,實(shí)現(xiàn)上述需求

如題,我不想把這方面的代碼嵌入到項(xiàng)目當(dāng)中,因?yàn)槿绻且粋€(gè)特別大的項(xiàng)目,我要是寫一堆 performance.mark 我得寫哭了,很顯然這個(gè)方式是不現(xiàn)實(shí)的,然后我就想到是否可以通過 webpack 實(shí)現(xiàn)該需求呢?

那必須可以啊,解析當(dāng)前的內(nèi)容,然后通過拿到對應(yīng)的資源去添加該屬性,但是不建議直接通過內(nèi)容去匹配,比如內(nèi)容是這樣的:

  1. <div class="a"
  2.        this is <div class="a"> element 
  3.    </div> 

 

 

 

哇嘎理工啊,如果直接把 loader 添加到 webpack 的配置當(dāng)中,那么對于整個(gè)項(xiàng)目來說當(dāng)前 loader 訪問到的是當(dāng)前打包文件內(nèi)的所有內(nèi)容,能寫嗎?肯定是不可以的,正則讓你寫到死啊

那通過 babel 解析 ast 去做渲染呢,這樣可以準(zhǔn)確的拿到對應(yīng)的屬性了啊,這樣不就可以了嗎?大概的方向?qū)α?,但是直接使用的情況下,babel 會(huì)對當(dāng)前所有的內(nèi)容資源進(jìn)行轉(zhuǎn)譯,這明顯不是我所需要的:

  1. // unitl.js 
  2. export const fn1 = function() { 
  3.     return 1 
  4.  
  5. // component.js 
  6.  
  7. export default function() { 
  8.     return <div>this is <div class="a"> element </div> 

直接只用 babel 轉(zhuǎn)譯的話,上述的文件都會(huì)通過 babel 轉(zhuǎn)譯一遍,那么這樣對于我們來說并不是合理的,不能因?yàn)闉榱藱z測元素性能而導(dǎo)致頁面構(gòu)建速度變慢嗎?更何況這還不是最優(yōu)解

這時(shí)候我想到了一個(gè)辦法,也是我目前使用的一個(gè)辦法,大家可以看看是否真的是最優(yōu)解,我目前是考慮到這里了:

「通過 webpack plugin 在 build 前,給當(dāng)前模塊添加一個(gè) loader,在當(dāng)前的 loader 內(nèi)去通過 babel 轉(zhuǎn)譯添加 elementtiming」

知道了如何做就開始擼代碼了,下面是調(diào)用方式:

  1. // webpack.config.js 
  2.  
  3. const ElementRenderingWebpackPlugin = require('element-rendering-webpack-plugin'
  4. module.exports = { 
  5.     plugin: [ 
  6.         new ElementRenderingWebpackPlugin() 
  7.     ] 

plugin 的實(shí)現(xiàn)也比較簡單,主要的工作是在 loader 部分:

  1. // element-rendering-webpack-plugin.js 
  2.  
  3. class MyPlugin { 
  4.   apply(compiler) { 
  5.     compiler.hooks.compilation.tap('MyPlugin', (compilation) => { 
  6.       compilation.hooks.buildModule.tap('SourceMapDevToolModuleOptionsPlugin', module => { 
  7.         if (module.resource) { 
  8.           if (/(\.((j|t)sx?)$)/.test(module.resource) &&  
  9.           !/node_modules/.test(module.resource)) { 
  10.             if (module.loaders) { 
  11.               module.loaders.push({ 
  12.                 loader: 'element-rendering-webpack-loader' 
  13.               }) 
  14.             } 
  15.           } 
  16.         } 
  17.       })       
  18.     })   
  19.   } 
  20. module.exports = MyPlugin 

「上面代碼就是在 compilation 生成后,就在模塊 build 前去做模塊的確認(rèn),只對我自己的業(yè)務(wù)和需要的代碼添加該 loader,這樣就可以繞過上面直接使用 babel 方法導(dǎo)致的構(gòu)建速度問題」

在此要對文件做一些過濾,因?yàn)槭?1.0 的出版,所以還有一些東西沒有完全考慮,還需要繼續(xù)優(yōu)化,這里提示一下暫時(shí)是不支持 vue 使用的,vue 模塊的 loader 太多了,我要多做測試才敢上線,還希望大家體諒

  1. // element-rendering-webpack-loader.js 
  2.  
  3.  
  4. const parser = require('@babel/parser'); 
  5. const traverse = require('@babel/traverse').default
  6. const { transformFromAstSync } = require('@babel/core'); 
  7. const t = require('@babel/types'); 
  8. let randomSet = new Set(); 
  9.  
  10. function UpdateAssets(asset) { 
  11.   let code = asset 
  12.   try { 
  13.     const ast = parser.parse(asset, { 
  14.       sourceType: 'module'
  15.       plugins: [ 
  16.         'flow'
  17.         'jsx' 
  18.       ] 
  19.     }); 
  20.     traverse(ast, { 
  21.       JSXElement(nodePath) { 
  22.         if (nodePath.node.type === 'JSXElement' && nodePath.node.openingElement.name.name === 'img') { 
  23.           return 
  24.         } 
  25.         updateAttr(nodePath.node); 
  26.       } 
  27.     }) 
  28.     code = transformFromAstSync(ast).code; 
  29.   } catch(e) { 
  30.     console.log(e) 
  31.   } 
  32.   return code; 
  33.  
  34. function updateAttr(node) { 
  35.   if (node.type === 'JSXElement') { 
  36.     let { openingElement, children } = node; 
  37.     let name = openingElement.name.name || openingElement.type 
  38.     let className = openingElement.attributes.filter(attr => { 
  39.       if (attr.type === 'JSXSpreadAttribute'return false 
  40.       return /class(Name)?/.test(attr.name.name
  41.     }) 
  42.     if (className.length) { 
  43.       name = className[0].value.value 
  44.     } 
  45.     if (!openingElement) return 
  46.     const elementtimingList = openingElement.attributes.filter(attr => { 
  47.       if (attr.type !== 'JSXSpreadAttribute' && attr.name.name === 'elementtiming') { 
  48.         return true 
  49.       } 
  50.     }) 
  51.     if (!elementtimingList.length) { 
  52.       openingElement.attributes.push(addElementttiming(name + '-' + Math.ceil(Math.random() * 100000))); 
  53.     } 
  54.     const markList = openingElement.attributes.filter(attr => { 
  55.       if (attr.type !== 'JSXSpreadAttribute' && attr.name.name === 'data-mark') { 
  56.         return true 
  57.       } 
  58.     }) 
  59.     if (!markList.length) { 
  60.       openingElement.attributes.push(addMark()); 
  61.     } 
  62.     children.map(childNode => updateAttr(childNode)); 
  63.   } 
  64.  
  65. function addElementttiming(name) { 
  66.   return t.jsxAttribute(t.jsxIdentifier('elementtiming'), t.stringLiteral(name)); 
  67.  
  68. function addMark() { 
  69.   let randomStatus = true
  70.   let markRandom = 0; 
  71.   while(randomStatus) { 
  72.     markRandom = Math.ceil(Math.random() * 100000); 
  73.     randomStatus = randomSet.has(markRandom); 
  74.     if (!randomStatus) { 
  75.       randomSet.add(markRandom); 
  76.     } 
  77.   } 
  78.   return t.jsxAttribute(t.jsxIdentifier('data-mark'), t.stringLiteral(markRandom + '')); 
  79.  
  80. module.exports = UpdateAssets; 

這里直接上代碼了,東西太多就不一行一行解釋了,代碼會(huì)開源,鏈接在底部自取慢慢看

「大概做的就是把當(dāng)前跑進(jìn)來的代碼通過 ast 轉(zhuǎn)譯,拿到 ast 對象后添加 elementtiming 屬性,data-mark 是用來做數(shù)據(jù)去重的」

好了,這時(shí)候最基礎(chǔ)的 「捕獲數(shù)據(jù)」 和 「不把性能檢測相關(guān)的代碼植入到業(yè)務(wù)當(dāng)中,實(shí)現(xiàn)上述需求」,那么接下來就該通過瀏覽器插件來展示這些數(shù)據(jù)

通過瀏覽器插件來展示這些數(shù)據(jù)

由于之前是真心沒寫過 chrome-extension ,可踩了不少坑,很多 version 2 可以用的東西 version 3 不支持

這里我直接就上核心部分的代碼了,剩下一些基礎(chǔ)配置類的大家自己到時(shí)候看代碼吧:

  1. // contentScript.js 
  2.  
  3. chrome.runtime.onMessage.addListener(function(request) { 
  4.   const { type, data } = request.data 
  5.   switch(type) { 
  6.     case 'selectedElement'
  7.       createMask(data) 
  8.       break; 
  9.     case 'cancelElement'
  10.       cancelMask() 
  11.       break; 
  12.   } 
  13. }) 
  14.  
  15. function createMask(data) { 
  16.   cancelMask() 
  17.   const div = document.createElement('div'
  18.   Object.keys(data).map(styleKey => div.style[styleKey] = data[styleKey] + 'px'
  19.   div.style.position = 'absolute' 
  20.   div.style.background = 'rgba(109, 187, 220, 0.5)' 
  21.   div.style.zIndex = '9999' 
  22.   div.id = 'mask-element' 
  23.   document.body.appendChild(div) 
  24.  
  25. function cancelMask() { 
  26.   const maskElement = document.querySelector('#mask-element'
  27.   if (maskElement !== null) { 
  28.     document.body.removeChild(maskElement) 
  29.   } 
  30.  
  31. function getElementTreeData(element, elementTreeData, performanceElementTimingObj) { 
  32.   let children = element.children 
  33.   for (let i = 0; i < children.length; ++i) { 
  34.     let childElement = children[i] 
  35.     let argObj = {} 
  36.     let nodeValue = '' 
  37.     let parsePerformanceElementTiming = {} 
  38.     if ('elementtiming' in childElement.attributes) { 
  39.       nodeValue = childElement.attributes.elementtiming.nodeValue 
  40.       argObj['elementtiming'] = true 
  41.       argObj['key'] = childElement.dataset.mark 
  42.       let performanceElementTiming = performanceElementTimingObj[argObj['key']] 
  43.       if (performanceElementTiming) { 
  44.         parsePerformanceElementTiming = JSON.parse(JSON.stringify(performanceElementTiming)) 
  45.       } 
  46.     } else { 
  47.       nodeValue = childElement.nodeName 
  48.       argObj['key'] = Math.ceil(Math.random() * 100000) 
  49.     } 
  50.     argObj = Object.assign({}, argObj, parsePerformanceElementTiming, { 
  51.       intersectionRect: childElement.getBoundingClientRect() 
  52.     }) 
  53.     if (/(NO)?SCRIPT/.test(nodeValue)) continue 
  54.     argObj['children'] = childElement.children.length ? getElementTreeData(childElement, [], performanceElementTimingObj) : [] 
  55.     argObj['title'] = nodeValue.replace(/-([0-9]*)$/, ''
  56.     elementTreeData.push(argObj) 
  57.   } 
  58.   return elementTreeData 
  59.  
  60. let performanceElementTimingList = [] 
  61. const observer = new PerformanceObserver((list) => { 
  62.   let elementTree = [] 
  63.   let performanceElementTimingObj = {} 
  64.   performanceElementTimingList = performanceElementTimingList.concat(list.getEntries()) 
  65.   performanceElementTimingList.map(performanceTimingItem => { 
  66.     if (performanceTimingItem.element !== null) { 
  67.       return performanceElementTimingObj[performanceTimingItem.element.dataset.mark] = performanceTimingItem 
  68.     } 
  69.   }) 
  70.   chrome.runtime.sendMessage( 
  71.     { 
  72.       type: 'performanceTree'
  73.       data: getElementTreeData(document.body, elementTree, performanceElementTimingObj) 
  74.     } 
  75.   ) 
  76. }); 
  77. observer.observe({ entryTypes: ["element"] }); 

contentScript 是 chrome-extension 內(nèi)訪問頁面元素的一個(gè)配置文件,當(dāng)然文件名自己隨便取,為了方便閱讀和理解,我直接跟著官方文檔的節(jié)奏走的,這里大家可以發(fā)現(xiàn)我上面有一個(gè)方法是 createMark 里面有創(chuàng)建元素和定位,這里是配合 devtools 里面的樹來使用的:

  1. // app.js  
  2. import { useState, useEffect } from 'react'
  3. import { Tree } from 'antd'
  4. import './App.css'
  5. function App() { 
  6.   const [treeData, setTreeData] = useState([])  
  7.   window.addEventListener('message', msg => { 
  8.     const { type, data } = msg.data 
  9.     if (type === 'performanceTree') { 
  10.       setTreeData(data) 
  11.     } 
  12.   }) 
  13.   useEffect(() => { 
  14.   }, [treeData]) 
  15.   return ( 
  16.     <div className="App"
  17.       <Tree 
  18.         showLine 
  19.         titleRender={ 
  20.           nodeData => { 
  21.             return ( 
  22.               <div onMouseOver={() => { selectedElement(nodeData) }} onMouseOut={cancelElement}> 
  23.                 {nodeData.title}{updateTime(nodeData)} 
  24.               </div> 
  25.             ) 
  26.           } 
  27.         } 
  28.         treeData={treeData} 
  29.       /> 
  30.     </div> 
  31.   ); 
  32.  
  33. function updateTime(nodeData) { 
  34.   let str = ' - ' 
  35.   if (nodeData.renderTime) { 
  36.     str += Math.round(nodeData.renderTime) 
  37.   } else { 
  38.     str += '該元素下非元素外不存在文本' 
  39.   } 
  40.   return str 
  41.  
  42. function selectedElement(nodeData) { 
  43.   console.log('selectedElement'
  44.   if (!nodeData.disabled) { 
  45.     postMessage( 
  46.       { 
  47.         type: 'selectedElement'
  48.         data: nodeData.intersectionRect 
  49.       }, 
  50.       '*' 
  51.     ) 
  52.   } 
  53.  
  54. function cancelElement () { 
  55.   console.log('cancelElement'
  56.   postMessage( 
  57.     { 
  58.       type: 'cancelElement' 
  59.     }, 
  60.     '*' 
  61.   ) 
  62.  
  63. export default App; 

為了頁面的美觀度,我用了 antd 去對頁面ui進(jìn)行優(yōu)化的,當(dāng)點(diǎn)擊某一個(gè)樹的時(shí)候,會(huì)畫一個(gè)框出來,標(biāo)明當(dāng)前元素的時(shí)間和對應(yīng)的元素在哪里:

這就是最后的效果,我是直接 react 腳手架搭完直接安裝的

尾聲

大概的實(shí)現(xiàn)思路和思考的過程,基本上我都描述的差不多了,過程當(dāng)中有很多次想過放棄,但是又不忍心拋棄自己之前的付出所以就堅(jiān)持下來了,也算是做出來的了,但是 elementtiming api 那里那個(gè)問題,還是需要我繼續(xù)研究和解決的,我會(huì)繼續(xù)和 WICG 那邊溝通,爭取可以讓它變得更好

可能有大佬看見會(huì)說這東西很簡單啊,沒什么值得思考地方,那我只想說dddd,我比較菜,得一步一步的學(xué),你們輕點(diǎn)噴哈

代碼開源了已經(jīng),歡迎大家互相討論學(xué)習(xí),也希望大家給點(diǎn)點(diǎn) star,多提 issue,如果有興趣的朋友我還希望大家一起來維護(hù)這個(gè)東西:

plugin: element-rendering-webpack-plugin

loader: element-rendering-webpack-loader

extension: element-rendering-extension

 

責(zé)任編輯:武曉燕 來源: 前端壹棧
相關(guān)推薦

2016-03-02 10:31:13

Siri語音搜索iOS

2023-01-18 23:52:07

RTA用戶粒度運(yùn)營

2024-01-10 14:40:56

顆粒度開發(fā)微服務(wù)

2012-07-25 09:15:16

盜版者客戶

2022-08-03 09:11:31

React性能優(yōu)化

2025-03-31 01:55:00

2021-03-22 11:10:09

Redis架構(gòu)MQ

2020-06-28 08:34:07

架構(gòu)師阿里軟件

2012-05-24 14:58:55

開源代碼

2021-07-06 10:03:05

軟件開發(fā) 技術(shù)

2021-02-05 15:35:21

Redis數(shù)據(jù)庫命令

2023-10-16 07:11:50

SSD原廠顆粒數(shù)據(jù)

2017-04-11 13:52:02

華為

2017-04-12 16:27:52

華為

2023-12-29 08:29:15

QPS系統(tǒng)應(yīng)用

2012-11-28 01:47:35

軟件測試測試

2023-03-21 17:06:24

樹莓派路由器

2021-09-09 09:29:27

AI

2021-06-03 08:01:12

JVM性能優(yōu)化

2009-06-10 22:00:57

JavaScript腳
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)