沐鳴娛樂_10種快速提高網站性能的方法

在這篇文章中,主要介紹10種快速提高網站性能的方法,你只需5分鐘內就可以將它應用到你的網站上,廢話不多說,讓我們進入正題吧 。

1. 文件壓縮

文件壓縮,可以減少網絡傳輸的字節數。有幾種壓縮算法。Gzip是最流行的,但是對於Brotli,你可以使用一種更新的、甚至更好的壓縮算法。如果想檢查您的服務器是否支持Brotli,可以使用 Brotli.pro。

如果你的服務器不支持Brotli,則可以按照以下簡單指南進行安裝 :

  • Nginx on Linux
  • Apache
  • NodeJs – Express

2. 圖像壓縮

未壓縮的圖像是一個巨大的潛在性能瓶頸。如果在將圖像提供給用戶之前沒有壓縮它們,那麼就會傳輸不必要的字節。有幾個有用的工具可以用於快速壓縮圖像且不損失可見質量。我主要使用Imagemin。它支持許多圖像格式,您w你以將其用作命令行界面或npm模塊。

imagemin img/* --out-dir=dist/images

你還可以 將npm 引入到項目里,使用imagemin-mozjpeg,可以將JPEG圖像壓縮到原有的60%:

const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');

(async() => {
  const files = await imagemin(
      ['img/*.jpg'],
      {
        destination: 'dist/img',
        plugins: [
          imageminMozjpeg({quality: 60}),
        ]
      }
  );
  console.log(files);
})();

就我而言,它將文件大小從4MB減小到100kB

3.圖像格式

使用現代圖像格式可以真正提高性能。 WebP 圖像比 JPEG 和 PNG 小,通常小25%-35%。 WebP 也被瀏覽器廣泛支持。

我們使用imagemin npm 包併為其添加WebP插件。 以下代碼將我的圖像的WebP版本輸出到dist文件夾中。

const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');

(async() => {
  const files = await imagemin(
      ['img/*.jpg'],
      {
        destination: 'dist/img',
        plugins: [
          imageminWebp({quality: 50})
        ]
      }
  );
  console.log(files);
})();

結果表明,與原始圖像相比,文件大小減少了98%,與壓縮的 JPG 文件相比,WebP 對圖像的壓縮效果更加明顯,WebP版本比壓縮的JPEG版本小43%。

4.圖像延遲加載

延遲加載圖像是一種稍後而不是提前加載屏幕外圖像的技術。當解析器遇到正確加載的圖像時,會減慢初始頁面加載速度。通過延遲加載,可以加快這個過程並在以後加載圖像。使用lazysize很容易做到這一點。使用lazysize腳本和瀏覽器對loading屬性的支持,你可以這樣優化:

![](image.jpg)

改成:

![](image.jpg)

該庫會處理其餘的工作,你可以使用瀏覽器驗證這一點。打開你的網站,找到你的圖像標籤。如果類從lazyload更改為lazyloaded,它就可以工作。

5.緩存 http 頭

緩存是一種快速提高站點速度的方法。它減少了訪問者的頁面加載時間。我們可以告訴瀏覽器在特定的時間緩存文件,如果你對後台的知識有些了解,那麼配置緩存方不是很難的事情。

我們可以使用以下 API 進行緩存:

  • Cache-Control
  • ETag
  • Last-Modified

6. 內聯關鍵的 css

css 是阻塞渲染的,這意味着瀏覽器必須先下載並處理所有CSS文件,然後才能繪製像素。 通過內聯關鍵的 CSS,可以大大加快此過程。 我們可以通過以下步驟完成此操作:

識別關鍵的 CSS

如果你不知道你的關鍵CSS是什麼,你可以使用Critcal, CriticalCSS或Penthouse。所有這些庫都從給定視口可見的html文件中提取CSS。

criticalCSS 事例如下:

var criticalcss = require("criticalcss");

var request = require('request');
var path = require( 'path' );
var criticalcss = require("criticalcss");
var fs = require('fs');
var tmpDir = require('os').tmpdir();

var cssUrl = 'https://web.dev/app.css';
var cssPath = path.join( tmpDir, 'app.css' );
request(cssUrl).pipe(fs.createWriteStream(cssPath)).on('close', function() {
  criticalcss.getRules(cssPath, function(err, output) {
    if (err) {
      throw new Error(err);
    } else {
      criticalcss.findCritical("https://web.dev/", { rules: jsON.parse(output) }, function(err, output) {
        if (err) {
          throw new Error(err);
        } else {
          console.log(output);
          // save this to a file for step 2
        }
      });
    }
  });
});

內聯關鍵 CSS

html解析器遇到樣式標籤,並立即處理關鍵的CSS。

<head>
  <style>
  body {...}
  /* ... rest of the critical CSS */
  </style>
</head>

滯后非關鍵CSS

非關鍵的CSS不需要立即進行處理。 瀏覽器可以在onload事件之後加載它,因此用戶不必等待。

<link rel="preload" href="/assets/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/assets/styles.css"></noscript>

7. JavaScript 異步/延遲加載/延遲加載

HTML 也是阻塞渲染,瀏覽器必須等待 js 執行后才能完成對HTML的解析。但是我們可以告訴瀏覽器等待JavaScript執行。

異步加載JavaScript

使用屬性async,可以告訴瀏覽器異步加載腳本。

<script src="app.js" async></script>

延遲JavaScript

defer屬性告訴瀏覽器在 HTML 解析器解析完文檔之後運行腳本,但在事件發生之前,DOMContentLoaded會被觸發。

<script src="app.js" defer></script>

重複排序內聯的腳本

內聯腳本立即執行,瀏覽器對其進行解析。 因此,您可以將它們放在HTML的末尾,緊接在body標記之前。

8.使用資源提示優化性能

HTML5的資源提示(Resource Hints)可以簡單地理解為預加載,瀏覽器根據開發者提供的後續資源的提示進行有選擇性的加載和優化。“有選擇性”這一項是必須的且極其重要的,也是有別早先替代方案的重點,因為很多情況下,預加載會受到所分配到的計算資源以及帶寬資源的限制,瀏覽器有權放棄那些成本較高的加載項。

資源提示幫助開發人員告訴瀏覽器稍後可能加載的頁面。該規範定義了四種原語

  • preconnect
  • dns-prefetch
  • prefetch
  • prerender

此外,對於資源提示,我們使用了鏈接屬性的preload關鍵字。

preconnect

預鏈接, 使用方法如下:

<link rel="preconnect" href="https://example.com">

我們訪問一個站點時,簡單來說,都會經過以下的步驟:

  1. DNS 解析
  2. TCP 握手
  3. 如果為 Https 站點,會進行TLS握手

使用preconnect后,瀏覽器會針對特定的域名,提前初始化鏈接(執行上述三個步驟),節省了我們訪問第三方資源的耗時。需要注意的是,我們一定要確保preconnect的站點是網頁必需的,否則會浪費瀏覽器、網絡資源。

DNS Prefetch

DNS 預解析, 這個大多數人都知道,用法也很簡單:

<link rel="dns-prefetch" href="http://example.com">

DN S解析,簡單來說就是把域名轉化為ip地址。我們在網頁里使用域名請求其他資源的時候,都會先被轉化為ip地址,再發起鏈接。dns-prefeth使得轉化工作提前進行了,縮短了請求資源的耗時。

什麼時候使用呢?當我們頁面中使用了其他域名的資源時,比如我們的靜態資源都放在cdn上,那麼我們可以對cdn的域名進行預解析。瀏覽器的支持情況也不錯。

prefetch

預拉取, 使用方法如下:

<link rel="prefetch" href="index.html" as="document">
<link rel="prefetch" href="main.js" as="script">
<link rel="prefetch" href="main.css" as="style">
<link rel="prefetch" href="font.woff" as="font">
<link rel="prefetch" href="image.webp" as="image">

link標籤里的as參數可以有以下取值:

audio: 音頻文件
video: 視頻文件  
Track: 網絡視頻文本軌道 
script: javascript文件
style: css樣式文件
font: 字體文件   
image: 圖片   
fetch: XHR、Fetch請求
worker: Web workers
embed: 多媒體<embed>請求 
object:  多媒體<object>請求
document: 網頁

預拉取用於標識從當前網站跳轉到下一個網站可能需要的資源,以及本網站應該獲取的資源。這樣可以在將來瀏覽器請求資源時提供更快的響應。

如果正確使用了預拉取,那麼用戶在從當前頁面前往下一個頁面時,可以很快得到響應。但是如果錯誤地使用了預拉取,那麼瀏覽器就會下載額外不需要的資源,影響頁面性能,並且造成網絡資源浪費。

這裏需要注意的是,使用了prefetch,資源僅僅被提前下載,下載后不會有任何操作,比如解析資源。

prerender

預渲染,使用方法如下:

<link rel="prerender" href="//example.com/next-page.html">

prerender比prefetch更進一步。不僅僅會下載對應的資源,還會對資源進行解析。解析過程中,如果需要其他的資源,可能會直接下載這些資源。這樣,用戶在從當前頁面跳轉到目標頁面時,瀏覽器可以更快的響應。

preload

<link rel="preload" href="..." as="...">
<link rel="prefetch" href="...">

注意preload需要寫上正確的as屬性,才能正常工作喔(prefetch不需要)。但是它們有什麼區別呢?

  • preload 是用於預加載當前頁的資源,瀏覽器會優先加載它們
  • prefetch 是用於預加載後續導航使用的資源,瀏覽器也會加載它們,但優先級不高

9. 固定好你的谷歌字體

Google字體很棒,它們提供優質的服務,並被廣泛使用。 如果你不想自己託管字體,那麼Google字體是一個不錯的選擇。 你需要的是學習如何引用它們,哈里·羅伯茨(Harry Roberts)寫了一篇有關《The Fastest Google Fonts》的出色深度文章。 強烈建議你閱讀它。

如果你快速取用,那麼可以使用下面集成片段的谷歌字體:

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=...&display=swap"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap" media="print" onload="this.media='all'"/>
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap"/></noscript>

10. 使用 service worker 緩存資源

service worker是瀏覽器在後台運行的腳本。緩存可能是最常用的特性,也是你應該使用的特性。我認為這不是一個選擇的問題。通過使用 service worker實現緩存,可以使 用戶 與站點的交互更快,並且即使用戶不在線也可以訪問站點。

總結

在這篇文章中,展示了 10 種快速的網絡性能,你可以在5分鐘內應用到你的網站,以提高你的網站速度。

感謝大家的觀看與支持,我們下期再見,不要忘了三連哦。

原文:https://dev.to/marcradziwill/

站長推薦

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

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

沐鳴登錄測速_深入理解React Diff算法

點擊進入react源碼調試倉庫。

fiber上的updateQueue經過React的一番計算之後,這個fiber已經有了新的狀態,也就是state,對於類組件來說,state是在render函數里被使用的,既然已經得到了新的state,那麼當務之急是執行一次render,得到持有新state的ReactElement。

假設render一次之後得到了大量的ReactElement,而這些ReactElement之中若只有少量需要更新的節點,那麼顯然不能全部去更新它們,此時就需要有一個diff過程來決定哪些節點是真正需要更新的。

源碼結構

我們以類組件為例,state的計算髮生在類組件對應的fiber節點beginWork中的updateClassInstance函數中,在狀態計算完畢之後,緊跟着就是去調finishClassComponent執行diff、打上effectTag(即新版本的flag)。

打上effectTag可以標識這個fiber發生了怎樣的變化,例如:新增(Placement)、更新(Update)、刪除(Deletion),這些被打上flag的fiber會在complete階段被收集起來,形成一個effectList鏈表,只包含這些需要操作的fiber,最後在commit階段被更新掉。

function updateClassComponent(
   current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, renderLanes: Lanes,) {
   ...
   // 計算狀態
   shouldUpdate = updateClassInstance(
     current,
     workInProgress,
     Component,
     nextProps,
     renderLanes,
   );
   
   ...
   
   // 執行render,進入diff,為fiber打上effectTag
   const nextUnitOfWork = finishClassComponent(
     current, 
     workInProgress,
     Component,
     shouldUpdate,
     hasContext,
     renderLanes,
     );
     return nextUnitOfWork;
 }

在finishClassComponent函數中,調用reconcileChildFibers去做diff,而reconcileChildFibers實際上就是ChildReconciler,這是diff的核心函數,
該函數針對組件render生成的新節點的類型,調用不同的函數進行處理。

function ChildReconciler(shouldTrackSideEffects) {
 
   ...
   function reconcileSingleElement(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      element: ReactElement,
      lanes: Lanes,
   ): Fiber {
     // 單節點diff
   }
   
  function reconcileChildrenArray(
     returnFiber: Fiber,
     currentFirstChild: Fiber | null,
     newChildren: Array<*>,
     lanes: Lanes,
  ): Fiber | null {
    // 多節點diff
  }
   
   ...
 
   function reconcileChildFibers(
     returnFiber: Fiber,
     currentFirstChild: Fiber | null,
     newChild: any, lanes: Lanes,
   ): Fiber | null {
     const isObject = typeof newChild === 'object' && newChild !== null;
     if (isObject) {
       // 處理單節點
       switch (newChild.$$typeof) {
         case REACT_ELEMENT_TYPE:
           return placeSingleChild(
             reconcileSingleElement(
             returnFiber,
             currentFirstChild,
             newChild,
             lanes,
           ),
        );
        
        case REACT_PORTAL_TYPE:
        ...
        
        case REACT_LAZY_TYPE:
        ...
      
      }
    }
    if (typeof newChild === 'string' || typeof newChild === 'number') {
      // 處理文本節點
    }
    if (isArray(newChild)) {
      // 處理多節點
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
     );
   }
   
   ...
   
 }
 
 return reconcileChildFibers;
 
}

Diff的主體

關於Diff的參与者,在reconcileChildren函數的入參中可以看出

workInProgress.child = reconcileChildFibers(
 workInProgress,
 current.child,
 nextChildren,
 renderLanes,
 );
  • workInProgress:作為父節點傳入,新生成的第一個fiber的return會被指向它。
  • current.child:舊fiber節點,diff生成新fiber節點時會用新生成的ReactElement和它作比較。
  • nextChildren:新生成的ReactElement,會以它為標準生成新的fiber節點。
  • renderLanes:本次的渲染優先級,最終會被掛載到新fiber的lanes屬性上。

可以看出,diff的兩個主體是:oldFiber(current.child)和newChildren(nextChildren,新的ReactElement),它們是兩個不一樣的數據結構。

比如現在有組件<Example/>,它計算完新的狀態之後,要基於這兩個東西去做diff,分別是現有fiber樹中(current樹)<Example/>對應fiber的所有子fiber節點<Example/>的render函數的執行結果,即那些ReactElements

<Example/>對應fiber的所有子fiber節點:oldFiber

 current樹中
 <Example/> fiber | | A --sibling---> B --sibling---> C

<Example/>的render函數的執行結果,newChildren

 current fiber 對應的組件render的結果
 [
    {$$typeof: Symbol(react.element), type: "div", key: "A" },
    {$$typeof: Symbol(react.element), type: "div", key: "B" }, 
    {$$typeof: Symbol(react.element), type: "div", key: "B" },
 ]

Diff的基本原則

對於新舊兩種結構來說,場景有節點自身更新、節點增刪、節點移動三種情況。面對複雜的情況,即使最前沿的算法,複雜度也極高。面對這種情況,React以如下策略應對:

  • 即使兩個元素的子樹完全一樣,但前後的父級元素不同,依照規則div元素及其子樹會完全銷毀,並重建一個p元素及其子樹,不會嘗試復用子樹。
舊
<div>
 <span>a</span>
 <span>b</span>
</div>

新
<p>
 <span>a</span>
 <span>b</span>
</p>
  • 使用tag(標籤名)和 key識別節點,區分出前後的節點是否變化,以達到盡量復用無變化的節點。
舊
<p key="a">aa</p>
<h1 key="b">bb</h1>

新
<h1 key="b">bb</h1>
<p key="a">aa</p>

因為tag 和 key的存在,所以React可以知道這兩個節點只是位置發生了變化。

場景

上面說到diff算法應對三種場景:節點更新、節點增刪、節點移動,但一個fiber的子元素有可能是單節點,也有可能是多節點。所以依據這兩類節點可以再細分為:

  • 單節點更新、單節點增刪。
  • 多節點更新、多節點增刪、多節點移動。

什麼是節點的更新呢?對於DOM節點來說,在前後的節點類型(tag)和key都相同的情況下,節點的屬性發生了變化,是節點更新。若前後的節點tag或者key不相同,Diff算法會認為新節點和舊節點毫無關係。

以下例子中,key為b的新節點的className發生了變化,是節點更新。

舊
<div className={'a'} key={'a'}>aa</div>
<div className={'b'} key={'b'}>bb</div>

新
<div className={'a'} key={'a'}>aa</div>
<div className={'bcd'} key={'b'}>bb</div>

以下例子中,新節點的className雖然有變化,但key也變化了,不屬於節點更新

舊
<div className={'a'} key={'a'}>aa</div>
<div className={'b'} key={'b'}>bb</div>

新
<div className={'a'} key={'a'}>aa</div>
<div className={'bcd'} key={'bbb'}>bb</div>

以下例子中,新節點的className雖然有變化,但tag也變化了,不屬於節點更新

舊
<div className={'a'} key={'a'}>aa</div>
<div className={'b'} key={'b'}>bb</div>

新
<div className={'a'} key={'a'}>aa</div>
<p className={'bcd'} key={'b'}>bb</p>

下面來分開敘述一下單節點和多節點它們各自的更新策略。

單節點

若組件產出的元素是如下的類型:

<div key="a">aa</div>

那麼它最終產出的ReactElement為下面這樣(省略了一些與diff相關度不大的屬性)

{
   $$typeof: Symbol(react.element), type: "div", key: "a" 
   ...
}

單節點指newChildren為單一節點,但是oldFiber的數量不一定,所以實際有如下三種場景:

為了降低理解成本,我們用簡化的節點模型來說明問題,字母代表key。

  • 單箇舊節點
舊: A
新: A
  • 多箇舊節點
舊: A - B - C
新: B
  • 沒有舊節點
舊: --
新: A

對於單節點的diff,其實就只有更新操作,不會涉及位移和位置的變化,單節點的更新會調用reconcileSingleElement函數處理。該函數中對以上三種場景都做了覆蓋。但實際上面的情況對於React來說只是兩種,oldFiber鏈是否為空。因此,在實現上也只處理了這兩種情況。

oldFiber鏈不為空

遍歷它們,找到key相同的節點,然後刪除剩下的oldFiber節點,再用匹配的oldFiber,newChildren中新節點的props來生成新的fiber節點。

   function reconcileSingleElement(
     returnFiber: Fiber,
     currentFirstChild: Fiber | null,
     element: ReactElement,
     lanes: Lanes
   ): Fiber {
     const key = element.key;
     let child = currentFirstChild;
     while (child !== null) {
        if (child.key === key) {
          switch (child.tag) {
            case Fragment:
            ...
            
            case Block:
            ...
            
            default: {
              if (child.elementType === element.type) {
                 // 先刪除剩下的oldFiber節點
                deleteRemainingChildren(returnFiber, child.sibling);
                // 基於oldFiber節點和新節點的props新建新的fiber節點
                const existing = useFiber(child, element.props);
                existing.ref = coerceRef(returnFiber, child, element);
                existing.return = returnFiber; return existing;
              }
              break;
            }
         }
         
        deleteRemainingChildren(returnFiber, child);
        break;
      } else {
        // 沒匹配到說明新的fiber節點無法從oldFiber節點新建
        // 刪除掉所有oldFiber節點
        deleteChild(returnFiber, child);
     }
     child = child.sibling;
   }
   
 ...
 
 }

oldFiber鏈為空

對於沒有oldFiber節點的情況,只能新建newFiber節點。邏輯不複雜。

   function reconcileSingleElement(
     returnFiber: Fiber,
     currentFirstChild: Fiber | null,
     element: ReactElement,
     lanes: Lanes
   ): Fiber {
     const key = element.key;
     let child = currentFirstChild;
     while (child !== null) {
     
        // oldFiber鏈非空的處理
        ...
     } if (element.type === REACT_FRAGMENT_TYPE) {
        // 處理Fragment類型的節點
        ... 
     } else {
        // 用產生的ReactElement新建一個fiber節點
        const created = createFiberFromElement(element, returnFiber.mode, lanes);
        created.ref = coerceRef(returnFiber, currentFirstChild, element);
        created.return = returnFiber;
        return created;
     }
   }

單節點的更新就是這樣的處理,真正比較複雜的情況是多節點的diff。因為它涉及到節點的增刪和位移。

多節點

若組件最終產出的DOM元素是如下這樣:

<div key="a">aa</div>
<div key="b">bb</div>
<div key="c">cc</div>
<div key="d">dd</div>

那麼最終的newChildren為下面這樣(省略了一些與diff相關度不大的屬性)

[
 {$$typeof: Symbol(react.element), type: "div", key: "a" },
 {$$typeof: Symbol(react.element), type: "div", key: "b" },
 {$$typeof: Symbol(react.element), type: "div", key: "c" },
 {$$typeof: Symbol(react.element), type: "div", key: "d" }
]

多節點的變化有以下四種可能性。

  • 節點更新
舊: A - B - C
新: `A - B - C`
  • 新增節點
舊: A - B - C
新: A - B - C - `D - E`
  • 刪除節點
舊: A - B - C - `D - E`
新: A - B - C
  • 節點移動
舊: A - B - C - D - E
新: A - B - `D - C - E`

多節點的情況一定是屬於這四種情況的任意組合,這種情況會調用reconcileChildrenArray進行diff。按照以上四種情況,它會以newChildren為主體進行最多三輪遍歷,但這三輪遍歷並不是相互獨立的,事實上只有第一輪是從頭開始的,之後的每一輪都是上輪結束的斷點繼續。實際上在平時的實踐中,節點自身的更新是最多的,所以Diff算法會優先處理更新的節點。因此四輪遍歷又可以按照場景分為兩部分:

第一輪是針對節點自身屬性更新,剩下的兩輪依次處理節點的新增、移動,而重點又在移動節點的處理上,所以本文會着重講解節點更新和節點移動的處理,對刪除和新增簡單帶過。

節點更新

第一輪從頭開始遍歷newChildren,會逐個與oldFiber鏈中的節點進行比較,判斷節點的key或者tag是否有變化。

  • 沒變則從oldFiber節點clone一個props被更新的fiber節點,新的props來自newChildren中的新節點,這樣就實現了節點更新。
  • 有變化說明不滿足復用條件,立即中斷遍歷進入下邊的遍歷。Diff算法的複雜度也因為這個操作大幅降低。
let newIdx = 0;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
   ...
   // 更新節點,對於DOM節點來說,updateSlot內部會判斷
   // key 和 tag。任意一個不同,則返回null
   const newFiber = updateSlot( returnFiber,
     oldFiber,
     newChildren[newIdx],
     lanes,
   );
   // newFiber為null則說明當前的節點不是更新的場景,中止這一輪循環
   if (newFiber === null) {
     if (oldFiber === null) {
        oldFiber = nextOldFiber;
     }
     break;
   }
    ...
 }

我們來看一個例子,假設新舊的節點如下:

舊: A – B – C – D – E
新: A – B – D – C

在本輪遍歷中,會遍歷A – B – D – C。A和B都是key沒變的節點,可以直接復用,但當遍歷到D時,發現key變化了,跳出當前遍歷。例子中A 和 B是自身發生更新的節點,後面的D 和 C我們看到它的位置相對於oldFiber鏈發生了變化,會往下走到處理移動節點的循環中。

關於移動節點的參照物

為了方便說明,把保留在原位的節點稱為固定節點。經過這次循環的處理,可以看出固定節點是A 和 B。在newChildren中,最靠右的固定節點的位置至關重要,對於後續的移動節點的處理來說,它的意義是提供參考位置。所以,每當處理到最後一個固定節點時,要記住此時它的位置,這個位置就是lastPlacedIndex。關鍵代碼如下:

let newIdx = 0;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
 ...
 // 跳出邏輯
 
 ...
 // 如果不跳出,記錄最新的固定節點的位置
 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
 
 ...}

placeChild方法實際上是移動節點的方法,但當節點無需移動的時候,會返回當前節點的位置,對於固定節點來說,因為無需移動,所以返回的就是固定節點的index。

節點刪除

我們沒有提到對刪除節點的處理,實際上刪除節點比較簡單。

舊: A – B – C – D – E
新: A – B – C

因為遍歷的是newChildren,當它遍歷結束,但oldFiber鏈還沒有遍歷完,那麼說明剩下的節點都要被刪除。直接在oldFiber節點上標記Deletion的effectTag來實現刪除。

if (newIdx === newChildren.length) {
   // 新子節點遍歷完,說明剩下的oldFiber都是沒用的了,可以刪除
   deleteRemainingChildren(returnFiber, oldFiber);
   return resultingFirstChild;
}

deleteRemainingChildren調用了deleteChild,值得注意的是,刪除不僅僅是標記了effectTag為Deletion,還會將這個被刪除的fiber節點添加到父級的effectList中。

function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
   ...
   const last = returnFiber.lastEffect;
   // 將要刪除的child添加到父級fiber的effectList中,並添加上effectTag為刪除
   if (last !== null) {
     last.nextEffect = childToDelete;
     returnFiber.lastEffect = childToDelete;
   } else {
     returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
   }
   childToDelete.nextEffect = null;
   childToDelete.effectTag = Deletion;
}

節點新增

新增節點的場景也很好理解,當oldFiber鏈遍歷完,但newChildren還沒遍歷完,那麼餘下的節點都屬於新插入的節點,會新建fiber節點並以sibling為指針連成fiber鏈。

舊: A – B – C
新: A – B – C – D – E

插入的邏輯(省略了相關度不高的代碼)

if (oldFiber === null) {
 // 舊的遍歷完了,意味着剩下的都是新增的了
 for (; newIdx < newChildren.length; newIdx++) { // 首先創建newFiber
    const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
    ...
    // 再將newFiber連接成以sibling為指針的單向鏈表
    if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
    } else {
        previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
  }
  return resultingFirstChild;
}

節點移動

節點的移動是如下場景:

舊 A – B – C – D – E – F
新 A – B – D – C – E

經過第一輪遍歷的處理,固定節點為A B,最新的固定節點的位置(lastPlacedIndex)為1(B的位置)。此時oldFiber鏈中還剩C – D – E – F,newChildren中還剩D – C – E。

接下來的邏輯對於位置不一樣的節點,它自己會先更新再移動。因為此時剩餘的節點位置變了,更新又要復用oldFiber節點,所以為了在更新時方便查找,會將剩餘的oldFiber節點放入一個以key為鍵,值為oldFiber節點的map中。稱為existingChildren。

由於newChildren 和 oldFiber節點都沒遍歷完,說明需要移動位置。此刻需要明確一點,就是這些節點都在最新的固定節點的右邊

移動的邏輯是:newChildren中剩餘的節點,都是不確定要不要移動的,遍歷它們,每一個都去看看這個節點在oldFiber鏈中的位置(舊位置),遍歷到的節點有它在newChildren中的位置(新位置):

如果舊位置在lastPlacedIndex的右邊,說明這個節點位置不變。

原因是舊位置在lastPlacedIndex的右邊,而新節點的位置也在它的右邊,所以它的位置沒變化。因為位置不變,所以它成了固定節點,把lastPlacedIndex更新成新位置。

如果舊位置在lastPlacedIndex的左邊,當前這個節點的位置要往右挪。

原因是舊位置在lastPlacedIndex的左邊,新位置卻在lastPlacedIndex的右邊,所以它要往右挪,但它不是固定節點。此時無需更新lastPlacedIndex。

我們來用上邊的例子過一下這部分邏輯。

舊 A – B – C – D – E – F
新 A – B – D – C – E

位置固定部分 A – B,最右側的固定節點為B,lastPlacedIndex為1。這時剩餘oldFiber鏈為C – D – E – F,existingChildren為

{
   C: '節點C',
   D: '節點D',
   E: '節點E',
   F: '節點F'
}

newChildren的剩餘部分D – C – E繼續遍歷。

首先遍歷到D,D在oldFiber鏈中(A – B – C – D – E)的位置為3

3 > 1,oldFiber中D的位置在B的右邊,newChildren中也是如此,所以D的位置不動,此時最新的固定節點變成了D,更新lastPlacedIndex為3。並從existingChildren中刪除D,

{
   C: '節點C',
   E: '節點E',
   F: '節點F'
}

再遍歷到C,C在oldFiber鏈中(A – B – C – D – E)的索引為2

2 < 3,C原來在最新固定節點(D)的左邊,newChildren中C在D的右邊,所以要給它移動到右邊。並從existingChildren中刪除C。

{
   E: '節點E',
   F: '節點F'
}

再遍歷到E,E在oldFiber鏈中(A – B – C – D – E)的位置為4

4 > 3,oldFiber鏈中E位置在D的位置的右邊,新位置中也是如此,所以E的位置不動,此時最新的固定節點變成了E,更新lastPlacedIndex為4。並從existingChildren中刪除E,

{
   F: '節點F'
}

這個時候newChildren都處理完了,針對移動節點的遍歷結束。此時還剩一個F節點,是在oldFiber鏈中的,因為newChildren都處理完了,所以將它刪除即可。

existingChildren.forEach(child => deleteChild(returnFiber, child));

可以看到,節點的移動是以最右側的固定節點位置作為參照的。這些固定節點是指位置未發生變化的節點。每次對比節點是否需要移動之後,及時更新固定節點非常重要。

源碼

了解了上邊的多節點diff原理后,將上邊的關鍵點匹配到源碼上更方便能進一步理解。下面放出帶有詳細註釋的源碼。

 function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<*>,
    lanes: Lanes,
): Fiber | null {
    /* * returnFiber:currentFirstChild的父級fiber節點
       * currentFirstChild:當前執行更新任務的WIP(fiber)節點
       * newChildren:組件的render方法渲染出的新的ReactElement節點
       * lanes:優先級相關
    * */
    
    // resultingFirstChild是diff之後的新fiber鏈表的第一個fiber。
    let resultingFirstChild: Fiber | null = null;
    // resultingFirstChild是新鏈表的第一個fiber。
    // previousNewFiber用來將後續的新fiber接到第一個fiber之後
    let previousNewFiber: Fiber | null = null;
    
    // oldFiber節點,新的child節點會和它進行比較
    let oldFiber = currentFirstChild;
    // 存儲固定節點的位置
    let lastPlacedIndex = 0;
    // 存儲遍歷到的新節點的索引
    let newIdx = 0;
    // 記錄目前遍歷到的oldFiber的下一個節點
    let nextOldFiber = null;
    
    // 該輪遍歷來處理節點更新,依據節點是否可復用來決定是否中斷遍歷
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
        // newChildren遍歷完了,oldFiber鏈沒有遍歷完,此時需要中斷遍歷
        if (oldFiber.index > newIdx) {
            nextOldFiber = oldFiber; oldFiber = null;
        } else {
            // 用nextOldFiber存儲當前遍歷到的oldFiber的下一個節點
            nextOldFiber = oldFiber.sibling;
        }
        // 生成新的節點,判斷key與tag是否相同就在updateSlot中
        // 對DOM類型的元素來說,key 和 tag都相同才會復用oldFiber
        // 並返回出去,否則返回null
        const newFiber = updateSlot(
            returnFiber,
            oldFiber,
            newChildren[newIdx],
            lanes,
        );
        
        // newFiber為 null說明 key 或 tag 不同,節點不可復用,中斷遍歷
        if (newFiber === null) {
            if (oldFiber === null) {
            // oldFiber 為null說明oldFiber此時也遍歷完了
            // 是以下場景,D為新增節點
            // 舊 A - B - C 
            // 新 A - B - C - D oldFiber = nextOldFiber;
            }
            break;
        }
        if (shouldTrackSideEffects) {
            // shouldTrackSideEffects 為true表示是更新過程
            if (oldFiber && newFiber.alternate === null) {
                // newFiber.alternate 等同於 oldFiber.alternate 
                // oldFiber為WIP節點,它的alternate 就是 current節點
                // oldFiber存在,並且經過更新后的新fiber節點它還沒有current節點,
                // 說明更新后展現在屏幕上不會有current節點,而更新后WIP
                // 節點會稱為current節點,所以需要刪除已有的WIP節點
                deleteChild(returnFiber, oldFiber);
                }
            }
            // 記錄固定節點的位置
            lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
            // 將新fiber連接成以sibling為指針的單向鏈表
            if (previousNewFiber === null) {
                resultingFirstChild = newFiber;
            } else {
                previousNewFiber.sibling = newFiber;
            }
            previousNewFiber = newFiber;
            // 將oldFiber節點指向下一個,與newChildren的遍歷同步移動
            oldFiber = nextOldFiber;
         }
         
        // 處理節點刪除。新子節點遍歷完,說明剩下的oldFiber都是沒用的了,可以刪除.
        if (newIdx === newChildren.length) {
            // newChildren遍歷結束,刪除掉oldFiber鏈中的剩下的節點
            deleteRemainingChildren(returnFiber, oldFiber);
            return resultingFirstChild;
        }
        
        // 處理新增節點。舊的遍歷完了,能復用的都復用了,所以意味着新的都是新插入的了
        if (oldFiber === null) {
            for (; newIdx < newChildren.length; newIdx++) {
            
                // 基於新生成的ReactElement創建新的Fiber節點
                const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
                if (newFiber === null) {
                    continue;
                }
                // 記錄固定節點的位置lastPlacedIndex
                lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); 
                // 將新生成的fiber節點連接成以sibling為指針的單向鏈表
                if (previousNewFiber === null) {
                    resultingFirstChild = newFiber;
                } else {
                    previousNewFiber.sibling = newFiber; 
                }
                previousNewFiber = newFiber;
            }
            return resultingFirstChild;
        }
        // 執行到這是都沒遍歷完的情況,把剩餘的舊子節點放入一個以key為鍵,值為oldFiber節點的map中
        // 這樣在基於oldFiber節點新建新的fiber節點時,可以通過key快速地找出oldFiber
        const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
        
        // 節點移動
        for (; newIdx < newChildren.length; newIdx++) {
            // 基於map中的oldFiber節點來創建新fiber
            const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes, ); 
            if (newFiber !== null) {
                if (shouldTrackSideEffects) {
                    if (newFiber.alternate !== null) {
                        // 因為newChildren中剩餘的節點有可能和oldFiber節點一樣,只是位置換了,
                        // 但也有可能是是新增的.
                        
                        // 如果newFiber的alternate不為空,則說明newFiber不是新增的。
                        // 也就說明着它是基於map中的oldFiber節點新建的,意味着oldFiber已經被使用了,所以需
                        // 要從map中刪去oldFiber
                        existingChildren.delete(
                            newFiber.key === null ? newIdx : newFiber.key,
                        );
                     }
                  }
                  
                 // 移動節點,多節點diff的核心,這裏真正會實現節點的移動
                 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
                 // 將新fiber連接成以sibling為指針的單向鏈表
                if (previousNewFiber === null) {
                    resultingFirstChild = newFiber;
                } else {
                    previousNewFiber.sibling = newFiber; 
                }
                previousNewFiber = newFiber;
            }
         }
        if (shouldTrackSideEffects) {
           // 此時newChildren遍歷完了,該移動的都移動了,那麼刪除剩下的oldFiber
           existingChildren.forEach(child => deleteChild(returnFiber, child));
        }
        return resultingFirstChild;
 }

總結

Diff算法通過key和tag來對節點進行取捨,可直接將複雜的比對攔截掉,然後降級成節點的移動和增刪這樣比較簡單的操作。對oldFiber和新的ReactElement節點的比對,將會生成新的fiber節點,同時標記上effectTag,這些fiber會被連到workInProgress樹中,作為新的WIP節點。樹的結構因此被一點點地確定,而新的workInProgress節點也基本定型。這意味着,在diff過後,workInProgress節點的beginWork節點就完成了。接下來會進入completeWork階段。

原文來自:https://segmentfault.com/a/1190000039021724

站長推薦

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

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

沐鳴平台網址_看看這13句 JavaScript單行代碼,會讓你看起來像個專家!

1. 獲取一個隨機布爾值 (true/false)

這個函數使用 Math.random() 方法返回一個布爾值(true 或 false)。Math.random 將在 0 和 1 之間創建一個隨機數,之後我們檢查它是否高於或低於 0.5。這意味着得到真或假的幾率是 50%/50%。

const randomBoolean = () => Math.random() >= 0.5;
console.log(randomBoolean());
// Result: a 50/50 change on returning true of false

2. 檢查日期是否為工作日

使用這個方法,你就可以檢查函數參數是工作日還是周末。

const isWeekday = (date) => date.getDay() % 6 !== 0;
console.log(isWeekday(new Date(2021, 0, 11)));
// Result: true (Monday)
console.log(isWeekday(new Date(2021, 0, 10)));
// Result: false (Sunday)

3. 反轉字符串

有幾種不同的方法來反轉一個字符串。以下代碼是最簡單的方式之一。

const reverse = str => str.split('').reverse().join('');
reverse('hello world');     
// Result: 'dlrow olleh'

4. 檢查當前 Tab 頁是否在前台

我們可以通過使用 document.hidden 屬性來檢查當前標籤頁是否在前台中。

const isBrowserTabInView = () => document.hidden;
isBrowserTabInView();
// Result: returns true or false depending on if tab is in view / focus

5. 檢查数字是否為奇數

最簡單的方式是通過使用模數運算符(%)來解決。如果你對它不太熟悉,這裡是 Stack Overflow 上的一個很好的圖解。

const isEven = num => num % 2 === 0;
console.log(isEven(2));
// Result: true
console.log(isEven(3));
// Result: false

6. 從日期中獲取時間

通過使用 toTimeString() 方法,在正確的位置對字符串進行切片,我們可以從提供的日期中獲取時間或者當前時間。

const timeFromDate = date => date.toTimeString().slice(0, 8);
console.log(timeFromDate(new Date(2021, 0, 10, 17, 30, 0))); 
// Result: "17:30:00"
console.log(timeFromDate(new Date()));
// Result: will log the current time

7. 保留小數點(非四舍五入)

使用 Math.pow() 方法,我們可以將一個数字截斷到某個小數點。

const toFixed = (n, fixed) => ~~(Math.pow(10, fixed) * n) / Math.pow(10, fixed);
// Examples
toFixed(25.198726354, 1);       // 25.1
toFixed(25.198726354, 2);       // 25.19
toFixed(25.198726354, 3);       // 25.198
toFixed(25.198726354, 4);       // 25.1987
toFixed(25.198726354, 5);       // 25.19872
toFixed(25.198726354, 6);       // 25.198726

8. 檢查元素當前是否為聚焦狀態

我們可以使用 document.activeElement 屬性檢查一個元素當前是否處於聚焦狀態。

const elementIsInFocus = (el) => (el === document.activeElement);
elementIsInFocus(anyElement)
// Result: will return true if in focus, false if not in focus

9. 檢查瀏覽器是否支持觸摸事件

const touchSupported = () => {
  ('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch);
}
console.log(touchSupported());
// Result: will return true if touch events are supported, false if not

10. 檢查當前用戶是否為蘋果設備

我們可以使用 navigator.platform 來檢查當前用戶是否為蘋果設備。

const isAppleDevice = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
console.log(isAppleDevice);
// Result: will return true if user is on an Apple device

11. 滾動到頁面頂部

window.scrollTo() 方法會取一個 x 和 y 坐標來進行滾動。如果我們將這些坐標設置為零,就可以滾動到頁面的頂部。

注意:IE 不支持 scrollTo() 方法。

const goToTop = () => window.scrollTo(0, 0);
goToTop();
// Result: will scroll the browser to the top of the page

12. 獲取所有參數平均值

我們可以使用 reduce 方法來獲得函數參數的平均值

const average = (...args) => args.reduce((a, b) => a + b) / args.length;
average(1, 2, 3, 4);
// Result: 2.5

13. 轉換華氏度/攝氏度。(這個應該很少在國內用到吧)

處理溫度有時會讓人感到困惑。這 2 個功能將幫助你將華氏溫度轉換為攝氏溫度,反之亦然。

const celsiusToFahrenheit = (celsius) => celsius * 9/5 + 32;
const fahrenheitToCelsius = (fahrenheit) => (fahrenheit - 32) * 5/9;
// Examples
celsiusToFahrenheit(15);    // 59
celsiusToFahrenheit(0);     // 32
celsiusToFahrenheit(-20);   // -4
fahrenheitToCelsius(59);    // 15
fahrenheitToCelsius(32);    // 0

原文翻譯來自:https://medium.com/dailyjs/13-JavaScript-one-liners-thatll-make-you-look-like-a-pro-29a27b6f51cb

站長推薦

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

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

沐鳴註冊平台官網_node.js+express 做301重定向實驗

本地項目啟動:

1.添加代碼

 if(req.url === '/apply'){
            res.writeHead(301,{
                'Location':'https://www.baidu.com/'
            })
        }

之後http://localhost:8080/apply會跳轉到https://www.baidu.com/ ,跳轉肉眼不可見

項目停掉之後,在瀏覽器地址欄輸入http://localhost:8080/apply  仍然會跳轉到https://www.baidu.com/ ,說明瀏覽器記錄了這個跳轉。

手動刪除掉瀏覽器緩存,只清楚緩存的圖片和文件就可以刪掉:

輸入http://localhost:8080/apply,不再跳轉到https://www.baidu.com/了。

我重啟項目,看一下發送的請求:

劃線表示永久重定向到緩存。打開看上線了網站的案例:

沒有把重定向緩存,所以它刪除綁定后,是不會再跳轉到自定義域名的。

關於301跳轉的問題,我們特別邀請 Baiduspider 技術專家對此做了解答。

問:我設置了 301 跳轉,多久可以生效?

答:目前百度無法承諾 301 跳轉的生效時間,因為站長感受到的生效時間會受多因素影響,比如 Baiduspider 再次抓取這個頁面發現其設置了 301 的時間、網頁的重要程度以及自身質量等。

問:301 跳轉生效后,原網頁是否會被刪除?

答:不會,會與跳轉后的新網頁同時存在。

問:原網頁新網頁都存在,相當於兩個內容重複的頁面,排名怎麼處理?

答:通常百度會認為跳轉后的新網頁更加重要,原網頁是被放棄的網頁,會讓新網頁繼承原網頁屬性,在搜索結果中會優先展現新網頁。

問:將老域名跳轉到新域名,是僅首頁 301 就可以了?還是必須所有頁面 301?

答:必須所有頁面 301 至新域名的相應頁面。

問:原域名所有頁面都跳轉至新域頁首頁會有什麼影響?

答:新域名首頁將會替換舊域名首頁,新域名首頁僅繼承舊域名首頁的屬性,不會產生 1+1>2 的效果。舊域名在短時間內仍然可查。但隨着時間推移,舊域名由於質量下降最終會被清除出數據庫。

站長推薦

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

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

沐鳴開戶_ECMAScript 6 的Map映射

映射(Map)是 ECMAScript 6 規範中引入的一種數據結構。這是一種存儲鍵值對列表很方便的方法,類似於其他編程語言中的詞典或者哈希表。



什麼是映射

JavaScript 的對象(Object),本質上是鍵值對的集合(Hash結構),但是傳統上只能用字符串當作鍵,這給使用帶來了很大的限制。為了解決這個問題,ECMAScript 6 引入了 Map 數據結構。它類似於對象,也是鍵值對的集合,但是”鍵”的範圍不僅僅局限於字符串,而是各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構(對象結構)提供了”字符串—值”的對應,而 Map 結構提供了”值—值”的對應,是一種更完善的 Hash 結構的實現。

下面來看一個簡單的示例,了解Map的基本用法:

//聲明map實例
const page_info = new Map()
// 向 map 中添加元素
page_info.set("seo", {
"keywords": "infoq、Map",
"description":"Map對象是一種簡單的鍵/值映射,其中的鍵和值可以是任意值(原始值或對象的值)"
})
page_info.set("title", "JavaScript es6的map映射")
console.log(page_info)
console.log(typeof page_info) // object

輸出結果為:

Map {
'seo' => {
keywords: 'infoq、Map',
description: 'Map對象是一種簡單的鍵/值映射,其中的鍵和值可以是任意值(原始值或對象的值)'
},
'title' => 'javascript es6的map映射'
}
object

從輸出結果看,本質上Map(映射)就是一個 object 對象。



Object 與 Map 區別

Object 和 Map 的相似之處在於,都是按鍵存取一個值,而且鍵都是可以刪除的。可以看出,二者之間是非常相似的,它的不同這之處在於:

 MapObject意外的鍵 Map  默認情況不包含任何鍵。只包含顯式插入的鍵。

一個  Object  有一個原型, 原型鏈上的鍵名有可能和你自己在對象上的設置的鍵名產生衝突。

注意:  雖然 ES5 開始可以用  Object.create(null)  來創建一個沒有原型的對象,但是這種用法不太常見。

鍵的類型一個  Map 的鍵可以是 任意值 ,包括函數、對象或任意基本類型。一個 Object  的鍵必須是一個  String  或是 Symbol 。鍵的順序 Map  中的 key 是有序的。因此,當迭代的時候,一個  Map  對象以插入的順序返回鍵值。

一個  Object  的鍵是無序的

注意:自ECMAScript 2015規範以來,對象 確實 保留了字符串和 Symbol 鍵的創建順序; 因此,在只有字符串鍵的對象上進行迭代將按插入順序產生鍵。

size  Map  的鍵值對個數可以輕易地通過 size  屬性獲取 Object  的鍵值對個數只能手動計算迭代 Map  是 iterable 的,所以可以直接被迭代。迭代一個 Object 需要以某種方式獲取它的鍵然後才能迭代。性能在頻繁增刪鍵值對的場景下錶現更好。在頻繁添加和刪除鍵值對的場景下未作出優化。



Map映射常用方法

常用的 Map 方法有:賦值 set(key, value)  、獲取 get(key)  、移除指定鍵名及其對應的值delete(key) 、判斷是否存在 has(key)  、 獲取所有值 values()  、 key/value 迭代器 entries() 、遍歷 forEach() 和 清空所有鍵/值對 clear()   等。

聲明並初始化

const new_map = new Map();
console.log(new_map); //輸出:Map {}

賦值 set

賦值使用 map.set(key,value) ,可以用於增加新的 鍵/值 對或者修改 鍵/值 對,返回整個Map對象。

const page_info = new Map()
// 設置值
page_info.set("seo", {
"keywords": "infoq、Map",
"description":"Map對象是一種簡單的鍵/值映射,其中的鍵和值可以是任意值(原始值或對象的值)"
});
console.log(page_info);
page_info.set("seo", "seo信息");
console.log(page_info);

上面的示例增加值,並修改值。

Map {
'seo' => {
keywords: 'infoq、Map',
description: 'Map對象是一種簡單的鍵/值映射,其中的鍵和值可以是任意值(原始值或對象的值)'
}
}
Map { 'seo' => 'seo信息' }

獲取鍵值 get

使用  get(key)  獲取鍵值,如果獲取的  key->value  不存則返回  undefined

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
const title = page_info.get("title");
const seo_info = page_info.get("seo");
console.log(title); //javascript es6的map映射
console.log(seo_info); //undefined

刪除鍵值 delete

map.delete(key)  刪除指定  key  的鍵值對,返回成功或失敗結果,刪除成功返回 true ,刪除失敗返回 false 。

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }

const deleted_author = page_info.delete("author");
const deleted_seo = page_info.delete("seo");
console.log(deleted_author); // true
console.log(deleted_seo); // false
console.log(page_info); // Map { 'title' => 'javascript es6的map映射' }

判斷鍵值是否存在 has

使用 map.has(key) 判斷指定 key 是否存在。

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
console.log(page_info); // Map { 'title' => 'javascript es6的map映射' }

console.log(page_info.has("title")); // true
console.log(page_info.has("seo")); // false

獲取所有鍵值 values()

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info.values()); // [Map Iterator] { 'javascript es6的map映射', 'infoq' }

key/value 迭代器 entries()

使用 map.entries() 返回一個包含Map對象中每一個 [key, value] 數組的Iterator迭代器。

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info.entries());

輸出的結果為:

[Map Entries] {
[ 'title', 'javascript es6的map映射' ],
[ 'author', 'infoq' ]
}

遍歷所有鍵值 forEach(callback)

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
page_info.forEach((value,key)=>{
console.log(key,value);
});

輸出的結果為:

title javascript es6的map映射
author infoq

清空Map映射所有鍵值 clear()

使用 map.clear() 清空Map所有的鍵值對。

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
page_info.clear();
console.log(page_info); // Map {}



與其它數據結構的轉換

Map映射轉為數組

Map轉為數組最方便方法是使用擴展運算符 …

const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log([...page_info]); // [ [ 'title', 'javascript es6的map映射' ], [ 'author', 'infoq' ] ]

Map映射轉為對象

function mapToObj(map) {
const obj = Object.create(null);
map.forEach((v,k)=>{
obj[k] = v;
});
return obj;
}
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");

console.log( mapToObj(page_info));

輸出結果為:

[Object: null prototype] {
title: 'javascript es6的map映射',
author: 'infoq'
}

數組轉為Map映射

將數組傳入Map構造函數即可,即 new Map(array) 。

const page_info = [
["title","javascript es6的map映射"],
["author","infoq"]
];
console.log(new Map(page_info)); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }

對象轉為Map

對象轉為Map映射通過 Object.entries() 。

const page_info = {
title:"javascript es6的map映射",
author:"infoq"
};
console.log(new Map(Object.entries(page_info))); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }

映射Map轉為jsON

Map 轉為 jsON ,步驟是先把Map轉為對象,即前面的 mapToObj ,然後使用 JSON.stringify 方法

function mapToObj(map) {
const obj = Object.create(null);
map.forEach((v,k)=>{
obj[k] = v;
});
return obj;
}
function mapToJson(map){
return JSON.stringify(mapToObj(map));
}
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log( mapToJson(page_info)); // {"title":"javascript es6的map映射","author":"infoq"}

原文 https://xie.infoq.cn/article/b12c6d6700992222617fb2e30


站長推薦

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

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

沐鳴總代平台_為什麼 Vue3.0 要重寫響應式系統

面試的時候經常被問到 響應式 相關的內容,而vue3.0 更新后,面試官又有了新的武器;

面試官: 為什麼 Vue3.0 要重寫響應式系統?

懵逼樹上懵逼果,懵逼樹下你和我,面試官在問什麼,我該怎麼回答,完全不知道怎麼回事;

有些經驗的小夥伴可能會從解釋 Proxy 的好處開始簡單聊一下,比如: Proxy 是直接代理對象,而不是劫持對象的屬性;更好的數組監控;

這樣的回答,勉強算是合格吧

那到底應該怎麼答呢?

面試官背後的出題邏輯

別急,咱們先整理一下思路,孫子兵法有雲:“知己知彼,百戰不殆”;面試就像打仗,你來我往,所以我們需要換位思考,想一想,為什麼面試官會問這樣一個問題?面試官想從這個問題里得到什麼回答?這個問題可以考察哪些技術點? 想清楚這個問題,再回到自己身上,這些技術點,你都掌握了嗎?

說得直白一點,面試就像考試,你需要先 讀題、審題才能答好這道題;

為什麼很多人認為 “面試造火箭,工作擰螺絲”?因為沒有換位思考,沒有想清楚面試題背後的邏輯;

那我們想清楚這個邏輯之後,需要我們做的就是提取技術點,整理思路,做出對應解答;當然, 前提是你需要具備這些技術能力

那麼接下來,我就嘗試拆解一下這個面試題了,提取其中的知識點。

對於你來說,就是要看看這些知識點,你都掌握了多少?

為什麼 Vue3.0 要重寫響應式系統 ?

為什麼重寫?如果之前好好的,重寫就沒有意義,那之前存在什麼問題,現在是怎麼解決的?就是關鍵點了;

不知道你對 Vue2.x 的響應式掌握多少,是不是欠下了技術的債呢?沒關係,我來幫你還債,先梳理 Vue2.x 的響應式;

其實基於這個面試題,背後還有很多技術點,上面這些,是與當前題目有直接關係的,實際面試中,很有可能基於這些技術點,在進行深入交流,這裏就不擴展了,你能把現在這些問題理清楚,就算賺到了;

Vue2.x 響應式

其實關於這一點,在Vue 的官方文檔中,早已經有過說明了,而且說得非常詳細;官方文檔: https:// cn.vuejs.org/v2/guide/r eactivity.html

當你把一個普通的 JavaScript 對象傳入 Vue 實例作為 data 選項,Vue 將遍歷此對象所有的 property,並使用 Object.defineProperty 把這些 property 全部轉為getter/setter。Object.defineProperty 是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因。

這些 getter/setter 對用戶來說是不可見的,但是在內部它們讓 Vue 能夠追蹤依賴,在 property 被訪問和修改時通知變更。這裏需要注意的是不同瀏覽器在控制台打印數據對象時對 getter/setter 的格式化並不同,所以建議安裝 vue-devtools 來獲取對檢查數據更加友好的用戶界面。

每個組件實例都對應一個 watcher 實例,它會在組件渲染的過程中把“接觸”過的數據 property 記錄為依賴。之後當依賴項的 setter 觸發時,會通知 watcher,從而使它關聯的組件重新渲染。

我們使用官方給的一張圖示,來梳理整個流程;


我們先來看一段代碼

響應式原理

data 中的 obj 就是一個普通的 JavaScript 對象,通過點擊 Click 按鈕,將獲取到的隨機數賦值給 this.message ,而 this.message 指向的就是 data 中 obj 對象的 message 屬性;當message 發生數據改變時,頁面中 H1 標籤的內容會隨之改變,這個過程就是就是響應式的;那麼Vue 是如何實現的呢?

首先,Vue 內部使用 Object.defineProperty() 將 Data 中的每一個成員都轉換為 getter / setter 的形式;getter 用來依賴收集,setter 用來派發更新;而模板內容,最終會被編譯為 render 函數,在 render 函數中,我們能發現 _v(_s(message)) message 被訪問了,就會觸發 getter 來進行依賴收集,而在代碼中的點擊事件中,一旦事件處理程序被觸發執行,那麼 message 則會被修改,就會觸發 setter來進行派發更新;

雖然流程理清楚了,但是總感覺少點什麼,怎麼才能更通透呢?

我們用代碼來模擬整個的實現過程;

defineProperty 模擬代碼

defineProperty 的基本用法,直接看手冊就行了: https:// developer.mozilla.org/z h-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

我們來看看代碼:

<div id="app"> 
    hello 
</div> 
<script> 
    // 模擬 Vue 中的 data 選項 
    let data = { 
      msg: 'hello' 
    } 
    // 模擬 Vue 的實例 
    let vm = {} 
    // 數據劫持:當訪問或者設置 vm 中的成員的時候,做一些干預操作 
    Object.defineProperty(vm, 'msg', { 
      // 可枚舉(可遍歷) 
      enumerable: true, 
      // 可配置(可以使用 delete 刪除,可以通過 defineProperty 重新定義) 
      configurable: true, 
      // 當獲取值的時候執行 
      get () { 
        console.log('get: ', data.msg) 
        return data.msg 
      }, 
      // 當設置值的時候執行 
      set (newValue) { 
        console.log('set: ', newValue) 
        if (newValue === data.msg) { 
          return 
        } 
        data.msg = newValue 
        // 數據更改,更新 DOM 的值 
        document.querySelector('#app').textContent = data.msg 
      } 
    }) 
    // 測試 
    vm.msg = 'Hello World' 
    console.log(vm.msg) 
</script>

你沒有看錯,加上註釋,一共 36行代碼,這就是 Vue2.x 對響應式實現的整個流程;

繼續實現多個數據的響應式

<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模擬 Vue 中的 data 選項
    let data = {
      msg: 'hello',
      count: 10
    }
    // 模擬 Vue 的實例
    let vm = {}
    proxyData(data)
    function proxyData(data) {
      // 遍歷 data 對象的所有屬性
      Object.keys(data).forEach(key => {
        // 把 data 中的屬性,轉換成 vm 的 setter/setter
        Object.defineProperty(vm, key, {
          enumerable: true,
          configurable: true,
          get () {
            console.log('get: ', key, data[key])
            return data[key]
          },
          set (newValue) {
            console.log('set: ', key, newValue)
            if (newValue === data[key]) {
              return
            }
            data[key] = newValue
            // 數據更改,更新 DOM 的值
            document.querySelector('#app').textContent = data[key]
          }
        })
      })
    }
    // 測試
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>

上面的代碼只是模擬了 響應式 的原理,但Vue在實現中,肯定不會那麼簡單,接下來,我們看一下源碼呀……

Vue2 源碼解讀

首先找到響應式代碼的處理位置:

關鍵位置 作用 源碼位置
function Vue () {} Vue 構造函數 core/instance/index.js:8
Vue.prototype._init 初始化組件實例對象 core/instance/init.js:16
initState 初始化組件狀態相關成員 core/instance/state.js:48
initData 初始化用戶傳入的 data 數據 core/instance/state.js:112
observe 觀察 data core/observer/index.js:110
關鍵位置 作用 源碼位置
observe 觀察 data core/observer/index.js:110
class Observer Observer 邏輯 core/observer/index.js:37
walk 遍歷對象成員分別處理 core/observer/index.js:64
definereactive 為組件實例定義響應式數據 core/observer/index.js:135
Object.defineProperty 攔截數據的訪問和修改 core/observer/index.js:157

看完Vue2.x 響應式的代碼,我們再回過頭來思考最開始的問題, 為什麼 Vue3.0 要重寫響應式系統 ?

為什麼重寫?如果之前好好的,重寫就沒有意義,那之前存在什麼問題,換句話問就是 defineProperty 有什麼問題?

Object.defineProperty 的問題

其實, defineProperty 的問題,在Vue2.x 的手冊中,已經說過了;“哎,很多人就是不看文檔啊”

https:// cn.vuejs.org/v2/guide/r eactivity.html#%E5%AF%B9%E4%BA%8E%E6%95%B0%E7%BB%84

下面分別使用 Vue2 和 Vue3 實現了一個小功能,代碼一模一樣,功能當然也一樣,但是,在 Vue2 中就會有Bug,而運行在vue3中的,則沒有任何問題;

Vue2:

<template>
  <div class="about">
    <h1>This is an about page</h1>
    <p v-for="(v, k) in users">
      {{ v.names }}
    </p>
    <button @click="changes">更新</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, names: "路飛-v2" },
        { id: 2, names: "鳴人-v2" },
      ],
    };
  },

  methods: {
    changes() {
      // this.users[0] = {id:'0',names:'liuneng'}
      // this.users[1].names = 'lnsdsdfg'
      this.users[1] = { id: "1", names: "劉能-v2" };
    },
  },
};
</script>

<style lang="stylus" scoped></style>

Vue3:

<template>
  <div class="about">
    <h1>This is an about page</h1>
    <p v-for="(v, k) in users">
      {{ v.names }}
    </p>

    <button @click="changes">更新</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [
        { id: 1, names: "路飛-v3" },
        { id: 2, names: "鳴人-v3" },
      ],
    };
  },

  methods: {
    changes() {
      // this.users[0] = {id:'0',names:'liuneng'}
      // this.users[1].names = 'lnsdsdfg'
      this.users[1] = { id: "1", names: "劉能-v3" };
    },
  },
};
</script>

其核心點在於 defineProperty 不能很好的實現對數組下標的監控,而在 Vue2 的實現代碼中,沒有更好的方案對此進行改善,尤大索性直接放棄了實現;關於這個問題,尤大也在 github 做過回應,截個圖給大家看看;

那麼,Vue 目前還沒有解決的問題,Vue3中顯然是已經解決的了,問題是,Vue3 是如何解決的呢?前面在 Vue3 的代碼中我們使用的是傳統的 Options Api 來實現的數據響應式, 而在 Vue3 中全新的 Composition Api 也實現了響應式系統,我們先來感受一下 Composition Api 的基礎用法

Composition API 的響應式系統

ref 響應式

<template>
  <!-- 不需要.value -->
  <button @click="addNu"> Composition API: {{nu}}</button>
</template>

<script>
// 引入 ref 
import {ref} from "vue"
export default {
  setup() {
    // 定義 ref 響應式數據
    const nu = ref(1);
    // 定義函數
    function addNu(){
      nu.value++;
    }

    // 將數據和方法返回,即可在模板中直接使用
    return {
      nu,
      addNu
    };
  },
};
</script>

reactive 響應式

<template>
  <!-- 不需要.value -->
  <button @click="addNu"> Composition API: {{nu}}</button>
  <!-- reactive 響應式數據 -->
  <h2>{{revData.name}}</h2>
  <h3 @click="ageAdd">年齡:{{revData.age}}</h3>
  <p v-for="(v,k) in revData.skill"> {{v}} </p> 
</template>

<script>
// 引入 ref 
import {reactive, ref} from "vue"
export default {
  setup() {
    // 定義 ref 響應式數據
    const nu = ref(1);

    // reactive 響應式數據
    const revData = reactive({
      name: '路飛',
      age: 22,
      skill:['橡膠機關槍','吃雞腿'],
    })

    function ageAdd(){
      revData.age++
    }

    // 定義函數
    function addNu(){
      nu.value++;
    }

    // 將數據和方法返回,即可在模板中直接使用
    return {
      nu,
      addNu,
      revData,
      ageAdd
    };
  },
};
</script>

Vue3 中的響應式是如何實現的呢?關鍵點在於Proxy 函數;

Proxy 實現原理

使用 Proxy 實現的響應式代碼,要比使用 defineProperty 的代碼簡單得多,因為 Proxy 天然的能夠對整個對象做監聽,而不需要對數據行遍歷后做監聽,同時也就解決了數組下標的問題;

我們來一段模擬代碼看一下:

<div id="app">
    hello
  </div>
  <script>
    // 模擬 Vue 中的 data 選項
    let data = {
      msg: 'hello',
      count: 0
    }

    // 模擬 Vue 實例
    const vm = new Proxy(data, {
      // 執行代理行為的函數
      // 當訪問 vm 的成員會執行
      get (target, key) {
        console.log('get, key: ', key, target[key])
        return target[key]
      },
      // 當設置 vm 的成員會執行
      set (target, key, newValue) {
        console.log('set, key: ', key, newValue)
        if (target[key] === newValue) {
          return
        }
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
      }
    })

    // 測試
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>

來自:https://zhuanlan.zhihu.com/p/346241358

站長推薦

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

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

沐鳴註冊平台官網_css3屬性: will-change

1. CPU和GPU

CPU即中央處理器,它的功能主要是解釋計算機指令以及處理計算機軟件中的數據,也被稱為主板。

GPU即圖形處理器,是與處理和繪製圖形相關的硬件。GPU是專為執行複雜的數學和幾何計算而設計的,有了它,CPU就從圖形處理的任務中解放出來,可以執行其他更多的系統任務

硬件加速(或者說GPU加速)是指在計算機中透過把計算量非常大的工作分配給專門的硬件來處理來減輕CPU的工作量的技術。在css transition, transform和animation的世界里,他暗示我們應該卸載進程到GPU,因此加快速度。這種情況通過向它自己的層疊加元素,當加載動畫的時候可以加速渲染。

硬件加速依賴於瀏覽器渲染頁面使用的layering model,當特定的操作(css 3D變形)作用於頁面上的一個元素,元素移動到它自己的layer,在這個layer中元素合一不受頁面其他元素的干擾獨立渲染,然後複合到頁面中去。在這種隔離內容渲染的工作方式下,如果頁面的變化僅僅是該元素的變形,其餘部分不必被重新渲染,這會帶來顯著的速度優勢。值得注意的是只有3D變形會有自己的layer,2D變形不會。

CSS的動畫、變形、漸變並不會自動的觸發GPU加速,而是使用瀏覽器稍慢的軟件渲染引擎。然而一些瀏覽器提供了hardware acceleration by means of certain properties來獲取更高的渲染性能。 舉個例子,opacity屬性是幾個能夠加速的屬性之一,因為GPU可以方便的處理。基本上任何層的透明度漸變瀏覽器都會交給GPU處理來加速。除了opacity能夠使用GPU處理的就是CSS 3D變形了

2. translateZ() (or translate3d()) Hack

我們都通過translateZ()或者translate3d() hack來騙取瀏覽器觸發硬件加速,具體做法就是為元素添加沒有變化的3D變形,比如元素在2維空間可以通過添加以下CSS來硬件加速

transform: translate3d(0, 0, 0);

然而強制使用hack方式創建layer並不是長久之計,創建layer的技術可以使頁面加速,但是也有代價,它們佔用RAM和GPU存儲空間(考慮到移動設備的存儲容量有限),所以必須小心使用,確保這麼做真的對頁面渲染有所幫助。為了避免創建layer的hacks,一個允許我們提前通知瀏覽器我們將對元素做何種變化的CSS屬性被引入,這樣瀏覽器可以優化處理元素渲染的方式,為元素提前準備昂貴的動畫處理操作,這就是wiil-change屬性。

3.wiil-change

will-change屬性可以提前通知瀏覽器我們要對元素做什麼動畫,這樣瀏覽器可以提前準備合適的優化設置。這樣可以避免對頁面響應速度有重要影響的昂貴成本。元素可以更快的被改變,渲染的也更快,這樣頁面可以快速更新,表現的更加流暢。

當對於素使用 CSS 3D變形時,元素及其內容可以在合成到頁面之前被創建到我們之前說的layer。然而把元素放到layer中是個昂貴的操作,這將會導致變形動畫延遲一個課件的瞬間,也就是flicker
為了避免這種延時,我們可以在發生之前通知瀏覽器,這樣瀏覽器會有一定的時間去準備這些變化,當發生的時候layer已經準備好了,這樣動畫酒會很流暢,不會閃屏。

will-change: transform;

可以寫多個屬性變化,逗號隔開

will-change: transform, opacity;

注意事項:

不要將 will-change 應用到太多元素上:瀏覽器已經儘力嘗試去優化一切可以優化的東西了。有一些更強力的優化,如果與 will-change 結合在一起的話,有可能會消耗很多機器資源,如果過度使用的話,可能導致頁面響應緩慢或者消耗非常多的資源。濫用會使頁面崩潰。。。

*,
*::before,
*::after {
    will-change: all;
}

雖然看起來很屌,但其實對頁面渲染傷害很大,這樣的規則設了和沒設沒什麼區別,瀏覽器本來就嘗試最優的渲染所有元素,就等於你讓老師重點照顧班裡每個同學一樣,就是廢話!

其實這甚至是有害的,因為一些操作會佔用太多的資源,甚至會導致頁面奔潰,就等於強制要求老師為每個學生補課,累死了。。。

有節制地使用:通常,當元素恢復到初始狀態時,瀏覽器會丟棄掉之前做的優化工作。但是如果直接在樣式表中顯式聲明了 will-change 屬性,則表示目標元素可能會經常變化,瀏覽器會將優化工作保存得比之前更久。所以最佳實踐是當元素變化之前和之後通過腳本來切換 will-change 的值。

不要過早應用 will-change 優化:如果你的頁面在性能方面沒什麼問題,則不要添加 will-change 屬性來榨取一丁點的速度。 will-change 的設計初衷是作為最後的優化手段,用來嘗試解決現有的性能問題。它不應該被用來預防性能問題。過度使用 will-change 會導致大量的內存佔用,並會導致更複雜的渲染過程,因為瀏覽器會試圖準備可能存在的變化過程。這會導致更嚴重的性能問題。

給它足夠的工作時間:這個屬性是用來讓頁面開發者告知瀏覽器哪些屬性可能會變化的。然後瀏覽器可以選擇在變化發生前提前去做一些優化工作。所以給瀏覽器一點時間去真正做這些優化工作是非常重要的。使用時需要嘗試去找到一些方法提前一定時間獲知元素可能發生的變化,然後為它加上 will-change 屬性。

在變化前立即為元素添加will-change幾乎沒有作用,可能還不如不設置,因為會導致新的layer創建,如下:

.element:hover {
    will-change: transform;
    transition: transform 2s;
    transform: rotate(30deg) scale(1.5);
}

需要給瀏覽器足夠的時間,如下:

.element {
    /* style rules */
    transition: transform 1s ease-out;
}
.element:hover {
    will-change: transform;
}
.element:active {
    transform: rotateY(180deg);
}

will-change顧名思義,通知瀏覽器即將發生的變化,而不是正在發生的變化。使用will-change,我們要求瀏覽器重點照顧我們聲明的元素,為了這個瀏覽器需要一定的時間來組織優化操作,這樣當變化發生的時候,優化才能沒有延遲的作用到元素

語法:

/* 關鍵字值 */
will-change: auto;
will-change: scroll-position;
will-change: contents;
will-change: transform;        /* <custom-ident>示例 */
will-change: opacity;          /* <custom-ident>示例 */
will-change: left, top;        /* 兩個<animateable-feature>示例 */
 
/* 全局值 */
will-change: inherit;
will-change: initial;
will-change: unset;
 
## auto ## 
表示沒有特別指定哪些屬性會變化,瀏覽器需要自己去猜,然後使用瀏覽器經常使用的一些常規方法優化。
## <animateable-feature> ##
 1) scroll-position 告訴瀏覽器,要不久后動畫啦(改變滾動條的位置或者使之產生動畫)
 2) contents:告訴瀏覽器 不久后改變元素內容中的某些東西,或者使它們產生動畫。
## <custom-ident> ## 
不久后改變指定的屬性名或者使之產生動畫。如果屬性名是簡寫,則代表所有與之對應的簡寫或者全寫的屬性。

瀏覽器兼容性:

Desktop

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 36 36 (36)[1] 未實現 24 未實現

Mobile

Feature Android Firefox Mobile (Gecko) IE Phone Opera Mobile Safari Mobile
Basic support 37 36.0 (36) [1] 未實現 未實現 未實現

來自:https://www.cnblogs.com/hongplum/p/14622574.html

站長推薦

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

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

沐鳴平台網址_ES11(2020)新特性:String 的 matchAll 方法、動態導入語句 import()等

Ecma標準定義了ECMAScript 2020語言。它是ECMAScript語言規範的第11版。自從1997年第一版出版以來,ECMAScript已經發展成為世界上使用最廣泛的通用編程語言之一。它被稱為嵌入在web瀏覽器中的語言,但也被廣泛應用於服務器和嵌入式應用程序。

那麼ES11又引入了那些新特性呢?

1. String 的 matchAll 方法

2. 動態導入語句 import()

3. import.meta

4. export * as ns from ‘module’

5. Promise.allSettled

6. 新增數據類型: BigInt

7. 頂層對象: globalThis

8. 空值合併運算符: ??

9. 可選鏈操作符:?.

一、matchAll

matchAll() 方法返回一個包含所有匹配正則表達式的結果的迭代器。可以使用 for…of 遍歷,或者使用 展開運算符(…) 或者 Array.from 轉換為數組.

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const matchs = str.matchAll(regexp);
console.log(matchs); // RegExpStringIterator {}
console.log([...matchs])
/*
0: (4) ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]
1: (4) ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]
length: 2
/*

RegExp.exec()  和 matchAll() 區別:

在 matchAll 出現之前,通過在循環中調用 regexp.exec() 來獲取所有匹配項信息。

const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
let match;

while ((match = regexp.exec(str)) !== null) {
  console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."

如果使用 matchAll ,就可以不必使用 while 循環加 exec 方式

const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);

for (const match of matches) {
  console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."

二、import()

import 標準的用法是導入的木塊是靜態的,會使所有被帶入的模塊在加載時就別編譯,無法做到按需加載編譯,降低了首頁的加載速度。在某些場景中,你可能希望根據條件導入模塊,或者按需導入模塊,這是就可以使用動態導入代替靜態導入了

在import() 之前,我們需要更具條件導入模塊時只能使用 require() 

if (xx) {
  const module = require('/module')  
}

// 現在可以這麼寫
if (xx) {
  const module = import('/module')
}

@babel/preset-env 已經包含了 @babel/plugin-syntax-dynamic-import,因此如果要使用 import() 語法,只需要配置 @babel/preset-env 即可。

另外:import() 返回的是一個Promise 對象:

// module.js
export default {
  name: 'shenjp'
}

// index.js
if (true) {
  let module = import('./module.js');
  console.log(module); // Promise {<pending>
  module.then(data => console.log(data)); // Module {default: {name: "shenjp"}, __esModule: true, Symbol(Symbol.toStringTag): "Module"}
}

三、import.meta

import.meta對象是由ECMAScript實現的,它帶有一個null的原型對象。這個對象可以擴展,並且它的屬性都是可寫,可配置和可枚舉的。

<script type="module" src="my-module.mjs"></script>
console.log(import.meta); // { url: "file:///home/user/my-module.mjs" }

因為 import.meta 必須要在模塊內部使用,如果不加 type=”module”,控制台會報錯:Cannot use ‘import.meta’ outside a module。

在項目中需要下載 @open-wc/webpack-import-meta-loader  才能正常使用。

module: {
    rules: [
        {
            test: /\.js$/,
            use: [
                require.resolve('@open-wc/webpack-import-meta-loader'),
                {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            "@babel/preset-env",
                            "@babel/preset-react"
                        ]
                    },
                }
            ]
        }
    ]
}

效果如下:

//src/index.js
import React from 'react';
console.log(import.meta);//{index.js:38 {url: "http://127.0.0.1:3000/src/index.js"}}

四、export * as ns from ‘module’

ES2020新增了 export * as XX from ‘module’,和 import * as XX from ‘module’

// module.js
export * as ns from './info.js'

可以理解為下面兩條語句的合併:

import * as ns from './info.js';
export { ns };

需要注意的是:export * as ns from ‘module’ 並不會真的導入模塊,因此在該模塊中無法使用 ns。

五、Promise.allSettled

Promise.allSettled()方法返回一個在所有給定的promise都已經fulfilled或rejected后的promise,並帶有一個對象數組,每個對象表示對應的promise結果。

當您有多個彼此不依賴的異步任務成功完成時,或者您總是想知道每個promise的結果時,通常使用它。

想比較之下, Promise.all() 更適合做相互依賴的Promise,只要有一個失敗就結束

const promise1 = Promise.resolve(100);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'info'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'name'))

Promise.allSettled([promise1, promise2, promise3]).
    then((results) => console.log(result));
/* 
    [
        { status: 'fulfilled', value: 100 },
        { status: 'rejected', reason: 'info' },
        { status: 'fulfilled', value: 'name' }
    ]
*/

可以看出,Promise.allSettled() 成功之後返回的也是一個數組,但是改數組的每一項都是一個對象,每個對象都有一個status屬性,值為 fulfilled 和 rejected .

如果status是 fulfilled,那麼改對象的另一個屬性是 value ,對應的是該Promise成功后的結果。

如果status是 rejected,那麼對象的另一個屬性是 reason,對應的是該Promise失敗的原因。

六、BigInt

BigInt 是一種数字類型的數據,它可以表示任意精度格式的整數。在此之前,JS 中安全的最大数字是 9009199254740991,即2^53-1,在控制台中輸入 Number.MAX_SAFE_INTEGER 即可查看。超過這個值,JS 沒有辦法精確表示。另外,大於或等於2的1024次方的數值,JS 無法表示,會返回 Infinity。

BigInt 即解決了這兩個問題。BigInt 只用來表示整數,沒有位數的限制,任何位數的整數都可以精確表示。為了和 Number 類型進行區分,BigInt 類型的數據必須添加後綴 n.

//Number類型在超過9009199254740991后,計算結果即出現問題
const num1 = 90091992547409910;
console.log(num1 + 1); //90091992547409900

//BigInt 計算結果正確
const num2 = 90091992547409910n;
console.log(num2 + 1n); //90091992547409911n

我們還可以使用 BigInt 對象來初始化 BigInt 實例:

console.log(BigInt(999)); // 999n 注意:沒有 new 關鍵字!!!

需要說明的是,BigInt 和 Number 是兩種數據類型,不能直接進行四則運算,不過可以進行比較操作。

console.log(99n == 99); //true
console.log(99n === 99); //false 
console.log(99n + 1);//TypeError: Cannot mix BigInt and other types, use explicit conversionss

七、GlobalThis

JS 中存在一個頂層對象,但是,頂層對象在各種實現里是不統一的。

從不同的 JavaScript 環境中獲取全局對象需要不同的語句。在 Web 中,可以通過 window、self 取到全局對象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它們都無法獲取,必須使用 global。

var getGlobal = function () {
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
    throw new Error('unable to locate global object');
};

ES2020 中引入 globalThis 作為頂層對象,在任何環境下,都可以簡單的通過 globalThis 拿到頂層對象。

八、空值合併運算符

ES2020 新增了一個運算符 ??。當左側的操作數為 null 或者 undefined時,返回其右側操作數,否則返回左側操作數。

在之前我們經常會使用 || 操作符,但是使用 || 操作符,當左側的操作數為 0 、 null、 undefined、 NaN、 false、 ” 時,都會使用右側的操作數。如果使用 || 來為某些變量設置默認值,可能會遇到意料之外的行為。

?? 操作符可以規避以上問題,它只有在左操作數是 null 或者是 undefined 時,才會返回右側操作數。

const someValue = 0;
const defaultValue = 100;
let value = someValue ?? defaultValue; // someValue 為 0 ,value 的值是 0

九、可選鏈操作符

可選鏈操作符 ?. 允許讀取位於連接對象鏈深處的屬性的值,而不必明確驗證鏈中的每個引用是否有效。?. 操作符的功能類似於 . 鏈式操作符,不同之處在於,在引用為空(nullish, 即 null 或者 undefined) 的情況下不會引起錯誤,該表達式短路返回值是 undefined。

例如,我們要訪問 info 對象的 animal 的 reptile 的 tortoise。但是我們不確定 animal, reptile 是否存在,因此我們需要這樣寫:

const tortoise = info.animal && info.animal.reptile && info.animal.reptile.tortoise;

因為 null.reptile 或  undefined.reptile 會拋出錯誤:TypeError: Cannot read property ‘reptile’ of undefined 或 TypeError: Cannot read property ‘reptile’ of null,為了避免報錯,如果我們需要訪問的屬性更深,那麼這個這句代碼會越來越長。

有了可選鏈之後我們就可以簡化:

const tortoise = info.animal?.reptile?.tortoise;

可以看到可選鏈操作符 ?. 和空位合併操作符一樣,都是針對的 null 和 undefined 這兩個值。

站長推薦

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

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

沐鳴註冊平台官網_ES6數組的擴展:Array.from()和Array.of()

一、 Array.from() : 將偽數組對象或可遍歷對象轉換為真數組

1.何為偽數組

如果一個對象的所有鍵名都是正整數或零,並且有length屬性,那麼這個對象就很像數組,語法上稱為“類似數組的對象”(array-like object),即為偽數組。

var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function 

上面代碼中,對象obj就是一個類似數組的對象。但是“類似數組的對象”並不是數組,因為它們不具備數組特有的方法。對象obj沒有數組的push方法,使用該方法就會報錯。

2.有哪些是偽數組

典型的“類似數組的對象”是函數的arguments對象,以及大多數 DOM 元素集,還有字符串。

3.如何轉化為真數組

①數組的slice方法可以將“類似數組的對象”變成真正的數組

function doSomething(){
    console.log(arguments)
    var args = Array.prototype.slice.call(arguments);
    args.push("hj")
    console.log(args)
    return args
}
doSomething(1,2,3) 

或者你也可以寫成:

function doSomething(){
    var args = [].slice.call(arguments);
    return args
}
doSomething(1,2,3) 

儘管這種方法,也可以實現將類數組轉變為數組的目的,但並不直觀。ES6新增Array.from()方法來提供一種明確清晰的方式以解決這方面的需求,更推薦後者的辦法。

②Array.from()

<button>測試1</button>
<br>
<button>測試2</button>
<br>
<button>測試3</button>
<br>
<script type="text/JavaScript">
let btns = document.getElementsByTagName("button")
console.log("btns",btns);//得到一個偽數組
//btns.forEach(item=>console.log(item)) Uncaught TypeError: btns.forEach is not a function
Array.from(btns).forEach(item=>console.log(item))將偽數組轉換為數組
</script> 

在ES6中,擴展運算符(…)也可以將某些數據結構轉為數組。只不過它需要在背後調用遍歷器接口Symbol.iterator。值得注意的是如果一個對象沒有部署遍歷器接口,使用擴展運算符是無法將類似數組對象轉換成數組。

function doSomething (){ 
  return [...arguments] 
}
doSomething('a','b','c'); // ["a","b","c"] 

4.Array.from()用法

Array.from接受三個參數,但只有input是必須的:

  • input: 你想要轉換的類似數組對象和可遍歷對象
  • map: 類似於數組的map方法,用來對每個元素進行處理,將處理后的值放入返回的數組
  • context: 綁定map中用到的this

只要是部署了iterator接口的數據結構,Array.from都能將其轉為數組:

let arr = Array.from('juejin'); 
console.log(arr); //["j", "u", "e", "j", "i", "n"] 

Array.from還可以接受第二個參數,作用類似於數組的map方法,用來對每個元素進行處理,處理后的值放入返回的數組。

Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]
// 等同於
Array.from([1,2,3].map(x => x * x)) 

如果map函數裏面用到了this關鍵字,還可以傳入Array.from的第三個參數,用來綁定this。

Array.from()可以將各種值轉為真正的數組,並且還提供map功能。這實際上意味着,只要有一個原始的數據結構,你就可以先對它的值進行處理,然後轉成規範的數組結構,進而就可以使用數量眾多的數組方法。

Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack'] 

二、Array.of(v1, v2, v3) : 將一系列值轉換成數組

當調用 new Array( )構造器時,根據傳入參數的類型與數量的不同,實際上會導致一些不同的結果, 例如:

let items = new Array(2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // undefined
console.log(items[1]) ; 
let items = new Array(1, 2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // 1
console.log(items[1]) ; // 2 

當使用單個數值參數來調用 Array 構造器時,數組的長度屬性會被設置為該參數。 如果使用多個參數(無論是否為數值類型)來調用,這些參數也會成為目標數組的項。數組的這種行為既混亂又有風險,因為有時可能不會留意所傳參數的類型。

ES6 引入了Array.of( )方法來解決這個問題。該方法的作用非常類似Array構造器,但在使用單個數值參數的時候並不會導致特殊結果。Array.of( )方法總會創建一個包含所有傳入參數的數組,而不管參數的數量與類型

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2 

Array.of基本上可以用來替代Array()或newArray(),並且不存在由於參數不同而導致的重載,而且他們的行為非常統一。

來自:https://www.cnblogs.com/xzsj/p/14628004.html

站長推薦

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

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

沐鳴登錄網站_dotnet高性能buffer

1 前言

我曾經寫過《雜談.netcore的Buffer相關新類型》的博客,簡單介紹過BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>這些基礎類型,在實際項目中,我們更需要的是更上層的高效緩衝區申請、buffer寫入、buffer讀取功能。本文將介紹如何利用這些基礎類型,封裝成易於使用的buffer相關操作類,這些類的源代碼在MemoryExtensions庫里。

2 buffer知識

buffer的申請

通過經驗與實驗數據,根據不同場景與buffer大小,選擇合適的申請方式。

申請式 特點 局限
stackalloc byte 非常快速 堆棧上分配內存塊,容量小且在方法返回時緩衝區丟棄
new byte[] 當小於1KB時速度快 頻繁創建導致內存碎片,GC壓力大
ArrayPool.Rent 適合大的緩衝區租賃,幾乎無內存分配 緩衝區小於1KB時,租賃不如new來得快

IBufferWriter 接口

此接口支持獲取緩衝區的寫入Span或GetMemory給外部直接寫入數據,寫入完成之後調用Advance(int)方法,告訴writer實際的寫入大小。

我們來對比一下MemoryStream的Write()方法,比如要寫一個int類型的值,我們不得不將int轉為4字節的byte[],然後傳byte[]到Write()方法。這個4字節的byte[]是一個副作用,它的存在原於外部無法獲取和擴大MemoryStream的緩衝區。

3 BufferWriter的實現

根據“buffer的申請”幾種方式,我們實現多種不同的BufferWriter。

RecyclableBufferWriter

可回收的自動擴容BufferWriter,適合於大的緩衝區的場景。它的緩衝區通過ArrayPool來租賃,用完之後,要Dispose()歸還到ArrayPool。優點是內存分配少,缺點是租賃比直接創建小的緩衝區還要慢。


  
   Copy
  var writer = new RecyclableBufferWriter<byte>(4);
writer.Write((byte)1);
writer.Write(new byte[] { 2, 3, 4 });
writer.WriteBigEndian(int.MaxValue);           
var writtern = writer.WrittenSpan; // 1,2,3,4,127,255,255,255

// return the buffer to pool
writer.Dispose();

ResizableBufferWriter

自動擴容的BufferWriter,適合小的動態緩衝區的場景。它的沖區通過new Array來創建,通過Array.Resize擴容。優點是cpu性能好,缺點是內存分配高。


  
   Copy
  var writer = new ResizableBufferWriter<byte>(4);
writer.Write((byte)1);
writer.Write(new byte[] { 2, 3, 4 });
writer.WriteBigEndian(int.MaxValue);           
var writtern = writer.WrittenSpan; // 1,2,3,4,127,255,255,255

FixedBufferWriter

固定大小緩衝區,就是我們自己new的Array,包裝為IBufferWriter對象。


  
   Copy
  var array = new byte[16];

var writer = array.CreateWriter();
writer.WriteBigEndian(18);
writer.WriteBigEndian(2.01f);

4 IBufferWriter 的擴展

經常會遇到將int、double等諸多数字類型寫入IBufferWriter的場景,期間還涉及平台的BigEndian或LittleEndian,我們給IBufferWriter<byte>編寫重載的擴展方法。

方法 說明
WriteBigEndian(this IBufferWriter , short) short
WriteBigEndian(this IBufferWriter , int) int
WriteBigEndian(this IBufferWriter , long) long
WriteBigEndian(this IBufferWriter , ushort) ushort
WriteBigEndian(this IBufferWriter , uint) uint
WriteBigEndian(this IBufferWriter , ulong) ulong
WriteBigEndian(this IBufferWriter , float) float
WriteBigEndian(this IBufferWriter , double) double
WriteLittleEndian(this IBufferWriter , short) short
WriteLittleEndian(this IBufferWriter , int) int
WriteLittleEndian(this IBufferWriter , long) long
WriteLittleEndian(this IBufferWriter , ushort) ushort
WriteLittleEndian(this IBufferWriter , uint) uint
WriteLittleEndian(this IBufferWriter , ulong) ulong
WriteLittleEndian(this IBufferWriter , float) float
WriteLittleEndian(this IBufferWriter , double) double

5 ref BufferReader

同樣的,我們也經常遇到從緩衝區中讀取為int、double等諸多数字類型的場景,所以也需要設計一個高效的BufferReader。


  
   Copy
  public ref struct BufferReader
{
    /// <summary>
    /// 未讀取的數據
    /// </summary>
    private ReadOnlySpan<byte> span;
}

給它設計ReadLittleEndian和ReadBigEndian相關Api

方法 說明
ReadBigEndian(out short) short
ReadBigEndian(out int) int
ReadBigEndian(out long) long
ReadBigEndian(out ushort) ushort
ReadBigEndian(out uint) uint
ReadBigEndian(out ulong) ulong
ReadBigEndian(out float) float
ReadBigEndian(out double) double
ReadLittleEndian(out short) short
ReadLittleEndian(out int) int
ReadLittleEndian(out long) long
ReadLittleEndian(out ushort) ushort
ReadLittleEndian(out uint) uint
ReadLittleEndian(out ulong) ulong
ReadLittleEndian(out float) float
ReadLittleEndian(out double) double

6 關於MemoryExtensions庫

本文提到的這些類或結構體,在MemoryExtensions庫里都有實現,可以直接使用,其中BufferWriter技術已經在WebApiClient里大量應用。

原文:https://www.cnblogs.com/kewei/p/14285873.html

站長推薦

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

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