文章旨在整理收集前端基礎、計算機網絡、瀏覽器、vue、react源碼以及算法相關知識點。內容來自平時的閱讀學習以及實踐理解,因爲內容量巨大,會進行持續的更新和補充。文章中涉及大量優秀博文的引用,大都在文末註明引用,若有遺漏或侵權,請聯繫做者處理。css
版本 | 操做時間 | 操做說明 |
---|---|---|
v_0.1.0 | 2019-4-16 20:57 | 第一版 |
v_0.1.1 | 2019-4-17 13:20 | 正則表達式 |
v_0.1.2 | 2019-4-18 17:20 | 調整原型鏈相關表述 |
v_0.1.3 | 2019-4-18 21:11 | 跨域解決方案、設計模式、迴流重繪 |
v_0.1.4 | 2019-4-21 15:02 | 修正事件循環表述、增長requestAnimationFrame理解 |
v_0.2.0 | 2019-4-21 17:17 | Vue源碼相關:nextTick原理解析 |
v_0.2.1 | 2019-4-24 11:33 | Vue源碼相關:v-on原理解析 |
v_0.2.2 | 2019-4-24 11:33 | 算法相關,排序與動態規劃 |
設置父元素高度;造成bfc;僞元素clear: both;html
引用:Flex 佈局教程:語法篇
容器元素display: flex
; 容器屬性前端
flex-direction // 決定主軸的方向(即項目的排列方向)。
flex-wrap // 若是一條軸線排不下,如何換行。
flex-flow // 是flex-direction屬性和flex-wrap屬性的簡寫形式,默認值爲row nowrap。
justify-content // 定義了項目在主軸上的對齊方式。
align-items // 屬性定義項目在交叉軸上如何對齊。
align-content // 屬性定義了多根軸線的對齊方式。若是項目只有一根軸線,該屬性不起做用。
複製代碼
元素屬性vue
order // 屬性定義項目的排列順序。數值越小,排列越靠前,默認爲0。
flex-grow // 屬性定義項目的放大比例,默認爲0,即若是存在剩餘空間,也不放大。
flex-shrink // 屬性定義了項目的縮小比例,默認爲1,即若是空間不足,該項目將縮小
flex-basis // 屬性定義了在分配多餘空間以前,項目佔據的主軸空間(main size)
flex // flex-grow, flex-shrink 和 flex-basis的簡寫,默認值爲0 1 auto
align-self // align-self屬性容許單個項目有與其餘項目不同的對齊方式,可覆蓋align-items屬性。
複製代碼
函數的對象屬性,包含一個constructor屬性,指向函數自身:Fun.prototype.constructor === Fun。node
原型鏈由原型對象構成,是js對象繼承的機制。每一個對像都有[[prototype]]屬性,主流瀏覽器以__proto__
暴露給用戶。__proto__
指向對象的構造函數的原型屬性(prototype)。 prototype的__proto__
又指向它**prototype對象(注意,這個prototype是一個對象結構)**的構造函數的原型;經過__proto__
屬性,造成一個引用鏈。在訪問對象的屬性時,若是對象自己不存在,就會沿着這條[[prototype]]鏈式結構一層層的訪問。react
能夠經過new來新建一個對象的函數。ios
函數擁有原型(prototype
);而對象能夠經過原型鏈關係([[prototype]]
)實現繼承。函數是一種特殊對象,也擁有__proto__
,即函數也會繼承。函數的原型鏈是:函數靜態方法—>原生函數類型的原型(擁有函數原生方法)—>原生對象原型(擁有對象原生方法) —> null
class A {}; class B extends A {};
git
B.__proto__ = A; B.prototype.__proto__ = A.prototype
;es6
類型:標記引用(沒法解決循環引用問題);標記清除(如今主流回收算法)。 標記清除算法的核型概念是:從根部(在JS中就是全局對象)出發定時掃描內存中的對象。凡是能從根部到達的對象,都是還須要使用的。那些沒法由根部出發觸及到的對象被標記爲再也不使用,稍後進行回收。github
對於再也不用到的內存,沒有及時釋放,就叫作內存泄漏(memory leak)。 常見內存泄漏:
事件循環是指: 執行一個宏任務,而後執行清空微任務列表,循環再執行宏任務,再清微任務列表。
異步任務分爲宏任務(macrotask)與微任務 (microtask),不一樣的API註冊的任務會依次進入自身對應的隊列中,而後等待Event Loop將它們依次壓入執行棧中執行。
- 微任務 microtask(jobs): promise / ajax / Object.observe(該方法已廢棄)
- 宏任務 macrotask(task): setTimout / script / IO / UI Rendering
每次事件循環:
[b, f, i]
;[a, c, j]
;in outer
,in outer2
;in promise
;[a, c, j, g]
,添加h到microtask->[i, h]
;in promise2
;in promise's promise
,至此,microtask清空。in timeout
;[j,g].push(d)
, [].push(e) // 微任務隊列
;in time's promise
;in timeout2
;in promise's timeout
;in timeout's timeout
。requestAnimationFrame(cb)與setTimeout(cb, 0)同時註冊時,可能在setTimeout以前執行,也可能在它以後執行。由於只有在一輪瀏覽器渲染結束時纔回去調用raf。
執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境鬥魚一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。
全局執行環境是最外圍的執行環境,在web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法被建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬。(全局執行環境直到應用程序推出時纔會被銷燬)。
當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈,保證對執行環境有權訪問的全部變量和函數的有序訪問。
做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量對象,其在最開始的時候只包含一個變量:arguments。做用域鏈的下一個變量來自包含環境,而️再下一個變量對象則來自下一個包含環境一直延續到全局執行環境。做用域鏈在建立的時候就會被生成,保存在內部的[[scope]]屬性當中。其本質是一個指向變量對象的指針列表,它指引用但不實際包含變量對象。
函數建立過程當中,會建立一個預先包含外部活動對象的做用域鏈,並始終包含全局環境的變量對象。這個做用域鏈被保存在內部的[[scope]]屬性中,當函數執行時,會經過複製該屬性中的對象構建起執行環境的做用域鏈。
執行上下文是指當前Javascript代碼被解析和執行時所在環境的抽象概念,JavaScript 任何代碼的運行都是在執行上下文中。
執行上下文分爲兩個過程:
主要作了三件事
變量賦值,語句執行等。
閉包是指有權訪問另外一個函數做用域中的變量的函數。其本質是函數的做用域鏈中保存着外部函數變量對象的引用。能夠經過[[scope]]屬性查看。所以,即便外部函數被銷燬,可是因爲外部環境中定義的變量在閉包的scope中還保持着引用,因此這些變量暫時不會被垃圾回收機制回收,所以依然能夠在閉包函數中正常訪問。**注意:同一個函數內部的閉包函數共享同一個外部閉包環境。**從下圖能夠看出,即便返回的閉包裏面沒有引用c,d變量,可是因爲內部函數closure1中引用鏈,因此即便closure1未調用,閉包做用域中依然保存這些變量的引用。
<script>
引入<script>
<script defer>
: 延遲加載,元素解析完成後執行<script async>
: 異步加載,但執行時會阻塞元素渲染瀏覽器->頁面的加載與渲染
拷貝一層,內部若是是對象,則只是單純的複製指針地址。
Object.assign()
// 保持引用關係
function cloneForce(x) {
// =============
const uniqueList = []; // 用來去重
// =============
let root = {};
// 循環數組
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 深度優先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化賦值目標,key爲undefined則拷貝到父元素,不然拷貝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
// =============
// 數據已經存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
break; // 中斷本次循環
}
// 數據不存在
// 保存源數據,在拷貝數據中對應的引用
uniqueList.push({
source: data,
target: res,
});
// =============
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循環
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
複製代碼
將屢次高頻操做優化爲只在最後一次執行,一般使用的場景是:用戶輸入,只需再輸入完成後作一次輸入校驗便可
function debounce(fn, wait, immediate) {
let timer = null;
return function(...args) {
if (immediate && !timer) {
fn.apply(this, args);
}
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
};
}
複製代碼
每隔一段時間後執行一次,也就是下降頻率,將高頻操做優化成低頻操做,一般使用場景: 滾動條事件 或者 resize 事件,一般每隔 100~500 ms執行一次便可。
function throttle(fn, wait, immediate) {
let timer = null;
let callNow = immediate;
return function(...args) {
if (callNow) {
fn.apply(this, args);
callNow = false;
}
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null;
}, wait);
}
}
}
複製代碼
let和const能夠造成塊級做用域,而且不存在聲明提高。而且在同一個塊級做用域內不可重複聲明。
好比 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: 'bar' }
;
let s = Symbol();
typeof s
// "symbol"
複製代碼
結構相似數組,成員惟一。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
複製代碼
操做方法:
WeakSet 結構與 Set 相似,也是不重複的值的集合。可是,它與 Set 有兩個區別。
首先,WeakSet 的成員只能是對象,而不能是其餘類型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
複製代碼
其次,WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。
相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
複製代碼
WeakMap與Map的區別有兩點。
首先,WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫 ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);
複製代碼
實現一個簡單的Promise
const FULLFILLED = 'FULLFILLED';
const REJECTED = 'REJECTED';
const PENDING = 'PENDING';
const isFn = val => typeof val === 'function';
class Promise {
constructor(handler) {
this._state = PENDING;
this._value = null;
this.FULLFILLED_CBS = [];
this.REJECTED_CBS = [];
try {
handler(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err);
}
}
_resolve(value) {
const async = () => {
if (this._state !== PENDING) {
return;
}
this._state = FULLFILLED;
const fullfilLed = (val) => {
this._value = val;
this.FULLFILLED_CBS.forEach(cb => cb(val));
};
const rejected = (err) => {
this._value = err;
this.REJECTED_CBS.forEach(cb => cb(err));
};
if (value instanceof Promise) {
value.then(fullfilLed, rejected);
} else {
fullfilLed(value);
}
}
requestAnimationFrame(async);
}
_reject(err) {
const async = () => {
if (this._state !== PENDING) {
return;
}
this._state = REJECTED;
this._value = err;
this.REJECTED_CBS.forEach(cb => cb(err));
}
requestAnimationFrame(async);
}
then(onFullfilled, onRejected) {
return new Promise((resolve, reject) => {
const handerResolve = (value) => {
try {
if (!isFn(onFullfilled)) {
resolve(value);
}
const res = onFullfilled(value);
if (res instanceof Promise) {
res.then(resolve, reject);
} else {
resolve(res);
}
} catch (err) {
reject(err);
}
};
const handlerReject = (err) => {
try {
if (!isFn(onRejected)) {
reject(err);
}
const res = onRejected(err);
if (res instanceof Promise) {
res.then(resolve, reject);
} else {
reject(res);
}
} catch (err) {
reject(err);
}
};
switch(this._state) {
case PENDING:
this.FULLFILLED_CBS.push(handerResolve);
this.REJECTED_CBS.push(handlerReject);
break;
case FULLFILLED:
handerResolve(this._value);
break;
case REJECTED:
handlerReject(this._value);
break;
default: break;
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(cb) {
const P = this.constructor;
return this.then(
/** 使用then保證promise的流是正常的,由於promise的下一步老是創建在上一步執行完的基礎上 */
value => P.resolve(cb()).then(() => value),
reason => P.resolve(cb()).then(() => { throw reason })
);
}
static all(list) {
return new Promise(resolve, reject) {
let count = 0;
const values = [];
const P = this.constructor;
for (let [key, fn] of list) {
P.resolve(fn).then(res => {
values[key] = res;
count++;
if (count === list.length) {
resolve(values);
}
}, err => reject(err));
}
}
}
static race(list) {
return new Promise((resolve, reject) => {
const values = [];
let count = 0;
const P = this.constructor;
for (let fn of list) {
P.resolve(fn).then(res => {
resolve(res);
}, err => {
reject(err);
})
}
})
}
static resolve(value) {
if (value instanceof Promise) {
return value;
}
return new Promise(resolve => resolve(value));
}
static reject(value) {
return new Promise((resolve, reject) => reject(value));
}
}
複製代碼
async函數返回一個promise對象。async函數內部return語句返回的值,會成爲then方法回調函數的參數。
function spawn(genF) {
return new Promise(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then((v) => {
step(() => gen.next(v));
}, (e) => {
step(() => gen.throw(e));
});
}
step(() => gen.next(undefined));
}
}
複製代碼
es6的類語法是es5構造函數的語法糖,可是也有一些不一樣點
抓住兩點便可:假設A爲父類,B爲子類那麼
B.__proto__ = A; // 子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
B.prototype.__proto__ = A.prototype; // 子類prototype屬性的__proto__屬性,表示方法(實例)的繼承,老是指向父類的prototype屬性。
複製代碼
在一個函數中,首先填充幾個參數,而後再返回一個新的函數的技術,稱爲函數的柯里化。一般可用於在不侵入函數的前提下,爲函數 預置通用參數,供屢次重複調用。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
複製代碼
兩種模糊匹配:{m,n} [abc]
字符組: [a-z] [^a-z]
;常見簡寫模式:
\d // 數字 digit
\D // 非數字
\w // 表示數字、大小寫字母和下劃線word
\W // 非單詞
\s // [ \t\v\n\r\f] 空白符,space
\S // 非空白符
. // 通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外
複製代碼
量詞 {m, } {m}, ?, +, *
貪婪匹配:儘量多的匹配
var regex = /\d{2,5}/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["123", "1234", "12345", "12345"]
```
複製代碼
惰性匹配:量詞後面加個問號,匹配最少能知足要求的字符串
var regex = /\d{2,5}?/g;
var string = "123 1234 12345 123456";
console.log( string.match(regex) );
// => ["12", "12", "34", "12", "34", "12", "34", "56"]
複製代碼
分支結構 /good|nice/
^ // 匹配開頭
$ // 匹配結尾
\b // 匹配單詞邊界
\B // 匹配非單詞邊界
(?=p) // 正向先行斷言 其中p是一個子模式,即p前面的位置。
(?!p) // 負向先行斷言 其中p是一個子模式,即不是p的前面的位置
複製代碼
數字的千位分隔符表示法:好比把"12345678",變成"12,345,678"。
const reg = /\B(?=(\d{3})+\b)/g
複製代碼
這個正則的意思是匹配1到多組3個相連的數字前面的位置|123 // 123前面的|表明這個位置
且這些數字的右側是單詞的邊界,好比 123|``123|.456
,左側是非單詞邊界,即數字前仍是單詞好比9|123
。
分組和分支結構括號提供分組功能,如(ab)+
;(JavaScript|Regular Expression)
捕獲分組 單純的括號在正則裏面是捕獲分組的意思
反向引用 使用+序號來引用當前正則裏面的捕獲項,好比\1
引用第一個捕獲
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
複製代碼
非捕獲數組 (?:p) 想用括號又不想產生匹配項的首選
正則在涉及貪婪量詞,惰性量詞以及分支的時候每每會形成回溯,更多細節:JS正則表達式完整教程(略長)
瀏覽器下載html文件並進行編譯,轉化成相似下面的結構
圖片來源再談 load 與 DOMContentLoaded遇到 script 標籤時,首先阻塞後續內容的解析(可是不會阻塞下載,chrome對同一個域名支持6個http同時下載),當下載完成後,便執行js文件中的同步代碼。而後接着解析html
若是script標籤設置了async或者defer標籤,下載過程不會阻塞文檔解析。defer執行將會放到html解析完成以後,dcl事件以前。async則是下載完就執行,而且阻塞解析。
在 body 中第一個 script 資源下載完成以前,瀏覽器會進行首次渲染。將該 script 標籤前面的 DOM 樹和 CSSOM 合併成一棵 Render 樹,渲染到頁面中。這是頁面從白屏到首次渲染的時間節點,比較關鍵。若是是第一個標籤是link,表現有點奇怪,下載和解析會阻塞html的解析,並有可能進行首次渲染,也可能不渲染。
渲染過程大體爲:計算樣式
Recalculate style
->佈局:layout->更新佈局樹update layer tree -> 繪製paint
DOM 構建的意思是,將文檔中的全部 DOM 元素構建成一個樹型結構。
將文檔中的全部 css 資源合併。生成css rule tree
將 DOM 樹和 CSS 合併成一棵渲染樹,render 樹在合適的時機會被渲染到頁面中。(好比遇到 script 時, 該 script 尚未下載到本地時)。
以下圖:
在 body 中第一個 script 資源下載完成以前,瀏覽器會進行首次渲染。將該 script 標籤前面的 DOM 樹和 CSSOM 合併成一棵 Render 樹,渲染到頁面中。這是頁面從白屏到首次渲染的時間節點,比較關鍵。若是是第一個標籤是link,表現有點奇怪,下載和解析會阻塞html的解析,並有可能進行首次渲染,也可能不渲染。隨後js的下載和執行依然會阻塞解析,可是css的下載和解析則不會阻塞html的解析。
因爲html頁面經過相應的標籤從不一樣的域名下加載靜態資源文件是被瀏覽器容許的,因此能夠經過動態建立script標籤的方式,來實現跨域通訊,缺點是隻支持get請求
let script = document.createElement('script');
script.src = 'http://www.example.com?args=agrs&callback=callback';
document.body.appendChild(script);
function callback(res) { console.log(res);}
複製代碼
RS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。 它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。IE8+:IE8/9須要使用XDomainRequest對象來支持CORS。 Access-Control-Allow-Origin: http://www.example.com
,經過Access-Control-Allow-Credentials: true
控制是否容許發送cookie
正向代理(forward)是一個位於客戶端【用戶A】和原始服務器(origin server)【服務器B】之間的服務器【代理服務器Z】,爲了從原始服務器取得內容,用戶 A 向代理服務器 Z 發送一個請求並指定目標(服務器B),而後代理服務器 Z 向服務器 B 轉交請求並將得到的內容返回給客戶端。客戶端必需要進行一些特別的設置才能使用正向代理。
對於客戶端而言代理服務器就像是原始服務器,而且客戶端不須要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接着反向代理將判斷向何處(原始服務器)轉交請求,並將得到的內容返回給客戶端。
Nginx 是一個高性能的HTTP和反向代理服務器,同時也是一個 IMAP/POP3/SMTP 代理服務器。
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server_names_hash_max_size 512;
server_names_hash_bucket_size 128;
server {
# 端口
listen 8888;
# 匹配請求中的host值
server_name localhost;
# 監聽請求路徑
location / {
# 查找目錄
root D:/www/work/branch/test/;
# 默認查找
index index.html index.htm;
}
# 反向代理接口
location /h5nc {
# 代理服務器
proxy_pass http://test.xxx.com;
}
}
}
複製代碼
# 反向代理接口
location /h5nc {
# 代理服務器
proxy_pass http://test.xxx.com;
}
複製代碼
負載均衡是Nginx 比較經常使用的一個功能,可優化資源利用率,最大化吞吐量,減小延遲,確保容錯配置,將流量分配到多個後端服務器。
這裏舉出經常使用的幾種策略
輪詢(默認),請求過來後,Nginx 隨機分配流量到任一服務器
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
複製代碼
weight=number 設置服務器的權重,默認爲1,權重大的會被優先分配
upstream backend {
server 127.0.0.1:3000 weight=2;
server 127.0.0.1:3001 weight=1;
}
複製代碼
backup 標記爲備份服務器。當主服務器不可用時,將傳遞與備份服務器的鏈接。
upstream backend {
server 127.0.0.1:3000 backup;
server 127.0.0.1:3001;
}
複製代碼
ip_hash 保持會話,保證同一客戶端始終訪問一臺服務器。
upstream backend {
ip_hash;
server 127.0.0.1:3000 backup;
server 127.0.0.1:3001;
}
複製代碼
least_conn 優先分配最少鏈接數的服務器,避免服務器超載請求過多。
upstream backend {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
複製代碼
當元素的尺寸、結構或觸發某些屬性時,瀏覽器會從新渲染頁面,稱爲迴流。此時,瀏覽器須要從新通過計算,計算後還須要從新頁面佈局,所以是較重的操做。
從上圖中能夠看到迴流過程當中發生了
recalculate style
->layout
->update layer tree
->paint
->composite layers
;
當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時因爲只須要UI層面的從新像素繪製,所以損耗較少。
從上圖中能夠看到重繪過程當中發生了
recalculate style
->update layer tree
->paint
->composite layers
;
從以上圖中對比能夠看出,重繪的過程比迴流要少作一步操做,即layout
vm.$nextTick([callback])
將回調延遲到下次 DOM 更新循環以後執行。在修改數據以後當即使用它,而後等待 DOM 更新。它跟全局方法 Vue.nextTick 同樣,不一樣的是回調的 this 自動綁定到調用它的實例上。
附上源代碼,不感興趣能夠直接跳過代碼,不要緊。
/* @flow */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx) // this binding
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
複製代碼
降級處理順序爲
Promise.resolve().then
->MutationObserver
->setImmediate
->setTimeout
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue) // fulshSchedulerQueue核心代碼:watcher.run()更新視圖
}
}
}
複製代碼
頁面視圖的渲染更新,也是向nextTick回調隊列中添加一個回調函數,這個函數內部也保存着一個更新隊列,不過這個隊列是個同步的。
而這個回調會在設置data屬性值的過程當中添加到nextTick的cbs裏面的,早於用戶使用nextTick添加的回調,因此能夠保證回調的調用順序。咱們來實驗一下:
因爲數據修改是同步的操做,而視圖更新是異步的,因此咱們兩次打印data.nextTickText都是修改後的值,而視圖更新是異步的,因爲第一個nexttick回調在視圖更新回調函數fulshSchedulerQueue以前添加,因此此時讀取到的是視圖更新以前的值。咱們經過斷電跟蹤也能夠看出此輪事件回調裏面保存的三個函數最終會分別執行printNextTick、flushSchedulerQueue、printNextTick
之前面試的時候,別人問我nextTick的原理,我答完以後,別人接着問我那v-on呢?我當時一臉懵逼,v-on不就是事件綁定嗎?你想表達什麼?(黑人問號臉.png)
@
Function | Inline Statement | Object
event
.stop,.prevent,.capture,.self,.{keyCode | keyAlias},.native,.once,.left,.right,.middle,.passive
vue的事件綁定沒有像react那樣作事件代理,只是單純的將事件所對應函數的this綁定到當前的vm。vue的事件綁定處理在vnode生成以前,即在生成ast模板層面就作好了處理,後續的函數只是負責調用。
genHandlers
去對修飾符作預處理,包括修飾符和是否爲native,源碼是一些常規的處理,這裏就不貼出來了,感興趣的能夠自行查閱,代碼目錄/src/compiler/codegen/events.js
// 冒泡排序的本質是,兩層循環,每一層外循環,將數組中的最大或者最小值挪到數組的尾部
function bubbleSort(arr) {
let i, j;
const len = arr.length;
for (i = 0; i < len - 1; i++) {
for (j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j + 1], arr[j]] = [arr[j], arr[j + 1]];
}
}
}
return arr;
}
// 選擇排序 選擇排序遍歷並與自身比較,將最小或者最大的數組項與自身替換
function selectSort(arr) {
const len = arr.length;
let i, j;
for (i = 0; i < len - 1; i++) {
for ( j = i + 1; j < len; j++) {
if (arr[i] > arr[j]) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
return arr;
}
// 插入排序
function insertSort(arr) {
const len = arr.length;
let i, j;
for (i = 1; i < len; i++) {
for (j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
} else {
break;
}
}
}
return arr;
}
// 快速排序
function quickSort(arr) {
if(arr.length <= 1) {
return arr; //遞歸出口
}
const baseItem = arr.splice(0, 1)[0];
const leftArr = [], rightArr = [];
arr.forEach(item => {
if (item <= baseItem) {
leftArr[leftArr.length] = item;
} else {
rightArr[rightArr.length] = item;
}
});
return [...quickSort(leftArr), baseItem, ...quickSort(rightArr)];
}
複製代碼
// 斐波那契數列
function fiber(n) {
if (n <= 2) {
return 1;
}
return fiber(n - 1) + fiber(n - 2);
};
// 斐波那契數列動態規劃優化。使用緩存
function fiberProcess(n) {
if (n <= 2) {
return 1;
}
const cache = {
1: 1,
2: 1
};
let i = 3;
for (i; i <= n; i++) {
cache[i] = cache[i - 1] + cache[i - 2];
}
return cache[n];
}
複製代碼
// 尋找最長公共子串 動態規劃
function maxSubStr(A, B) {
const lenA = A.length, lenB = B.length;
let i = 0, j = 0;
const tmp = [];
let max = 0, index = 0;
for (i; i< lenA + 1; i++) {
tmp[i] = [];
for (j = 0; j < lenB + 1; j++) {
if (i === 0 || j === 0) {
tmp[i][j] = 0;
} else if (A[i - 1] === B[j - 1]) {
tmp[i][j] = tmp[i - 1][j - 1] + 1;
if (tmp[i][j] > max) {
max = tmp[i][j];
index = i;
}
} else {
tmp[i][j] = 0;
}
}
}
if (max > 0) {
return A.slice(index - max, index);
}
return '';
}
複製代碼