前端筆記--前端必備知識清單(持續更新)

文章旨在整理收集前端基礎、計算機網絡、瀏覽器、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 算法相關,排序與動態規劃

1、HTML與CSS

1.css盒模型

  • w3c盒模型:content + padding + border + margin。元素寬高(css)等於content的寬高。
  • IE盒模型:元素的寬高(css)等於content + padding + border的寬度。

2.BFC塊級格式化上下文

  • 造成條件:根元素;position: absolute/fixed; float;overflow:not visible;display: inline-block / table。
  • 應用:清除浮動

3.居中佈局

  • 水平居中 行內元素:text-align: center;塊級元素:margin:auto;
  • 垂直居中 line-height; absolute; flex;
  • 水平垂直居中 absolute;僞元素造成行內元素居中;flex + justify-content + align-items

4.清除浮動

設置父元素高度;造成bfc;僞元素clear: both;html

5.flex經常使用api

引用: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屬性。
複製代碼

6.nodeType

  • 若是節點是元素節點,則 nodeType 屬性將返回 1
  • 若是節點是屬性節點,則 nodeType 屬性將返回 2

2、JavaScript

1.原型與繼承

原型:prototype

函數的對象屬性,包含一個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

new Fun操做符原理

  • Object.create(Fun.prototype);
  • 執行Fun,即相似類中的調用constructor()(爲何是執行constructor,由於constructor指向函數自己);
  • 若是constructor沒有明確返回函數或對象,則返回第一步建立的對象。

2.函數執行相關

堆內存與棧內存

  1. 棧內存:由編譯器自動分配與釋放。咱們能夠直接操做棧內存中的值。js的基本類型存放在棧內存中,按值引用;
  2. 堆內存:鏈表結構的類型,能夠動態分配大小,js引用類型佔用內存空間的大小不固定,存儲在堆內存中。因爲JS不容許直接訪問堆內存中的位置,所以咱們不能直接操做js的引用類型。而是生成一個指針,並將它放到棧內存中,經過這個指針來操做引用類型。

垃圾回收

類型:標記引用(沒法解決循環引用問題);標記清除(如今主流回收算法)。 標記清除算法的核型概念是:從根部(在JS中就是全局對象)出發定時掃描內存中的對象。凡是能從根部到達的對象,都是還須要使用的。那些沒法由根部出發觸及到的對象被標記爲再也不使用,稍後進行回收。github

內存泄漏

對於再也不用到的內存,沒有及時釋放,就叫作內存泄漏(memory leak)。 常見內存泄漏:

  1. 不合理的計時器(引用過期的外部變量)
  2. 被共享的閉包做用域(函數內部的函數共享一個閉包環境)
  3. 脫離dom引用(dom中元素被清除,可是變量還保持這對dom元素的引用)
  4. 意外的全局變量(未聲明或者指向全局的this.xxx)

事件循環

事件循環是指: 執行一個宏任務,而後執行清空微任務列表,循環再執行宏任務,再清微任務列表。

異步任務分爲宏任務(macrotask)與微任務 (microtask),不一樣的API註冊的任務會依次進入自身對應的隊列中,而後等待Event Loop將它們依次壓入執行棧中執行。

- 微任務 microtask(jobs): promise / ajax / Object.observe(該方法已廢棄)
- 宏任務 macrotask(task): setTimout / script / IO / UI Rendering

  • 產生宏任務隊列的代碼:script(總體代碼)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 環境)
  • 產生微任務的代碼:Promise、MutaionObserver、process.nextTick(Node.js環境)

每次事件循環:

  1. 執行完主執行線程中的任務。
  2. 取出micro-task中任務執行直到清空。
  3. 取出macro-task中一個任務執行。
  4. 取出micro-task中任務執行直到清空。
  5. 重複3和4。

實例執行過程
  1. 執行完主執行線程中的任務(這裏簡單,沒按執行順序寫)
    • 添加microtask隊列[b, f, i];
    • 添加macrotask隊列[a, c, j];
    • 輸出in outer,in outer2;
  2. 執行microtask
    • 取出b -> 輸出in promise;
    • 取出f -> 添加g到macrotask隊列[a, c, j, g],添加h到microtask->[i, h]
    • 取出i,輸出in promise2;
    • 取出g,輸出in promise's promise,至此,microtask清空。
  3. 執行macrotask
    • 取出a,輸出in timeout;
    • 取出c,[j,g].push(d), [].push(e) // 微任務隊列;
    • 檢測到微任務隊列有子項e,取出執行,輸出in time's promise;
    • 清空完微任務,繼續執行宏任務隊列,取出j,輸出in timeout2;
    • 取出g,執行輸出in promise's timeout;
    • 取出d,執行輸出in timeout's timeout

requestAnimationFrame

requestAnimationFrame(cb)與setTimeout(cb, 0)同時註冊時,可能在setTimeout以前執行,也可能在它以後執行。由於只有在一輪瀏覽器渲染結束時纔回去調用raf。

執行環境及做用域

執行環境

執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境鬥魚一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。
全局執行環境是最外圍的執行環境,在web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法被建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬。(全局執行環境直到應用程序推出時纔會被銷燬)。

做用域鏈

當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈,保證對執行環境有權訪問的全部變量和函數的有序訪問。
做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量對象,其在最開始的時候只包含一個變量:arguments。做用域鏈的下一個變量來自包含環境,而️再下一個變量對象則來自下一個包含環境一直延續到全局執行環境。做用域鏈在建立的時候就會被生成,保存在內部的[[scope]]屬性當中。其本質是一個指向變量對象的指針列表,它指引用但不實際包含變量對象。

[[scope]]

函數建立過程當中,會建立一個預先包含外部活動對象的做用域鏈,並始終包含全局環境的變量對象。這個做用域鏈被保存在內部的[[scope]]屬性中,當函數執行時,會經過複製該屬性中的對象構建起執行環境的做用域鏈。

執行上下文

定義

執行上下文是指當前Javascript代碼被解析和執行時所在環境的抽象概念,JavaScript 任何代碼的運行都是在執行上下文中。

類型

  1. 全局執行上下文
  2. 函數執行上下文
  3. eval函數執行上下文

如何工做

執行上下文分爲兩個過程:

  1. 建立階段
  2. 執行階段
建立階段

主要作了三件事

  1. this綁定(這解釋了爲何this的值取決於函數的調用方式)
  2. 建立詞法環境。(建立活動對象,變量和函數聲明的位置標記(聲明提高過程);根據[[scope]]建立做用域鏈)
  3. 建立變量環境(變量環境也是詞法法環境,用於存儲var聲明的變量。)
執行階段

變量賦值,語句執行等。

閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數。其本質是函數的做用域鏈中保存着外部函數變量對象的引用。能夠經過[[scope]]屬性查看。所以,即便外部函數被銷燬,可是因爲外部環境中定義的變量在閉包的scope中還保持着引用,因此這些變量暫時不會被垃圾回收機制回收,所以依然能夠在閉包函數中正常訪問。**注意:同一個函數內部的閉包函數共享同一個外部閉包環境。**從下圖能夠看出,即便返回的閉包裏面沒有引用c,d變量,可是因爲內部函數closure1中引用鏈,因此即便closure1未調用,閉包做用域中依然保存這些變量的引用。

3.js引用方式

  • html 靜態<script>引入
  • js 動態插入<script>
  • <script defer>: 延遲加載,元素解析完成後執行
  • <script async>: 異步加載,但執行時會阻塞元素渲染

其中藍色表明js腳本網絡加載時間,紅色表明js腳本執行時間,綠色表明html解析。
更多分析見下文 瀏覽器->頁面的加載與渲染

4.對象的拷貝

淺拷貝

拷貝一層,內部若是是對象,則只是單純的複製指針地址。

  1. Object.assign()
  2. { ...obj }

深拷貝

  1. JSON.parse(JSON.stringify(source)),缺點層次太深會爆棧,沒法解決循環引用問題。
  2. 參考深拷貝的終極探索(99%的人都不知道),採用循環代替遞歸實現深層次的拷貝。大體思想以下:
// 保持引用關係
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;
}
複製代碼

5.模塊化

  • es6模塊
  • commonJS/AMD模塊

es6模塊與CommonJS模塊的差別

  1. CommonJS模塊輸出的是一個值的拷貝,ES6模塊輸出的是值的引用。
  2. CommonJS模塊是運行時加載,ES6模塊是編譯時輸出接口。

6.防抖和節流

防抖 (debounce)

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

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);
    };
}
複製代碼

節流(throttle)

每隔一段時間後執行一次,也就是下降頻率,將高頻操做優化成低頻操做,一般使用場景: 滾動條事件 或者 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);
        }
    }
}
複製代碼

7.ES6/ES7

let/const

let和const能夠造成塊級做用域,而且不存在聲明提高。而且在同一個塊級做用域內不可重複聲明。

解構賦值

好比 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: 'bar' };

函數擴展

  1. 參數默認值
  2. rest參數
  3. 箭頭函數

數組擴展

  1. Array.from() // 將類數組轉化爲數組
  2. Array.of() // 更語義明確的數組構建函數:用於將一組值,轉換爲數組。
  3. entries(),keys() 和 values()
  4. 實例方法includes
  5. flat(num?)// 數組扁平化
  6. 數組空位,es6明確將數組空位轉化成undefined

對象擴展

  1. 屬性簡寫
  2. 屬性名錶達式
  3. 可枚舉性
    • for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
    • Object.keys():返回對象自身的全部可枚舉的屬性的鍵名。
    • JSON.stringify():只串行化對象自身的可枚舉的屬性。
    • Object.assign(): 忽略enumerable爲false的屬性,只拷貝對象自身的可枚舉
  4. Object.is()
  5. Object.assign()
  6. Object.keys(),Object.values(),Object.entries()

Symbol

let s = Symbol();

typeof s
// "symbol"
複製代碼

Symbol.for(), Symbol.keyFor()

Set,Map

Set

結構相似數組,成員惟一。

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
複製代碼

操做方法:

  • add(value):添加某個值,返回 Set 結構自己。
  • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • has(value):返回一個布爾值,表示該值是否爲Set的成員。
  • clear():清除全部成員,沒有返回值。

WeapSet

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 之中。

Map

相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,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

WeakMap與Map的區別有兩點。

首先,WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。

其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。

Proxy

Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫 ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。

var proxy = new Proxy(target, handler);
複製代碼

Promise

實現一個簡單的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/await

async 函數是什麼?一句話,它就是 Generator 函數的語法糖。

async函數返回一個promise對象。async函數內部return語句返回的值,會成爲then方法回調函數的參數。

async原理

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));
    }
}
複製代碼

Class

es6的類語法是es5構造函數的語法糖,可是也有一些不一樣點

  1. class的原型屬性不可枚舉
  2. 不使用new操做符,class會報錯
  3. class新增存取函數
  4. 不存在聲明提高

Class的繼承

抓住兩點便可:假設A爲父類,B爲子類那麼

B.__proto__ = A; // 子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
B.prototype.__proto__ = A.prototype; // 子類prototype屬性的__proto__屬性,表示方法(實例)的繼承,老是指向父類的prototype屬性。
複製代碼

Module

  • es6模塊
  • commonJS/AMD模塊

es6模塊與CommonJS模塊的差別

  1. CommonJS模塊輸出的是一個值的拷貝,ES6模塊輸出的是值的引用。
  2. CommonJS模塊是運行時加載,ES6模塊是編譯時輸出接口。

8.函數柯里化

在一個函數中,首先填充幾個參數,而後再返回一個新的函數的技術,稱爲函數的柯里化。一般可用於在不侵入函數的前提下,爲函數 預置通用參數,供屢次重複調用。

const add = function add(x) {
	return function (y) {
		return x + y
	}
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21
複製代碼

9.正則表達式

匹配模式

  1. 兩種模糊匹配:{m,n} [abc]

  2. 字符組: [a-z] [^a-z];常見簡寫模式:

    \d // 數字 digit
    \D // 非數字
    \w // 表示數字、大小寫字母和下劃線word
    \W // 非單詞
    \s // [ \t\v\n\r\f] 空白符,space
    \S // 非空白符
    . // 通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外
    複製代碼
  3. 量詞 {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"]
      複製代碼
  4. 分支結構 /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

正則的括號

  1. 分組和分支結構括號提供分組功能,如(ab)+;(JavaScript|Regular Expression)

  2. 捕獲分組 單純的括號在正則裏面是捕獲分組的意思

  3. 反向引用 使用+序號來引用當前正則裏面的捕獲項,好比\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
    複製代碼
  4. 非捕獲數組 (?:p) 想用括號又不想產生匹配項的首選

正則回溯與性能

正則在涉及貪婪量詞,惰性量詞以及分支的時候每每會形成回溯,更多細節:JS正則表達式完整教程(略長)

10.設計模式

設計模式

3.瀏覽器

1.頁面的加載與渲染

從輸入 url 到展現的過程

  1. DNS查找相應的ip地址
    瀏覽器DNS緩存->電腦本地host文件查找->運營商DNS的解析
  2. 三次握手
    • 客戶端發送 syn(同步序列編號) 請求,進入 syn_send 狀態,等待確認
    • 服務端接收並確認 syn 包後發送 syn+ack 包,進入 syn_recv 狀態
    • 客戶端接收 syn+ack 包後,發送 ack 包,雙方進入 established 狀態
  3. 發送請求,分析 url,設置請求報文(頭,主體)
  4. 服務器返回請求的文件 (html)
  5. 瀏覽器渲染
    • HTML parser --> DOM Tree
    • CSS parser --> Style Tree
    • attachment --> Render Tree
    • layout: 佈局

瀏覽器加載與渲染

瀏覽器下載html文件並進行編譯,轉化成相似下面的結構

圖片來源再談 load 與 DOMContentLoaded
瀏覽器會對轉化後的數據結構自上而下進行分析:首先開啓下載線程,對全部的資源進行優先級排序下載(注意,這裏僅僅是下載,區別於解析過程,解析過程可能被阻塞)。同時主線程會對文檔進行解析

css文件的解析

  • header中的css的下載不會阻塞html的解析,當css文件下載完成,將阻塞html解析,優先解析css,會阻礙渲染。
  • body中的css下載和解析都不會阻礙html的解析,會阻礙渲染。(存在特殊狀況,見下文)

js文件的解析

1. 普通的script:

遇到 script 標籤時,首先阻塞後續內容的解析(可是不會阻塞下載,chrome對同一個域名支持6個http同時下載),當下載完成後,便執行js文件中的同步代碼。而後接着解析html

2. defer和async

若是script標籤設置了async或者defer標籤,下載過程不會阻塞文檔解析。defer執行將會放到html解析完成以後,dcl事件以前。async則是下載完就執行,而且阻塞解析。

body中的首個script/link

在 body 中第一個 script 資源下載完成以前,瀏覽器會進行首次渲染。將該 script 標籤前面的 DOM 樹和 CSSOM 合併成一棵 Render 樹,渲染到頁面中。這是頁面從白屏到首次渲染的時間節點,比較關鍵。若是是第一個標籤是link,表現有點奇怪,下載和解析會阻塞html的解析,並有可能進行首次渲染,也可能不渲染。

渲染過程大體爲:計算樣式Recalculate style->佈局:layout->更新佈局樹update layer tree -> 繪製paint

3.相關概念

dom構建

DOM 構建的意思是,將文檔中的全部 DOM 元素構建成一個樹型結構。

css構建

將文檔中的全部 css 資源合併。生成css rule tree

render 樹

將 DOM 樹和 CSS 合併成一棵渲染樹,render 樹在合適的時機會被渲染到頁面中。(好比遇到 script 時, 該 script 尚未下載到本地時)。

4.總結

以下圖:

  1. 瀏覽器首先下載該地址所對應的 html 頁面。(若是html很大,瀏覽器並不會等待html徹底下載完,而是採用分段下載,分段解析)
  2. 瀏覽器解析 html 頁面的 DOM 結構。
  3. 開啓下載線程對文檔中的全部資源按優先級排序下載。
  4. 主線程繼續解析文檔,到達 head 節點 ,head 裏的外部資源無非是外鏈樣式表和外鏈 js。
    • 發現有外鏈 css 或者外鏈 js,若是是外鏈 js ,則中止解析後續內容,等待該資源下載,下載完後馬上執行。若是是外鏈 css,繼續解析後續內容。
  5. 解析到 body

在 body 中第一個 script 資源下載完成以前,瀏覽器會進行首次渲染。將該 script 標籤前面的 DOM 樹和 CSSOM 合併成一棵 Render 樹,渲染到頁面中。這是頁面從白屏到首次渲染的時間節點,比較關鍵。若是是第一個標籤是link,表現有點奇怪,下載和解析會阻塞html的解析,並有可能進行首次渲染,也可能不渲染。隨後js的下載和執行依然會阻塞解析,可是css的下載和解析則不會阻塞html的解析。

  1. 文檔解析完畢,頁面從新渲染。當頁面引用的全部 js 同步代碼執行完畢,觸發 DOMContentLoaded 事件。而後執行佈局:layout->更新佈局樹update layer tree -> 繪製paint
  2. html 文檔中的圖片資源,js 代碼中有異步加載的 css、js 、圖片資源都加載完畢以後,load 事件觸發(與DOMContentLoaded沒有必然的前後順序關係)。

2.跨域解決方案

1.jsonp跨域

因爲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);}
複製代碼

2.document.domain + iframe 跨域

3.window.name + iframe 跨域

4.location.hash + iframe 跨域

5.postMessage跨域

6.跨域資源共享 CORS

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

7.WebSocket協議跨域

8.node代理跨域,服務器轉發

9.ngnix代理跨域

正向代理

正向代理(forward)是一個位於客戶端【用戶A】和原始服務器(origin server)【服務器B】之間的服務器【代理服務器Z】,爲了從原始服務器取得內容,用戶 A 向代理服務器 Z 發送一個請求並指定目標(服務器B),而後代理服務器 Z 向服務器 B 轉交請求並將得到的內容返回給客戶端。客戶端必需要進行一些特別的設置才能使用正向代理。

反向代理

對於客戶端而言代理服務器就像是原始服務器,而且客戶端不須要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接着反向代理將判斷向何處(原始服務器)轉交請求,並將得到的內容返回給客戶端。

ngnix

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 比較經常使用的一個功能,可優化資源利用率,最大化吞吐量,減小延遲,確保容錯配置,將流量分配到多個後端服務器。

這裏舉出經常使用的幾種策略

  1. 輪詢(默認),請求過來後,Nginx 隨機分配流量到任一服務器

    upstream backend {
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
    }
    複製代碼
  2. weight=number 設置服務器的權重,默認爲1,權重大的會被優先分配

    upstream backend {
        server 127.0.0.1:3000 weight=2;
        server 127.0.0.1:3001 weight=1;
    }
    複製代碼
  3. backup 標記爲備份服務器。當主服務器不可用時,將傳遞與備份服務器的鏈接。

    upstream backend {
        server 127.0.0.1:3000 backup;
        server 127.0.0.1:3001;
    }
    複製代碼
  4. ip_hash 保持會話,保證同一客戶端始終訪問一臺服務器。

    upstream backend {
        ip_hash;  
        server 127.0.0.1:3000 backup;
        server 127.0.0.1:3001;
    }
    複製代碼
  5. least_conn 優先分配最少鏈接數的服務器,避免服務器超載請求過多。

    upstream backend {
        least_conn;
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
    }
    複製代碼

3.迴流與重繪

迴流

當元素的尺寸、結構或觸發某些屬性時,瀏覽器會從新渲染頁面,稱爲迴流。此時,瀏覽器須要從新通過計算,計算後還須要從新頁面佈局,所以是較重的操做。

從上圖中能夠看到迴流過程當中發生了recalculate style -> layout -> update layer tree-> paint-> composite layers;

重繪

當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時因爲只須要UI層面的從新像素繪製,所以損耗較少。

從上圖中能夠看到重繪過程當中發生了recalculate style -> update layer tree-> paint-> composite layers;

從以上圖中對比能夠看出,重繪的過程比迴流要少作一步操做,即layout

4.計算機網絡基礎

5.前端工程化

6.Vue源碼相關

1.nextTick

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
    })
  }
}
複製代碼
  1. nextTick優先採用microtask實現,不直接使用macrotask的緣由是某些狀況下會形成bug,好比state剛好在重繪以前更改。

降級處理順序爲Promise.resolve().then -> MutationObserver -> setImmediate -> setTimeout

  1. nextTick的邏輯是,首先執行回調函數的添加,這個過程可能在同步任務代碼中添加多個回調,而且使用pending變量保證回調隊列最終只執行一次。同步任務執行完畢後,取出這個存放在microtask異步隊列中的flushCallbacks函數,執行回調隊列並清空。
  2. 從這個函數裏面,咱們並不能確認nextTick回調就是在dom渲染以後執行,因此咱們仍是要去看看vue的渲染機制。
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

2.v-on

之前面試的時候,別人問我nextTick的原理,我答完以後,別人接着問我那v-on呢?我當時一臉懵逼,v-on不就是事件綁定嗎?你想表達什麼?(黑人問號臉.png)

官方文檔

v-on

  • 縮寫: @
  • 預期: Function | Inline Statement | Object
  • 參數 event
  • 修飾符 .stop,.prevent,.capture,.self,.{keyCode | keyAlias},.native,.once,.left,.right,.middle,.passive

vue的事件綁定沒有像react那樣作事件代理,只是單純的將事件所對應函數的this綁定到當前的vm。vue的事件綁定處理在vnode生成以前,即在生成ast模板層面就作好了處理,後續的函數只是負責調用。

源碼解析

  1. 首先,vue將html字符串中的v-on解析成el.events或者el.nativeEvents
  2. 而後使用genHandlers去對修飾符作預處理,包括修飾符和是否爲native,源碼是一些常規的處理,這裏就不貼出來了,感興趣的能夠自行查閱,代碼目錄/src/compiler/codegen/events.js

7.React源碼相關

8.算法相關

排序算法

// 冒泡排序的本質是,兩層循環,每一層外循環,將數組中的最大或者最小值挪到數組的尾部
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 '';
}
複製代碼

引用參考

  1. 中高級前端大廠面試祕籍,爲你保駕護航金三銀四,直通大廠(上)
  2. JS事件循環
  3. Flex 佈局教程:語法篇
  4. 前端筆記--JS執行上下文
  5. script標籤中defer和async屬性的區別
  6. [譯]頁面生命週期:DOMContentLoaded, load, beforeunload, unload解析
  7. 再談 load 與 DOMContentLoaded
  8. 面試帶你飛:這是一份全面的 計算機網絡基礎 總結攻略
  9. 深拷貝的終極探索(99%的人都不知道)
  10. JS正則表達式完整教程(略長)
  11. 正確面對跨域,別慌
  12. Nginx學習筆記(反向代理&搭建集羣)
  13. 前端想要了解的Nginx
  14. 你真的瞭解迴流和重繪嗎
  15. Js 的事件循環(Event Loop)機制以及實例講解
相關文章
相關標籤/搜索