沐鳴:_React Server Component 可能並沒有那麼香

前段時間 React 團隊發布了一項用於解決 React 頁面在多接口請求下的性能問題的解決方案 React Server Components。當然該方案目前還在草案階段,官方也只是發了視頻和一個示例 demo 來說明這個草案。

Server Components

官方在視頻和 RFC 中說明了產生這個方案的主要原因是因為大量的 react 組件依賴數據請求才能做渲染。如果每個組件自己去請求數據的話會出現子組件要等父組件數據請求完成渲染子組件的時候才會開始去請求子組件的數據,也就是官方所謂的 WaterFall 數據請求隊列的問題。而將數據請求放在一起請求又非常不便於維護。

既然組件需要數據才能渲染,那為什麼接口不直接返回渲染后的組件呢?所以他們提出了 Server Components 的解決方案。我們暫且不管這其中的邏輯有沒有道理,先來看看該方案的大體流程是怎樣的。

方案的大概就是將 React 組件拆分成 Server 組件(.server.tsx)和 Client 組件(.client.tsx)兩種類型。其中 Server 組件會在服務端直接渲染並返回。與 SSR 的區別是 Server Components 返回的是序列化的組件數據,而不是最終的 html。

可能帶來的問題

通過接口將組件和組件的數據一併返回的方式帶來了打包體積的優勢,但是它真的能像 React Hooks 一樣香嗎?我覺得並不然。

接口返回

常規做法里前端 js 中加載組件,接口返回組件需要的數據。而 React Server Components 中則是將二者合二為一,雖然在打包體積上有所優化,但是明顯是把這體積轉義到了接口返回中。特別是在類似列表這種有分頁的請求中,這種劣勢會更明顯。明明組件只需要在初始的時候進行加載,但是因為被融合進接口裡了,每次接口都會返回冗餘的組件結構,這樣也不知道是好還是不好。可能後續需要優化一下接口二次返回只返回數據會比較好。

服務器成本問題

這裏所說的服務器成本有很多,首先是機器本身的成本。將客戶端渲染行為遷移到服務端時候勢必會增加服務端的壓力,用戶量上來之後這塊的成本是成量級的在增加的。關於這個問題,官方提供的回復是隨着服務器的成本降低勢必 Server Components 帶來的優勢會抵消這塊的劣勢。

Question: This might become more expensive for applications. In the search demo, finding those search results plus rendering them on the server is a more expensive operation than just an API call sent from the client.

Reply: We are moving some of the rendering to the server–so it’s true that your server will be doing more work than before. But server costs are constantly going down, and far more powerful than most consumer devices. I think React Server Components will give you the ability to make that tradeoff and choose where you best want the work to be done, on a per component basis. And that’s not something that’s easily possible today.
via: 《RFC: React Server Components》

不過以目前我所在的業務情況來看,服務器的成本還是非常貴的,為了降低成本大家紛紛將邏輯下發到邊緣計算甚至是客戶端處理。一方面是為了節省成本,另一方面也是為了降低壓力加快處理。

除了機器本身的成本之外,請求的成本也會增加。畢竟除了數據請求之外還要處理組件渲染,而且這塊作為組件耦合不好進行拆分。相比較常規方案,使用 js 文件加載組件到客戶端,接口單純返回數據,這塊的時間成本增加了非常多。特別是常規方案中 JS 文件加載完之後是在瀏覽器中緩存的,後續的成本非常小。

體積問題可能還好,但是請求時間增加了這個可能就非常致命了。

心智負擔

這點在 RFC 中也有說明。由於 Server Components 中無法使用 useState, useReduce, useEffect, DOM API 等方法,勢必這會給使用者帶來大量的心智負擔。雖然官方說會使用工具讓開發者做到無感,且會提供運行時報錯,但是我相信光是想什麼時候需要寫 Server Componet 什麼時候需要寫 Client Component 就已經腦殼疼了吧,更別提還有個 Shared Component 了。

另外還有就是增加了跨端的流程之後,調試的成本也會變的非常高。別說很多人沒有服務端的經驗,就算是有相關經驗的同學可能也沒辦法很好的在服務端進行快速定位。關於這個問題官方提供的說法是可以依賴內部的錯誤監控和日誌服務。

回歸問題的本質

讓我們回歸到問題的本質,React Server Component 的目的其實是為了解決接口請求分散在各組件中帶來的子組件的數據請求需要等待父組件請求完成渲染子組件時才能開始請求的數據請求隊列問題。那麼除了 Server Component 之外沒有其它的解決方案了嗎?其實不然。

const [childData, setData] = useState([]); useEffect(() => { fetchChildData.then(setData); }, []); if(!data.length) { return null; } return ( {data.length + childData.length} ); } ReactDOM.render(, document.querySelector(‘#root’));” title=”” data-original-title=”複製”>

import React, {useState, useEffect} from 'react';
import ReactDOM from 'react-dom';

function App() {
  const [data, setData] = useState([]);
  useEffect(() => {
    fetchData.then(setData);
  }, []);
  
  return (
    <div> {!data.length ? 'loading' : null} <Child data={data} /> </div>
  );
}

function Child({data}) {
  const [childData, setData] = useState([]);
  useEffect(() => {
    fetchChildData.then(setData);
  }, []);
  
  if(!data.length) {
    return null;
  }
  
  return (
    <div>{data.length + childData.length}</div>
  );
}

ReactDOM.render(<App />, document.querySelector('#root'));

如示例代碼所示,只要加載組件,但是在無數據情況下不返回 DOM 也是可以做到子組件的數據先請求而無需等待的。當然這種需要認為的在寫法上進行優化,但我也仍然認為比大費周章的去做 Server Component 要好很多。

至於 Server Component 帶來的打包體積優化這個問題,我覺得 RFC 裏面的評論說的非常的好。”比起 83KB(gzip 后大概是 20KB)打包體積,我覺得在項目中為了格式化日期使用一個 83KB 的庫這才是更大的問題。“

Removing a 83KB (20KB gzip) library isn’t a big deal, I would say the bigger problem here is that you’re using a 83KB library to format dates.

via: 《RFC: React Server Component》

實際上官方列舉的兩點關於日期處理以及 Markdown 格式處理的庫,可以看到都是針對於數據進行處理的需求。針對這種情況如果覺得這塊的體積非常”貴“的話完全是可以讓服務端將格式化后的數據返回,這樣豈不是更小成本的解決了這個問題?

後記

看完 《RFC: React Server Component》 中所有的討論,大部分人對 Server Component 還是持不贊成的態度的,認為它可能並沒有像 React Hooks 那樣解決業務中的實際痛點。就目前暴露的提案,我個人也覺得 Server Component 是弊大於利的。目前就期望官方如果要實現的話能解耦實現,不要影響未使用 Server Component 的 React 用戶打包體積。

當然該提案我覺得不是沒有好處,它最大的好處我個人認為是帶來了 React 組件序列化的官方標準。為多端、多機、多語言之間實現 React 組件交流提供了基礎。基於這套序列化方案,我們可以實現組件緩存存儲,多機器併發渲染組件等。至於多語言實現也是在 RFC 討論中大家比較關心的問題,通過這套序列化標準讓其它語言去實現 React 組件也不是沒有可能。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10114

沐鳴登錄_JavaScript數據類型轉換

前言

JavaScript是一門動態語言,所謂的動態語言可以暫時理解為在語言中的一切內容都是不確定的。比如一個變量,這一時刻是個整型,下一時刻可能會變成字符串了。雖然變量的數據類型是不確定的,但是各種運算符對數據類型是有要求的。如果運算符發現,運算子的類型與預期不符,就會自動轉換類型。

本文主要介紹數據類型強制轉換和自動轉換,自動轉換是基於強制轉換之上。強制轉換主要指使用Number、String和Boolean三個函數,手動將各種類型的值,分佈轉換成数字、字符串或者布爾值

一、強制轉換

1、其他的數據類型轉換為String

方式一:toString()方法

調用被轉換數據類型的toString()方法,該方法不會影響到原變量,它會將轉換的結果返回,但是注意:null和undefined這兩個值沒有toString,如果調用他們的方法,會報錯

var a = 123
a.toString()//"123"
var b = null;
b.toString()//"報錯"
var c = undefined
c.toString()//"報錯"

採用 Number 類型的 toString() 方法的基模式,可以用不同的基輸出数字,例如二進制的基是 2,八進制的基是 8,十六進制的基是 16

var iNum = 10;
alert(iNum.toString(2));        //輸出 "1010"
alert(iNum.toString(8));        //輸出 "12"
alert(iNum.toString(16));       //輸出 "A"

方式二:String()函數

使用String()函數做強制類型轉換時,對於Number和Boolean實際上就是調用的toString()方法,
但是對於null和undefined,就不會調用toString()方法,它會將null直接轉換為”null”,將undefined 直接轉換為”undefined”

var a = null
String(a)//"null"
var b = undefined
String(b)//"undefined"

String方法的參數如果是對象,返回一個類型字符串;如果是數組,返回該數組的字符串形式。

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

2、其他的數據類型轉換為Number

方式一:使用Number()函數

下面分成兩種情況討論,一種是參數是原始類型的值,另一種是參數是對象

(1)原始類型值

①字符串轉数字

Ⅰ 如果是純数字的字符串,則直接將其轉換為数字

Ⅱ 如果字符串中有非数字的內容,則轉換為NaN

Ⅲ 如果字符串是一個空串或者是一個全是空格的字符串,則轉換為0

Number('324') // 324
Number('324abc') // NaN
Number('') // 0

②布爾值轉数字:true轉成1,false轉成0

Number(true) // 1
Number(false) // 0

③undefined轉数字:轉成NaN

Number(undefined) // NaN

④null轉数字:轉成0

Number(null) // 0

⑤Number() 接受數值作為參數,此時它既能識別負的十六進制,也能識別0開頭的八進制,返回值永遠是十進制值

Number(3.15);    //3.15
Number(023);     //19
Number(0x12);    //18
Number(-0x12);   //-18

(2)對象

簡單的規則是,Number方法的參數是對象時,將返回NaN,除非是包含單個數值的數組。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

方式二:parseInt() & parseFloat()

這種方式專門用來對付字符串,parseInt()一個字符串轉換為一個整數,可以將一個字符串中的有效的整數內容取出來,然後轉換為Number。parseFloat()把一個字符串轉換為一個浮點數。parseFloat()作用和parseInt()類似,不同的是它可以獲得有效的小數。

console.log(parseInt('.21'));        //NaN
console.log(parseInt("10.3"));        //10
console.log(parseFloat('.21'));      //0.21
console.log(parseFloat('.d1'));       //NaN
console.log(parseFloat("10.11.33"));  //10.11
console.log(parseFloat("4.3years"));  //4.3
console.log(parseFloat("He40.3"));    //NaN

parseInt()在沒有第二個參數時默認以十進制轉換數值,有第二個參數時,以第二個參數為基數轉換數值,如果基數有誤返回NaN

console.log(parseInt("13"));          //13
console.log(parseInt("11",2));        //3
console.log(parseInt("17",8));        //15
console.log(parseInt("1f",16));       //31

兩者的區別:Number函數將字符串轉為數值,要比parseInt函數嚴格很多。基本上,只要有一個字符無法轉成數值,整個字符串就會被轉為NaN。

parseInt('42 cats') // 42
Number('42 cats') // NaN

上面代碼中,parseInt逐個解析字符,而Number函數整體轉換字符串的類型。
另外,對空字符串的處理也不一樣

Number("   ");     //0    
parseInt("   ");   //NaN

3、其他的數據類型轉換為Boolean

它的轉換規則相對簡單:只有空字符串(“”)、null、undefined、+0、-0 和 NaN 轉為布爾型是 false,其他的都是 true,空數組、空對象轉換為布爾類型也是 true,甚至連false對應的布爾對象new Boolean(false)也是true

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

二、自動轉換

遇到以下三種情況時,JavaScript 會自動轉換數據類型,即轉換是自動完成的,用戶不可見。

1.自動轉換為布爾值

JavaScript 遇到預期為布爾值的地方(比如if語句的條件部分),就會將非布爾值的參數自動轉換為布爾值。系統內部會自動調用Boolean函數。

if ('abc') {
  console.log('hello')
}  // "hello"

2.自動轉換為數值

算數運算符(+ – * /)跟非Number類型的值進行運算時,會將這些值轉換為Number,然後在運算,除了字符串的加法運算

true + 1 // 2
2 + null // 2
undefined + 1 // NaN
2 + NaN // NaN 任何值和NaN做運算都得NaN
'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN

一元運算符也會把運算子轉成數值。

+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

3.自動轉換為字符串

字符串的自動轉換,主要發生在字符串的加法運算時。當一個值為字符串,另一個值為非字符串,則後者轉為字符串。

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

三、總結

1. 強制轉換的各種情況

2. 自動轉換的的各種情況

只有空字符串(“”)、null、undefined、+0、-0 和 NaN 轉為布爾型是 false,其他的都是 true

除了加法運算符(+)有可能把運算子轉為字符串,其他運算符都會把運算子自動轉成數值。一元運算符也會把運算子轉成數值。

字符串的自動轉換,主要發生在字符串的加法運算時。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10115

沐鳴娛樂業務:_web名詞解釋

HTML:超文本標記語言,標準通用標記語言下的一個應用。

CSS:層疊樣式表(英文全稱:Cascading Style Sheets),是一種用來表現 HTML(標準通用標記語言的一個應用)或 XML(標準通用標記語言的一個子集)等文件樣式的語言,用於為 HTML 文檔定義布局。

JavaScript:一種直譯式腳本語言,其主要作用是在不與服務器交互的情況下修改 HTML 頁面內容, 為網頁添加各式各樣的動態功能。Ecma 國際以 JavaScript 為基礎制定了 ECMAScript 標準。

jQuery:是一個快速、簡潔的 JavaScript 框架,是一個優秀的JavaScript 代碼庫(或 JavaScript 框架)。

DOM:文檔對象模型(Document Object Model,簡稱 DOM), 是 W3C 組織推薦的處理可擴展標誌語言的標準編程接口。

UI:即 User Interface(用戶界面)的簡稱。泛指用戶的操作界面,包含於移動 APP、網頁、智能穿戴設備等。

CSS3:是 CSS(層疊樣式表)技術的升級版本,於 1999 年開始制訂,2001 年 5 月 23 日 W3C 完成了 CSS3 的工作草案,主要包括盒子模型、列表、超鏈接方式、語言模塊、背景和邊框、文字特效、多欄布局等模塊 。

CSS hack:通過在 CSS 樣式中加入一些特殊的符號,區別不同瀏覽器製作不同的 CSS 樣式的設置,解決瀏覽器显示網頁特效不兼容性問題。

PHP: 超文本預處理器(Hypertext Preprocessor),PHP 將程序嵌入到 HTML 文檔中去執行,是 Web 開發動態網頁製作技術之一。

IFRAME:是 HTML 標籤,作用是文檔中的文檔,或者浮動的框架(FRAME)。

Html5:萬維網的核心語言,標準通用標記語言下的一個應用超文本標記語言(HTML)的第五次重大修改,其主要的目標是將互聯網語義化,以便更好地被人類和機器閱讀,並同時更好地支持網頁中嵌入各種媒體。

OOP: 面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)是一種計算機編程架構。

Bootstrap:是美國 Twitter 公司的設計師 Mark Otto 和 Jacob Thornton 合作基於 HTML、CSS、JavaScript 開發的簡潔、直觀的前端開發框架,使得 Web 開發更加快捷。

Less: 是一種 CSS 預處理語言,它擴充了 CSS 語言,增加了諸如變量、混入、函數等功能,讓 CSS 更易維護,方便製作主題和擴充。使用 CSS 的語法。

Sass: Sass(Syntactically Awesome Style Sheets)是一個相對新的編程語言,Sass 為 web 前端開發定義一套新的語法規則和函數,以加強和提升 CSS,Sass 的安裝需要 Ruby 環境。

MySQL:是一個關係型數據庫管理系統,由瑞典 MySQL AB公司開發,目前屬於 Oracle 旗下產品。

API:(Application Programming Interface,應用程序編程接口)是一些預先定義的函數,目的是提供應用程序與開發人員基於某軟件或硬件得以訪問一組例程的能力,而無需訪問源碼,也無需理解內部工作機制的細節。

HTTP:超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最為廣泛的一種網絡協議。

RESTful: 表現層狀態轉化(Representational State Transfer)軟件架構風格,提供了一組設計原則和約束條件。

Web:(World Wide Web)即全球廣域網,也稱為萬維網,它是一種基於超文本和 HTTP 的、全球性的、動態交互的、跨平台的分佈式信息系統。

Ajax: 即 “Asynchronous JavaScript And XML” ( 異 步JavaScript 和 XML),是指一種創建交互式網頁應用的網頁開發技術。

XML:可擴展標記語言,標準通用標記語言的子集,是一種用於標記电子文件使其具有結構性的標記語言。

jsON:(JavaScript Object Notation, js 對象簡譜) 是一種輕量級的數據交換格式。

JSONP:(JSON with Padding)是 JSON 的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。

Laravel: Laravel是一套簡潔、優雅的php Web開發框架(PHP Web Framework)。

BLADE: 快速搭建一個 Web 應用程序的開源框架

Session:會話,指瀏覽器和服務器的一次交互。

Cookie:有時也用其複數形式 Cookies,指某些網站為了辨別用戶身份、跟蹤 session 而儲存在用戶本地終端上的數據(通常經過加密)。

jQuery Mobile:是 jQuery 框架的一個組件,用於創建移動端 Web 應用的的前端框架。

ES6: ECMAScript 語言規範第六版。ECMAScript 是一種由
Ecma 國際通過 ECMA-262 標準化的腳本程序設計語言,是 JavaScript的標準。

Node.js:是 JavaScript 運行在服務端的平台。

Express: 是一個簡潔而靈活的 node.js Web 應用框架。

vue: 是一套構建用戶界面的漸進式框架。

webpack: 是一個模塊打包工具,將 Web 開發的各種資源打包壓縮在指定的文件中。

Canvas: 畫布,是 html5 中新增的標籤,用於網頁實時生成圖像,並且可以操作圖像內容。

SVG:可縮放矢量圖形,是基於可擴展標記語言 XML(標準通用標記語言的子集)用於描述二維矢量圖形的一種圖形格式。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10112

沐鳴:_在Vue.js中加載字體的最佳做法

博客原文:https://blog.zhangbing.site/2021/04/07/best-practices-for-loading-fonts-in-vue/

添加字體不應該對性能產生負面影響。在本文中,我們將探討在 vue 應用程序中加載字體的最佳實踐。


正確聲明font-face的字體

確保正確聲明字體是加載字體的重要方面。這是通過使用 font-face 屬性來聲明你選擇的字體來實現的。在你的Vue項目中,這個聲明可以在你的根css文件中完成。在進入這個問題之前,我們先來看看Vue應用的結構。

/root
  public/
    fonts/
      Roboto/
        Roboto-Regular.woff2
        Roboto-Regular.woff
    index.html
  src/
    assets/
      main.css
    components/
    router/
    store/
    views/
    main.js

我們可以像這樣在 main.css 中進行 font-face 聲明:

// src/assets/main.css

@font-face {
  font-family: "Roboto";
  font-weight: 400;
  font-style: normal;
  font-display: auto;
  unicode-range: U+000-5FF;
  src: local("Roboto"), url("/fonts/Roboto/Roboto-Regular.woff2") format("woff2"), url("/fonts/Roboto/Roboto-Regular.woff") format("woff");
}

首先要注意的是 font-display:auto。使用 auto 作為值可以讓瀏覽器使用最合適的策略來显示字體。這取決於一些因素,如網絡速度、設備類型、閑置時間等。

要想更多地控制字體的加載方式,你應該使用 font-display: block,它指示瀏覽器短暫地隱藏文本,直到字體完全下載完畢。其他可能的值有 swap、fallback 和 optional。你可以在這裏閱讀更多關於它們的信息。

需要注意的是 unicode-range: U+000-5FF,它指示瀏覽器只加載所需的字形範圍(U+000 – U+5FF)。你還想使用woff和woff2字體格式,它們是經過優化的格式,可以在大多數現代瀏覽器中使用。

另外需要注意的是 src 順序。首先,我們檢查字體的本地副本是否可用(local(“Roboto”))並使用它。很多Android設備都預裝了Roboto,在這種情況下,我們將使用預裝的副本。如果沒有本地副本,則在瀏覽器支持的情況下繼續下載woff2格式。否則,它會跳至支持的聲明中的下一個字體。

預加載字體

一旦你的自定義字體被聲明,你可以使用 <link rel=”preload”> 告訴瀏覽器提前預加載字體。在 public/index.html 中,添加以下內容:

<link rel="preload" as="font" href="./fonts/Roboto/Roboto-Regular.woff2" type="font/woff2" crossorigin="anonymous">

rel = “preload” 指示瀏覽器儘快開始獲取資源,as = “font” 告訴瀏覽器這是一種字體,因此它優先處理請求。還要注意crossorigin=“anonymous”,因為如果沒有這個屬性,預加載的字體會被瀏覽器丟棄。這是因為瀏覽器是以匿名方式獲取字體的,所以使用這個屬性就可以匿名請求。

使用 link=preload 可以增加自定義字體在需要之前被下載的機會。這個小調整大大加快了字體的加載時間,從而加快了您的Web應用程序中的文本渲染。

使用link = preconnect託管字體

當使用Google fonts等網站的託管字體時,你可以通過使用 link=preconnect 來獲得更快的加載時間。它告訴瀏覽器提前建立與域名的連接。

如果您使用的是Google字體提供的Roboto字體,則可以在 public/index.html 中執行以下操作:

<link rel="preconnect" href="https://fonts.gstatic.com">
...
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">

這樣就可以建立與原點https://fonts.gstatic.com 的初始連接,當瀏覽器需要從原點獲取資源時,連接已經建立。從下圖中可以看出兩者的區別。

當加載字體時沒有使用 link=preconnect 時,你可以看到連接所需的時間(DNS查找、初始連接、SSL等)。當像這樣使用link=preconnect 時,結果看起來非常不同。

在這裏,你會發現DNS查找、初始連接和SSL所花費的時間已經不存在了,因為前面已經進行了連接。

使用service workers緩存字體

字體是靜態資源,變化不大,所以它們是緩存的好候選。理想情況下,您的Web服務器應該為字體設置一個較長的 max-age expires 頭,這樣瀏覽器緩存字體的時間就會更長。如果你正在構建一個漸進式網絡應用(PWA),那麼你可以使用service workers來緩存字體,並直接從緩存中為它們提供服務。

要開始使用Vue構建PWA,請使用vue-cli工具生成一個新項目:

vue create pwa-app

選擇Manually select features選項,然後選擇Progressive Web App (PWA) Support

這些就是我們生成PWA模板所需要的唯一東西。完成后,你就可以把目錄改為 pwa-app,然後為app服務。

cd pwa-app
yarn serve

你會注意到在 src 目錄下有一個文件 registerServiceWorker,其中包含了默認的配置。在項目的根目錄下,如果vue.config.js 不存在,請創建它,如果存在,請添加以下內容:

// vue.config.js
module.exports = {
  pwa: {
    workboxOptions: {
      skipWaiting: true,
      clientsClaim: true,
    }
  }
}

vue-cli工具使用PWA plugin生成service worker。在底層,它使用Workbox來配置service worker和它控制的元素、要使用的緩存策略以及其他必要的配置。

在上面的代碼片段中,我們要確保我們的應用程序始終由service worker的最新版本控制。這是必要的,因為它確保我們的用戶總是查看應用程序的最新版本。您可以簽出Workbox配置文檔,以獲得對生成的service worker行為的更多控制。

接下來,我們將自定義字體添加到 public 目錄。我有以下結構:

root/
  public/
    index.html
    fonts/
      Roboto/
        Roboto-Regular.woff
        Roboto-Regular.woff2

一旦完成了Vue應用程序的開發,就可以通過從終端運行以下命令來構建它:

yarn build

這將結果輸出到 dist 文件夾中。如果你檢查文件夾的內容,你會注意到一個類似於 precache-manifest.1234567890.js 的文件。它包含了要緩存的資產列表,這隻是一個包含修訂版和URL的鍵值對的列表。

self.__precacheManifest = (self.__precacheManifest || []).concat([
  {
    "revision": "3628b4ee5b153071e725",
    "url": "/fonts/Roboto/Roboto-Regular.woff2"
  },
  ...
]);

public/ 文件夾中的所有內容都是默認緩存的,其中包括自定義字體。有了這個地方,你可以用像service這樣的包來serve你的應用程序,或者把 dist 文件夾託管在web服務器上查看結果。

在隨後的訪問中,字體是從緩存中加載的,這可以加快應用程序的加載時間。

結論

在這篇文章中,我們研究了在Vue應用程序中加載字體時應用的一些最佳實踐。使用這些實踐將確保你提供的字體看起來不錯,而不影響應用的性能。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10203

沐鳴測速登錄地址_Js阻止事件冒泡與阻止默認事件:理解stopPropagation(),preventDefault(),return false的區別

這篇文章主要講解js中阻止事件冒泡,阻止默認事件的方法,理解stopPropagation(),preventDefault(),return false的區別。

1.event.stopPropagation()方法

event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執行。不讓事件向documen上蔓延,但是默認事件任然會執行,當你掉用這個方法的時候,如果點擊一個連接,這個連接仍然會被打開。

提示:請使用 event.isPropagationStopped() 方法來檢查指定的事件上是否調用了該方法。

2.event.preventDefault()方法

取消事件的默認動作。該方法將通知 Web 瀏覽器不要執行與事件關聯的默認動作(如果存在這樣的動作)。例如:

form表單如果 type 屬性是 “submit”,在事件傳播的任意階段可以調用任意的事件句柄,通過調用該方法,可以阻止提交表單。

a元素中href連接,如果調用此方法是,連接不會被打開。

注意

1、如果 Event 對象的 cancelable 屬性是 fasle,那麼就沒有默認動作,或者不能阻止默認動作。無論哪種情況,調用該方法都沒有作用。

2、該方法會發生冒泡,冒泡會傳遞到上一層的父元素。

3.return false;

這個方法比較暴力,他會同事阻止事件冒泡也會阻止默認事件;寫上此代碼,連接不會被打開,事件也不會傳遞到上一層的父元素;

可以理解為return false就等於同時調用了event.stopPropagation()和event.preventDefault()

4.實例講解

這是html代碼,在div裏面套了一個a標籤,連接到fly63前端網。

<div id="box">
<a id="box_1" href="http://www.fly63.com">fly63前端網</a>
</div>

第一種:不阻止事件冒泡和默認事件

document.getElementById('box').onclick=function(e){
console.log("1");//不阻止事件冒泡會打印1,頁面跳轉;
}

第二種:阻止默認事件

document.getElementById('box').onclick=function(e){
console.log("1");
}
document.getElementById('box_1').onclick=function(e){
e.preventDefault();//阻止默認事件
}

我們會發現阻止了默認事件,點擊a標籤頁面不會跳轉,但是會打印出1。說明e.preventDefault()只能阻止默認動作,但是冒泡仍然會發生。

第三種:阻止事件冒泡

document.getElementById('box').onclick=function(e){
console.log("1");
}
document.getElementById('box_1').onclick=function(e){
e.stopPropagation();//阻止事件冒泡
}

點擊a標籤,頁面會跳轉到fly63前端網,但是在控制台中是沒有打印“1”的。

第三種:阻止事件冒泡和默認事件

document.getElementById('box').onclick=function(e){
console.log("1");
}
document.getElementById('box_1').onclick=function(e){
e.stopPropagation();
e.preventDefault();//阻止默認事件
}

等同於

document.getElementById('box_1').onclick=function(e){
return false;
}

頁面不會跳轉,也不會打印出1。這裏return false;等於同時調用了event.stopPropagation()和event.preventDefault()。

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10211

沐鳴註冊平台官網_理解vue中計算屬性computed,以及使用場景

在vue中,計算屬性是用於自動監聽依賴值的變化,從而動態返回內容,監聽是一個過程,在監聽的值變化時,可以觸發一個回調,並做一些事情。其特點:

監測的是依賴值,依賴值不變的情況下其會直接讀取緩存進行復用,變化的情況下才會重新計算。

數據可以進行邏輯處理,減少模板中計算邏輯。

對計算屬性中的數據進行監視。

計算屬性由兩部分組成:get和set,分別用來獲取計算屬性和設置計算屬性。默認只有get,如果需要set,要自己添加。另外set設置屬性,並不是直接修改自身的值,而是修改它的依賴。

1、應用場景

很多時候我們在頁面中可能需要對一些數據進行封裝轉換,如:計算總價,或對一些變量進行拼接等等。這時候就可以用到它

2、computed和方法的區別

其時在methods里定義一個方法來封裝邏輯也是可以的,但是computed的寫法要更簡介一些,,調用起來也容易,不需要加()

computed中其時是自帶get和set訪問器的,只不過在一般在定義的時候我們不需要一一現實,而且computed是帶有緩存的,如果多次重複使用屬性的話,computed里定義的方法只會調用一次

3、案例說明

計算屬性中可以用於進行一些比較複雜的操作,比如計算總價:

<div id="app">
<span v-text="price"></span>
<input v-model="list[0].price"></input>
</div>
<script>
new Vue({
el:'#app',
data:{
list:[
{name:'香蕉',price: 6},
{name:'哈密瓜',price: 10},
{name:'蘋果',price: 5},
],
},
computed:{
price:function(){
let result=0
for(let i in this.list){
result+=Number(this.list[i].price)
}
return result
}
}
})
</script>

頁面直接显示總價格為21。在輸入框綁定list第一個元素的price值。當修改輸入框中数字時候,我們會發現總價格會自動更新。

4、計算屬性的可緩存性

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們才會重新求值。這就意味着只要 message 還沒有發生改變,多次訪問 reversedMessage 計算屬性會立即返回之前的計算結果,而不必再次執行函數。

這也同樣意味着下面的計算屬性將不再更新,因為 Date.now() 不是響應式依賴:

computed: {
  now: function () {
    return Date.now()
  }
}

5、與watch進行對比

watch:監測的是屬性值, 只要屬性值發生變化,其都會觸發執行回調函數來執行一系列操作。

computed:監測的是依賴值,依賴值不變的情況下其會直接讀取緩存進行復用,變化的情況下才會重新計算。

計算屬性不能執行異步任務,計算屬性必須同步執行,watch可執行異步任務,遇到異步任務,就交給偵聽屬性

watch也可以檢測computed屬性

總結

計算屬性適合用在模板渲染中,某個值是依賴了其它的響應式對象甚至是計算屬性計算而來;而偵聽屬性適用於觀測某個值的變化去完成一段複雜的業務邏輯。

computed能做的,watch都能做,反之則不行

能用computed的盡量用computed

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10212

沐鳴娛樂業務:_ES12(2021)新特性: replaceAll、Promise.any、WeakRefs、数字分隔符、邏輯運算符和賦值表達式

2021年3月13日,ES2021 候選提案發布了其最終功能集的版本。如果它能夠在今年6月的ECMA 大會上通過,就會成為官方的標準!

這個候選提案提及到ECMAScript新特性如下所示:

String.prototype.replaceAll()

Promise.any

邏輯運算符和賦值表達式

數值分隔符

WeakRef and Finalizers

這些新的特性已經進入第四階段且已添加到谷歌 Chrome V8 引擎中。

1. replaceAll

返回一個全新的字符串,所有符合匹配規則的字符都將被替換掉

const str = 'hello world';
str.replaceAll('l', ''); // "heo word"

2. Promise.any

Promise.any() 接收一個Promise可迭代對象,只要其中的一個 promise 成功,就返回那個已經成功的 promise 。如果可迭代對象中沒有一個 promise 成功(即所有的 promises 都失敗/拒絕),就返回一個失敗的 promise

const promise1 = new Promise((resolve, reject) => reject('我是失敗的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失敗的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{
  console.log(values);
})
.catch(e=>{
  console.log(e);
});

3. WeakRefs

ES2021 了新的類 WeakRefs。允許創建對象的弱引用。這樣就能夠在跟蹤現有對象時不會阻止對其進行垃圾回收。對於緩存和對象映射非常有用。

必須用 new關鍵字創建新的 WeakRef,並把某些對象作為參數放入括號中。當你想讀取引用(被引用的對象)時,可以通過在弱引用上調用 deref() 來實現。

const myWeakRef = new WeakRef({
name: '星野',
year: '25'
})

myWeakRef.deref()
// => { name: '星野', year: '25' }

myWeakRef.deref().name
// => '星野'

與 WeakRef 緊密相連的還有另一個功能,名為 finalizers 或 FinalizationRegistry。這個功能允許你註冊一個回調函數,這個回調函數將會在對象被垃圾回收時調用。

// 創建 FinalizationRegistry:
const reg = new FinalizationRegistry((val) => {
console.log(val)
})

(() => {
// 創建新對象:
const obj = {}

//為 “obj” 對象註冊 finalizer:
//第一個參數:要為其註冊 finalizer 的對象。
//第二個參數:上面定義的回調函數的值。
reg.register(obj, 'obj has been garbage-collected.')
})()
// 當 "obj" 被垃圾回收時輸出:
// 'obj has be

4. 邏輯運算符和賦值表達式

邏輯運算符和賦值表達式,新特性結合了邏輯運算符(&&,||,??)和賦值表達式而JavaScript已存在的 複合賦值運算符有:

a ||= b
//等價於
a = a || (a = b)

a &&= b
//等價於
a = a && (a = b)

a ??= b
//等價於
a = a ?? (a = b)

5. 数字分隔符

数字分隔符,可以在数字之間創建可視化分隔符,通過_下劃線來分割数字,使数字更具可讀性

const money = 1_000_000_000;
//等價於
const money = 1000000000;

1_000_000_000 === 1000000000; // true

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10199

沐鳴總代平台_JavaScript: 數組reduce實例方法

對比map、 forEach、 filter 等數組方法,reduce比它們更強。

一、 reduce定義和用法

reduce() 方法接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終計算為一個值。

reduce() 可以作為一個高階函數,用於函數的 compose。

注意: reduce() 對於空數組是不會執行回調函數的。

語法:

array.reduce(function(prev, cur, index, arr), init)

  • prev (上一次調用回調返回的值,或者是提供的初始值(initialValue))
  • cur (數組中當前被處理的元素)
  • index (當前元素在數組中的索引)
  • arr (調用的數組)
  • init (傳遞給函數的初始值)

二、reduce瀏覽器支持情況

三、reduce累加

帶初始值

var arr = [1,2,3,4]
var sum = arr.reduce((pre, item) => {
    return pre + item
}, 10)
console.log(sum) // 20

不帶初始值

var arr = [1,2,3,4]
var sum = arr.reduce((pre, item) => {
    return pre + item
},)
console.log(sum) // 10

四、reduce數組去重

var arr = [1,2,3,3,2,1,4]
arr.reduce((acc, cur) => {
  if (!(acc.includes(cur))) {
    acc.push(cur)
  }
  return acc
}, [])
// [1, 2, 3, 4]

五、reduce求數組項最大值

var arr = [1, 2, 3, 4];
arr.reduce((prev, cur) => {
    return Math.max(prev,cur);
});
//4

六、reduce將二維數組轉為一維數組

var arr = [[1,2], [3,4], [5,6]]
arr.reduce((acc, cur) => {
  return acc.concat(cur)
}, [])
// [1,2,3,4,5,6]

七、reduce對象里的屬性求和

var arr = [
    {subject: 'Math', score: 90},
    {subject: 'Chinese', score: 90},
    {subject: 'English', score: 100}
]
arr.reduce((pre, cur) => {
    return cur.score + pre
}, 0)
//280

八、reduce計算數組中每個元素出現的個數

var arr = [1, 2,3,3,2,1,2,1]
arr.reduce((acc, cur) => {
  if (!(cur in acc)) {
    acc[cur] = 1
  } else {
    acc[cur] += 1
  }
  return acc
}, {})
//{1: 3, 2: 3, 3: 2}

九、reduce按屬性給數組分類

var arr = [
    {subject: 'Math', score: 90},
    {subject: 'Chinese', score: 90},
    {subject: 'English', score: 100},
    {subject: 'Math', score: 80},
    {subject: 'Chinese', score: 95}
];
arr.reduce((acc, cur) => {
  if (!acc[cur.type]) {
    acc[cur.type] = [];
  }
  acc[cur.type].push(cur)
  return acc
}, {})

十、reduce實現map

var arr = [1, 2, 3, 4]
Array.prototype.reduceMap = function(callback) {
  return this.reduce((acc, cur, index, array) => {
    const item = callback(cur, index, array)
    acc.push(item)
    return acc
  }, [])
}
arr.reduceMap((item, index) => {
  return item + index
})
// [1, 3, 5, 7]

十一、reduce實現forEach

var arr = [1, 2, 3, 4]
Array.prototype.reduceForEach = function(callback) {
  this.reduce((acc, cur, index, array) => {
    callback(cur, index, array)
  }, [])
}

arr.reduceForEach((item, index, array) => {
  console.log(item, index)
})
// 1234
// 0123

十二、reduce實現filter

var arr = [1, 2, 3, 4]
Array.prototype.reduceFilter = function (callback) {
   return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      acc.push(cur)
    }
    return acc
  }, [])
}
arr.reduceFilter(item => item % 2 == 0) // 過濾出偶數項。
// [2, 4]

十三、reduce實現find

var arr = [1, 2, 3, 4]
var obj = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
Array.prototype.reduceFind = function (callback) {
  return this.reduce((acc, cur, index, array) => {
    if (callback(cur, index, array)) {
      if (acc instanceof Array && acc.length == 0) {
        acc = cur
      }
    }    
    if ((index == array.length - 1) && acc instanceof Array && acc.length == 0) {
      acc = undefined
    }
    return acc
  }, [])
}
arr.reduceFind(item => item % 2 == 0) // 2
obj.reduceFind(item => item.a % 2 == 0) // {a: 2}
obj.reduceFind(item => item.a % 9 == 0) // undefined

碰到數組複雜操作的時候,就是reduce大顯身手的時候。深入研究reduce的用法,對開發大有裨益。

來自:https://segmentfault.com/a/1190000039774558

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10209

沐鳴娛樂_ES10(2019)新特性:Array.flat()和Array.flatMap()、String.trimStart()和String.trimEnd()等

2019年1月末的時候,ECMA TC39終於確定了ES2019版JavaScript的新增內容,ES2019 將會增加arrays, objects, strings, symbols, try/catch 和 jsON 等方面的特徵。

ES10新特性(2019)

行分隔符(U + 2028)和段分隔符(U + 2029)符號現在允許在字符串文字中,與jsON匹配

更加友好的 JSON.stringify

新增了Array的flat()方法和flatMap()方法

新增了String的trimStart()方法和trimEnd()方法

Object.fromEntries()

Symbol.prototype.description

String.prototype.matchAll

Function.prototype.toString()現在返回精確字符,包括空格和註釋

簡化try {} catch {},修改 catch 綁定

新的基本數據類型BigInt

globalThis

import()

Legacy RegEx

私有的實例方法和訪問器

1.行分隔符(U + 2028)和段分隔符(U + 2029)符號現在允許在字符串文字中,與JSON匹配

以前,這些符號在字符串文字中被視為行終止符,因此使用它們會導致SyntaxError異常。

2.更加友好的 JSON.stringify

如果輸入 Unicode 格式但是超出範圍的字符,在原先JSON.stringify返回格式錯誤的Unicode字符串。現在實現了一個改變JSON.stringify的第3階段提案,因此它為其輸出轉義序列,使其成為有效Unicode(並以UTF-8表示)

3.新增了Array的flat()方法和flatMap()方法

flat()和flatMap()本質上就是是歸納(reduce) 與 合併(concat)的操作。

Array.prototype.flat()

flat() 方法會按照一個可指定的深度遞歸遍曆數組,並將所有元素與遍歷到的子數組中的元素合併為一個新數組返回。

flat()方法最基本的作用就是數組降維

var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
 
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
 
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
 
//使用 Infinity 作為深度,展開任意深度的嵌套數組
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]

其次,還可以利用flat()方法的特性來去除數組的空項其次,還可以利用flat()方法的特性來去除數組的空項 var arr4 = [1, 2, , 4, 5]; arr4.flat(); // [1, 2, 4, 5]

var arr1 = [1, 2, 3, 4];
 
arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]
 
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
 
// 只會將 flatMap 中的函數返回的數組 “壓平” 一層
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]


4.新增了String的trimStart()方法和trimEnd()方法

新增的這兩個方法很好理解,分別去除字符串首尾空白字符,這裏就不用例子說聲明了。

5.Object.fromEntries()

Object.entries()方法的作用是返回一個給定對象自身可枚舉屬性的鍵值對數組,其排列與使用 for…in 循環遍歷該對象時返回的順序一致(區別在於 for-in 循環也枚舉原型鏈中的屬性)。

而Object.fromEntries() 則是 Object.entries() 的反轉。

Object.fromEntries() 函數傳入一個鍵值對的列表,並返回一個帶有這些鍵值對的新對象。這個迭代參數應該是一個能夠實現@iterator方法的的對象,返回一個迭代器對象。它生成一個具有兩個元素的類似數組的對象,第一個元素是將用作屬性鍵的值,第二個元素是與該屬性鍵關聯的值。

通過 Object.fromEntries, 可以將 Map 轉化為 Object:

const sym = Symbol('The description');

以前,訪問描述的唯一方法是將符號轉換為字符串:

assert.equal(String(sym), 'Symbol(The description)');

現在引入了getter Symbol.prototype.description以直接訪問描述:

assert.equal(sym.description, 'The description');


7.String.prototype.matchAll

matchAll() 方法返回一個包含所有匹配正則表達式及分組捕獲結果的迭代器。 在 matchAll 出現之前,通過在循環中調用regexp.exec來獲取所有匹配項信息(regexp需使用/g標誌:

const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
 
while ((matches = regexp.exec(str)) !== null) {
  console.log(`Found ${matches[0]}. Next starts at ${regexp.lastIndex}.`);
  // expected output: "Found foo. Next starts at 9."
  // expected output: "Found foo. Next starts at 19."
}

如果使用matchAll ,就可以不必使用while循環加exec方式(且正則表達式需使用/g標誌)。使用matchAll 會得到一個迭代器的返回值,配合 for…of, array spread, or Array.from() 可以更方便實現功能:

const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
let matches = str.matchAll(regexp);
 
for (const match of matches) {
  console.log(match);
}
// Array [ "foo" ]
// Array [ "foo" ]
 
// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
matches = str.matchAll(regexp);
 
Array.from(matches, m => m[0]);
// Array [ "foo", "foo" ]


8.Function.prototype.toString()現在返回精確字符,包括空格和註釋

function /* comment */ foo /* another comment */() {}
 
// 之前不會打印註釋部分
console.log(foo.toString()); // function foo(){}
 
// ES2019 會把註釋一同打印
console.log(foo.toString()); // function /* comment */ foo /* another comment */ (){}
 
// 箭頭函數
const bar /* comment */ = /* another comment */ () => {};
 
console.log(bar.toString()); // () => {}


9.修改 catch 綁定

在 ES10 之前,我們必須通過語法為 catch 子句綁定異常變量,無論是否有必要。很多時候 catch 塊是多餘的。 ES10 提案使我們能夠簡單的把變量省略掉。

不算大的改動。之前是

try {} catch(e) {} 

現在是

try {} catch {}


10.新的基本數據類型BigInt

現在的基本數據類型(值類型)不止5種(ES6之後是六種)了哦!加上BigInt一共有七種基本數據類型,分別是: String、Number、Boolean、Null、Undefined、Symbol、BigInt

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10197

沐鳴登錄網站_Javascript正則中的命名捕獲分組

假設你在一段陌生的代碼中看到這樣一個函數: 

function toLocalDate(date) {
  return date.replace(/(\d{2})-(\d{2})-(\d{4})/, "$2-$1-$3")
}

單看這個函數你能知道它是想把“日-月-年”替換成“月-日-年”,還是反過來?匿名捕獲分組沒法做到這一點,那就該命名捕獲分組上場了:

function toLocalDate(date){
  return date.replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<day>-$<month>-$<year>")
}

俗話說的好,“一個好的變量名賽過一行註釋”,命名捕獲分組很大的一個作用就是它能起到註釋的作用。

另外,命名捕獲分組還有一個好處,那就是假如你在修改一個正則時,在已有分組的左邊引入了新的分組,那麼你還得記得更新已有的反向引用的数字。比如將 (foo)\1 改成了 (bar)(foo)\1,那你得把原來的 \1 改成 \2,replace() 方法的第二個參數里的 $1 也同樣得改,用命名分組不會有這個問題。

語法

命名捕獲分組自身的語法是 (?<name>…),比普通的分組多了一個 ?<name> 字樣,其中 name 的起法就和你平時起變量名一樣即可(不過在這裏關鍵字也可用)。

反向引用一個命名分組的語法是 \k<name>,注意命名分組同樣可以通過数字索引來反向引用,比如:

/(?<foo>a)\k<foo>\1/.test("aaa") // true

在 replace() 方法的替換字符串中反向引用是用 $<name>:

"abc".replace(/(?<foo>a)/, "$<foo>-") // "a-bc",同樣 $1 仍然可用

總結一下就是,和命名分組相關的有三種語法,分別是 ?<name>、\k<name>、$<name>,相同點是都用尖括號包裹着分組名。

在 API 中的使用

在 exec() 和 match() 中的使用:

const groups = "04-25-2017".match(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/).groups // {month: "04", day: "25", year: "2017"}

const {day, month, year} = groups

exec() 和 match() 方法返回的匹配結果數組上多了一個 groups 屬性,裏面存放着每個命名分組的名稱以及它們匹配到的值,利用 ES6 的解構語法,可以方便的提取出想要的字段。注意這個 groups 屬性只有在當前正則里至少存在一個命名分組的前提下才會存在,比如:

/(\d{2})-(\d{2})-(\d{4})/.exec("04-25-2017").groups // undefined,因為沒有命名分組

在 replace(/…/, replacement) 中的使用:

replacement 是字符串的情況上面已經舉過例子了,這裏主要講它是函數的情況:

"04-25-2017".replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, (...args) => {
  const groups = args.slice(-1)[0]
  const {day, month, year} = groups
  return `${day}-${month}-${year}`
}) // "25-04-2017"

也就是說,在實參列表的最末尾,多傳了一個 groups 對象。同樣,如果正則里沒有命名分組,這個參數不會存在。

異常情況

分組名不能有重複項:

/(?<foo>a)(?<foo>b)/ // SyntaxError: Duplicate capture group name

反向引用一個不存在的分組名:

/\k<foo>/u // SyntaxError: Invalid named capture referenced

/\k<foo>/.test("k<foo>") // true, 非 Unicode 下為了向後兼容,k 前面的 \ 會被丟棄

在 reaplce() 方法的替換字符串中引用一個不存在的分組:

"abc".replace(/(?<foo>.*)/, "$<bar>") // SyntaxError: Invalid replacement string

"abc".replace(/(.*)/, "$<bar>") // "$<bar>",不包含命名分組時會向後兼容

總結

V8 目前已經完全實現了命名捕獲分組的提案 https://tc39.github.io/proposal-regexp-named-groups/。

命名分組雖然帶來了一些好處,但我個人覺得,正則越長越難讀懂,尤其增加的長度是一堆小括號和尖括號。在可讀性上,命名分組也許會起到反作用,尤其對正則苦手來說。 

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10196