前端問答整理

link 與 import 的區別?

  • link 是一種 html 標籤,它沒有兼容性問題,並且能夠經過 js 的 DOM 操做動態的引入樣式表。
  • @import 是css提供的一種引入樣式表的語法,不能夠動態加載,不兼容ie5如下。
  • 因爲link 是html標籤,因此它引入的樣式是在頁面加載時同時加載的,@import須要在頁面加載完成以後再加載。

聖盃佈局與雙飛翼佈局的理解和區別,並用代碼實現

聖盃佈局和雙飛翼佈局都是三欄佈局,左右盒子寬度固定,中間盒子自適應(固比固),區別是他們的實現的思想。 聖盃佈局:css

<div id="header">#header</div>
<div id="container">
  <div id="center" class="column">#center</div>
  <div id="left" class="column">#left</div>
  <div id="right" class="column">#right</div>
</div>

<div id="footer">#footer</div>
// style
body {
  min-width: 600px; 
}
#container {
  padding-left: 200px;  
  padding-right: 200px; 
}
#container .column {
  height: 200px;
  position: relative;
  float: left;
}
#center {
  width: 100%;
}
#left {
  width: 200px;          
  right: 200px;        
  margin-left: -100%;
}
#right {
  width: 200px;         
  margin-right: -200px;
}
#footer {
  clear: both;
}

/*** IE6 Fix ***/
* html #left {
  left: 200px;
}
複製代碼

聖盃佈局的思想是:給內容區設置左右 padding 防止遮擋,將中間的三欄所有 float left,經過 margin 負間距把左右內容區移到上方,再經過 relative + left 和right 的設置,將左右內容區移動到目標位置。html

雙飛翼佈局:前端

<body>
<div id="hd">header</div> 
  <div id="middle">
    <div id="inside">middle</div>
  </div>
  <div id="left">left</div>
  <div id="right">right</div>
  <div id="footer">footer</div>
</body>

<style>
#hd{
    height:50px;
}
#middle{
    float:left;
    width:100%;/*左欄上去到第一行*/     
    height:100px;
}
#left{
    float:left;
    width:180px;
    height:100px;
    margin-left:-100%;
}
#right{
    float:left;
    width:200px;
    height:100px;
    margin-left:-200px;
}

/*給內部div添加margin,把內容放到中間欄,其實整個背景仍是100%*/ 
#inside{
    margin:0 200px 0 180px;
    height:100px;
}
#footer{ 
   clear:both; /*記得清楚浮動*/  
   height:50px;     
} 
</style>
複製代碼

雙飛翼佈局:給middle建立子div放置內容,再經過子div設置margin-left right 爲左右兩欄留出位置,其他與聖盃佈局思路一致。vue

如今也可使用 flex-box 輕鬆實現。react

CSS3中transition和animation的屬性分別有哪些

transition 過渡動畫: (1) transition-property:屬性名稱 (2) transition-duration: 間隔時間 (3) transition-timing-function: 動畫曲線 (4) transition-delay: 延遲 animation 關鍵幀動畫: (1) animation-name:動畫名稱 (2) animation-duration: 間隔時間 (3) animation-timing-function: 動畫曲線 (4) animation-delay: 延遲 (5) animation-iteration-count:動畫次數 (6) animation-direction: 方向 (7) animation-fill-mode: 禁止模式webpack

用遞歸算法實現,數組長度爲5且元素的隨機數在2-32間不重複的值

let array = new Array(5)
    const fillArray = (arr, index = 0, min = 2, max = 32) =>{
        const num = Math.floor(Math.random() * (max - min + 1)) + min
        if(index < arr.length) {
          if(!arr.includes(num)) {
            arr[index++] = num
          }
          return fillArray(arr, index)
        } 
        return arr
    }
    console.log(fillArray(array));
複製代碼

寫一個方法去掉字符串中的空格

string.split(' ').join('')
// 或
string.replace(/\s/g, '')
複製代碼

在頁面上隱藏元素的方法有哪些?

visibility: hidden;
margin-left: -100%;
opacity: 0;
transform: scale(0);
display: none;
width: 0; height: 0; overflow: hidden;
複製代碼

寫一個把字符串大小寫切換的方法

let str = 'aBCdEFg'
const reverseStr = (val) =>{
	return val.split('').map(item=>{
		if(item.toLowerCase() === item) {
			return item.toUpperCase()
		} else {
			return item.toLowerCase()
		}
	}).join('')
}
console.log(reverseStr(str));
複製代碼

CSS3新增僞類有哪些並簡要描述

CSS3 中規定僞類使用一個 : 來表示;僞元素則使用 :: 來表示nginx

簡述超連接target屬性的取值和做用

_top 在 frame 或者 iframe 中使用較多。直接在頂層的框架中載入目標文檔,加載整個窗口。

react-router 裏的 <Link> 標籤和<a>標籤有什麼區別?

他們的本質都是 a 標籤,<Link> 是 react-router 裏實現路由跳轉的連接,通常配合 <Route> 使用。React Router 會接管 Link 的默認跳轉連接行爲。Link 主要作了三件事es6

  1. 有onclick那就執行onclick
  2. click的時候阻止a標籤默認事件(這樣子點擊123就不會跳轉和刷新頁面)
  3. 再取得跳轉href(便是to),用history(前端路由兩種方式之一,history & hash)跳轉,此時只是連接變了,並無刷新頁面

單頁面應用(SPA)路由實現原理

  1. hash: hash 本意是用來做錨點的,方便用戶在一個很長的文檔裏進行上下的導航,用來作 SPA 的路由控制並不是它的本意。然而,hash 知足這麼一種特性:改變 url 的同時,不刷新頁面,再加上瀏覽器也提供 onhashchange 這樣的事件監聽,所以,hash 能用來作路由控制。
  2. history:早期的 history 只能用於多頁面進行跳轉。在 HTML5 規範中,history 新增瞭如下幾個 API
history.pushState();         // 添加新的狀態到歷史狀態棧
history.replaceState();     // 用新的狀態代替當前狀態
history.state             // 返回當前狀態對象
複製代碼

經過history.pushState或者history.replaceState,也能作到:改變 url 的同時,不會刷新頁面。可是onhashchange 能夠監聽hash的變化,但history的變化沒法直接監聽,須要經過攔截可能改變history的途徑來監聽history的變化。可能改變url的方法有三種web

  • 點擊瀏覽器前進後退
  • 點擊a標籤
  • 直接在js中修改路由 第一種能夠經過onpopstate事件監聽,第二第三實際上是一種,a標籤的默認事件能夠經過js禁止

如何配置React Router

  1. 選擇路由器類型,好比BrowserRouter 和 HashRouter,須要確保其渲染在根目錄之下
  2. 使用路由匹配器,Switch 和 Route,通常用Switch 包裹 Route,Switch 用於渲染與路徑匹配的第一個子 Route 或 Redirect。
  3. 使用導航組件 跳轉到目標路由。若是要強制導航,可使用
  4. 可使用 React Router 提供的一些 Hooks 從組件內部進行導航
  5. 可使用 React 提供的 Lazy 與 Suspense 組件動態加載路由組件,能夠延遲加載未用到的組件

居中爲何要使用transform(爲何不使用marginLeft/Top)

transform transform 屬於合成屬性(composite property),對合成屬性進行 transition/animation 動畫將會建立一個合成層(composite layer),這使得被動畫元素在一個獨立的層中進行動畫。一般狀況下,瀏覽器會將一個層的內容先繪製進一個位圖中,而後再做爲紋理(texture)上傳到 GPU,只要該層的內容不發生改變,就不必進行重繪(repaint),瀏覽器會經過從新複合(recomposite)來造成一個新的幀。 margin top / left top/left屬於佈局屬性,該屬性的變化會致使重排(reflow/relayout),所謂重排即指對這些節點以及受這些節點影響的其它節點,進行CSS計算->佈局->重繪過程,瀏覽器須要爲整個層進行重繪並從新上傳到 GPU,形成了極大的性能開銷。算法

大文件分片上傳,斷點續傳

思路:核心是利用 Blob.prototype.slice 方法,和數組的 slice 方法類似,調用的 slice 方法能夠返回原文件的某個切片 這樣咱們就能夠根據預先設置好的切片最大數量將文件切分爲一個個切片,而後藉助 http 的可併發性,調用Promise.all同時上傳多個切片,這樣從本來傳一個大文件,變成了同時傳多個小的文件切片,能夠大大減小上傳時間 另外因爲是併發,傳輸到服務端的順序可能會發生變化,因此咱們還須要給每一個切片記錄順序。 當所有分片上傳成功,通知服務端進行合併。當有一個分片上傳失敗時,提示「上傳失敗」。在從新上傳時,經過文件 MD5 獲得文件的上傳狀態,當服務器已經有該 MD5 對應的切片時,表明該切片已經上傳過,無需再次上傳,當服務器找不到該 MD5 對應的切片時,表明該切片須要上傳,用戶只需上傳這部分切片,就能夠完整上傳整個文件,這就是文件的斷點續傳。

移動端適配1px的問題

產生緣由:與DPR(devicePixelRatio)設備像素比有關,它表示默認縮放爲100%的狀況下,設備像素和CSS像素的比值:物理像素 /CSS像素。 目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏來講,設備的物理像素要實現1像素,而DPR=2,因此css 像素只能是 0.5。通常設計稿是按照750來設計的,它上面的1px是以750來參照的,而咱們寫css樣式是以設備375爲參照的,因此咱們應該寫的0.5px就行了啊! 試過了就知道,iOS 8+系統支持,安卓系統不支持。

我經常使用的解決方案是 使用僞元素,爲僞元素設置絕對定位,而且和父元素左上角對齊。將僞元素的長和寬先放大2倍,而後再設置一個邊框,以左上角爲中心,縮放到原來的0.5倍 除了這個方法以外還有使用圖片代替,或者使用box-shadow代替,可是沒有僞元素效果好。

淺拷貝與深拷貝

首先數據類型有兩種,一種基本數據類型(String, Number, Boolean, Null, Undefined,Symbol)和引用數據類型(Array,Object)。

基本數據類型是直接存儲在棧內存中的。引用數據類型則是在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體。

深拷貝和淺拷貝的區別就是:淺拷貝只複製指向某個對象的指針,而不復制對象自己,新舊對象仍是共享同一塊內存。但深拷貝會另外創造一個如出一轍的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。

淺拷貝與賦值的區別:賦值獲得的對象與原對象指向的是同一個存儲空間,不管哪一個對象發生改變,其實都是改變的存儲空間的內容,所以,兩個對象是聯動的。而淺拷貝會建立一個新對象,若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址。 改變賦值對象的任意屬性都會改變原對象,但改變淺拷貝對象基本類型的屬性不會改變原對象基本屬性的值,改變引用類型的屬性纔會改變原對象對應的值。

淺拷貝的實現

  1. Object.assign
  2. Array.prototype.slice()
  3. Array.prototype.concat()
  4. 解構賦值 let { ...x } = obj;

深拷貝的實現

  1. JSON.parse(JSON.stringify())
  2. lodash.cloneDeep
  3. 手寫遞歸 遍歷對象、數組直到裏邊都是基本數據類型,而後再去複製,就是深度拷貝

表單能夠跨域嗎 說說你對跨域的瞭解

答案:form表單是能夠跨域的。 首先,跨域問題產生的緣由是瀏覽器的同源策略,沒有同源策略的網絡請求有可能會致使CSRF攻擊,就是攻擊者盜用了你的身份,以你的名義發送惡意請求,由於瀏覽器會自動將cookie附加在HTTP請求的頭字段Cookie中,因此服務端會覺得攻擊者的操做就是你本人的操做,因此瀏覽器就默認禁止了請求跨域。

經常使用的解決方式:

  1. JSONP 處理get請求
  2. 跨域資源共享 CORS(須要後端配置,通用作法)
  3. nginx反向代理解決跨域。nginx 是一款輕量級的 HTTP 服務器,能夠用於服務端的反向代理。反向代理是指以代理服務器來接受請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給請求鏈接的客戶端,此時代理服務器對外就表現爲一個服務器。咱們能夠利用這個特性來處理跨域的問題,將請求轉發到真正的後端域名就能夠啦。

回到form表單,它也是能夠跨域的,由於form提交是不會攜帶cookie的,你也沒辦法設置一個hidden的表單項,而後經過js拿到其餘domain的cookie,由於cookie是基於域的,沒法訪問其餘域的cookie,因此瀏覽器認爲form提交到某個域,是沒法利用瀏覽器和這個域之間創建的cookie和cookie中的session的,故而,瀏覽器沒有限制表單提交的跨域問題。

瀏覽器同源策略的本質是,一個域名的 JS ,在未經容許的狀況下,不得讀取另外一個域名的內容。但瀏覽器並不阻止你向另外一個域名發送請求。

微任務,宏任務與事件循環(Event Loop)

全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。 運行機制:

(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。

(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。

(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。

(4)主線程不斷重複上面的第三步。

複製代碼

這種運行機制又稱爲Event Loop(事件循環)。

宏任務和微任務都是異步任務,主要區別在於他們的執行順序。 宏任務:包括總體代碼script,setTimeout,setInterval、setImmediate。 微任務:原生Promise(有些實現的promise將then方法放到了宏任務中)、process.nextTick、 MutationObserver

js異步有一個機制,就是遇到宏任務,先執行宏任務,將宏任務放入event queue,而後再執行微任務,將微任務放入event queue最騷的是,這兩個queue不是一個queue。當你往外拿的時候先從微任務裏拿這個回調函數,而後再從宏任務的queue上拿宏任務的回掉函數。

例子1:

setTimeout(()=>{
  console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
  console.log('Promise1')
  resolve()
})
p.then(()=>{
  console.log('Promise2')    
})
複製代碼

輸出結果:Promise1,Promise2,setTimeout1 Promise自己是同步的當即執行函數,Promise1做爲同步代碼直接輸出,回調函數進入微任務隊列 由於Promise是microtasks,會在同步任務執行完後會先把微任務隊列中的值取完以後,再去宏任務隊列取值。

例子2:

Promise.resolve().then(()=>{
  console.log('Promise1')  
  setTimeout(()=>{
    console.log('setTimeout2')
  },0)
})

setTimeout(()=>{
  console.log('setTimeout1')
  Promise.resolve().then(()=>{
    console.log('Promise2')    
  })
},0)
複製代碼

輸出結果是Promise1,setTimeout1,Promise2,setTimeout2

  1. 查看微任務隊列,取出微任務隊列中的值,輸出 Promise 1。
  2. 生成一個異步任務宏任務setTimeout2。
  3. 微任務隊列清空,查看宏任務隊列,setTimeout1在setTimeout2以前,取出setTimeout1
  4. 生成一個promise2的微任務
  5. 清空微任務隊列中的值,輸出 Promise2。
  6. 查看宏任務隊列,輸出setttimeout2

setTimeOut,Promise, async/await的區別

考察這三者在事件循環中的區別,事件循環中分爲宏任務隊列和微任務隊列。

  1. settimeout的回調函數放到宏任務隊列裏,等到執行棧清空之後執行;
  2. promise.then裏的回調函數會放到微任務隊列裏,等宏任務裏面的同步代碼執行完再執行;
  3. async 函數返回一個 Promise 對象,當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再執行函數體內後面的語句。能夠理解爲,是讓出了線程,跳出了 async 函數體。

Promise

對象用於表示一個異步操做的最終完成 (或失敗), 及其結果值.

promise 的方法

1. Promise.resolve(value)

返回一個狀態由給定value決定的Promise對象。

  1. 若是傳入的 value 自己就是 Promise 對象,則該對象做爲 Promise.resolve 方法的返回值返回。
  2. 若是該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態由then方法執行決定
  3. 其餘狀況,返回一個狀態已變成 resolved 的 Promise 對象。

2. Promise.reject

類方法,且與 resolve 惟一的不一樣是,返回的 promise 對象的狀態爲 rejected。

3. Promise.all

類方法,多個 Promise 任務同時執行。 若是所有成功執行,則以數組的方式返回全部 Promise 任務的執行結果。 若是有一個 Promise 任務 rejected,則只返回 rejected 任務的結果。

4. Promise.race

類方法,多個 Promise 任務同時執行,返回最早執行結束的 Promise 任務的結果,無論這個 Promise 結果是成功仍是失敗。

5. 其餘

實例方法:

  • Promise.prototype.then 爲 Promise 註冊回調函數
  • Promise.prototype.catch 實例方法,捕獲異常

Promise 狀態

Promise 對象有三個狀態,而且狀態一旦改變,便不能再被更改成其餘狀態。

pending,異步任務正在進行。 resolved (也能夠叫fulfilled),異步任務執行成功。 rejected,異步任務執行失敗

使用總結

首先初始化一個 Promise 對象,能夠經過兩種方式建立,這兩種方式都會返回一個 Promise 對象。

一、new Promise(fn)
二、Promise.resolve(fn)
複製代碼

而後調用上一步返回的 promise 對象的 then 方法,註冊回調函數。最後註冊 catch 異常處理函數,

Promise All 實現

  1. 接收一個 Promise 實例的數組或具備 Iterator 接口的對象,
  2. 若是元素不是 Promise 對象,則使用 Promise.resolve 轉成 Promise 對象
  3. 若是所有成功,狀態變爲 resolved,返回值將組成一個數組傳給回調
  4. 只要有一個失敗,狀態就變爲 rejected,返回值將直接傳遞給回調
  5. Promise.all() 的返回值也是新的 Promise 對象

Async/Await內部實現

async 作了什麼

async 函數會返回一個 Promise 對象,若是在函數中 return 一個直接量,async 會把這個直接量經過 Promise.resolve() 封裝成 Promise 對象。

await 在等啥

await 能夠用於等待一個 async 函數的返回值,注意到 await 不只僅用於等 Promise 對象,它能夠等任意表達式的結果,因此,await 後面實際是能夠接普通函數調用或者直接量的。

await 等到了要等的,而後呢

若是它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。

若是它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,而後獲得 resolve 的值,做爲 await 表達式的運算結果。

async/await 優點在哪?

async/await 的優點在於處理 then 鏈,尤爲是每個步驟都須要以前每一個步驟的結果時,async/await 的代碼對比promise很是清晰明瞭,簡直像同步代碼同樣。

實現原理

async/await 就是 Generator 的語法糖,使得異步操做變得更加方便。async 函數就是將 Generator 函數的星號(*)替換成 async,將 yield 替換成await。 不同的是:

  1. async函數內置執行器,函數調用以後,會自動執行,輸出最後結果。而Generator須要調用next。
  2. 返回值是Promise,async函數的返回值是 Promise 對象,Generator的返回值是 Iterator(迭代器),Promise 對象使用起來更加方便。

簡單實習代碼:

function asyncToGenerator(generatorFunc) {
  return function() {
    // 先調用generator函數 生成迭代器
    const gen = generatorFunc.apply(this, arguments)
    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let generatorResult
        try {
          generatorResult = gen[key](arg)
        } catch (error) {
          return reject(error)
        }
        const { value, done } = generatorResult
        if (done) {
          return resolve(value)
        } else {
          return Promise.resolve(
            value
          ).then(
            function onResolve(val) {
              step("next", val)
            },
            function onReject(err) {
              step("throw", err)
            },
          )
        }
      }
      step("next")
    })
  }
}
複製代碼

如何避免給每一個 async 寫 try/catch

寫一個輔助函數:

async function errorCaptured(asyncFunc) {
    try {
        let res = await asyncFunc()
        return [null, res]
    } catch (e) {
        return [e, null]
    }
}
複製代碼

使用方式:

async function func() {
    let [err, res] = await errorCaptured(asyncFunc)
    if (err) {
        //... 錯誤捕獲
    }
    //...
}
複製代碼

移動端適配

  1. rem適配:本質是佈局等比例的縮放,經過動態設置html的font-size來改變rem的大小。
    • 動態改寫<meta>標籤中的縮放比例
    • <html>元素添加data-dpr屬性,而且動態改寫data-dpr的值
    • <html>元素添加font-size屬性,而且動態改寫font-size的值
    • 用插件將將px轉成rem。
  2. vw 方案: 若是設計稿使用750px寬度,則100vw = 750px,即1vw = 7.5px。那麼咱們能夠根據設計圖上的px值直接轉換成對應的vw值。若是不想本身計算,咱們可使用PostCSS的插件postcss-px-to-viewport,讓咱們能夠直接在代碼中寫px。
  3. 搭配rem和vw 給根元素大小設置隨着視口變化而變化的vw單位,這樣就能夠實現動態改變其大小。限制根元素字體大小的最大最小值,配合body加上最大寬度和最小寬度

閉包

簡單來講,閉包就是指有權訪問另外一個函數做用域中的變量的函數。建立閉包最多見方式,就是在一個函數內部建立或返回另外一個函數。 好比:

var a = function () {
  var test = {};
  setTimeout(function () {
    console.log(test);
  }, 1000);
}
複製代碼

上面的例子中,test在a中定義,但在setTimeout的參數(函數)中對它保持了引用。當a被執行了,儘管a已經執行完(已經執行完),理論上來講a這個函數執行過程當中產生的變量、對象均可以被銷燬。但test因爲被引用,因此不能隨這個函數執行結束而被銷燬,直到定時器裏的函數被執行掉。

防抖 與 節流

防抖:將屢次高頻操做優化爲只在最後一次執行,一般使用的場景是:用戶輸入,只需再輸入完成後作一次輸入校驗便可。

實現邏輯:在事件被觸發n秒後再執行回調,若是在這n秒內又被觸發,則從新計時。 實現代碼:

// 定時器
const debounce = (fn, ms = 0) => {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), ms);
  };
};
複製代碼

節流:每隔一段時間後執行一次,也就是下降頻率,將高頻操做優化成低頻操做,一般使用場景: 滾動條事件 或者 resize 事件,一般每隔 100~500 ms執行一次便可。

實現邏輯:規定在一個單位時間內,只能觸發一次函數。若是這個單位時間內觸發屢次函數,只有一次生效 實現代碼:

//定時器
const throttle = function (func, delay) {
  let timeoutId = null;
  return function (...args) {
    if (!timeoutId) {
      timeoutId = setTimeout(function () {
        func.apply(this, args);
        timeoutId = null
      }, delay);
    }
  }
}

// 時間戳
const throttle = function(func, delay) {
    let prev = Date.now();
    return function(...args) {
        let now = Date.now();
        if (now - prev >= delay) {
            func.apply(this, args);
            prev = Date.now();
        }
    }    
}
複製代碼

原型與原型鏈

JavaScript並不是經過類而是直接經過構造函數來建立實例。

function Dog(name, color) {
    this.name = name
    this.color = color
    this.bark = () => {
        console.log('wangwang~')
    }
}

const dog1 = new Dog('dog1', 'black')
const dog2 = new Dog('dog2', 'white')
複製代碼

上述代碼就是聲明一個構造函數並經過構造函數建立實例的過程。在上面的代碼中,有兩個實例被建立,它們有本身的名字、顏色,但它們的bark方法是同樣的,而經過構造函數建立實例的時候,每建立一個實例,都須要從新建立這個方法,再把它添加到新的實例中,形成了很大的浪費。

所以須要用到原型(prototype),每個構造函數都擁有一個prototype屬性,能夠經過原型來定義一個共享的方法,讓這兩個實例對象的方法指向相同的位置。

Person.prototype.bark = function() {
  console.log("wangwang~~");
}
複製代碼

JavaScript中全部的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有本身的原型對象,這樣層層上溯,就造成了一個相似鏈表的結構,這就是原型鏈。 原型鏈與原型的關係能夠用下面這個圖來概覽。

this、bind、call、apply

首先this的指向:this 永遠指向最後調用它的那個對象 怎麼改變this的指向:

1. 使用 ES6 的箭頭函數

箭頭函數的 this 始終指向函數定義時的 this,而非執行時。

2. 在函數內部使用 _this = this

3. 使用 apply、call、bind

apply、call、bind 都是能夠改變 this 的指向的,可是這三個函數稍有不一樣

  1. fun.call(thisArg[, arg1[, arg2[, ...]]])
  2. fun.apply(thisArg, [argsArray])

call 與 apply是類似的,只是調用方法不一樣,apply只能接收兩個對象:一個新的this對象和一個參數數組。call 則能夠接收一個this對象和多個參數

例子:

function add(a, b){
  return a + b;  
}
function sub(a, b){
  return a - b;  
}

// apply() 的用法
var a1 = add.apply(sub, [4, 2]); // sub 調用 add 的方法
var a2 = sub.apply(add, [4, 2]);

a1; // 6 將sub的指針指向了add,因此最後執行的是add
a2; // 2

// call() 的用法
var a1 = add.call(sub, 4, 2);
複製代碼

他們與bind 的區別是,這兩個方法會當即調用,bind()不會當即調用,須要手動去調用: 例子:

window.onload = function() {
  var fn = {
    num: 2,
    fun: function() {
      document.getElementById("box").onclick = (function() {
        console.log(this.num);
      }).bind(this);
      // }).call(this);
      // }).apply(this);
    }
        /*
         * 這裏的 this 是 fun,因此能夠正確地訪問 num,
         * 若是使用 bind(),會在點擊以後打印 2;
         * 若是使用 call() 或者 apply(),那麼在刷新網頁的時候就會打印 2
        */
    }
    fn.fun();
}

複製代碼

js設計模式

1.工廠模式

建立一個函數,返回相同的屬性和方法,能夠無數次調用。經常使用於產生大量類似的商品,去作一樣的事情,實現一樣的效果。

2.單例模式

定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現的方法爲先判斷實例存在與否,若是存在則直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。

適用場景:一個單一對象。好比:彈窗,不管點擊多少次,彈窗只應該被建立一次。

3. 策略模式

定義:定義一系列的算法,把他們一個個封裝起來,而且使他們能夠相互替換。

策略模式的目的就是將算法的使用算法的實現分離開來。

例子:

/*策略類*/
var levelOBJ = {
    "A": function(money) {
        return money * 4;
    },
    "B" : function(money) {
        return money * 3;
    },
    "C" : function(money) {
        return money * 2;
    } 
};
/*環境類*/
var calculateBouns =function(level,money) {
    return levelOBJ[level](money);
};
console.log(calculateBouns('A',10000)); // 40000
複製代碼

4.代理模式

定義:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。

經常使用的虛擬代理形式:某一個花銷很大的操做,能夠經過虛擬代理的方式延遲到這種須要它的時候纔去建立(例:使用虛擬代理實現圖片懶加載)

中介者模式

定義:經過一箇中介者對象,其餘全部的相關對象都經過該中介者對象來通訊,而不是相互引用,當其中的一個對象發生改變時,只須要通知中介者對象便可。經過中介者模式能夠解除對象與對象之間的緊耦合關係。

發佈-訂閱模式

發佈-訂閱模式實際上是一種對象間一對多的依賴關係,當一個對象的狀態發送改變時,全部依賴於它的對象都將獲得狀態改變的通知。

訂閱者(Subscriber)把本身想訂閱的事件註冊(Subscribe)到調度中心(Event Channel),當發佈者(Publisher)發佈該事件(Publish Event)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者註冊到調度中心的處理代碼。 例子:Vue的EventBus

觀察者模式

觀察者模式也是定義對象間的一種一對多依賴關係,使得當每個被依賴對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。它的目標與發佈-訂閱者模式是一致的。

被觀察對象經過 subscribe 方法和 unsubscribe 方法添加和刪除一個觀察者,經過 broadcast 方法向觀察者推送消息。

document.body.addEventListener('click', ()=>{}};
複製代碼

上面的例子就是一個最簡單的觀察者模式。document.body 在這裏就是一個被觀察對象, 全局對象是觀察者,當 click 事件觸發的時候,觀察者會調用 clickHandler 方法。

與發佈訂閱者模式的區別:觀察者模式中主體和觀察者仍是存在必定的耦合性,而發佈訂閱者模式中,在主體與觀察者之間引入消息調度中心,全部的消息傳遞過程都經過消息調度中心完成,也就是說具體的業務邏輯代碼將會是在消息調度中心內,而主體和觀察者之間實現了徹底的鬆耦合,與此同時帶來的問題就是,程序的可讀性下降了。

數組判斷方式

1. Array.isArray()

2. instanceof

instanceof 用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。語法

object instanceof constructor
複製代碼

因此若是這個Object的原型鏈上可以找到Array構造函數的話,那麼這個Object應該及就是一個數組,反之則不是

const a = [];
console.log(a instanceof Array);//true
複製代碼

3. Object.prototype.toString

不能夠直接調用數組自身的toString()方法,由於返回的會是內容的字符串,只有對象的toString方法會返回對象的類型。因此咱們須要「借用」對象的toString方法,須要使用call或者apply方法來改變toString方法的執行上下文。

const a = ['Hello','Howard'];
Object.prototype.toString.call(a);//"[object Array]"
複製代碼

4.constructor

實例化的數組擁有一個constructor屬性,這個屬性指向生成這個數組的方法。

const a = [];
console.log(a.constructor == Array);//true
複製代碼

可是constructor屬性是能夠改寫的,一旦被改寫,那這種判斷方式就無效了。

數組去重

  1. [...new Set(array)]

    set 是 es6 提供的新的數據結構,它相似數組,可是成員的值都是惟一的,

  2. for 循環嵌套,splice去重

  3. 新建一個空的結果數組,for 循環原數組,判斷結果數組是否存在當前元素,若是有相同的值則跳過,不相同則push進數組。判斷是否存在的方法有indexOf,array.includes

  4. 利用filter

function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //當前元素,在原始數組中的第一個索引==當前索引值,不然返回當前元素
    return arr.indexOf(item, 0) === index;
  });
}
複製代碼

js 數據類型

7 種原始類型:

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol 引用類型
  • Object
  • Array

MVC,MVP,MVVM

MVC(model,view,controller)

MVC除了把應用程序分紅View、Model層,還額外的加了一個Controller層,它的職責爲進行Model和View之間的協做(路由、輸入預處理等)的應用邏輯(application logic);Model進行處理業務邏輯。

用戶的對View操做之後,View捕獲到這個操做,會把處理的權利交移給Controller(Pass calls);Controller會對來自View數據進行預處理、決定調用哪一個Model的接口;而後由Model執行相關的業務邏輯;當Model變動了之後,會經過觀察者模式(Observer Pattern)通知View;View經過觀察者模式收到Model變動的消息之後,會向Model請求最新的數據,而後從新更新界面。

優勢:

  1. 把業務邏輯和展現邏輯分離,模塊化程度高。且當應用邏輯須要變動的時候,不須要變動業務邏輯和展現邏輯,只須要Controller換成另一個Controller就好了(Swappable Controller)。

  2. 觀察者模式能夠作到多視圖同時更新。 缺點:

  3. Controller測試困難

  4. View沒法組件化。View是強依賴特定的Model的,若是須要把這個View抽出來做爲一個另一個應用程序可複用的組件就困難了。由於不一樣程序的的Domain Model是不同的。

MVP

MVP模式把MVC模式中的Controller換成了Presenter。

和MVC模式同樣,用戶對View的操做都會從View交移給Presenter。Presenter會執行相應的應用程序邏輯,而且對Model進行相應的操做;而這時候Model執行完業務邏輯之後,也是經過觀察者模式把本身變動的消息傳遞出去,可是是傳給Presenter而不是View。Presenter獲取到Model變動的消息之後,經過View提供的接口更新界面。

優勢

  1. 測試簡單
  2. 能夠組件化

缺點

  1. presenter中除了應用邏輯之外,還有大量的View->Model,Model->View的手動同步邏輯,形成Presenter比較笨重,維護起來會比較困難。

MVVM(Model-View-ViewMode)

ViewModel的含義就是 "Model of View",視圖的模型。

MVVM的調用關係和MVP同樣。可是,在ViewModel當中會有一個叫Binder,之前所有由Presenter負責的View和Model之間數據同步操做交由給Binder處理。你只須要在View的模版語法當中,指令式地聲明View上的顯示的內容是和Model的哪一塊數據綁定的。當ViewModel對進行Model更新的時候,Binder會自動把數據更新到View上去,當用戶對View進行操做(例如表單輸入),Binder也會自動把數據更新到Model上去。這種方式稱爲:雙向數據綁定。

優勢

  1. 便於維護
  2. 可組件化
  3. 簡化測試

缺點:

  1. 對於於大型的圖形應用程序,視圖狀態較多,ViewModel的構建和維護的成本都會比較高

Vue的生命週期有哪些?

  • 建立:beforeCreate,created;
  • 載入:beforeMount,mounted;
  • 更新:beforeUpdate,updated;
  • 銷燬:beforeDestroy,destroyed;

Vue 組件通訊

1. props/$emit

2. $children/$parent

子實例能夠經過this.$parent訪問父實例,子實例則被推入父實例的$children數組中,拿到實例表明能夠訪問此組件的全部方法和data。this.$parent是一個對象,$children是一個數組。

3. provide/ inject

provide/ inject 是vue2.2.0新增的api, 簡單來講就是父組件中經過provide來提供變量, 而後再子組件中經過inject來注入變量。

注意:這裏不論子組件嵌套有多深, 只要調用了inject 那麼就能夠注入provide中的數據,而不侷限於只能從當前父組件的props屬性中回去數據。

例子: A 是 B 的父組件,B是C的父組件

// A.vue

<template>
 <div>
   <comB></comB>
 </div>
</template>

<script>
 import comB from '../components/test/comB.vue'
 export default {
   name: "A",
   provide: {
     for: "demo"
   },
   components:{
     comB
   }
 }
</script>

// B.vue

<template>
 <div>
   {{demo}}
   <comC></comC>
 </div>
</template>

<script>
 import comC from '../components/test/comC.vue'
 export default {
   name: "B",
   inject: ['for'],
   data() {
     return {
       demo: this.for
     }
   },
   components: {
     comC
   }
 }
</script>
// C.vue
<template>
 <div>
   {{demo}}
 </div>
</template>

<script>
 export default {
   name: "C",
   inject: ['for'],
   data() {
     return {
       demo: this.for
     }
   }
 }
</script>
複製代碼

4. ref/this.$refs

若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例,能夠經過實例直接調用組件的方法或訪問數據。

5. Vuex

6. 本地存儲localStorage / sessionStorage

7. $attrs$listeners

若是三個嵌套組件A->B,B->C,若是須要在A中對C組件賦值,並監聽C組件的emit事件,可使用vue2.4.0引入的$attrs$listeners,和新增的inheritAttrs選項。

// A組件
...
<div>
 <h2>組件A 數據項:{{myData}}</h2>
 <B @changeMyData="changeMyData" :myData="myData"></B>
</div>
...

// B組件
...
<div>
  <h3>組件B</h3>
  <C v-bind="$attrs" v-on="$listeners"></C>
</div>
...
<script>
 import C from './C'
 export default {
 components: { C },
 inheritAttrs: false,
 props: ['pChild1'],
 mounted () {
   this.$emit('onTest1')
 }
}
</script>
...

// C組件
<template>
 <div>
   <h5>組件C</h5>
   <input v-model="myc" @input="hInput" />
 </div>
</template>
<script>
export default {
 props: { myData: { String } },
 created() {
   this.myc = this.myData;  // 在組件A中傳遞過來的屬性
   console.info(this.$attrs, this.$listeners);
 },
 methods: {
   hInput() {
     this.$emit("changeMyData", this.myc); // // 在組件A中傳遞過來的事件
   }
 }
};
</script>
複製代碼

注意:inheritAttrs屬性,他用來判斷組件的根元素是否繼承,那些它沒有從父組件繼承的屬性,在上面的例子中咱們將它設爲false,區別見下圖

// 默認爲true

// 改成false

8.EventBus

建立EventBus方法: 引入Vue 並導出它的一個實例,也能夠直接在main.js中初始化,這樣咱們獲取到的 EventBus 是一個 全局的事件總線

Vue.prototype.$bus = new Vue()
複製代碼

而後就能夠經過on/off/emit,發佈訂閱事件了。

React 組件通訊

父->子組件 prop傳遞

子->父組件

  1. 父組件定義方法setValue直接經過傳遞給子組件,子組件在內部直接調用父組件方法更改狀態

兄弟組件

  1. context
  2. 狀態管理庫
  3. 發佈訂閱,相似Vue的eventBus

Vue 響應式原理

原理

當你把一個普通的 JavaScript 對象傳入 Vue 實例做爲 data 選項,Vue 將遍歷此對象全部的 property,並使用 Object.defineProperty 把這些 property 所有轉爲 getter/setter。它們讓 Vue 可以追蹤依賴,在 property 被訪問和修改時通知變動。

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

可是Vue不能檢測數組和對象的變化。

  • 對於對象:

Vue 沒法檢測 property 的添加或移除。因爲 Vue 會在初始化實例時對 property 執行 getter/setter 轉化,因此 property 必須在 data 對象上存在才能讓 Vue 將它轉換爲響應式的。可是,可使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應式 property。

  • 對於數組:

    • 當你利用索引直接設置一個數組項時,例如:vm.items[indexOfItem] = newValue
    • 當你修改數組的長度時,例如:vm.items.length = newLength

    這兩種狀況不能監測

因爲 Vue 不容許動態添加根級響應式 property,因此你必須在初始化實例前聲明全部根級響應式 property,哪怕只是一個空值。

異步更新隊列

Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher 被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。Vue 在內部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,若是執行環境不支持,則會採用 setTimeout(fn, 0) 代替。

當你設置 vm.someData = 'new value',該組件不會當即從新渲染。當刷新隊列時,組件會在下一個事件循環「tick」中更新。爲了在數據變化以後等待 Vue 完成更新 DOM,能夠在數據變化以後當即使用 Vue.nextTick(callback)。這樣回調函數將在 DOM 更新完成後被調用。

this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
複製代碼

由於 $nextTick() 返回一個 Promise 對象,因此你可使用新的 ES2017 async/await 語法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}
複製代碼

React 核心原理

理解虛擬DOM

virtual dom 其實是對實際Dom的一個抽象,是一個js對象。react全部的表層操做其實是在操做virtual dom。

它的內部邏輯是:用JavaScript 對象表示 DOM 信息和結構,根據這個用 JavaScript 對象表示的樹結構來構建一棵真正的DOM樹。當狀態變動的時候,用新渲染的對象樹去和舊的樹進行對比,記錄這兩棵樹差別(diff算法)。記錄下來的不一樣就是咱們須要對頁面真正的 DOM 操做,而後把它們應用在真正的 DOM 樹上,頁面就變動了。這樣就能夠作到:視圖的結構確實是整個全新渲染了,可是最後操做DOM的時候確實只變動有不一樣的地方。

DOM是很慢的,一個簡單的div元素就能夠打印出來不少東西,並且操做起來一不當心就會致使頁面重排。相對於 DOM 對象,原生的 JavaScript 對象處理起來更快,並且更簡單。DOM 樹上的結構、屬性信息咱們均可以很容易地用 JavaScript 對象表示出來。既然原來 DOM 樹的信息均可以用 JavaScript 對象來表示,反過來,你就能夠根據這個用 JavaScript 對象表示的樹結構來構建一棵真正的DOM樹。

Virtual DOM 本質上就是在 JS 和 DOM 之間作了一個緩存。能夠類比 CPU 和硬盤,既然硬盤這麼慢,咱們就在它們之間加個緩存:既然 DOM 這麼慢,咱們就在它們 JS 和 DOM 之間加個緩存。JS只操做Virtual DOM,最後的時候再把變動寫入DOM。

JSX

JSX(Javscriptのxml)是JavaScript的一個語法擴展,是 React.createElement(component, props, ...children) 函數的語法糖。React.createElement函數最會生成一個對象,咱們稱之爲React對象或者另外一個名字--虛擬DOM。

  • 你能夠把它做爲一個變量的值,在if和for循環中使用它
  • 也能把它當作參數傳遞給函數
  • 還能做爲函數的返回值返回。
  • jsx中的變量用{}包裹,能夠防止xss攻擊

服務端渲染

React 支持服務端渲染。

爲何要服務端渲染(SSR)呢?這與單頁應用(SPA )的興起息息相關。與傳統的 SSR 應用相比, SPA 在速度和用戶體驗方面具備很大的優點。 可是這裏有一個問題。SPA 的初始服務端請求一般返回一個沒有 DOM 結構的 HTML 文件,其中只包含一堆 CSS 和 JS links。而後,應用須要另外 fetch 一些數據來呈現相關的 HTML 標籤。 這意味着用戶將不得不等待更長時間的初始渲染。這也意味着爬蟲可能會將你的頁面解析爲空。 所以,關於這個問題的解決思路是:首先在服務端上渲染你的 app(渲染首屏),接着再在客戶端上使用 SPA。

SSR優勢

  • 加快了首屏渲染時間
  • 完整的可索引的 HTML 頁面(有利於 SEO)

咱們可使用next.js構建支持服務端渲染的React應用程序。

數據流

React中,數據流是自上而下的單向數據流。

React 最新的生命週期

  1. 掛載
    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()
  2. 更新
    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  3. 卸載
    • componentWillUnmount()
  4. 拋出錯誤時
    • static getDerivedStateFromError()
    • componentDidCatch()

即將被廢除的三個生命週期

  • UNSAFE_componentWillMount()
  • UNSAFE_componentWillUpdate()
  • UNSAFE_componentWillReceiveProps()

static getDerivedStateFromProps

getDerivedStateFromProps 會在調用 render 方法以前調用,而且在初始掛載及後續更新時都會被調用。它應返回一個對象來更新 state,若是返回 null 則不更新任何內容。 當 props 變化時,建議使用getDerivedStateFromProps 生命週期更新 state,UNSAFE_componentWillReceiveProps即將被棄用

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)以前調用。它使得組件能在發生更改以前從 DOM 中捕獲一些信息(例如,滾動位置)。今生命週期的任何返回值將做爲參數傳遞給 componentDidUpdate()。

Vue 中 computed 與watch 有什麼區別

計算屬性(computed)

適用於須要獲取依賴某項或多項變量複雜計算獲取到的結果的場景。 好比:

computed:{
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
}
複製代碼

你也能夠經過在表達式中調用方法來達到相同的效果,不一樣的是計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們纔會從新求值。這就意味着只要 message 尚未發生改變,屢次訪問 reversedMessage 計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。

偵聽屬性(watch)

Watch 觀察 Vue 實例上的一個表達式或者一個函數計算結果的變化。回調函數獲得的參數爲新值和舊值。表達式只接受監督的鍵路徑('a.b.c')。對於更復雜的表達式,用一個函數取代。

此外,它還有兩個屬性 deep和immediate

Watch中能夠執行任何邏輯,如函數節流、Ajax異步獲取數據,甚至操做 DOM(不建議)

他們的區別

  • watch:監測的是屬性值, 只要屬性值發生變化,其都會觸發執行回調函數來執行一系列操做;
  • computed:監測的是依賴值,依賴值不變的狀況下其會直接讀取緩存進行復用,變化的狀況下才會從新計算。
  • 有點很重要的區別是:計算屬性不能執行異步任務,計算屬性必須同步執行。也就是說計算屬性不能向服務器請求或者執行異步任務。若是遇到異步任務,就交給偵聽屬性。Watch也能夠檢測computed屬性。

總結

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

React 淺比較

因爲PureComponent的shouldeComponentUpdate裏,實際是對props/state進行了一個淺對比,因此對於嵌套的對象不適用,沒辦法比較出來。這是由於React 的淺比較中,當對比的類型爲Object的時候而且key的長度相等的時候,淺比較也僅僅是用Object.is()對Object的value作了一個基本數據類型的比較,因此若是key裏面是對象的話,有可能出現比較不符合預期的狀況,因此淺比較是不適用於嵌套類型的比較

React 與 Vue 的區別

1. 監聽數據變化的原理不一樣

  • Vue 經過 getter/setter 以及一些函數的劫持,能精確知道數據變化,不須要特別的優化就能達到很好的性能
  • React 默認是經過比較引用的方式進行的,若是不優化(PureComponent/shouldComponentUpdate)可能致使大量沒必要要的VDOM的從新渲染

2. 數據流的不一樣

  • Vue 支持雙向綁定
  • React 是單向數據流,稱之爲 onChange/setState()模式。

3. 模板渲染方式不一樣

  • React 是經過JSX渲染模板,深層來說是react 的模板渲染是經過原生JS實現模板中的常見語法(好比插值,條件,循環等)來實現的

  • Vue是經過一種拓展的HTML語法進行渲染。它是在和組件JS代碼分離的單獨的模板中,經過指令來實現的,好比條件語句就須要 v-if 來實現

  • react中render函數是支持閉包特性的,因此咱們import的組件在render中能夠直接調用。可是在Vue中,因爲模板中使用的數據都必須掛在 this 上進行一次中轉,因此咱們import 一個組件完了以後,還須要在 components 中再聲明下。

React怎麼作數據的檢查和變化

Vue經過Object.defineProporty來劫持對象的get,set方法,實現雙向綁定。 相比較react而言,react是單向數據流動的ui渲染框架,自己不存在數據的檢測這一機制,全部的數據改變都是經過setState來手動實現的。

vue和react都在其內部實現了‘虛擬dom’的概念,即將須要渲染的真實dom虛擬成一個js對象,渲染的時候,經過對這個新舊js對象進行比較,來計算出真實dom須要渲染的最小操做,以達到優化性能的目的。react和vue都有其diff算法,原理也很相似。

React 高階組件(HOC)

高階組件的概念應該是來源於JavaScript的高階函數:

高階函數就是接受函數做爲輸入或者輸出的函數,高階組件(HOC)是一個接受組件組做輸入並返回組件的函數。HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。實際上是裝飾器的語法糖。

在高階函數以前,React 也曾使用過mixin,但在工程中大量使用mixin會致使一些問題,

  1. 破壞組件封裝性: Mixin可能會引入不可見的屬性。例如在渲染組件中使用Mixin方法,給組件帶來了不可見的屬性(props)和狀態(state)。而且Mixin可能會相互依賴,相互耦合,不利於代碼維護。
  2. 不一樣的Mixin中的方法可能會相互衝突

HOC 能夠實現什麼功能

他能夠:

  1. 組合渲染,條件渲染
  2. 操做props
  3. 獲取refs
  4. 操做state

使用 HOC 的優勢

  1. 抽取重複代碼,實現組件複用,常見場景:頁面複用。
  2. 條件渲染,控制組件的渲染邏輯(渲染劫持),常見場景:權限控制。
  3. 捕獲/劫持被處理組件的生命週期,常見場景:組件渲染性能追蹤、日誌打點。

使用HOC的缺點

  1. HOC須要在原組件上進行包裹或者嵌套,若是大量使用HOC,將會產生很是多的嵌套,這讓調試變得很是困難。
  2. HOC能夠劫持props,在不遵照約定的狀況下也可能形成衝突。

React Hooks

Hooks 是16.7添加的新特性。它的優勢是

  1. 減小狀態邏輯複用的風險
  2. 避免地獄式嵌套
  3. 讓組件更容易理解

寫一個自定義的Hook

export const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  // 記住最新的回調函數.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // 設置定時器
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
    // 這裏至關於一個做用在組件生命週期裏的 setInterval 和 clearInterval 的組合。
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

複製代碼

React Fiber

react在進行組件渲染時,從setState開始到渲染完成整個過程是同步的(「一鼓作氣」)。若是須要渲染的組件比較龐大,js執行會佔據主線程時間較長,會致使頁面響應度變差,使得react在動畫、手勢等應用中效果比較差。

卡頓緣由:Stack(原來的算法)的工做流程很像函數的調用過程。父組件裏調子組件,能夠類比爲函數的遞歸。在setState後,react會當即開始從父節點開始遍歷,以找出不一樣。將全部的Virtual DOM遍歷完成後,才能給出當前須要修改真實DOM的信息,並傳遞給renderer,進行渲染,而後屏幕上纔會顯示這次更新內容。對於特別龐大的vDOM樹來講,這個過程會很長(x00ms),在這期間,主線程是被js佔用的,所以任何交互、佈局、渲染都會中止,給用戶的感受就是頁面被卡住了。

爲了解決這個問題,react團隊通過兩年的工做,重寫了react中核心算法。並在v16版本中發佈了這個新的特性,簡稱爲Fiber。

Fiber實現了本身的組件調用棧,它以鏈表的形式遍歷組件樹,能夠靈活的暫停、繼續和丟棄執行的任務。實現方式是使用了瀏覽器的requestIdleCallback這一 API。官方的解釋是這樣的:

window.requestIdleCallback()會在瀏覽器空閒時期依次調用函數,這就可讓開發者在主事件循環中執行後臺或低優先級的任務,並且不會對像動畫和用戶交互這些延遲觸發但關鍵的事件產生影響。函數通常會按先進先調用的順序執行,除非函數在瀏覽器調用它以前就到了它的超時時間。

由於瀏覽器是單線程,它將GUI描繪,時間器處理,事件處理,JS執行,遠程資源加載通通放在一塊兒。當作某件事,只有將它作完才能作下一件事。若是有足夠的時間,瀏覽器是會對咱們的代碼進行編譯優化。只有讓瀏覽器休息好,他才能跑的更快。

React 優化

代碼分割

  1. import()
  2. lazy + Suspense

前端優化

SPA 首頁白屏優化

引起緣由

SPA的應用啓動的方式都是極其相似的,都是在html 中提供一個 root 節點,而後把應用掛載到這個節點上。

這樣的模式,使用 webpack 打包以後,通常就是三個文件:

  1. 一個體積很小、除了提供個 root 節點之外的沒什麼卵用的html(大概 1-4 KB)
  2. 一個體積很大的 js(50 - 1000 KB 不等)
  3. 一個 css 文件(固然若是你把 css 打進 js 裏了,也可能沒有)

這樣形成的直接後果就是,用戶在 js 文件加載、執行完畢以前,頁面是徹底空白的。 也就是說,這個時候

首屏體積(首次渲染須要加載的資源體積) = html + js + css

優化方式

  1. SSR(服務端渲染)
  2. prerender-spa-plugin(第三方插件)

咱們可使用 prerender-spa-plugin,爲項目添加骨架屏和 Loading 狀態

懶加載

  1. 圖片懶加載
  2. 組件懶加載

減小請求次數

  • 小圖片合併雪碧圖;
  • JS、CSS文件選擇性合併;
  • 避免重複的資源請求。

減小文件大小

  • 壓縮CSS、JS、圖片;
  • 儘量控制DOM節點數;
  • 精簡css、 JavaScript,移除註釋、空格、重複css和腳本。
  • 開啓Gzip
相關文章
相關標籤/搜索