記錄近期面試題,面試總結

記錄近期換工做時遇到的面試題和麪試題答案

css 部分

盒模型

問題:說一下 css 的盒模型css

盒模型分爲標準模型和怪異盒模型(IE 盒模型)html

標準盒模型:盒模型的寬高只是內容(content)的寬高前端

怪異盒模型:盒模型的寬高是內容(content)+填充(padding)+邊框(border)的總寬高vue

問題:css 如何設置兩種模型react

/* 標準模型 */
box-sizing:content-box;

 /*IE模型*/
box-sizing:border-box;
複製代碼

問題:有沒有遇到過邊距重疊,怎麼解決 邊距重疊問題以下圖所示 linux

利用 BFC 去解決,下方詳細說明 BFCgit

參考代碼:利用 BFC 的特性,把另外一個盒子放入另外一個獨立的 BFC 中,從而使得兩個盒子之間互相不受影響es6

<section class="top">
    <h1>上</h1>
    margin-bottom:30px;
</section>
<div style="overflow: hidden;">
    <section class="bottom">
    <h1>下</h1>
    margin-top:50px;
    </section>
</div>
複製代碼

BFC

問題:說一下 BFCgithub

  1. 什麼是 BFC面試

    BFC(Block Formatting Context)格式化上下文,是 Web 頁面中盒模型佈局的 CSS 渲染模式,指一個獨立的渲染區域或者說是一個隔離的獨立容器。

  2. 造成 BFC 的條件

    • 浮動元素,float 除 none 之外的值
    • 定位元素,position(absolute,fixed)
    • display 爲如下其中之一的值 inline-block,table-cell,table-caption
    • overflow 除了 visible 之外的值(hidden,auto,scroll)
  3. BFC 的特性

    • 內部的 Box 會在垂直方向上一個接一個的放置。
    • 垂直方向上的距離由 margin 決定
    • bfc 的區域不會與 float 的元素區域重疊。
    • 計算 bfc 的高度時,浮動元素也參與計算
    • bfc 就是頁面上的一個獨立容器,容器裏面的子元素不會影響外面元素。

rem 原理

rem 佈局的本質是等比縮放,通常是基於寬度,假設將屏幕寬度分爲 100 份,每份寬度是 1rem,1rem 的寬度是屏幕寬度/100,,而後子元素設置 rem 單位的屬性

經過改變 html 元素的字體大小,就能夠設置子元素的實際大小。

比 rem 更好的方案(缺點兼容很差)

vw(1vw 是視口寬度的 1%,100vw 就是視口寬度),vh(100vh 就是視口高度)

實現三欄佈局

(兩側定寬,中間自適應)

這裏寫出五種實現方式:

  1. flex 方式實現
/* css */
.box {
    display: flex;
    justify-content: center;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    height: 100%;
}
.content {
    background-color: yellow;
    flex: 1;
}
.right {
    width: 200px;
    background-color: green;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
複製代碼
  1. 浮動方式,此方式 content 必須放在最下邊
/* css */
.box {
      height: 200px;
    }
.left {
    width: 200px;
    background-color: red;
    float: left;
    height: 100%;
}
.content {
    background-color: yellow;
    height: 100%;
}
.right {
    width: 200px;
    background-color: green;
    float: right;
    height: 100%;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="right"></div>
    <div class="content"></div>
</div>
複製代碼
  1. 絕對定位方式實現
/* css */
.box {
    position: relative;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    left: 0;
    height: 100%;
    position: absolute;
}
.content {
    background-color: yellow;
    left: 200px;
    right: 200px;
    height: 100%;
    position: absolute;
}
.right {
    width: 200px;
    background-color: green;
    right: 0;
    height: 100%;
    position: absolute;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
複製代碼
  1. 表格佈局實現方式
/* css */
.box {
    display: table;
    height: 200px;
}
.left {
    width: 200px;
    background-color: red;
    height: 100%;
    display: table-cell;
}
.content {
    background-color: yellow;
    height: 100%;
    display: table-cell;
}
.right {
    width: 200px;
    background-color: green;
    height: 100%;
    display: table-cell;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
複製代碼
  1. grid 網格佈局
/* css */
.box {
    display: grid;
    grid-template-columns: 200px auto 200px;
    grid-template-rows: 200px;
}
.left {
    background-color: red;
}
.content {
    background-color: yellow;
}
.right {
    background-color: green;
}

/* html */
<div class="box">
    <div class="left"></div>
    <div class="content"></div>
    <div class="right"></div>
</div>
複製代碼

使一個盒子水平垂直居中

這裏寫出五種實現方式:

  1. 寬度和高度已知的
/* css */
#box{
    width: 400px;
    height: 200px;
    position: relative;
    background: red;
}
#box1{
    width: 200px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -100px;
    margin-top: -50px;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
複製代碼
  1. 寬度和高度未知
/* css */
 #box{
    width: 800px;
    height: 400px;
    position: relative;
    background: red;
}
#box1{
    width: 100px;
    height: 50px;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
複製代碼
  1. flex 佈局
/* css */
#box{
    width: 400px;
    height: 200px;
    background: #f99;
    display: flex;
    justify-content: center;//實現水平居中
    align-items: center;//實現垂直居中
}
#box1{
    width: 200px;
    height: 100px;
    background: green;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
複製代碼
  1. 平移 定位+transform
/* css */
#box{
    width: 400px;
    height: 200px;
    background: red;
    position: relative;
}
#box1{
    width: 200px;
    height: 100px;
    background: #9ff;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
複製代碼
  1. table-cell 佈局
/* css */
#box{
    display: table-cell;
    vertical-align: middle
}
#box1{
    margin: 0 auto;
}
/* html */
<div id="box">
    <div id="box1">

    </div>
</div>
複製代碼

js 部分

什麼是閉包,閉包的用途

可以讀取其餘函數內部變量的函數,或簡單理解爲定義在一個函數內部的函數,內部函數持有外部函數內變量的引用。

閉包的用途:

  • 讀取其餘函數內部的變量
  • 讓變量的值始終保存在內存中
  • JavaScript中閉包的應用都有關鍵詞return,引用 JavaScript 祕密花園中的一段話就是由於:

閉包是 JavaScript 一個很是重要的特性,這意味着當前做用域老是可以訪問外部做用域中的變量。 由於 函數 是 JavaScript 中惟一擁有自身做用域的結構,所以閉包的建立依賴於函數。

call, apply, bind 區別? 怎麼實現 call,apply 方法

類似之處:

  1. 都是用來改變函數的 this 對象的指向的。
  2. 第一個參數都是 this 要指向的對象。
  3. 均可以利用後續參數傳參。 區別:
  4. call 接受函數傳參方式爲:fn.call(this, 1, 2, 3)
  5. apply 接受函數傳參方式爲:fn.apply(this,[1, 2, 3])
  6. bind 的返回值爲一個新的函數,須要再次調用: fn.bind(this)(1, 2, 3)

手動實現 call 方法:

Function.prototype.myCall = function(context = window, ...rest) {
    context.fn = this; //此處this是指調用myCall的function
    let result = context.fn(...rest);
    //將this指向銷燬
    delete context.fn;
    return result;
};
複製代碼

手動實現 apply 方法:

Function.prototype.myCall = function(context = window, params = []) {
    context.fn = this; //此處this是指調用myCall的function
    let result
    if (params.length) {
        result = context.fn(...params)
    }else {
        result = context.fn()
    }
    //將this指向銷燬
    delete context.fn;
    return result;
};
複製代碼

手動實現 bind 方法:

Function.prototype.myBind = function(oThis, ...rest) {
    let _this = this;
    let F = function() {}
    // 根據 bind 規定,若是使用 new 運算符構造 bind 的返回函數時,第一個參數綁定的 this 失效
    let resFn = function(...parmas) {
        return _this.apply(this instanceof resFn ? this : oThis, [
            ...rest,
            ...parmas
        ]);
    };
    // 繼承原型
    if (this.prototype) {
        F.prototype = this.prototype;
        resFn.prototype = new F;
    }
    return resFn;
};
複製代碼

怎麼理解原型和原型鏈

每一個函數都有 prototype

每個對象都有 __proto__

實例的 __proto__ 指向構造函數的 prototype

js 引擎會沿着 __proto__ -> ptototype 的順序一直往上方查找,找到 Object.prototype 爲止,Object 爲原生底層對象,到這裏就中止了查找,若是沒有找到,就會報錯或者返回 undefined

js 繼承的幾種方式

經常使用繼承:組合繼承,寄生組合繼承

組合繼承: 利用 call 繼承父類上的屬性,用子類的原型等於父類實例去繼承父類的方法

缺點:調用兩次父類,形成性能浪費

function Parent(name) {
    this.name = name;
}

Parent.prototype.say = function() {
    console.log(this.name);
};
function Child(name) {
    Parent.call(this, name)
}
Child.prototype = new Parent;
let c = new Child("YaoChangTuiQueDuan");
c.say()
複製代碼

寄生組合繼承:利用 call 繼承父類上的屬性,用一個乾淨的函數的原型去等於父類原型,再用子類的原型等於乾淨函數的實例

function Parent(name) {
    this.name = name;
}

Parent.prototype.say = function() {
    console.log(this.name);
};

function ExtendMiddle() {}

function Child(name) {
    Parent.call(this, name)
}

ExtendMiddle.prototype = Parent.prototype;
Child.prototype = new ExtendMiddle

let c = new Child("YaoChangTuiQueDuan");
c.say()
複製代碼

eventloop

請參考文章 Eventloop 不可怕,可怕的是趕上 Promise

new 的過程當中作了什麼? 手動實現一個 new

  1. 新生成了一個對象
  2. 連接到原型
  3. 綁定 this
  4. 返回新對象
function create(...rest) {
    // 建立一個空的對象
    let obj = new Object()
    // 得到構造函數
    let Con = rest.shift()
    // 連接到原型
    obj.__proto__ = Con.prototype
    // 綁定 this,執行構造函數
    let result = Con.apply(obj, arguments)
    // 確保 new 出來的是個對象
    return typeof result === 'object' ? result : obj
}
複製代碼

說一些經常使用的 es6

  • let/const
  • 模板字符串
  • 解構賦值
  • 塊級做用域
  • Promise
  • Class
  • 函數默認參數
  • 模塊化
  • 箭頭函數
  • Set
  • Map
  • Array.map
  • 等等

Promise 的基本使用和原理,以及簡易版的 Promise 實現

Promise 的幾個特性:

  1. Promise 捕獲錯誤與 try/catch 相同
  2. Promise 擁有狀態變化,而且狀態變化不可逆
  3. Promise 屬於微任務
  4. Promise 中的.then 回調是異步的
  5. Promsie 中.then 每次返回的都是一個新的 Promise
  6. Promise 會存儲返回值

Promise 的簡單實現:

class MyPromise {
    constructor(fn) {
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
        this.state = "PADDING";
        this.value = "";
        fn(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(value) {
        if (this.state === "PADDING") {
            this.state = "RESOLVED";
            this.value = value;
            this.resolvedCallbacks.forEach(cb => cb());
        }
    }

    reject(value) {
        if (this.state === "PADDING") {
            this.state = "REJECTED";
            this.value = value;
            this.rejectedCallbacks.forEach(cb => cb());
        }
    }

    then(resolve = function() {}, reject = function() {}) {
        if (this.state === "PADDING") {
            this.resolvedCallbacks.push(resolve);
            this.rejectedCallbacks.push(reject);
        }
        if (this.state === "RESOLVED") {
            resolve(this.value);
        }
        if (this.state === "REJECTED") {
            reject(this.value);
        }
    }
}
複製代碼

有沒有使用過 async/await 說一下和 Promise 的區別、聯繫

  1. 使用了Promise,並無和Promise衝突
  2. 在寫法上徹底是同步,沒有任何回調函數
  3. await 必須在 async 函數中使用
  4. 若是 await 後面的方法是一個 Promise,會返回 Promise 的處理結果,等待 Promise 處理成功後在去執行下面的代碼,若是等待的不是 Promise 對象,則返回該值自己,而後去同步的執行下方的代碼
  5. 寫在 await 後面的代碼,至關因而執行 Promise.resolve()
  6. 使用 try/catch 來捕獲錯誤

如何實如今Object上使用for...of迭代器

能夠在 Object 的 prototype 上掛載一個 Symbol.iterator 方法,該方法返回一個對象,對象中包含 next 方法, next 方法也會返回一個對象,對象中包含 valuedone。 value 表示 每次迭代完成的值,done 表示是否迭代完成,爲 false 時會繼續迭代,爲 true 表示迭代完成,中止迭代

Object.prototype[Symbol.iterator] = function () {
  let values = Object.values(this);
  let keys = Object.keys(this);
  let len = 0;
  return {
    next() {
      return {
        value: {
          value: values[len],
          key: keys[len++]
        },
        done: len > values.length
      }
    }
  }
}
複製代碼

CommonJS 和 ES6 中的模塊化的二者區別

  1. 前者支持動態導入,也就是 require(${path}/xx.js),後者目前不支持,可是已有提案
  2. 前者是同步導入,由於用於服務端,文件都在本地,同步導入即便卡住主線程影響也不大。然後者是異步導入,由於用於瀏覽器,須要下載文件,若是也採用同步導入會對渲染有很大影響
  3. 前者在導出時都是值拷貝,就算導出的值變了,導入的值也不會改變,因此若是想更新值,必須從新導入一次。可是後者採用實時綁定的方式,導入導出的值都指向同一個內存地址,因此導入值會跟隨導出值變化
  4. 後者會編譯成 require/exports 來執行的

module.exports 和 exports 有什麼關係

爲了方便,Node爲每一個模塊提供一個 exports 變量,指向 module.exports,至關於 exports 是 module.exports 地址的引用

會產生的問題:若是將 exports 新賦值了一個對象,如: exports = {},這個時候,就會打斷與 module.exports 的聯繫,會致使導出不成功

什麼是防抖和節流?有什麼區別?

  • 控制頻繁調用事件的調用頻率
  • 防抖和節流的做用都是防止函數屢次調用。
  • 區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於wait,防抖的狀況下只會調用一次,而節流的狀況會每隔必定時間(參數wait)調用函數。

深拷貝用來解決什麼問題?如何實現

如何區分深拷貝與淺拷貝,簡單點來講,就是假設B複製了A,當修改A時,看B是否會發生變化,若是B也跟着變了,說明這是淺拷貝,若是B沒變,那就是深拷貝。

因爲 js 中分爲兩種變量類型,基本數據類型和引用數據類型,基本數據類型包括,number,string,boolean,null,undefined,symbol。

引用數據類型(Object類)有常規名值對的無序對象 {a:1},數組 [1,2,3],以及函數等。

而這兩類數據存儲分別是這樣的:

  • 基本類型--名值存儲在棧內存中,例如let a=1;

    當你 b=a 複製時,棧內存會新開闢一個內存,例如這樣:

    因此當你此時修改 a=2,對 b 並不會形成影響,由於此時的 b 已更換內存地址,不受 a 的影響了

  • 引用數據類型--名存在棧內存中,值存在於堆內存中,可是棧內存會提供一個引用的地址指向堆內存中的值

    當 b=a 進行拷貝時,其實複製的是 a 的引用地址,而並不是堆裏面的值。

    而當咱們 a[0]=1 時進行數組修改時,因爲 a 與 b 指向的是同一個地址,因此天然 b 也受了影響,這就是所謂的淺拷貝了。

    那,若是咱們須要實現深拷貝的效果,就須要在堆內存中也開闢一個新的的內存空間來存放 b 的值,如圖:

深拷貝的實現方式:

  1. json.parse & json.stringify

    先將一個對象轉爲json對象。而後再解析這個json對象,但此方法有幾個缺點:

    • 若是 obj 裏面有時間對象,序列化後,時間將只是字符串的形式,而不是時間對象
    • 若是 obj 裏有RegExp、Error對象,則序列化的結果將只獲得空對象
    • 若是 obj 裏有函數,undefined,則序列化的結果會把函數或 undefined 丟失
    • 若是 ob j裏有 NaN、Infinity 和 -Infinity,則序列化的結果會變成 null
    • 只能序列化對象的可枚舉的自有屬性,若是 obj 中的對象是有構造函數生成的,則序列化後,會丟棄對象的 constructor
    • 若是對象中存在循環引用的狀況也沒法正確實現深拷貝
  2. 遞歸方式實現深拷貝(ps:這裏只是簡易版,完整版建議看 lodash 的 cloneDeep 方法源碼)

源碼地址:github.com/lodash/loda…

function deepClone(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    let toString = Object.prototype.toString;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) {
            //判斷ojb子元素是否爲對象,若是是,遞歸複製
            if ( toString.call(obj[key]) === "[object Object]" || toString.call(obj[key]) === "[object Array]" ) {
                objClone[key] = deepClone(obj[key]);
            } else {
                //若是不是,簡單複製
                objClone[key] = obj[key];
            }
        }
    }
    return objClone;
}
複製代碼

什麼是堆,什麼是棧

棧(stack):棧會自動分配內存空間,會自動釋放,存放基本類型,簡單的數據段,佔據固定大小的空間。 堆(heap):動態分配的內存,大小不定也不會自動釋放,存放引用類型

爲何會有棧內存和堆內存之分?

一般與垃圾回收機制有關。爲了使程序運行時佔用的內存最小。

當一個方法執行時,每一個方法都會創建本身的內存棧,在這個方法內定義的變量將會逐個放入這塊棧內存裏,隨着方法的執行結束,這個方法的內存棧也將天然銷燬了。所以,全部在方法中定義的變量都是放在棧內存中的;

當咱們在程序中建立一個對象時,這個對象將被保存到運行時數據區中,以便反覆利用(由於對象的建立成本一般較大),這個運行時數據區就是堆內存。堆內存中的對象不會隨方法的結束而銷燬,即便方法結束後,這個對象還可能被另外一個引用變量所引用(方法的參數傳遞時很常見),則這個對象依然不會被銷燬,只有當一個對象沒有任何引用變量引用它時,系統的垃圾回收機制纔會在覈實的時候回收它。

插入幾萬個 DOM,如何實現頁面不卡頓?

方案一:

分頁,懶加載,把數據分頁,而後每次接受必定的數據,避免一次性接收太多
複製代碼

方案二:

setInterval,setTimeout,requestAnimationFrame 分批渲染,讓數據在不一樣幀內去作渲染
複製代碼

方案三:

使用 virtual-scroll,虛擬滾動。

這種方式是指根據容器元素的高度以及列表項元素的高度來顯示長列表數據中的某一個部分,而不是去完整地渲染長列表,以提升無限滾動的性能

virtual-scroll原理:

在用戶滾動時,改變列表在可視區域的渲染部分

  • 計算當前可見區域起始數據的 startIndex
  • 計算當前可見區域結束數據的 endIndex
  • 計算當前可見區域的數據,並渲染到頁面中
  • 計算 startIndex 對應的數據在整個列表中的偏移位置 startOffset,並設置到列表上
  • 計算 endIndex 對應的數據相對於可滾動區域最底部的偏移位置 endOffset,並設置到列表上

以下圖所示:

startOffset 和 endOffset 會撐開容器元素的內容高度,讓其可持續的滾動;此外,還能保持滾動條處於一個正確的位置。

你知道的優化手段有哪些?

  1. 減小HTTP請求:使用 iconfont 字體圖標,使用精靈圖,合併js,合併 css
  2. 減小DNS查詢
  3. 將css放在頁面最上面,將js放在頁面最下面
  4. 壓縮js和css:減小文件體積,去除沒必要要的空白符、格式符、註釋,移除重複,無用代碼,使用 gzip
  5. 使用瀏覽器緩存
  6. 避免 css 選擇器層級嵌套太深
  7. 高頻觸發事件使用防抖和節流
  8. 使 ajax 可緩存
  9. 使用 cdn
  10. 持續補充...

瀏覽器

在瀏覽器地址欄鍵入URL,按下回車以後會經歷如下流程:

  1. 解析 url 到 dns 服務器
  2. dns 服務器返回 ip 地址到瀏覽器
  3. 跟隨協議將 ip 發送到網絡中
  4. 通過局域網到達服務器
  5. 進入服務器的 MVC 架構 Controller
  6. 通過邏輯處理,請求分發,調用 Model 層
  7. Model 和數據進行交互,讀取數據庫,將最終結果經過 view 層返回到網絡回到瀏覽器
  8. 瀏覽器根據請求回來的 html 和關聯的 css, js 進行渲染
  9. 在渲染的過程當中,瀏覽器根據 html 生成 dom 樹,根據 css 生成 css 樹
  10. 將 dom 樹和 css 樹進行整合,最終知道 dom 節點的樣式,在頁面上進行樣式渲染
  11. 瀏覽器去執行 js 腳本
  12. 最終展示頁面

瀏覽器渲染的過程,大體能夠分爲五步:

  1. html代碼轉化爲dom
  2. css代碼轉化爲cssom
  3. 結合dom和cssom,生成一顆渲染樹
  4. 生成佈局,即將全部的渲染樹的節點進行平面合成
  5. 將佈局繪製在屏幕上

瀏覽器緩存

當瀏覽器再次訪問一個已經訪問過的資源時,它會這樣作:

  1. 看看是否命中強緩存,若是命中,就直接使用緩存了。
  2. 若是沒有命中強緩存,就發請求到服務器檢查是否命中協商緩存。
  3. 若是命中協商緩存,服務器會返回 304 告訴瀏覽器使用本地緩存。
  4. 不然,請求網絡返回最新的資源。

瀏覽器緩存的位置:

  1. Service Worker: 它可讓咱們自由控制緩存哪些文件、如何匹配緩存、如何讀取緩存,而且緩存是持續性的。
  2. Memory Cache: 內存緩存,讀取內存中的數據確定比磁盤快。可是內存緩存雖然讀取高效,但是緩存持續性很短,會隨着進程的釋放而釋放。 一旦咱們關閉 Tab 頁面,內存中的緩存也就被釋放了。
  3. Disk Cache: Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,可是什麼都能存儲到磁盤中,比之 Memory Cache 勝在容量和存儲時效性上。

緩存的實現: 強緩存和協商緩存都是根據 HTTP Header 來實現的

什麼是重繪和迴流

迴流:佈局或者幾何屬性須要改變就稱爲迴流。 重繪:當節點須要更改外觀而不會影響佈局的,好比改變 color 就叫稱爲重繪

區別:

迴流必將引發重繪,而重繪不必定會引發迴流。好比:只有顏色改變的時候就只會發生重繪而不會引發迴流 當頁面佈局和幾何屬性改變時就須要迴流

好比:添加或者刪除可見的DOM元素,元素位置改變,元素尺寸改變——邊距、填充、邊框、寬度和高度,內容改變

vue&react

說一下 MVVM

MVVM是雙向數據綁定

  • M: Model 數據層
  • V: View 視圖層
  • VM: ViewModel 視圖層和數據層中間的橋,視圖層和數據層通訊的橋樑

view 層經過事件去綁定 Model 層, Model 層經過數據去綁定 View 層

什麼是 Virtual DOM 爲何使用 Virtual DOM

在以前,渲染數據時,會直接替換掉 DOM 裏的全部元素,換成新的數據,爲了渲染無用 DOM 所形成的性能浪費,因此出現了 Virtual DOM, Virtual DOM 是虛擬 DOM,是用 js 對象表示的樹結構,把 DOM 中的屬性映射到 js 的對象屬性中,它的核心定義無非就幾個關鍵屬性,標籤名、數據、子節點、鍵值等。當數據改變時,從新渲染這個 js 的對象結構,找出真正須要更新的 DOM 節點,再去渲染真實 DOM。Virtual DOM 本質就是在 JS 和 DOM 之間作了一個緩存

爲何操做真實 dom 有性能問題

由於 DOM 是屬於渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當咱們經過 JS 操做 DOM 的時候,其實這個操做涉及到了兩個線程之間的通訊,那麼勢必會帶來一些性能上的損耗。操做 DOM 次數一多,也就等同於一直在進行線程之間的通訊,而且操做 DOM 可能還會帶來重繪迴流的狀況,因此也就致使了性能上的問題。

Vue 的響應式原理

Vue 內部使用了 Object.defineProperty() 來實現數據響應式,經過這個函數能夠監聽到 setget 的事件。

  1. 首先利用 Object.defineProperty() 給 data 中的屬性去設置 set, get 事件
  2. 遞歸的去把 data 中的每個屬性註冊爲被觀察者
  3. 解析模板時,在屬性的 get 事件中去收集觀察者依賴
  4. 當屬性的值發生改變時,在 set 事件中去通知每個觀察者,作到所有更新

Vue 的模板如何被渲染成 HTML? 以及渲染過程

  1. vue 模板的本質是字符串,利用各類正則,把模板中的屬性去變成 js 中的變量,vif,vshow,vfor 等指令變成 js 中的邏輯
  2. 模板最終會被轉換爲 render 函數
  3. render 函數執行返回 vNode
  4. 使用 vNode 的 path 方法把 vNode 渲染成真實 DOM

Vue 的整個實現流程

  1. 先把模板解析成 render 函數,把模板中的屬性去變成 js 中的變量,vif,vshow,vfor 等指令變成 js 中的邏輯
  2. 執行 render 函數,在初次渲染執行 render 函數的過程當中 綁定屬性監聽,收集依賴,最終獲得 vNode,利用 vNode 的 Path 方法,把 vNode 渲染成真實的 DOM
  3. 在屬性更新後,從新執行 render 函數,不過這個時候就不須要綁定屬性和收集依賴了,最終生成新的 vNode
  4. 把新的 vNode 和 舊的 vNode 去作對比,找出真正須要更新的 DOM,渲染

什麼是 diff 算法,或者是 diff 算法用來作什麼

  • diff 是linux中的基礎命令,能夠用來作文件,內容之間的對比
  • vNode 中使用 diff 算法是爲了找出須要更新的節點,避免形成沒必要要的更新

Vuex是什麼

vuex 就像一個全局的倉庫,公共的狀態或者複雜組件交互的狀態咱們會抽離出來放進裏面。

vuex的核心主要包括如下幾個部分:

  • state:state裏面就是存放的咱們須要使用的狀態,他是單向數據流,在 vue 中不容許直接對他進行修改,而是使用 mutations 去進行修改
  • mutations: mutations 就是存放如何更改狀態的一些方法
  • actions: actions 是來作異步修改的,他能夠等待異步結束後,再去使用 commit mutations 去修改狀態
  • getters: 至關因而 state 的計算屬性

使用:

  • 獲取狀態在組件內部 computed 中使用 this.$store.state 獲得想要的狀態
  • 修改的話可在組件中使用 this.$store.commit 方法去修改狀態
  • 若是在一個組件中,方法,狀態使用太多。 可使用 mapState,mapMutations 輔助函數

Vue 中組件之間的通訊

父子組件:父組件經過 Props 傳遞子組件,子組件經過 $emit 通知父組件 兄弟組件:可使用 vuex 全局共享狀態,或 eventBus

如何解決頁面刷新後 Vuex 數據丟失的問題

能夠經過插件 vuex-persistedstate 來解決 插件原理:利用 HTML5 的本地存儲 + Vuex.Store 的方法,去同步本地和 store 中的數據,作到同步更新

Vue中插槽的用法

待補充...

Vue 的生命鉤子函數,調用每一個鉤子的時候作了什麼

  • beforeCreate: 組件實例剛被建立,在此時,組件內部的屬性還不能使用
  • created: 組件實例建立完成,屬性已經綁定,但 DOM 尚未建立完成,通常用來請求接口,不可操做 DOM
  • beforeMount: 模板掛載前
  • mounted: 模板掛載後,在這個鉤子中,模板已經掛載完畢,能夠去操做 DOM 了
  • beforeUpdate:組件更新前
  • update: 組件更新後
  • activated:在使用 keep-alive 時纔有的鉤子函數,組件被激活時調用
  • deactivated: 在使用 keep-alive 時纔有的鉤子函數,組件被移除時調用
  • beforeDestory: 在組件被移除前調用
  • destoryed: 組件被移除後調用,通常用來清除定時器
  • 若是有子組件的話,那麼調用順序爲:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

前端路由的兩種實現原理

兩種路由模式分爲 hash 模式 和 history 模式

hash 模式:

hash 模式背後的原理是 onhashchange 事件,能夠在window對象上監聽這個事件,在 hash 變化時,瀏覽器會記錄歷史,而且觸發事件回調,在 hash 模式中,地址欄中會帶一個 #

history 模式:

前面的 hashchange,你只能改變 # 後面的 url 片斷,而 history api 則給了前端徹底的自由

history api能夠分爲兩大部分,切換和修改,參考 MDN

使用 history 須要服務端的支持,在hash模式下,前端路由修改的是#中的信息,而瀏覽器請求時是不帶着的,因此沒有問題.可是在history下,你能夠自由的修改path,當刷新時,若是服務器中沒有相應的響應或者資源,會刷出一個404來

什麼是ssr, ssr和以前的後臺模板有什麼區別

待補充...

Vue 中 provide 是作什麼的?

待補充...

mixins 通常用來作什麼?

待補充...

nextTick 是什麼?

待補充...

算法

排序算法(快排,冒泡)

待補充...

介紹下深度優先遍歷和廣度優先遍歷,如何實現?

待補充...

筆試算法題

已知以下數組:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
編寫一個程序將數組扁平化去併除其中重複部分數據,最終獲得一個升序且不重複的數組

方法一:
function handleArr(arr) {
  let flatten = (arr) => arr.push ? arr.reduce((pre, current) => [...pre, ...flatten(current)], []) : [arr];
  return [...new Set(flatten(arr))].sort((a, b) => a - b);
}

方法二:
[...new Set(arr.toString().split(",").map(Number))].sort((a,b)=> a - b)

方法三:
[...new Set(arr.flat(Infinity))].sort((a,b)=>{ return a - b})
複製代碼

筆試執行題

let test = (function (a) {
  this.a = a;
  return function (b) {
    console.log(this.a + b);
  }
})((function(a){
  return a;
})(1,2))

test(4)
複製代碼

答案:5

解析:咱們把(function (a) { this.a = a; return function (b) { console.log(this.a + b); } })()自執行函數體叫作 z1

(function(a){ return a; })(1,2) 叫作 z2

function (b) { console.log(this.a + b); }叫作 n1

test 函數爲匿名自執行函數z1的返回值,實際上 test 函數爲 n1,函數 z1 接收 a 做爲參數,這裏的 a 實際上爲自執行函數 z2 返回值爲 1, 那麼 n1 函數中的參數 a 就是 1,在函數 z1this.a = a 這裏的 this 指向的是 window 因此在 window 上掛載了一個 a 的屬性,值爲 1

在 text(4) 執行時,傳入了 b 爲 4, 函數 n1 中的 this 指向的是 window,window 中有以前掛載的屬性 a 爲 1 因此函數執行結果爲 5

以上面試題爲我的理解,若有不對的地方,歡迎各位大佬在評論區指正,共勉!

相關文章
相關標籤/搜索