react 16做了哪些更新
- react作為一個ui庫,将前端程式設計由傳統的指令式程式設計轉變為聲明式程式設計,即所謂的資料驅動視圖,但如果簡單粗暴的操作,比如講生成的html直接采用innerHtml替換,會帶來重繪重排之類的性能問題。為了盡量提高性能,React團隊引入了虛拟dom,即采用js對象來描述dom樹,通過對比前後兩次的虛拟對象,來找到最小的dom操作(vdom diff),以此提高性能。
- 上面提到的reactDom diff,在react 16之前,這個過程我們稱之為stack reconciler,它是一個遞歸的過程,在樹很深的時候,單次diff時間過長會造成JS線程持續被占用,使用者互動響應遲滞,頁面渲染會出現明顯的卡頓,這在現代前端是一個緻命的問題。是以為了解決這種問題,react 團隊對整個架構進行了調整,引入了fiber架構,将以前的stack reconciler替換為fiber reconciler。采用增量式渲染。引入了任務優先級(expiration)和requestIdleCallback的循環排程算法,簡單來說就是将以前的一根筋diff更新,首先拆分成兩個階段:reconciliation與commit;第一個reconciliation階段是可打斷的,被拆分成一個個的小任務(fiber),在每一偵的渲染空閑期做小任務diff。然後是commit階段,這個階段是不拆分且不能打斷的,将diff節點的effectTag一口氣更新到頁面上。
- 由于reconciliation是可以被打斷的,且存在任務優先級的問題,是以會導緻commit前的一些生命周期函數多次被執行, 如componentWillMount、componentWillReceiveProps 和 componetWillUpdate,但react官方已申明這些問題,并将其标記為unsafe,在React17中将會移除
- 由于每次喚起更新是從根節點(RootFiber)開始,為了更好的節點複用與性能優化。在react中始終存workInprogressTree(future vdom) 與 oldTree(current vdom)兩個連結清單,兩個連結清單互相引用。這無形中又解決了另一個問題,當workInprogressTree生成報錯時,這時也不會導緻頁面渲染崩潰,而隻是更新失敗,頁面仍然還在
React hooks原理
在React 16前,函數式元件不能擁有狀态管理?因為16以前隻有類元件有對應的執行個體,而16以後Fiber 架構的出現,讓每一個節點都擁有對應的執行個體,也就擁有了儲存狀态的能力。
Hooks的本質就是閉包和兩級連結清單。
閉包是指有權通路另一個函數作用域中變量或方法的函數,建立閉包的方式就是在一個函數内建立閉包函數,通過閉包函數通路這個函數的局部變量, 利用閉包可以突破作用鍊域的特性,将函數内部的變量和方法傳遞到外部。
hooks 連結清單
一個元件包含的hooks 以連結清單的形式存儲在fiber節點的memoizedState屬性上,currentHook連結清單就是目前正在周遊的fiber節點的。nextCurrentHook 就是即将被添加到正在周遊fiber節點的hooks的新連結清單
let currentHook: Hook | null = null;
let nextCurrentHook: Hook | null = null;
type Hooks = {
memoizedState: any, // 指向目前渲染節點 Fiber
baseState: any, // 初始化 initialState, 最新的state
baseUpdate: Update<any> | null,
// 目前需要更新的 Update ,每次更新完之後,會指派上一個 update,友善 react 在渲染錯誤的邊緣,資料回溯
queue: UpdateQueue<any> | null,// 可以讓state變化的,即update或dispach産生的update
next: Hook | null, // link 到下一個 hooks
}
state連結清單
其實state 連結清單不是hooks獨有的,類操作的setState也存在。
-
memoizedState,cursor 是存在哪裡的?如何和每個函數元件一一對應的?
react 會生成一棵元件樹(或Fiber 單連結清單),樹中每個節點對應了一個元件,hooks 的資料就作為元件的一個資訊,存儲在這些節點上,伴随元件一起出生,一起死亡。
-
為什麼隻能在函數最外層調用 Hook?
memoizedState 是按 hook定義的順序來放置資料的,如果 hook 順序變化,memoizedState 并不會感覺到。
-
自定義的 Hook 是如何影響使用它的函數元件的?
共享同一個 memoizedState,共享同一個順序。
-
“Capture Value” 特性是如何産生的?
每一次 ReRender 的時候,都是重新去執行函數元件了,對于之前已經執行過的函數元件,并不會做任何操作。
react setState 異步更新
setState 實作原理
setState 通過一個隊列機制來實作 state 更新,當執行 setState() 時,會将需要更新的 state 淺合并後放入 狀态隊列,而不會立即更新 state,隊列機制可以高效的批量更新 state。如果不通過setState,直接修改this.state 的值,則不會放入狀态隊列,當下一次調用 setState 對狀态隊列進行合并時,之前對 this.state 的修改将會被忽略,造成無法預知的錯誤。
setState()有的同步有的異步?
在React中, 如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state 。所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval産生的異步調用。
原因: 在React的setState函數實作中,會根據一個變量isBatchingUpdates判斷是直接更新this.state還是放到隊列中回頭再說,而isBatchingUpdates預設是false,也就表示setState會同步更新this.state,但是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改為true,而當React在調用事件處理函數之前就會調用這個batchedUpdates,造成的後果,就是由React控制的事件處理過程setState不會同步更新this.state。
setState的“異步”并不是說内部由異步代碼實作,其實本身執行的過程和代碼都是同步的,隻是合成事件和鈎子函數的調用順序在更新之前,導緻在合成事件和鈎子函數中沒法立馬拿到更新後的值,形式了所謂的“異步”,可以通過第二個參數 setState(partialState, callback) 中的callback拿到更新後的結果。
調用風險
當調用 setState 時,實際上是會執行 enqueueSetState 方法,并會對 partialState 及 _pendingStateQueue 隊列進行合并操作,最終通過 enqueueUpdate 執行 state 更新。
而 performUpdateIfNecessary 擷取 _pendingElement、 _pendingStateQueue、_pendingForceUpdate,并調用 reaciveComponent 和 updateComponent 來進行元件更新。
但,如果在 shouldComponentUpdate 或 componentWillUpdate 方法裡調用 this.setState 方法,就會造成崩潰。 這是因為在 shouldComponentUpdate 或 componentWillUpdate 方法裡調用 this.setState 時,this._pendingStateQueue!=null,則 performUpdateIfNecessary 方法就會調用 updateComponent 方法進行元件更新,而 updateComponent 方法又會調用 shouldComponentUpdate和componentWillUpdate 方法,是以造成循環調用,使得浏覽器記憶體占滿後崩潰。
React Fiber
掉幀:在頁面元素很多,且需要頻繁重新整理的場景下,React 15 會出現掉幀的現象,其根本原因,是大量的同步計算任務阻塞了浏覽器的 UI 渲染。預設情況下,JS 運算、頁面布局和頁面繪制都是運作在浏覽器的主線程當中,他們之間是互斥的關系。如果 JS 運算持續占用主線程,頁面就沒法得到及時的更新。當我們調用setState更新頁面的時候,React 會周遊應用的所有節點,計算出差異,然後再更新 UI,整個過程不能被打斷。如果頁面元素很多,整個過程占用的時機就可能超過 16 毫秒,就容易出現掉幀的現象。
如何解決主線程長時間被 JS 運算?将JS運算切割為多個步驟,分批完成。在完成一部分任務之後,将控制權交回給浏覽器,讓浏覽器有時間進行頁面的渲染。等浏覽器忙完之後,再繼續之前未完成的任務。
React 15 及以下版本通過遞歸的方式進行渲染,使用的是 JS 引擎自身的函數調用棧,它會一直執行到棧空為止。而Fiber實作了自己的元件調用棧,它以連結清單的形式周遊元件樹,可以靈活的暫停、繼續和丢棄執行的任務。實作方式是使用了浏覽器的requestIdleCallback
window.requestIdleCallback()會在浏覽器空閑時期依次調用函數,這就可以讓開發者在主事件循環中執行背景或低優先級的任務,而且不會對像動畫和使用者互動這些延遲觸發但關鍵的事件産生影響。函數一般會按先進先調用的順序執行,除非函數在浏覽器調用它之前就到了它的逾時時間。
React 架構内部的運作可以分為 3 層:
- Virtual DOM 層,描述頁面長什麼樣。
- Reconciler 層,負責調用元件生命周期方法,進行 Diff 運算等。
- Renderer 層,根據不同的平台,渲染出相應的頁面,比較常見的是 ReactDOM 和 ReactNative。
Fiber 表征reconciliation階段所能拆分的最小工作單元,其實指的是一種連結清單樹,它可以用一個純 JS 對象來表示:
const fiber = {
stateNode: {}, // 節點執行個體
child: {}, // 子節點
sibling: {}, // 兄弟節點
return: {}, // 表示處理完成後傳回結果所要合并的目标,通常指向父節點
};
Reconciler差別
- 以前的 Reconciler 被命名為Stack Reconciler。Stack Reconciler 運作的過程是不能被打斷的,必須一條道走到黑;
- Fiber Reconciler 每執行一段時間,都會将控制權交回給浏覽器,可以分段執行;
從Stack Reconciler到Fiber Reconciler,源碼層面其實就是幹了一件遞歸改循環的事情。
scheduling(排程)
scheduling(排程)是fiber reconciliation的一個過程,主要是進行任務配置設定,達到分段執行。任務的優先級有六種:
- synchronous,與之前的Stack Reconciler操作一樣,同步執行
- task,在next tick之前執行
- animation,下一幀之前執行
- high,在不久的将來立即執行
- low,稍微延遲執行也沒關系
- offscreen,下一次render時或scroll時才執行
優先級高的任務(如鍵盤輸入)可以打斷優先級低的任務(如Diff)的執行,進而更快的生效。
Fiber Reconciler 在執行過程中,會分為 2 個階段:
- 階段一,生成 Fiber 樹,得出需要更新的節點資訊。這一步是一個漸進的過程,可以被打斷。
- 階段二,将需要更新的節點一次過批量更新,這個過程不能被打斷。
階段一可被打斷的特性,讓優先級更高的任務先執行,從架構層面大大降低了頁面掉幀的機率。
參考:
React Fiber 原理介紹
HOC 與render props差別
Render Props: 把将要包裹的元件作為props屬性傳入,然後容器元件調用這個屬性,并向其傳參
實作方式:
- 通過props.children(props),props.children傳回的是UI元素。<RenderProps> JSX 标簽中的所有内容都會作為一個 children prop 傳遞給 RenderProps元件。因為 RenderProps 将 {props.children} 渲染在一個 <div> 中,被傳遞的這些子元件最終都會出現在輸出結果中。
// 定義
const RenderProps = props => <div>
{props.children(props)}
</div>
// 調用
<RenderProps>
{() => <>Hello RenderProps</>}
</RenderProps>
- 通過props中的任何函數, 自行定義傳入内容
// 定義
const LoginForm = props => {
const flag = false;
const allProps = { flag, ...props };
if (flag) {
return <>{props.login(allProps)}</>
} else {
return <>{props.notLogin(allProps)}</>
}
}
// 調用
<LoginForm
login={() => <h1>LOGIN</h1>}
noLogin={() => <h1>NOT LOGIN</h1>}
/>
優點:
1、支援ES6
2、不用擔心props命名問題,在render函數中隻取需要的state
3、不會産生無用的元件加深層級
4、render props模式的建構都是動态的,所有的改變都在render中觸發,可以更好的利用元件内的生命周期。
HOC: 接受一個元件作為參數,傳回一個新的元件的函數。
class Home extends React.Component {
// UI
}
export default Connect()(Home);
高階元件由于每次都會傳回一個新的元件,對于react來說,這是不利于diff和狀态複用的,是以高階元件的包裝不能在render 方法中進行,而隻能像上面那樣在元件聲明時包裹,這樣也就不利于動态傳參。
2、複用性強,HOC為純函數且傳回值為元件,可以多層嵌套
3、支援傳入多個參數,增強了适用範圍
缺點:
1、當多個HOC一起使用時,無法直接判斷子元件的props是哪個HOC負責傳遞的
2、多個元件嵌套,容易産生同樣名稱的props
3、HOC可能會産生許多無用的元件,加深了元件的層級
總的來說,render props其實和高階元件類似,就是在puru component上增加state,響應react的生命周期。
React 通信
react的資料流是單向的,最常見的就是通過props由父元件向子元件傳值。
- 父向子通信: 傳入props
- 子向父通信:父元件向子元件傳一個函數,然後通過這個函數的回調,拿到子元件傳過來的值
- 父向孫通信:利用context傳值。React.createContext()
- 兄弟間通信:
1、找一個相同的父元件,既可以用props傳遞資料,也可以用context的方式來傳遞資料。
2、用一些全局機制去實作通信,比如redux等
3、釋出訂閱模式
react合成事件
React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一個事件對象,即浏覽器原生事件的跨浏覽器包裝器。
為什麼要使用合成事件?
-
進行浏覽器相容,實作更好的跨平台
React 采用的是頂層事件代理機制,能夠保證冒泡一緻性,可以跨浏覽器執行。React 提供的合成事件用來抹平不同浏覽器事件對象之間的差異,将不同平台事件模拟合成事件。
-
避免垃圾回收
事件對象可能會被頻繁建立和回收,是以 React 引入事件池,在事件池中擷取或釋放事件對象。即 React 事件對象不會被釋放掉,而是存放進一個數組中,當事件觸發,就從這個數組中彈出,避免頻繁地去建立和銷毀(垃圾回收)。
- 友善事件統一管理和事務機制
實作原理
在 React 中,“合成事件”會以事件委托方式綁定在 document 對象上,并在元件解除安裝(unmount)階段自動銷毀綁定的事件。
合成事件和原生事件
當真實 DOM 元素觸發事件,會冒泡到 document 對象後,再處理 React 事件;是以會先執行原生事件,然後處理 React 事件;最後真正執行 document 上挂載的事件。
合成事件和原生事件最好不要混用。 原生事件中如果執行了stopPropagation方法,則會導緻其他React事件失效。因為所有元素的事件将無法冒泡到document上,所有的 React 事件都将無法被注冊。
合成事件的事件池
合成事件對象池,是 React 事件系統提供的一種性能優化方式。合成事件對象在事件池統一管理,不同類型的合成事件具有不同的事件池。
react與vue差別
1. 監聽資料變化的實作原理不同
Vue通過 getter/setter以及一些函數的劫持,能精确知道資料變化。
React預設是通過比較引用的方式(diff)進行的,如果不優化可能導緻大量不必要的VDOM的重新渲染。
2. 資料流不同
Vue1.0中可以實作兩種雙向綁定:父子元件之間props可以雙向綁定;元件與DOM之間可以通過v-model雙向綁定。
Vue2.x中父子元件之間不能雙向綁定了(但是提供了一個文法糖自動幫你通過事件的方式修改)。
React一直不支援雙向綁定,提倡的是單向資料流,稱之為onChange/setState()模式。
3. HoC和mixins
Vue組合不同功能的方式是通過mixin,Vue中元件是一個被包裝的函數,并不簡單的就是我們定義元件的時候傳入的對象或者函數。
React組合不同功能的方式是通過HoC(高階元件)。
4. 模闆渲染方式的不同
模闆的文法不同,React是通過JSX渲染模闆, Vue是通過一種拓展的HTML文法進行渲染。
模闆的原理不同,React通過原生JS實作模闆中的常見文法,比如插值,條件,循環等。而Vue是在群組件JS代碼分離的單獨的模闆中,通過指令來實作的,比如 v-if 。
舉個例子,說明React的好處:react中render函數是支援閉包特性的,是以我們import的元件在render中可以直接調用。但是在Vue中,由于模闆中使用的資料都必須挂在 this 上進行一次中轉,是以我們import 一個元件完了之後,還需要在 components 中再聲明下。
5. 渲染過程不同
Vue可以更快地計算出Virtual DOM的差異,這是由于它會跟蹤每一個元件的依賴關系,不需要重新渲染整個元件樹。
React當狀态被改變時,全部子元件都會重新渲染。通過shouldComponentUpdate這個生命周期方法可以進行控制,但Vue将此視為預設的優化。
6. 架構本質不同
Vue本質是MVVM架構,由MVC發展而來;
React是前端元件化架構,由後端元件化發展而來。
性能優化
1. 靜态資源使用 CDN
CDN是一組分布在多個不同地理位置的 Web 伺服器。當伺服器離使用者越遠時,延遲越高。
2. 無阻塞
頭部内聯的樣式和腳本會阻塞頁面的渲染,樣式放在頭部并使用link方式引入,腳本放在尾部并使用異步方式加載
3. 壓縮檔案
壓縮檔案可以減少檔案下載下傳時間。
- 在 webpack 可以使用如下插件進行壓縮:
- JavaScript:UglifyPlugin
- CSS :MiniCssExtractPlugin
- HTML:HtmlWebpackPlugin
- 使用 gzip 壓縮。通過向 HTTP 請求頭中的 Accept-Encoding 頭添加 gzip 辨別來開啟這一功能。
4. 圖檔優化
- 圖檔懶加載
- 響應式圖檔:浏覽器根據螢幕大小自動加載合适的圖檔。
- 降低圖檔品質:方法有兩種,一是通過 webpack 插件 image-webpack-loader,二是通過線上網站進行壓縮。
5. 減少重繪重排
- 降低 CSS 選擇器的複雜性
- 使用 transform 和 opacity 屬性更改來實作動畫
- 用 JavaScript 修改樣式時,最好不要直接寫樣式,而是替換 class 來改變樣式。
- 如果要對 DOM 元素執行一系列操作,可以将 DOM 元素脫離文檔流,修改完成後,再将它帶回文檔。推薦使用隐藏元素(display:none)或文檔碎片(DocumentFragement),都能很好的實作這個方案。
6. 使用 requestAnimationFrame 來實作視覺變化
7. webpack 打包, 添加檔案緩存
index.html 設定成 no-cache,這樣每次請求的時候都會比對一下 index.html 檔案有沒變化,如果沒變化就使用緩存,有變化就使用新的 index.html 檔案。
其他所有檔案一律使用長緩存,例如設定成緩存一年 maxAge: 1000 * 60 * 60 * 24 * 365。
前端代碼使用 webpack 打包,根據檔案内容生成對應的檔案名,每次重新打包時隻有内容發生了變化,檔案名才會發生變化。
- max-age: 設定緩存存儲的最大周期,超過這個時間緩存被認為過期(機關秒)。在這個時間前,浏覽器讀取檔案不會發出新請求,而是直接使用緩存。
- 指定 no-cache 表示用戶端可以緩存資源,每次使用緩存資源前都必須重新驗證其有效性
輸入url後發生了什麼
- DNS域名解析;
- 建立TCP連接配接(三次握手);
- 發送HTTP請求;
- 伺服器處理請求;
- 傳回響應結果;
- 關閉TCP連接配接(四次握手);
- 浏覽器解析HTML;
- 浏覽器布局渲染;
1. DNS域名解析: 拿到伺服器ip
用戶端收到你輸入的域名位址後,它首先去找本地的hosts檔案,檢查在該檔案中是否有相應的域名、IP對應關系,如果有,則向其IP位址發送請求,如果沒有,再去找DNS伺服器。
2. 建立TCP連結: 用戶端連結伺服器
TCP提供了一種可靠、面向連接配接、位元組流、傳輸層的服務。對于用戶端與伺服器的TCP連結,必然要說的就是『三次握手』。“3次握手”的作用就是雙方都能明确自己和對方的收、發能力是正常的。
用戶端發送一個帶有SYN标志的資料包給服務端,服務端收到後,回傳一個帶有SYN/ACK标志的資料包以示傳達确認資訊,最後用戶端再回傳一個帶ACK标志的資料包,代表握手結束,連接配接成功。
SYN —— 用于初如化一個連接配接的序列号。
ACK —— 确認,使得确認号有效。
RST —— 重置連接配接。
FIN —— 該封包段的發送方已經結束向對方發送資料。
用戶端:“你好,在家不。” -- SYN
服務端:“在的,你來吧。” -- SYN + ACK
用戶端:“好嘞。” -- ACK
3. 發送HTTP請求
4. 伺服器處理請求
5. 傳回響應結果
6. 關閉TCP連接配接(需要4次握手)
為了避免伺服器與用戶端雙方的資源占用和損耗,當雙方沒有請求或響應傳遞時,任意一方都可以發起關閉請求。
關閉連接配接時,伺服器收到對方的FIN封包時,僅僅表示用戶端不再發送資料了但是還能接收資料,而伺服器也未必全部資料都發送給用戶端,是以伺服器可以立即關閉,也可以發送一些資料給對方後,再發送FIN封包給對方來表示同意現在關閉連接配接,是以,己方ACK和FIN一般都會分開發送,進而導緻多了一次。
用戶端:“兄弟,我這邊沒資料要傳了,咱關閉連接配接吧。” -- FIN + seq
服務端:“收到,我看看我這邊有木有資料了。” -- ACK + seq + ack
服務端:“兄弟,我這邊也沒資料要傳你了,咱可以關閉連接配接了。” - FIN + ACK + seq + ack
用戶端:“好嘞。” -- ACK + seq + ack
7. 浏覽器解析HTML
浏覽器需要加載解析的不僅僅是HTML,還包括CSS、JS,以及還要加載圖檔、視訊等其他媒體資源。
浏覽器通過解析HTML,生成DOM樹,解析CSS,生成CSSOM樹,然後通過DOM樹和CSSPOM樹生成渲染樹。渲染樹與DOM樹不同,渲染樹中并沒有head、display為none等不必顯示的節點。
浏覽器的解析過程并非是串連進行的,比如在解析CSS的同時,可以繼續加載解析HTML,但在解析執行JS腳本時,會停止解析後續HTML,會出現阻塞問題。
8. 浏覽器渲染頁面
根據渲染樹布局,計算CSS樣式,即每個節點在頁面中的大小和位置等幾何資訊。HTML預設是流式布局的,CSS和js會打破這種布局,改變DOM的外觀樣式以及大小和位置。最後浏覽器繪制各個節點,将頁面展示給使用者。
replaint:螢幕的一部分重畫,不影響整體布局,比如某個CSS的背景色變了,但元素的幾何尺寸和位置不變。
reflow: 意味着元素的幾何尺寸變了,需要重新計算渲染樹。
細說浏覽器輸入URL後發生了什麼
浏覽器輸入 URL 後發生了什麼?
Babel Plugin與preset差別
Babel是代碼轉換器,比如将ES6轉成ES5,或者将JSX轉成JS等。借助Babel,開發者可以提前用上新的JS特性。
原始代碼 --> [Babel Plugin] --> 轉換後的代碼
Plugin
實作Babel代碼轉換功能的核心,就是Babel插件(plugin)。Babel插件一般盡可能拆成小的力度,開發者可以按需引進, 既提高了性能,也提高了擴充性。比如對ES6轉ES5的功能,Babel官方拆成了20+個插件。開發者想要體驗ES6的箭頭函數特性,那隻需要引入transform-es2015-arrow-functions插件就可以,而不是加載ES6全家桶。
Preset
可以簡單的把Babel Preset視為Babel Plugin的集合。想要将所有ES6的代碼轉成ES5,逐個插件引入的效率比較低下, 就可以采用Babel Preset。比如babel-preset-es2015就包含了所有跟ES6轉換有關的插件。
Plugin與Preset執行順序
可以同時使用多個Plugin和Preset,此時,它們的執行順序非常重要。
- 先執行完所有Plugin,再執行Preset。
- 多個Plugin,按照聲明次序順序執行。
- 多個Preset,按照聲明次序逆序執行。
比如.babelrc配置如下,那麼執行的順序為:
- Plugin:transform-react-jsx、transform-async-to-generator
- Preset:es2016、es2015
{
"presets": [
"es2015",
"es2016"
],
"plugins": [
"transform-react-jsx",
"transform-async-to-generator"
]
}
webpack hash差別
hash一般是結合CDN緩存來使用,通過webpack建構之後,生成對應檔案名自動帶上對應的MD5值。如果檔案内容改變的話,那麼對應檔案哈希值也會改變,對應的HTML引用的URL位址也會改變,觸發CDN伺服器從源伺服器上拉取對應資料,進而更新本地緩存。
- hash
hash是跟整個項目的建構相關,隻要項目裡有檔案更改,整個項目建構的hash值都會更改。同一次建構過程中生成的哈希都是一樣的。
output:{
path:path.join(__dirname, '/dist'),
filename: 'bundle.[name].[hash].js',
}
- chunkhash
根據不同的入口檔案(Entry)進行依賴檔案解析、建構對應的chunk,生成對應的哈希值。把一些公共庫和程式入口檔案區分開,單獨打包建構,接着我們采用chunkhash的方式生成哈希值,那麼隻要我們不改動公共庫的代碼,就可以保證其哈希值不會受影響。
output:{
path:path.join(__dirname, '/dist/js'),
filename: 'bundle.[name].[chunkhash].js',
}
采用chunkhash,項目主入口檔案Index.js及其對應的依賴檔案Index.css由于被打包在同一個子產品,共用相同的chunkhash。由于公共庫是不同的子產品,有單獨的chunkhash。是以Index檔案的更改不會影響公共庫。如果index.js更改了代碼,css未改變,由于該子產品發生了改變,導緻css檔案會重複建構。
- contenthash
根據檔案内容建立出唯一 hash。當檔案内容發生變化時,[contenthash] 才會發生變化。
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
}
typeof 如何判斷的
js 在底層存儲變量的時候,會在變量的機器碼的低位1-3位存儲其類型資訊:
對象: 000
浮點數: 010
字元串: 100
布爾: 110
整數: 1
null:所有機器碼均為0
undefined:用 −2^30 整數來表示
// JavaScript 誕生以來便如此
typeof null === 'object';
在 JavaScript 最初的實作中,JavaScript 中的值是由一個表示類型的标簽和實際資料值表示的。對象的類型标簽是 0。由于 null 代表的是空指針(大多數平台下值為 0x00),是以,null 的類型标簽是 0,typeof null 也是以傳回 "object"。
怎樣開發和部署前端代碼
為了進一步提升網站性能,會把靜态資源和動态網頁分叢集部署,靜态資源會被部署到CDN節點上,網頁中引用的資源也會變成對應的部署路徑。當需要更新靜态資源的時候,同時也會更新html中的引用。
- 先部署頁面,再部署資源:在二者部署的時間間隔内,如果有使用者通路頁面,就會在新的頁面結構中加載舊的資源,并且把這個舊版本的資源當做新版本緩存起來,其結果就是:使用者通路到了一個樣式錯亂的頁面,除非手動重新整理,否則在資源緩存過期之前,頁面會一直執行錯誤。
- 先部署資源,再部署頁面:在部署時間間隔之内,有舊版本資源本地緩存的使用者通路網站,由于請求的頁面是舊版本的,資源引用沒有改變,浏覽器将直接使用本地緩存,這種情況下頁面展現正常;但沒有本地緩存或者緩存過期的使用者通路網站,就會出現舊版本頁面加載新版本資源的情況,導緻頁面執行錯誤,但當頁面完成部署,這部分使用者再次通路頁面又會恢複正常了。
- 配置超長時間的本地緩存 —— 節省帶寬,提高性能
- 采用内容摘要作為緩存更新依據 —— 精确的緩存控制
- 靜态資源CDN部署 —— 優化網絡請求
- 更改資源釋出路徑實作非覆寫式釋出 —— 平滑更新
大數相加
function add(a, b){
const maxLength = Math.max(a.length, b.length);
a = a.padStart(maxLength, 0);
b = b.padStart(maxLength, 0);
let t = 0;
let f = 0;
let sum = "";
for (let i = maxLength - 1; i >= 0; i--) {
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t / 10);
sum = `${t % 10}${sum}`;
}
if (f === 1){
sum = "1" + sum;
}
return sum;
}
斐波那契數列求和
function fib(n) {
if (n <= 0) {
return 0;
}
let n1 = 1;
let n2 = 1;
let sum = 1;
for(let i = 3; i <= n; i++) {
[n1, n2] = [n2, sum];
sum = n1 + n2;
}
return sum;
};