百度程序員開發避坑指南(前端篇)

日常工作開發中,遇到哪些坑是讓你印象深刻且具有挑戰的,它們是怎麼產生的,我們該如何避免?本期我們帶來與前端開發相關的三個問題:一次網頁資源加載問題的定位過程;CSS中的z-index層疊覆蓋問題;CSS3 transform 屬性對 position 的影響,希望能爲你的技術提升助力。

 

01

一次網頁資源加載問題的定位過程

 

今天討論的話題比較聚焦,就是有用戶報Web頁面資源或局部區域加載不出來的問題,該如何查?又該如何避免?

 

1.1 問題描述

用戶反饋頁面內的推薦列表加載不出來,而且能穩定復現。

 

1.2 分析

第一反應就是自己復現,但是不行啊,團隊沒有同學能復現,也無法接觸到用戶。那麼先理清楚此區域的實現邏輯,該區域的HMTL雖然已經下載,但默認不可見,而是依賴c.js(文件名簡化了)展現。基於此,我們懷疑:

  • c.js 以及其依賴的a.js, b.js等 加載非常慢或直接失敗;

  • c.js 以及其依賴邏輯發生JS異常。

 

1.3 監控實現

那麼該如何佐證,我們需要做下監控。

做監控需要考慮到實際場景,由於Head和Body都有外鏈請求,因此監控代碼要置於頂部且內聯,且代碼要精簡,從而可以最大程度拿到信息和減少首屏影響。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
window.addEventListener('error', event => {    if (event.srcElement !== window) {        console.log('資源加載失敗,加載資源的元素是:', event.srcElement);        send();  }    else {        console.log('JS報錯:', event.message);        send();    }}, true);
window.addEventListener('unhandledrejection', event => {    const error = event.reason || {};    console.log('JS報錯:', error);    send();});

 

上述可以監控到全局的 資源加載失敗 和 JS異常,包括未捕獲的Promise Rejection。此外,還需定位資源,通過向上遍歷獲取元素的XPath,XPath上帶上id或class屬性,避免全都是`div>div>div>div`,而摸不着頭腦。

 

上線後發現,有不少錯誤都是script error,這是因爲外鏈JS都是CDN域名。瀏覽器對於腳本執行遵守 same-origin policy ,爲避免域名信息泄漏給宿主域名,特意隱藏錯誤信息。

解決方案是給所有外鏈JS加上響應頭。

  •  
access-control-allow-origin: *

JS資源若託管在雲上CDN服務,那麼其CDN控制檯都會提供增加Header功能。

若是自有服務,就需要主動設置Header。這裏假設使用Koa2 框架,設置如下:

  •  
ctx.set('Access-Control-Allow-Origin', '*');

然後給所有該CDN域名的script元素增加crossorigin屬性,如:

  •  
<script src="https://cdn.a.com/static/a.js" crossorigin="anonymous"></script>

但發現其在Chrome瀏覽器是生效的,可以拿到真實錯誤堆棧。可惜,Safari瀏覽器不支持此機制,依然只能拿到script error。爲了Safari拿到真實錯誤,外鏈需要是同域。因此有個辦法是在www.a.com的Nginx做一層代理轉發,即 https://www.a.com/static/a.js 轉發到 https://cdn.a.com/static/a.js  。不過如此一來,就喪失CDN就近訪問的優勢,不適合常態化。但可以在定位用戶問題時,將用戶訪問臨時切換成 https://www.a.com/static/a.js。

 

那如何監控加載慢,可以在onload事件後獲取慢資源。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
const list = performance.getEntriesByType('resource');const len = list.length;const slowList = [];for (let i = 0; i < len; i++) {     const timing = list[i];     // 大於1s     if (timing.duration > 1000) {         slowList.push(timing);     }}send(slowList);

 

上報信息也包含了每個請求的TCP、下載等耗時,詳細可見Resource_Timing_API規範(https://developer.mozilla.org/en-US/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API)

要注意的是,該API信息受到CORS (https://developer.mozilla.org/en-US/docs/Glossary/CORS)影響,必須給資源設置如下響應頭才能拿到數據,否則很多字段取值都是0。

Timing-Allow-Origin: *

通過分析這些數據,我們可以知道資源耗時過程,這裏可以充分參照Chrome開發者工具提供的Timing子面板,瞭解關鍵時段耗時。比如同域名6個併發請求限制,若資源請求較多,則可能處於stalled(掛起等待)狀態。這些階段可能原因分析參考Chrome說明



圖片

 

由於有些圖片在頁面內不好確定位置,因此需要獲取其XPath,如何獲取呢?對於img標籤加載圖片,可以快速獲取:

  •  
  •  
  •  
// timing.name就是資源地址,適合const img = document.body.querySelector('img[src="' + timing.name + '"]');const xpath = getxpath(img);

 

額外信息獲取,其實除了問題信息本身外,用戶當時環境信息非常重要,如

1. performance.memory:獲取內存使用信息;

2. navigator.connection:用戶連接情況,可惜,此API不怎麼準;

如果是APP內頁面,可以調用端能力獲取到用戶網絡類型、網速。

 

1.4 數據分析

上線後,用戶依然訪問慢。根據日誌發現,用戶未有JS報錯,但根據慢資源日誌,發現有大量資源加載超過1s,有些資源甚至達到10s,其花費在建連階段耗時較大。看來問題還不是這麼簡單,那如何解釋用戶資源加載如此慢?用戶所在局域網環境差?接入運營商質量差?插件影響?CDN問題?

 

這些問題只有CDN可以去排查,我們首先拿到用戶訪問 https://www.a.com 的服務器端日誌,獲取到用戶IP。聯合雲CDN服務,根據用戶IP,竟然未查到任何CDN訪問日誌。但從日誌看,用戶確實請求了大量JS,並不是全部本地緩存。所以用戶IP可能不是訪問CDN的IP,有一種情況是使用代理。

 

所以想通過其他信息撈取用戶的CDN日誌,來確定訪問CDN的IP。思路是按照用戶UA、請求地址等非精確信息撈取CDN日誌,但由於撈取日誌量非常大,無法確定哪些日誌是該用戶的;而:

 

1.CDN域名一般是無cookie域名,沒有TRACEID;

2.Web頁面無法給script/link資源請求頭設置Traceid;

3.前端目前無法拿到CDN TCP建連IP;

綜上,除了用戶IP,種種精確定位信息都無效。因此這一步舉步維艱。

 

好在能聯繫上用戶,諮詢是否使用代理,結果:確實使用了代理,且根據用戶代理,實測代理非常慢,甚至加載超時,其代理請求走了香港服務器。通過代理出口IP,最終從海外CDN撈取到了用戶訪問日誌。同時也確認,用戶將所有 *.a.com 請求配置不使用代理,所以服務端能拿到的IP是用戶IP,而CDN拿到的是代理出口IP。

 

1.5 總結

Web能力終是有限,但通過足夠的輔助信息,以及全鏈路協作,在定位Case過程中也會事半功倍。該Case雖然歸因於用戶不合理代理配置,但也可基於此場景做技術優化。如任何首屏內容不依賴外鏈展現,減少核心邏輯JS體積,增加資源緩存率等。

 

 

02

CSS中的z-index層疊覆蓋問題

 

 

2.1 含義

z-index屬性指定了元素及其子元素的【z順序】,而【z順序】可以決定當元素髮生覆蓋的時候,哪個元素在上面。通常一個較大的z-index值的元素會覆蓋較低的那一個。

 

屬性指定兩件事:

  • 當前元素的堆疊順序

  • 當前元素是否建立新的堆疊上下文

 

2.2 屬性值

  • 默認值:z-index:auto;

  • 整數值:z-index:<integer>;

  • 繼承:z-index:inherit;

 

2.3 基本特性

  • 在CSS2.1時代,需要和定位元素配合使用;

  • 如果定位元素z-index沒有發生嵌套(並列的):

    • 後來者居上的準則;

    • 哪個大哪個上(z-index大小比較);

  • 如果定位元素z-index發生嵌套:

    • 祖先優先原則(前提:z-index是數值,不是auto)

 

以上爲z-index的基本介紹。

 

當業務越來越複雜,多種彈窗、toast、浮層各種組件,多人協同業務開發的情況下:

  • 老業務寫了個z-index:5000;

  • B同學調用一個全局彈層,原本設置爲100,想要覆蓋全局,z-index改爲10000;

  • C同學調用一個toast,原本設置爲2000,想要覆蓋彈層,z-index改爲100000;

  • ...

  • 層層覆蓋的情況下,不能無限改下去,爲了避免這樣的情況發生,減少不斷覆蓋的情況,那麼應該如何規定z-index的值呢;

 
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
const KEY = '_tbv_z_index_';
function initZIndex$() {    return (window[KEY] = 10000);}
// 初始化,只能被調用一次function once(fn) {    const flag = true;
    return function () {        if (flag) {            flag = false;            const args = Array.prototype.slice.call(arguments, 0);            fn.apply(args);        }    };}const initZIndex = once(initZIndex$);
// 外部調用&支持重置function zIndex$(zIndex) {    if (zIndex) {        return (window[KEY] = zIndex);    }
    return (window[KEY] += 1);}
// 組件mount時觸發+1const zIndex = zIndex$();

注意:還是要規範z-index的配置,不要亂用濫用隨意賦值,根據依賴規則合理使用;

 

 

03

CSS3 transform 屬性對 position 的影響

 

CSS3中引入了transform,定義了在二維或三維空間中元素的旋轉、縮放、平移等行爲,還能利用合成層原理開啓GPU加速,提升頁面動畫的流暢度。然而transform也不是「省油的燈」(並沒有說它不好的意思,我就很喜歡它),增強了頁面交互效果的同時它也有一些「副作用」容易讓人踩坑。

 

position: fixed 實現了固定定位的效果,元素不追隨滾動條進行滾動,普通元素的overflow屬性也無法對其進行裁剪,因此在一些需要固定頭部、固定懸浮按鈕的場景中十分好用。

 

但fixed遇上transform時表現的就不再那麼「強硬」,反而退化成了position: absolute的效果。在外層沒有transform影響時,固定定位元素的包含塊是根元素,可以近似認爲是<html>元素,因此fixed元素可以實現相對視口定位的效果。而當元素設置了transform時,便會創建一個新的包含塊(containing block),如果該元素的內部有元素設置了fixed定位,那麼該fixed元素的包含塊便不再是根元素,而變成了被設置了transform的元素。如果在開發過程中發現設置了position:fixed的元素隨着頁面滾動了,就可以看下fixed的元素外層是否有元素設置了transfrom。

 

除了包含塊之外,transform還會生成新的層疊上下文(stack context),使得元素內部和外部的z-index相互獨立,出現低z-index元素層級比高z-index元素還高的情況:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章