聖盃佈局和雙飛翼佈局都是三欄佈局,左右盒子寬度固定,中間盒子自適應(固比固),區別是他們的實現的思想。 聖盃佈局: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
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
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 中規定僞類使用一個 : 來表示;僞元素則使用 :: 來表示nginx
<Link>
標籤和<a>
標籤有什麼區別?他們的本質都是 a 標籤,<Link>
是 react-router 裏實現路由跳轉的連接,通常配合 <Route>
使用。React Router 會接管 Link 的默認跳轉連接行爲。Link 主要作了三件事es6
history.pushState(); // 添加新的狀態到歷史狀態棧
history.replaceState(); // 用新的狀態代替當前狀態
history.state // 返回當前狀態對象
複製代碼
經過history.pushState或者history.replaceState,也能作到:改變 url 的同時,不會刷新頁面。可是onhashchange 能夠監聽hash的變化,但history的變化沒法直接監聽,須要經過攔截可能改變history的途徑來監聽history的變化。可能改變url的方法有三種web
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 對應的切片時,表明該切片須要上傳,用戶只需上傳這部分切片,就能夠完整上傳整個文件,這就是文件的斷點續傳。
產生緣由:與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)。
基本數據類型是直接存儲在棧內存中的。引用數據類型則是在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中得到實體。
深拷貝和淺拷貝的區別就是:淺拷貝只複製指向某個對象的指針,而不復制對象自己,新舊對象仍是共享同一塊內存。但深拷貝會另外創造一個如出一轍的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。
淺拷貝與賦值的區別:賦值獲得的對象與原對象指向的是同一個存儲空間,不管哪一個對象發生改變,其實都是改變的存儲空間的內容,所以,兩個對象是聯動的。而淺拷貝會建立一個新對象,若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址。 改變賦值對象的任意屬性都會改變原對象,但改變淺拷貝對象基本類型的屬性不會改變原對象基本屬性的值,改變引用類型的屬性纔會改變原對象對應的值。
let { ...x } = obj;
答案:form表單是能夠跨域的。 首先,跨域問題產生的緣由是瀏覽器的同源策略,沒有同源策略的網絡請求有可能會致使CSRF攻擊,就是攻擊者盜用了你的身份,以你的名義發送惡意請求,由於瀏覽器會自動將cookie附加在HTTP請求的頭字段Cookie中,因此服務端會覺得攻擊者的操做就是你本人的操做,因此瀏覽器就默認禁止了請求跨域。
經常使用的解決方式:
回到form表單,它也是能夠跨域的,由於form提交是不會攜帶cookie的,你也沒辦法設置一個hidden的表單項,而後經過js拿到其餘domain的cookie,由於cookie是基於域的,沒法訪問其餘域的cookie,因此瀏覽器認爲form提交到某個域,是沒法利用瀏覽器和這個域之間創建的cookie和cookie中的session的,故而,瀏覽器沒有限制表單提交的跨域問題。
瀏覽器同源策略的本質是,一個域名的 JS ,在未經容許的狀況下,不得讀取另外一個域名的內容。但瀏覽器並不阻止你向另外一個域名發送請求。
全部任務能夠分紅兩種,一種是同步任務(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
考察這三者在事件循環中的區別,事件循環中分爲宏任務隊列和微任務隊列。
對象用於表示一個異步操做的最終完成 (或失敗), 及其結果值.
返回一個狀態由給定value決定的Promise對象。
類方法,且與 resolve 惟一的不一樣是,返回的 promise 對象的狀態爲 rejected。
類方法,多個 Promise 任務同時執行。 若是所有成功執行,則以數組的方式返回全部 Promise 任務的執行結果。 若是有一個 Promise 任務 rejected,則只返回 rejected 任務的結果。
類方法,多個 Promise 任務同時執行,返回最早執行結束的 Promise 任務的結果,無論這個 Promise 結果是成功仍是失敗。
實例方法:
Promise 對象有三個狀態,而且狀態一旦改變,便不能再被更改成其餘狀態。
pending,異步任務正在進行。 resolved (也能夠叫fulfilled),異步任務執行成功。 rejected,異步任務執行失敗
首先初始化一個 Promise 對象,能夠經過兩種方式建立,這兩種方式都會返回一個 Promise 對象。
一、new Promise(fn)
二、Promise.resolve(fn)
複製代碼
而後調用上一步返回的 promise 對象的 then 方法,註冊回調函數。最後註冊 catch 異常處理函數,
async 函數會返回一個 Promise 對象,若是在函數中 return 一個直接量,async 會把這個直接量經過 Promise.resolve() 封裝成 Promise 對象。
await 能夠用於等待一個 async 函數的返回值,注意到 await 不只僅用於等 Promise 對象,它能夠等任意表達式的結果,因此,await 後面實際是能夠接普通函數調用或者直接量的。
若是它等到的不是一個 Promise 對象,那 await 表達式的運算結果就是它等到的東西。
若是它等到的是一個 Promise 對象,await 就忙起來了,它會阻塞後面的代碼,等着 Promise 對象 resolve,而後獲得 resolve 的值,做爲 await 表達式的運算結果。
async/await 的優點在於處理 then 鏈,尤爲是每個步驟都須要以前每一個步驟的結果時,async/await 的代碼對比promise很是清晰明瞭,簡直像同步代碼同樣。
async/await 就是 Generator 的語法糖,使得異步操做變得更加方便。async 函數就是將 Generator 函數的星號(*)替換成 async,將 yield 替換成await。 不同的是:
簡單實習代碼:
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 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) { //... 錯誤捕獲 } //... } 複製代碼
<meta>
標籤中的縮放比例<html>
元素添加data-dpr屬性,而且動態改寫data-dpr的值<html>
元素添加font-size屬性,而且動態改寫font-size的值簡單來講,閉包就是指有權訪問另外一個函數做用域中的變量的函數。建立閉包最多見方式,就是在一個函數內部建立或返回另外一個函數。 好比:
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); }; }; 複製代碼
實現邏輯:規定在一個單位時間內,只能觸發一次函數。若是這個單位時間內觸發屢次函數,只有一次生效 實現代碼:
//定時器 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的指向:this 永遠指向最後調用它的那個對象 怎麼改變this的指向:
箭頭函數的 this 始終指向函數定義時的 this,而非執行時。
apply、call、bind 都是能夠改變 this 的指向的,可是這三個函數稍有不一樣
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(); } 複製代碼
建立一個函數,返回相同的屬性和方法,能夠無數次調用。經常使用於產生大量類似的商品,去作一樣的事情,實現一樣的效果。
定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現的方法爲先判斷實例存在與否,若是存在則直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。
適用場景:一個單一對象。好比:彈窗,不管點擊多少次,彈窗只應該被建立一次。
定義:定義一系列的算法,把他們一個個封裝起來,而且使他們能夠相互替換。
策略模式的目的就是將算法的使用算法的實現分離開來。
例子:
/*策略類*/ 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 複製代碼
定義:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。
經常使用的虛擬代理形式:某一個花銷很大的操做,能夠經過虛擬代理的方式延遲到這種須要它的時候纔去建立(例:使用虛擬代理實現圖片懶加載)
定義:經過一箇中介者對象,其餘全部的相關對象都經過該中介者對象來通訊,而不是相互引用,當其中的一個對象發生改變時,只須要通知中介者對象便可。經過中介者模式能夠解除對象與對象之間的緊耦合關係。
發佈-訂閱模式實際上是一種對象間一對多的依賴關係,當一個對象的狀態發送改變時,全部依賴於它的對象都將獲得狀態改變的通知。
訂閱者(Subscriber)把本身想訂閱的事件註冊(Subscribe)到調度中心(Event Channel),當發佈者(Publisher)發佈該事件(Publish Event)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者註冊到調度中心的處理代碼。 例子:Vue的EventBus
觀察者模式也是定義對象間的一種一對多依賴關係,使得當每個被依賴對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。它的目標與發佈-訂閱者模式是一致的。
被觀察對象經過 subscribe 方法和 unsubscribe 方法添加和刪除一個觀察者,經過 broadcast 方法向觀察者推送消息。
document.body.addEventListener('click', ()=>{}}; 複製代碼
上面的例子就是一個最簡單的觀察者模式。document.body 在這裏就是一個被觀察對象, 全局對象是觀察者,當 click 事件觸發的時候,觀察者會調用 clickHandler 方法。
與發佈訂閱者模式的區別:觀察者模式中主體和觀察者仍是存在必定的耦合性,而發佈訂閱者模式中,在主體與觀察者之間引入消息調度中心,全部的消息傳遞過程都經過消息調度中心完成,也就是說具體的業務邏輯代碼將會是在消息調度中心內,而主體和觀察者之間實現了徹底的鬆耦合,與此同時帶來的問題就是,程序的可讀性下降了。
instanceof 用於檢測構造函數的 prototype 屬性是否出如今某個實例對象的原型鏈上。語法
object instanceof constructor
複製代碼
因此若是這個Object的原型鏈上可以找到Array構造函數的話,那麼這個Object應該及就是一個數組,反之則不是
const a = []; console.log(a instanceof Array);//true 複製代碼
不能夠直接調用數組自身的toString()方法,由於返回的會是內容的字符串,只有對象的toString方法會返回對象的類型。因此咱們須要「借用」對象的toString方法,須要使用call或者apply方法來改變toString方法的執行上下文。
const a = ['Hello','Howard']; Object.prototype.toString.call(a);//"[object Array]" 複製代碼
實例化的數組擁有一個constructor屬性,這個屬性指向生成這個數組的方法。
const a = []; console.log(a.constructor == Array);//true 複製代碼
可是constructor屬性是能夠改寫的,一旦被改寫,那這種判斷方式就無效了。
[...new Set(array)]
set 是 es6 提供的新的數據結構,它相似數組,可是成員的值都是惟一的,
for 循環嵌套,splice去重
新建一個空的結果數組,for 循環原數組,判斷結果數組是否存在當前元素,若是有相同的值則跳過,不相同則push進數組。判斷是否存在的方法有indexOf,array.includes
利用filter
function unique(arr) { return arr.filter(function(item, index, arr) { //當前元素,在原始數組中的第一個索引==當前索引值,不然返回當前元素 return arr.indexOf(item, 0) === index; }); } 複製代碼
7 種原始類型:
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請求最新的數據,而後從新更新界面。
優勢:
把業務邏輯和展現邏輯分離,模塊化程度高。且當應用邏輯須要變動的時候,不須要變動業務邏輯和展現邏輯,只須要Controller換成另一個Controller就好了(Swappable Controller)。
觀察者模式能夠作到多視圖同時更新。 缺點:
Controller測試困難
View沒法組件化。View是強依賴特定的Model的,若是須要把這個View抽出來做爲一個另一個應用程序可複用的組件就困難了。由於不一樣程序的的Domain Model是不同的。
MVP模式把MVC模式中的Controller換成了Presenter。
和MVC模式同樣,用戶對View的操做都會從View交移給Presenter。Presenter會執行相應的應用程序邏輯,而且對Model進行相應的操做;而這時候Model執行完業務邏輯之後,也是經過觀察者模式把本身變動的消息傳遞出去,可是是傳給Presenter而不是View。Presenter獲取到Model變動的消息之後,經過View提供的接口更新界面。
優勢
缺點
ViewModel的含義就是 "Model of View",視圖的模型。
MVVM的調用關係和MVP同樣。可是,在ViewModel當中會有一個叫Binder,之前所有由Presenter負責的View和Model之間數據同步操做交由給Binder處理。你只須要在View的模版語法當中,指令式地聲明View上的顯示的內容是和Model的哪一塊數據綁定的。當ViewModel對進行Model更新的時候,Binder會自動把數據更新到View上去,當用戶對View進行操做(例如表單輸入),Binder也會自動把數據更新到Model上去。這種方式稱爲:雙向數據綁定。
優勢
缺點:
props
/$emit
$children
/$parent
子實例能夠經過this.$parent
訪問父實例,子實例則被推入父實例的$children
數組中,拿到實例表明能夠訪問此組件的全部方法和data。this.$parent
是一個對象,$children
是一個數組。
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> 複製代碼
若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例,能夠經過實例直接調用組件的方法或訪問數據。
$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
建立EventBus方法: 引入Vue 並導出它的一個實例,也能夠直接在main.js中初始化,這樣咱們獲取到的 EventBus 是一個 全局的事件總線
Vue.prototype.$bus = new Vue() 複製代碼
而後就能夠經過on/off/emit,發佈訂閱事件了。
當你把一個普通的 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。
對於數組:
這兩種狀況不能監測
因爲 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) // => '已更新' } } 複製代碼
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(Javscriptのxml)是JavaScript的一個語法擴展,是 React.createElement(component, props, ...children) 函數的語法糖。React.createElement函數最會生成一個對象,咱們稱之爲React對象或者另外一個名字--虛擬DOM。
React 支持服務端渲染。
爲何要服務端渲染(SSR)呢?這與單頁應用(SPA )的興起息息相關。與傳統的 SSR 應用相比, SPA 在速度和用戶體驗方面具備很大的優點。 可是這裏有一個問題。SPA 的初始服務端請求一般返回一個沒有 DOM 結構的 HTML 文件,其中只包含一堆 CSS 和 JS links。而後,應用須要另外 fetch 一些數據來呈現相關的 HTML 標籤。 這意味着用戶將不得不等待更長時間的初始渲染。這也意味着爬蟲可能會將你的頁面解析爲空。 所以,關於這個問題的解決思路是:首先在服務端上渲染你的 app(渲染首屏),接着再在客戶端上使用 SPA。
SSR優勢
咱們可使用next.js構建支持服務端渲染的React應用程序。
React中,數據流是自上而下的單向數據流。
即將被廢除的三個生命週期
getDerivedStateFromProps 會在調用 render 方法以前調用,而且在初始掛載及後續更新時都會被調用。它應返回一個對象來更新 state,若是返回 null 則不更新任何內容。 當 props 變化時,建議使用getDerivedStateFromProps 生命週期更新 state,UNSAFE_componentWillReceiveProps即將被棄用
getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)以前調用。它使得組件能在發生更改以前從 DOM 中捕獲一些信息(例如,滾動位置)。今生命週期的任何返回值將做爲參數傳遞給 componentDidUpdate()。
適用於須要獲取依賴某項或多項變量複雜計算獲取到的結果的場景。 好比:
computed:{ reversedMessage: function () { return this.message.split('').reverse().join('') } } 複製代碼
你也能夠經過在表達式中調用方法來達到相同的效果,不一樣的是計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們纔會從新求值。這就意味着只要 message 尚未發生改變,屢次訪問 reversedMessage 計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。
Watch 觀察 Vue 實例上的一個表達式或者一個函數計算結果的變化。回調函數獲得的參數爲新值和舊值。表達式只接受監督的鍵路徑('a.b.c')。對於更復雜的表達式,用一個函數取代。
此外,它還有兩個屬性 deep和immediate
Watch中能夠執行任何邏輯,如函數節流、Ajax異步獲取數據,甚至操做 DOM(不建議)
計算屬性適合用在模板渲染中,某個值是依賴了其它的響應式對象甚至是計算屬性計算而來的;而偵聽屬性適用於觀測某個值的變化去完成一段複雜的業務邏輯。
因爲PureComponent的shouldeComponentUpdate裏,實際是對props/state進行了一個淺對比,因此對於嵌套的對象不適用,沒辦法比較出來。這是由於React 的淺比較中,當對比的類型爲Object的時候而且key的長度相等的時候,淺比較也僅僅是用Object.is()對Object的value作了一個基本數據類型的比較,因此若是key裏面是對象的話,有可能出現比較不符合預期的狀況,因此淺比較是不適用於嵌套類型的比較
React 是經過JSX渲染模板,深層來說是react 的模板渲染是經過原生JS實現模板中的常見語法(好比插值,條件,循環等)來實現的
Vue是經過一種拓展的HTML語法進行渲染。它是在和組件JS代碼分離的單獨的模板中,經過指令來實現的,好比條件語句就須要 v-if 來實現
react中render函數是支持閉包特性的,因此咱們import的組件在render中能夠直接調用。可是在Vue中,因爲模板中使用的數據都必須掛在 this 上進行一次中轉,因此咱們import 一個組件完了以後,還須要在 components 中再聲明下。
Vue經過Object.defineProporty來劫持對象的get,set方法,實現雙向綁定。 相比較react而言,react是單向數據流動的ui渲染框架,自己不存在數據的檢測這一機制,全部的數據改變都是經過setState來手動實現的。
vue和react都在其內部實現了‘虛擬dom’的概念,即將須要渲染的真實dom虛擬成一個js對象,渲染的時候,經過對這個新舊js對象進行比較,來計算出真實dom須要渲染的最小操做,以達到優化性能的目的。react和vue都有其diff算法,原理也很相似。
高階組件的概念應該是來源於JavaScript的高階函數:
高階函數就是接受函數做爲輸入或者輸出的函數,高階組件(HOC)是一個接受組件組做輸入並返回組件的函數。HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。實際上是裝飾器的語法糖。
在高階函數以前,React 也曾使用過mixin,但在工程中大量使用mixin會致使一些問題,
他能夠:
使用 HOC 的優勢
使用HOC的缺點
Hooks 是16.7添加的新特性。它的優勢是
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在進行組件渲染時,從setState開始到渲染完成整個過程是同步的(「一鼓作氣」)。若是須要渲染的組件比較龐大,js執行會佔據主線程時間較長,會致使頁面響應度變差,使得react在動畫、手勢等應用中效果比較差。
卡頓緣由:Stack(原來的算法)的工做流程很像函數的調用過程。父組件裏調子組件,能夠類比爲函數的遞歸。在setState後,react會當即開始從父節點開始遍歷,以找出不一樣。將全部的Virtual DOM遍歷完成後,才能給出當前須要修改真實DOM的信息,並傳遞給renderer,進行渲染,而後屏幕上纔會顯示這次更新內容。對於特別龐大的vDOM樹來講,這個過程會很長(x00ms),在這期間,主線程是被js佔用的,所以任何交互、佈局、渲染都會中止,給用戶的感受就是頁面被卡住了。
爲了解決這個問題,react團隊通過兩年的工做,重寫了react中核心算法。並在v16版本中發佈了這個新的特性,簡稱爲Fiber。
Fiber實現了本身的組件調用棧,它以鏈表的形式遍歷組件樹,能夠靈活的暫停、繼續和丟棄執行的任務。實現方式是使用了瀏覽器的requestIdleCallback
這一 API。官方的解釋是這樣的:
window.requestIdleCallback()會在瀏覽器空閒時期依次調用函數,這就可讓開發者在主事件循環中執行後臺或低優先級的任務,並且不會對像動畫和用戶交互這些延遲觸發但關鍵的事件產生影響。函數通常會按先進先調用的順序執行,除非函數在瀏覽器調用它以前就到了它的超時時間。
由於瀏覽器是單線程,它將GUI描繪,時間器處理,事件處理,JS執行,遠程資源加載通通放在一塊兒。當作某件事,只有將它作完才能作下一件事。若是有足夠的時間,瀏覽器是會對咱們的代碼進行編譯優化。只有讓瀏覽器休息好,他才能跑的更快。
SPA的應用啓動的方式都是極其相似的,都是在html 中提供一個 root 節點,而後把應用掛載到這個節點上。
這樣的模式,使用 webpack 打包以後,通常就是三個文件:
這樣形成的直接後果就是,用戶在 js 文件加載、執行完畢以前,頁面是徹底空白的。 也就是說,這個時候
首屏體積(首次渲染須要加載的資源體積) = html + js + css
咱們可使用 prerender-spa-plugin,爲項目添加骨架屏和 Loading 狀態