主頁 > 企業開發 > Javascript 例外處理的一些經驗

Javascript 例外處理的一些經驗

2022-11-02 08:45:00 企業開發

 

 

寫在前面

 

為了提升應用穩定性,我們對前端專案開展了腳本例外治理的作業,對生產上報的js error進行了整體排查,試圖通過降低腳本例外的發生頻次來提升相關告警的準確率,結合最近在這方面閱讀的相關資料,嘗試階段性的做個總結,下面我們來介紹下js例外處理的一些經驗,

 

先說概念

 

什么是例外

 

先來看一下官方的定義:

 

Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions. 

 

描述的很簡單,我們總結一下就是代碼在執行程序中遇到了問題,程式已經無法正常運行了,Error物件會被拋出,這一點它不同于大部分編程語言里使用的例外物件Exception,甚至更適合稱之為錯誤,應該說事實也確實如此,Error物件在未被拋出時候和js里其他的普通物件沒有任何差別是不會引發例外的,同時Error 物件也可用于用戶自定義錯誤的基礎物件,

 

看下面兩個例子:

try {
  const 123variable = 2;
} catch(e) {
  console.log('捕獲到了:', e)
}

↓↓↓執行結果↓↓↓

 

 

 結論:只有在執行程序中的例外可以被捕獲,語法決議階段的例外或者不在當前同步任務中的例外都無法被捕獲,

<script>
  function throwSomeError() {
    throw new Error('拋個例外玩玩');
    console.log('我估計是涼了,不會執行我了!');
  }

  throwSomeError();
  console.log('那么我呢?')
</script>

<script>
  console.log('大家猜猜我會執行嗎?');
</script>

↓↓↓執行結果↓↓↓

 

 

 

以上紅色資訊里包含了例外資訊(message)和堆疊跟蹤(stack trace)資訊,對于定位代碼中的問題起到重要作用,可以看到堆疊跟蹤是從底部檔案位置21:15到頂部25:7位置的;前兩個console在遇到例外時候未被執行,第二個script標簽內的代碼被正常執行,

 

結論:當任務執行程序中出現未處理的例外,會一直沿著呼叫堆疊一層層向外拋出(有點像事件冒泡),最侄訓導致當前任務被終止執行,當前任務終止后JS 執行緒會繼續從任務佇列中提取下一個任務繼續執行,

 

例外的型別

錯誤名

描述

示例

EvalError

關于 eval [1]函式的錯誤,已不在當前ECMAScript規范中使用,不再會被運行時拋出,

throw new EvalError('EvalError', 'file.js', 10); // 可以由業務代碼主動拋出

RangeError

值不在允許的范圍內,典型的是試圖傳遞一個數值給一個范圍內不包含該數值的函式,此時應該引發RangeError,

const numObj = 123;

numObj.toFixed(-1); // Uncaught RangeError: toFixed() digits argument must be between 0 and 100 at Number.toFixed

ReferenceError

當一個不存在(或尚未初始化)的變數被參考時發生的錯誤,

const a = undefinedVariable; // Uncaught ReferenceError: undefinedVariable is not defined

SyntaxError

決議代碼階段,發現了不符合語法規范的代碼,

const 111variable = 1; // Uncaught SyntaxError: Invalid or unexpected token

TypeError

型別錯誤,用來表示值的型別是非預期型別,

const a = null;

a.doSomeThing(); // Uncaught TypeError: Cannot read properties of null (reading 'doSomeThing')

URIError

使用URI處理函式產生的錯誤

decodeURIComponent('%') // Uncaught URIError: URI malformed

 

1.以上這些例外很多都來會由Javascript引擎拋出,但例外型別都是實際的建構式,旨在生成一個新的例外實體,所以你可以:

// 獲取分頁資料
const getPagedData = https://www.cnblogs.com/88223100/archive/2022/11/02/(pageIndex, pageSize) => {
  if(pageIndex < 0 || pageSize < 0 || pageSize > 1000) {
    throw new RangeError(`pageIndex 必須大于0, pageSize必須在0和1000之間`);
  }

  return [];
}

// 轉換時間格式
const dateFormat = (dateObj) => {
  if(dateObj instanceof Date) {
    return 'formated date string';
  }

  throw new TypeError('傳入的日期型別錯誤');
}

2.Error實體被創建時不能被稱之為例外,只有在使用throw關鍵字將其拋出時才會引發例外;

new Error('出錯了!');
console.log('我吃嘛嘛香,喝嘛嘛棒!'); // 正常輸出 '我吃嘛嘛香,喝嘛嘛棒!'

3.技術上來講,你可以拋出任何型別的例外,而不僅僅是Error的實體,但請不要這么做,總是拋出正確的錯誤物件會讓我們更容易定位問題,同時可以保持錯誤處理的一致性,捕獲例外時候也總能夠拿到Error實體上的message和stack;

// bad
throw '出錯了';
throw 123;
throw [];
throw null;

 

例外捕獲

前面有提到如果引發例外后不做任何處理會冒泡似的在你的呼叫堆疊中向頂部傳播,直到導致當前任務崩潰,有時候發生致命錯誤時候我們確實希望安全的停止程式的運行,如果希望程式得以恢復一般我們會用到try...catch...finally代碼結構,它是js中處理例外的標準方式;

try {
  // 要運行的代碼,可能引發例外
  doSomethingMightThrowError();
} 
catch (error) {
  // 處理例外的代碼塊,當發生例外時將會被捕獲,如果不繼續throw則不會再向上傳播
  // error為捕獲的例外物件
  // 這里一般能讓程式恢復的代碼
  doRecovery();
}
finally {
  // 無論是否出現例外,始終都會執行的代碼
  doFinally();
}

被忽略的finally:此陳述句塊會在try和catch陳述句結束之后執行,無論結果是否報錯,

 

同時要注意,異步中的發生的例外無法被上層捕獲,比如:

// Timeout
try {
  setTimeout(() => {
    throw Error("定時器出錯了!");
  }, 1000);
} catch (error) {
  console.error(error.message);
}

// Events
try {
  window.addEventListener("click", function() {
    throw Error("點擊事件出錯了!");
  });
} catch (error) {
  console.error(error.message);
}

Promise本身是就可以捕獲例外,語法上也類似于try catch,一旦發生例外,程式跳過promise內的代碼繼續執行;可以使用了catch方法捕獲后進行處理,也可以使用then方法中的第二個引數處理例外,promise的例外物件同樣是冒泡的,前者捕獲了就不會拋給后者,參見示例:

const promiseA = new Promise((resolve,reject)=>{
   throw new Error('Promise出錯了!');
});

const doSomethingWhenResolve = () => {};

const doSomethingWhenReject = (error) => {
  logger.log(error)
}

// 使用catch捕獲
const promiseB = promiseA.then(doSomethingWhenResolve).catch(doSomethingWhenReject);
// 等價于
const promiseB = promise.then(doSomethingWhenResolve, doSomethingWhenResolve);

promiseB.then(() => {
  console.log('我又可以正常進到then方法了!');
}).catch(()=>{
  console.log('不會來這里!');
})

如何處理例外

 

例外的發生不可避免,所以在軟體開發中,合理的例外處理就成為了高質量代碼不可或缺的一部分,只有處理好了例外我們才能對程式中的意外情況進行有效的控制,我們最容易容易犯的一個問題就是將例外處理和業務的流程混為一談,

 

根據Clean Code的建議,面對例外我們可以遵循以下一些原則,提高代碼質量:

 

Prefer Exceptions to Returning Error Codes 

 

優先選擇例外而不是錯誤碼,

 

要理解這句話還是得結合例子,下面的第一段代碼定義了一個Laptop類,在它的sendShutDown方法實作中,用if陳述句去檢查了getID的回傳值中是否存在無效的deviceID,錯誤檢查會使呼叫者的代碼變得復雜不易閱讀業務邏輯,同時如果這個錯誤檢查被遺漏也會導致代碼出現問題,這個錯誤的處理可以交給語言讓整個程序更加優雅,第二段代碼中則將例外處理隔離了兩個不同的邏輯,這樣做會帶來一些優勢:

 

1.業務流程更加清晰易讀,我們把例外和業務流程理解為兩個不同的問題,可以分開去處理;

2.分開來的兩個邏輯都更加聚焦,代碼更簡潔;

3.將處理程式例外的職責交給了編程語言,明確了邊界;

// Dirty
class Laptop {
  sendShutDown() {
    const deviceID = getID(DEVICE_LAPTOP);
    if (deviceID !== DEVICE_STATUS.INVALID) {
      pauseDevice(deviceID);
      clearDeviceWorkQueue(deviceID);
      closeDevice(deviceID);
    } else {
      logger.log('Invalid handle for: ' + DEVICE_LAPTOP.toString());
    }
  }
  getID(status) {
    ...
    // 總是會回傳deviceID,無論是不是合法有效的
    return deviceID;
  }
}
// Clean
class Laptop {
  sendShutDown() {
    try {
      tryToShutDown();
    } catch (error) {
      logger.log(error);
    }
  }

  tryToShutDown() {
    const deviceID = getID(DEVICE_LAPTOP);
    pauseDevice(deviceID);
    clearDeviceWorkQueue(deviceID);
    closeDevice(deviceID);
  }

  getID(status) {
    ...
    throw new DeviceShutDownError('Invalid handle for: ' + deviceID.toString());
    ...
    return deviceID;
  }
}

Don't ignore caught error!

捕獲到例外后不要忽略例外處理!

 

在之前的代碼評審中就經常有看到我們同學會在catch塊中什么都不做,或者迫于eslint的檢查會寫一個console.log(error),這同樣意味著什么都沒有做,屬于眼睜睜看到例外發生了不采取任何措施,這樣的處理方式非常危險,因為這些例外通常由我們沒有考慮到的意外情況引起,從中能發現業務邏輯中不易發現的問題,一旦我們捕獲了這些例外,頂層的錯誤監控也不能主動捕獲到這些問題,程式也許沒有崩潰但如果沒有用戶告知我們,我們就無法發現用戶的哪些功能無法正常使用了,因此最起碼也要對這些例外做日志上報;

// bad
try {
  doSomethingMightThrowError();
} catch (error) {
  console.log(error);
}

// good
try {
  doSomethingMightThrowError();
} catch (error){
  console.error(error);
  message.error(error.message);
  logger.log(error);
}

Don't ignore rejected promises!

 

不要輕易忽略Promise的例外,除非你確定它已經被處理了!

 

這一塊我們還是有血淚教訓的,在接入AEM的專案中曾經在腳本例外的上報里將disable_unhandled_rejection開啟,禁止捕獲了所有Promise例外,當時是基于我們線上應用大部分的promise例外都是umi-request請求介面出錯和antd表單驗證錯誤,且未帶來什么線上問題,于是就天真的認為未捕獲的promise例外毫無危害;這個想法同樣危險,因為深入跟蹤發現介面請求出錯請求庫捕獲了例外并使用了message.error進行處理,表單驗證錯誤的例外同樣是antd在處理完之后選擇繼續向上拋出,這兩者確實沒什么危害,可當我們面對這些更多未做處理的Promise例外時候(比如介面回傳成功但約定的資料格式錯誤)同時又不做上報,我們就損失了很多線上問題的案發現場,只能抓瞎去盲猜復現,依賴用戶反饋,

 

查看以下案例:

// bad
fetchData().then(doSomethingMightThrowError).catch(console.log);

// good
fetchData()
 .then(doSomethingMightThrowError)
 .catch(error => {
   console.error(error);
   message.error(error.message);
   logger.log(error);
 });

Exceptions Hierarchy

 

使用自定義例外,讓例外層次結構分明,

 

管理好業務代碼中的例外是非常酷的一件事,上面章節有介紹到Javascript給我們提供的一些基礎的例外型別,這些例外型別并不與我們的業務相關,所以使用這些例外來控制代碼中的錯誤也顯得不那么恰當,我們的代碼正是對我們業務的建模,同樣的,我們也要將與業務相關的這些例外建模管理,對例外進行語意化,并在業務邏輯發生特定情況時觸發,否則就算呼叫方捕獲了例外也不知道該如何去處理,

 

這樣做往往會帶來一些好處:

 

1.使用error instanceof CustomBizError更容易識別例外,會讓判斷邏輯更簡潔且已讀,更容易處理捕獲到的例外并恢復程式,

2.通過標準化我們的自定義錯誤類,讓我們更容易做上層處理,比如上面有提到的介面例外我可以選擇不作為腳本例外全域上報,因為通常在介面例外里就已經上報了該資訊;

 

參考以下例子

 

export class RequestException extends Error {
  constructor(message) {
    super(`RequestException: ${mesage}`);
   }
}

export class AccountException extends Error {
  constructor(message) {
    super(`AccountException: ${message}`);
  }
}

const AccountController = {
  getAccount: (id) => {
    ...
    throw new RequestException('請求賬戶資訊失敗!');
    ...
  }
}

// 客戶端代碼,創建賬戶
const id = 1;
const account = AccountController.getAccount(id);
if(account){
  throw new AccountException('賬戶已存在!');
}

Provide context with exceptions

 

提供例外背景關系

 

例外一旦發生了,一般都會有例外資訊(message)和堆疊跟蹤(stack trace)資訊還有檔案名之類的來定位發生錯誤的現場,但哪怕是這樣在定位起來還是比較困難,所以一般建議去豐富例外資訊讓我們定位問題更加的快速,可以是在捕獲到例外的地方解釋我們的意圖,同時這些額外的資訊也都應該只是面向我們開發者用以定位問題,不需要讓使用者去感知這些例外背景關系,不在用戶界面中進行體現,

 

結合上一條的自定義錯誤,我們還要為這些自定義錯誤提供更加豐富個背景關系,

 

React 中的建議

 

區域UI的JS Error不應該導致整個應用崩潰白屏,我們應該把他的影響范圍控制在最小,這是一個容易形成共識的結論,于是React 16引入了錯誤邊界(Error Boundaries)的概念,

 

React Error Boundaries 官方檔案[2] 里提到:

 

錯誤邊界是一種 React 組件,這種組件可以捕獲發生在其子組件樹任何位置的 JavaScript 錯誤,并列印這些錯誤,同時展示降級 UI,而并不會渲染那些發生崩潰的子組件樹,錯誤邊界可以捕獲發生在整個子組件樹的渲染期間、生命周期方法以及建構式中的錯誤,

 

ProComponents[3]的很多組件應該都有使用Error Boundaries比如ProTable,用以例外發生時只對區域UI產生影響,查看@ant-design/pro-utils中的原始碼可以看到和官網的處理別無二致,更多的資訊查看官網有非常詳細的介紹:

import { Result } from 'antd';
import type { ErrorInfo } from 'react';
import React from 'react';

// eslint-disable-next-line @typescript-eslint/ban-types
class ErrorBoundary extends React.Component<
  { children?: React.ReactNode },
  { hasError: boolean; errorInfo: string }
> {
  state = { hasError: false, errorInfo: '' };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, errorInfo: error.message };
  }

  componentDidCatch(error: any, errorInfo: ErrorInfo) {
    // You can also log the error to an error reporting service
    // eslint-disable-next-line no-console
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <Result status="error" title="Something went wrong." extra={this.state.errorInfo} />;
    }
    return this.props.children;
  }
}

export { ErrorBoundary };

所以給我們的啟示是組件庫或者業務系統中的塊級的一些東西(spm模型中的c位)一定要考慮好組件級別的例外處理,

 

例外的全域上報

 

基本上這是對付不可預知例外的終極解法,自動收集錯誤報告并在達到閾值時做出告警,屬于在理想情況下例外發生后能讓研發同學們能第一時間發現并定位解決問題,主要會使用2個全域事件:

 

window.onerror事件

 

JS運行中的大部分例外(包括語法錯誤),都會觸發window上的error事件執行注冊的函式,不同于try catch,onerror既可以感知同步例外也可以感知異步任務的例外(除了promise例外),使用方法如下:

// message:錯誤資訊(字串),
// source:發生錯誤的腳本URL(字串)
// lineno:發生錯誤的行號(數字)
// colno:發生錯誤的列號(數字)
// error:Error物件(物件)
window.onerror = function(message, source, lineno, colno, error) {
   logger.log('捕獲到例外:',{ message, source, lineno, colno, error });
}

unhandledrejection事件

 

作為以上方案的補充版,promise例外的捕獲依賴于全域注冊unhandledrejection,使用方法如下

window.addEventListener('unhandledrejection', (e) => {
   console.error('catch', e)
 }, true)

寫在最后

 

其實總結下來我們的例外處理主要也只是干兩件事情:

 

1.將面向開發的例外資訊轉換成更友好的用戶界面提示;

2.將例外資訊上報到服務端讓研發同學去解決這些例外;

希望大家看了本篇文章有所識訓!

 

參考鏈接:

[1]https://developer.mozilla.org/zh-CN/Core_JavaScript_1.5_Reference/Global_Functions/eval

[2]https://reactjs.org/docs/error-boundaries.html

[3]https://procomponents.ant.design/

 

作 者 | 肖榮強(路遷)

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Some-experience-with-javascript-exception-handling.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/525058.html

標籤:其他

上一篇:行內元素&塊級元素的水平垂直居中方法

下一篇:JetpackCompose中的折疊布局

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more