天天看點

2021年7月 蝦皮、OPPO、富途等十幾家公司面經總結JS相關架構 Vue | Reactwebpack 及工程化Http 及浏覽器相關CSS 及 HTML常見場景及問題代碼程式設計相關

最近朋友内推面試了幾家公司(

貨拉拉

蝦皮

有贊

樂信

Qtrade蘋果樹

富途

塗鴉

OPPO

微保

微衆

元戎啟行

),也收獲了滿意的offer。整理了下面試遇到的問題,作為記錄。

JS相關

JS原型及原型鍊

function Person() {}
Person.prototype.name = 'Zaxlct';
Person.prototype.sayName = function() {
  alert(this.name);
}
var person1 = new Person();
//JS 在建立對象的時候,都有一個__proto__ 的内置屬性,用于指向建立它的構造函數的原型對象。
//每個對象都有 __proto__ 屬性,但隻有函數對象才有 prototype 屬性
// 對象 person1 有一個 __proto__屬性,建立它的構造函數是 Person,構造函數的原型對象是 Person.prototype
console.log(person1.__proto__ == Person.prototype) //true
//所有函數對象的__proto__都指向Function.prototype
String.__proto__ === Function.prototype  // true
String.constructor == Function //true
           
2021年7月 蝦皮、OPPO、富途等十幾家公司面經總結JS相關架構 Vue | Reactwebpack 及工程化Http 及浏覽器相關CSS 及 HTML常見場景及問題代碼程式設計相關

prototype.jpg

JS繼承的幾種方式

詳解

  1. 原型繼承
function Parent () {
  this.name = 'Parent'
  this.sex = 'boy'
}
function Child () {
  this.name = 'child'
}
// 将子類的原型對象指向父類的執行個體
Child.prototype = new Parent()
//優:繼承了父類的模闆,又繼承了父類的原型對象
//缺:1.無法實作多繼承(因為已經指定了原型對象了)
//   2.建立子類時,無法向父類構造函數傳參數
           
  1. 構造函數繼承

在子類構造函數内部使用

call或apply

來調用父類構造函數,複制父類的執行個體屬性給子類。

function Parent (name) {
  this.name = name
}
function Child () {
  //用.call 來改變 Parent 構造函數内的指向
  Parent.call(this, 'child')
}
//優:解決了原型鍊繼承中子類執行個體共享父類引用對象的問題,實作多繼承,建立子類執行個體時,可以向父類傳遞參數
//缺:構造繼承隻能繼承父類的執行個體屬性和方法,不能繼承父類原型的屬性和方法
           
  1. 組合繼承

    組合繼承就是将原型鍊繼承與構造函數繼承組合在一起。

  • 使用原型鍊繼承來保證子類能繼承到父類原型中的屬性和方法
  • 使用構造繼承來保證子類能繼承到父類的執行個體屬性和方法
  • 寄生組合繼承
  • class繼承
  • class

     中繼承主要是依靠兩個東西:
    • extends

    • super

    class Parent {
      constructor (name) {
        this.name = name
      }
      getName () {
        console.log(this.name)
      }
    }
    class Child extends Parent {
      constructor (name) {
        super(name)
        this.sex = 'boy'
      }
    }
               

    Event Loop 事件循環

    同步與異步、宏任務和微任務分别是函數兩個不同次元的描述。
    同步任務指的是,在主線程上排隊執行的任務,隻有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入任務隊列(

    task queue

    )的任務,隻有等主線程任務執行完畢,任務隊列開始通知主線程,請求執行任務,該任務才會進入主線程執行。

    當某個宏任務執行完後,會檢視是否有微任務隊列。如果有,先執行微任務隊列中的所有任務;如果沒有,在執行環境棧中會讀取宏任務隊列中排在最前的任務;執行宏任務的過程中,遇到微任務,依次加入微任務隊列。棧空後,再次讀取微任務隊列裡的任務,依次類推。

    同步(Promise)>異步(微任務(process.nextTick ,Promises.then, Promise.catch ,resove,reject,MutationObserver)>宏任務(setTimeout,setInterval,setImmediate))
    await阻塞 後面的代碼執行,是以跳出async函數執行下一個微任務

    Promise 與 Async/Await  差別

    async/await是基于Promise實作的,看起來更像同步代碼,
    • 不需要寫匿名函數處理Promise的resolve值
    • 錯誤處理: Async/Await 讓 try/catch 可以同時處理同步和異步錯誤。
    • 條件語句也跟錯誤處理一樣簡潔一點
    • 中間值處理(第一個方法傳回值,用作第二個方法參數) 解決嵌套問題
    • 調試友善
    const makeRequest = () => {
        try {
            getJSON().then(result => {
                // JSON.parse可能會出錯
                const data = JSON.parse(result)
                console.log(data)
            })
            // 取消注釋,處理異步代碼的錯誤
            // .catch((err) => {
            //   console.log(err)
            // })
        } catch (err) {
            console.log(err)
        }
    }
               
    使用

    aync/await

    的話,catch能處理

    JSON.parse

    錯誤:
    const makeRequest = async () => {
        try {
            // this parse may fail
            const data = JSON.parse(await getJSON())
            console.log(data)
        } catch (err) {
            console.log(err)
        }
    }
               

    promise怎麼實作鍊式調用跟傳回不同的狀态

    實作鍊式調用:使用

    .then()

    或者

    .catch()

    方法之後會傳回一個

    promise

    對象,可以繼續用

    .then()

    方法調用,再次調用所擷取的參數是上個

    then

    方法

    return

    的内容
    1. promise的三種狀态是 

      fulfilled

      (已成功)/

      pengding

      (進行中)/

      rejected

      (已拒絕)
    2. 狀态隻能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但發生改變便不可二次修改;
    3. Promise 中使用 

      resolve

       和 

      reject

       兩個函數來更改狀态;
    4. then 方法内部做的事情就是狀态判斷:
    • 如果狀态是成功,調用成功回調函數
    • 如果狀态是失敗,調用失敗回調函數

    函數柯裡化

    柯裡化(Currying)

     是把接收多個參數的原函數變換成接受一個單一參數(原來函數的第一個參數的函數)并傳回一個新的函數,新的函數能夠接受餘下的參數,并傳回和原函數相同的結果。
    1. 參數對複用
    2. 提高實用性
    3. 延遲執行 隻傳遞給函數一部分參數來調用它,讓它傳回一個函數去處理剩下的參數。柯裡化的函數可以延遲接收參數,就是比如一個函數需要接收的參數是兩個,執行的時候必須接收兩個參數,否則沒法執行。但是柯裡化後的函數,可以先接收一個參數
    // 普通的add函數
    function add(x, y) {
        return x + y
    }
    
    // Currying後
    function curryingAdd(x) {
        return function (y) {
            return x + y
        }
    }
    
    add(1, 2)           // 3
    curryingAdd(1)(2)   // 3
               

    JS對象深克隆

    遞歸周遊對象,解決循環引用問題
    解決循環引用問題,我們需要一個存儲容器存放目前對象和拷貝對象的對應關系(适合用key-value的資料結構進行存儲,也就是map),當進行拷貝目前對象的時候,我們先查找存儲容器是否已經拷貝過目前對象,如果已經拷貝過,那麼直接把傳回,沒有的話則是繼續拷貝。
    function deepClone(target) {
        const map = new Map()
        function clone (target) {
            if (isObject(target)) {
                let cloneTarget = isArray(target) ? [] : {};
                if (map.get(target)) {
                    return map.get(target)
                }
                map.set(target,cloneTarget)
                for (const key in target) {
                    cloneTarget[key] = clone(target[key]);
                }
                return cloneTarget;
            } else {
                return target;
            }
        }
        return clone(target)
    };
               

    JS子產品化

    nodeJS

    裡面的子產品是基于

    commonJS

    規範實作的,原理是檔案的讀寫,導出檔案要使用

    exports

    module.exports

    ,引入檔案用

    require

    。每個檔案就是一個子產品;每個檔案裡面的代碼會用預設寫在一個閉包函數裡面

    AMD

    規範則是非同步加載子產品,允許指定回調函數,

    AMD

     是 

    RequireJS

     在推廣過程中對子產品定義的規範化産出。

    AMD

    推崇依賴前置, 

    CMD

    推崇依賴就近。對于依賴的子產品AMD是提前執行,CMD是延遲執行。

    ES6

    中,我們可以使用 

    import

     關鍵字引入子產品,通過 

    exprot

     關鍵字導出子產品,但是由于ES6目前無法在浏覽器中執行,是以,我們隻能通過

    babel

    将不被支援的

    import

    編譯為目前受到廣泛支援的 

    require

    CommonJs 和 ES6 子產品化的差別:

    1. CommonJS 子產品輸出的是一個值的拷貝,ES6 子產品輸出的是值的引用。
    2. CommonJS 子產品是運作時加載,ES6 子產品是編譯時輸出接口。
    前端子產品化:CommonJS,AMD,CMD,ES6

    import 和 require 導入的差別

    import 的ES6 标準子產品;require 是 AMD規範引入方式;

    import是編譯時調用,是以必須放在檔案開頭;是解構過程 require是運作時調用,是以require理論上可以運用在代碼的任何地方;是指派過程。其實require的結果就是對象、數字、字元串、函數等,再把require的結果指派給某個變量

    異步加載JS方式

    1. 匿名函數自調動态建立script标簽加載js
    (function(){
        var scriptEle = document.createElement("script");
        scriptEle.type = "text/javasctipt";
        scriptEle.async = true;
        scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";
        var x = document.getElementsByTagName("head")[0];
        x.insertBefore(scriptEle, x.firstChild);  
     })();
               
    1. async屬性
    // async屬性規定一旦加載腳本可用,則會異步執行
    <script type="text/javascript" src="xxx.js" async="async"></script>
               
    1. defer屬性
    // defer屬性規定是否對腳本執行進行延遲,直到頁面加載為止
    <script type="text/javascript" src="xxx.js" defer="defer"></script>
               

    Set、Map、WeakSet、WeakMap

    Set對象可以存儲任何類型的資料。值是唯一的,沒有重複的值。

    Map對象儲存鍵值對,任意值都可以成為它的鍵或值。

    WeakSet 結構與 Set 類似,也是不重複的值的集合 . WeakMap 對象是一組鍵值對的集合

    不同:

    WeakSet

     的成員隻能是對象,而不能是其他類型的值。WeakSet 不可周遊。

    WeakMap

    隻接受對象作為鍵名(

    null

    除外),不接受其他類型的值作為鍵名。

    WeakMap

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

    call、apply

    call( this,a,b,c )

     在第一個參數之後的,後續所有參數就是傳入該函數的值。

    apply( this,[a,b,c] )

     隻有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。

    共同之處:都可以用來代替另一個對象調用一個方法,将一個函數的對象上下文從初始的上下文改變為由thisObj指定的新對象。

    所謂防抖,就是指觸發事件後在 n 秒内函數隻能執行一次所謂節流,就是指連續觸發事件但是在 n 秒中隻執行一次函數。

    addEventListener的第三個參數幹嘛的,為true時捕獲,false時冒泡

    Object.prototype.toString.call()

     判斷對象類型
    // new Set是實作數組去重,
    // Array.from()把去重之後轉換成數組
    let arr2 = Array.from(new Set(arr));
               

    詞法作用域與作用域鍊

    作用域規定了如何查找變量,也就是确定目前執行代碼對變量的通路權限。
    ES5隻有全局作用域沒和函數作用域,ES6增加塊級作用域

    暫時性死區:在代碼塊内,使用 let 和 const 指令聲明變量之前,該變量都是不可用的,文法上被稱為暫時性死區。

    JavaScript 采用詞法作用域(lexical scoping),也就是靜态作用域。

    函數的作用域在函數定義的時候就決定了。

    當查找變量的時候,會先從目前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的連結清單就叫做作用域鍊。

    new關鍵字做了4件事:

    function _new(constructor, ...arg) {
    // 建立一個空對象
      var obj = {};
      // 空對象的`__proto__`指向構造函數的`prototype`, 為這個新對象添加屬性 
      obj.__proto__ = constructor.prototype; 
      // 構造函數的作用域賦給新對象
      var res = constructor.apply(obj, arg); 
      // 傳回新對象.如果沒有顯式return語句,則傳回this
      return Object.prototype.toString.call(res) === '[object Object]' ? res : obj; 
    }
               

    不應該使用箭頭函數一些情況:

    • 當想要函數被提升時(箭頭函數是匿名的)
    • 要在函數中使用

      this/arguments

      時,由于箭頭函數本身不具有

      this/arguments

      ,是以它們取決于外部上下文
    • 使用命名函數(箭頭函數是匿名的)
    • 使用函數作為構造函數時(箭頭函數沒有構造函數)
    • 當想在對象字面是以将函數作為屬性添加并在其中使用對象時,因為咱們無法通路 

      this

       即對象本身。

    判斷數組的四種方法

    1. Array.isArray() 判斷
    2. instanceof 判斷: 檢驗構造函數的prototype屬性是否出現在對象的原型鍊中,傳回一個布爾值。

      let a = []; a instanceof Array; //true

    3. constructor判斷: 執行個體的構造函數屬性constructor指向構造函數

      let a = [1,3,4]; a.constructor === Array;//true

    4. Object.prototype.toString.call() 判斷

      let a = [1,2,3]; Object.prototype.toString.call(a) === '[object Array]';//true

    TS有什麼優勢

    1. 靜态輸入:靜态類型化是一種功能,可以在開發人員編寫腳本時檢測錯誤。
    2. 大型的開發項目:使用TypeScript工具來進行重構更變的容易、快捷。
    3. 更好的協作:類型安全是在編碼期間檢測錯誤,而不是在編譯項目時檢測錯誤。
    4. 更強的生産力:幹淨的 ECMAScript 6 代碼,自動完成和動态輸入等因素有助于提高開發人員的工作效率。

    interface 和 type的差別

    • interface 隻能定義對象類型。type聲明可以聲明任何類型。
    • interface 能夠聲明合并,兩個相同接口會合并。Type聲明合并會報錯
    • type可以類型推導

    架構 Vue | React

    Vue3.0 新特性

    雙向資料綁定 Proxy

    代理,可以了解為在對象之前設定一個“攔截”,當該對象被通路的時候,都必須經過這層攔截。意味着你可以在這層攔截中進行各種操作。比如你可以在這層攔截中對原對象進行處理,傳回你想傳回的資料結構。

    ES6 原生提供 Proxy 構造函數,MDN上的解釋為:Proxy 對象用于定義基本操作的自定義行為(如屬性查找,指派,枚舉,函數調用等)。

    const p = new Proxy(target, handler);
    //target: 所要攔截的目标對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
    //handler:一個對象,定義要攔截的行為
    
    const p = new Proxy({}, {
        get(target, propKey) {
            return '哈哈,你被我攔截了';
        }
    });
    
    console.log(p.name);
               
    新增的屬性,并不需要重新添加響應式處理,因為 Proxy 是對對象的操作,隻要你通路對象,就會走到 Proxy 的邏輯中。

    Vue3 Composition API

    Vue3.x

     推出了

    Composition API

    setup

     是元件内使用 Composition API的入口。

    setup

     執行時機是在 

    beforeCreate

     之前執行.

    reactive、ref 與 toRefs、isRef

    Vue3.x 可以使用reactive和ref來進行資料定義。
    // props 傳入元件對屬性
    // context 一個上下文對象,包含了一些有用的屬性:attrs,parent,refs
    setup(props, context) {
      // ref 定義資料
      const year = ref(0);
      // reactive 處理對象的雙向綁定
      const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
      setInterval(() => {
        year.value++;
        user.age++;
      }, 1000);
      return {
        year,
        // 使用toRefs,結構解構
        ...toRefs(user),
      };
    },
    // 提供isRef,用于檢查一個對象是否是ref對象
               

    watchEffect 監聽函數

    • watchEffect 不需要手動傳入依賴
    • watchEffect 會先執行一次用來自動收集依賴
    • watchEffect 無法擷取到變化前的值, 隻能擷取變化後的值

    computed可傳入get和set

    用于定義可更改的計算屬性
    const plusOne = computed({
     get: () => count.value + 1,
     set: val => { count.value = val - 1 }
    });
               

    使用TypeScript和JSX

    setup

    現在支援傳回一個渲染函數,這個函數傳回一個

    JSX

    JSX

    可以直接使用聲明在

    setup

    作用域的響應式狀态:
    export default {
     setup() {
     const count = ref(0);
     return () => (<div>{count.value}</div>);
     },
    };
               

    Vue 跟React 對比?

    相同點:
    1. 都有虛拟DOM(Virtual DOM 是一個映射真實DOM的JavaScript對象)
    2. 都提供了響應式群組件化的視圖元件。
    不同點:Vue 是

    MVVM

    架構,雙向資料綁定,當

    ViewModel

    Model

    進行更新時,通過資料綁定更新到

    View

    React是一個單向資料流的庫,狀态驅動視圖。

    State --> View --> New State --> New View

    ui = render (data)

    模闆渲染方式不同。React是通過JSX來渲染模闆,而Vue是通過擴充的HTML來進行模闆的渲染。

    元件形式不同,Vue檔案裡将HTML,JS,CSS組合在一起。react提供class元件和function組

    Vue封裝好了一些v-if,v-for,React什麼都是自己實作,自由度更高

    Vue 初始化過程,雙向資料綁定原理

    vue.js 則是采用資料劫持結合釋出者-訂閱者模式的方式,通過

    Object.defineProperty()

    來劫持各個屬性的

    setter

    getter

    dep.addSub

    來收集訂閱的依賴,

    watcher

    監聽資料的變化,在資料變動時釋出消息給訂閱者,觸發相應的監聽回調。
    監聽器

    Observer

    ,用來劫持并監聽所有屬性,如果有變動的,就通知訂閱者。訂閱者

    Watcher

    ,可以收到屬性的變化通知并執行相應的函數,進而調用對應update更新視圖。
    2021年7月 蝦皮、OPPO、富途等十幾家公司面經總結JS相關架構 Vue | Reactwebpack 及工程化Http 及浏覽器相關CSS 及 HTML常見場景及問題代碼程式設計相關

    v-model

     指令,它能輕松實作表單輸入和應用狀态之間的雙向綁定。

    computed: 支援緩存,隻有依賴資料結果發生改變,才會重新進行計算,不支援異步操作,如果一個屬性依賴其他屬性,多對一,一般用computed

    watch: 資料變,直接觸發相應操作,支援異步,監聽資料必須

    data

    中聲明過或者父元件傳遞過來的

    props中

    的資料,當資料變化時,觸發其他操作,函數有兩個參數

    vue-router實作原理

    端路由簡介以及vue-router實作原理原理核心就是 更新視圖但不重新請求頁面。路徑之間的切換,也就是元件的切換。vue-router實作單頁面路由跳轉模式:hash模式、history模式。根據設定mode參數

    hash模式

    :通過錨點值的改變,根據不同的值,渲染指定DOM位置的不同資料。每一次改變

    #

    後的部分,都會在浏覽器的通路曆史中增加一個記錄,使用”後退”按鈕,就可以回到上一個位置。

    history模式

    :利用 

    window.history.pushState

     API 來完成 URL 跳轉而無須重新加載頁面。

    vuex實作原理:

    Vue.use(vuex)

    會調用vuex的install方法

    beforeCreate

    鈎子前混入

    vuexInit

    方法,

    vuexInit

    方法實作了

    store

    注入

    vue元件執行個體

    ,并注冊了

    vuex

    store

    的引用屬性

    $store

    Vuex

    state

    狀态是響應式,是借助

    vue

    data

    是響應式,将

    state

    存入vue執行個體元件的data中;

    Vuex

    getters

    則是借助vue的計算屬性

    computed

    實作資料實時監聽。

    nextTick 的原理以及運作機制?

    nextTick的源碼分析

    vue進行DOM更新内部也是調用nextTick來做異步隊列控制。隻要觀察到資料變化,Vue 将開啟一個隊列,并緩沖在同一事件循環中發生的所有資料改變。如果同一個 watcher 被多次觸發,隻會被推入到隊列中一次。

    DOM至少會在目前事件循環裡面的所有資料變化完成之後,再統一更新視圖。而當我們自己調用nextTick的時候,它就在更新DOM的microtask(微任務隊列)後追加了我們自己的回調函數,

    進而確定我們的代碼在DOM更新後執行,同時也避免了setTimeout可能存在的多次執行問題。確定隊列中的微任務在一次事件循環前被執行完畢。

    Vue 實作一個高階元件

    高階元件就是一個函數,且該函數接受一個元件作為參數,并傳回一個新的元件。在不改變對象自身的前提下在程式運作期間動态的給對象添加一些額外的屬性或行為。
    // 高階元件(HOC)接收到的 props 應該透傳給被包裝元件即直接将原元件prop傳給包裝元件
    // 高階元件完全可以添加、删除、修改 props
    export default function Console(BaseComponent) {
      return {
        props: BaseComponent.props,
        mounted() {
          console.log("高階元件");
        },
        render(h) {
          console.log(this);
          // 将 this.$slots 格式化為數組,因為 h 函數第三個參數是子節點,是一個數組
          const slots = Object.keys(this.$slots)
            .reduce((arr, key) => arr.concat(this.$slots[key]), [])
            .map((vnode) => {
              vnode.context = this._self; // 綁定到高階元件上,vm:解決具名插槽被作為預設插槽進行渲染
              return vnode;
            });
     
          // 透傳props、透傳事件、透傳slots
          return h(
            BaseComponent,
            {
              on: this.$listeners,
              attrs: this.$attrs, // attrs 指的是那些沒有被聲明為 props 的屬性
              props: this.$props,
            },
            slots
          );
        },
      };
    }
               

    Vue.component()、Vue.use()、this.$xxx()

    Vue.component()方法注冊全局元件。
    • 第一個參數是自定義元素名稱,也就是将來在别的元件中使用這個元件的标簽名稱。
    • 第二個參數是将要注冊的Vue元件。
    import Vue from 'vue';
    // 引入loading元件 
    import Loading from './loading.vue';
    // 将loading注冊為全局元件,在别的元件中通過<loading>标簽使用Loading元件
    Vue.component('loading', Loading);
               
    Vue.use注冊插件,這接收一個參數。這個參數必須具有install方法。Vue.use函數内部會調用參數的install方法。
    • 如果插件沒有被注冊過,那麼注冊成功之後會給插件添加一個installed的屬性值為true。Vue.use方法内部會檢測插件的installed屬性,進而避免重複注冊插件。
    • 插件的install方法将接收兩個參數,第一個是參數是Vue,第二個參數是配置項options。
    • 在install方法内部可以添加全局方法或者屬性
    import Vue from 'vue';
    
    // 這個插件必須具有install方法
    const plugin = {
      install (Vue, options) {
        // 添加全局方法或者屬性
        Vue.myGlobMethod = function () {};
        // 添加全局指令
        Vue.directive();
        // 添加混入
        Vue.mixin();
        // 添加執行個體方法
        Vue.prototype.$xxx = function () {};
        // 注冊全局元件
        Vue.component()
      }
    }
    
    // Vue.use内部會調用plugin的install方法
    Vue.use(plugin);
               
    将Hello方法挂載到Vue的prototype上.
    import Vue from 'vue';
    import Hello from './hello.js';
    Vue.prototype.$hello = Hello;
               
    vue元件中就可以this.$hello('hello world')

    Vue父元件傳遞props資料,子元件修改參數

    • 父子元件傳值時,父元件傳遞的參數,數組和對象,子元件接受之後可以直接進行修改,并且父元件相應的值也會修改。控制台中發出警告。
    • 如果傳遞的值是字元串,直接修改會報錯。單向資料流,每次父級元件發生更新時,子元件中所有的 prop 都将會重新整理為最新的值。
    如果子元件想修改prop中資料:
    1. 定義一個局部變量,使用prop的值指派
    2. 定義一個計算屬性,處理prop的值并傳回

    Vue父子元件生命周期執行順序

    加載渲染過程
    父beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
    子元件更新過程
    父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
    父元件更新過程
    父beforeUpdate -> 父updated
    銷毀過程
    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

    Vue 自定義指令

    自定義指令提供了幾個鈎子函數:

    bind

    :指令第一次綁定到元素時調用

    inserted

    :被綁定元素插入父節點時調用

    update

    :所在元件的 VNode 更新時調用

    使用slot後可以在子元件内顯示插入的新标簽

    webpack 及工程化

    webpack的生命周期,及鈎子

    compiler(整個生命周期 [kəmˈpaɪlər]) 鈎子 https://webpack.docschina.org/api/compiler-hooks/compilation(編譯 [ˌkɑːmpɪˈleɪʃn]) 鈎子

    compiler

    對象包含了Webpack 環境所有的的配置資訊。這個對象在啟動 webpack 時被一次性建立,并配置好所有可操作的設定,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件将收到此 compiler 對象的引用。可以使用它來通路 webpack 的主環境。

    compilation

    對象包含了目前的子產品資源、編譯生成資源、變化的檔案等。當運作webpack 開發環境中間件時,每當檢測到一個檔案變化,就會建立一個新的 compilation,進而生成一組新的編譯資源。compilation 對象也提供了很多關鍵時機的回調,以供插件做自定義處理時選擇使用。

    compiler

    代表了整個

    webpack

    從啟動到關閉的

    生命周期

    ,而

    compilation

     隻是代表了一次新的

    編譯過程

    webpack 編譯過程

    Webpack 的編譯流程是一個串行的過程,從啟動到結束會依次執行以下流程:
    1. 初始化參數:從配置檔案和 Shell 語句中讀取與合并參數,得出最終的參數;
    2. 開始編譯:用上一步得到的參數初始化 

      Compiler

       對象,加載所有配置的插件,執行對象的 

      run

      方法開始執行編譯;
    3. 确定入口:根據配置中的 

      entry

       找出所有的入口檔案;
    4. 編譯子產品:從入口檔案出發,調用所有配置的 

      Loader

       對子產品進行翻譯,再找出該子產品依賴的子產品,再遞歸本步驟直到所有入口依賴的檔案都經過了本步驟的處理;
    5. 完成子產品編譯:在經過第4步使用 

      Loader

       翻譯完所有子產品後,得到了每個子產品被翻譯後的最終内容以及它們之間的依賴關系;
    6. 輸出資源:根據入口和子產品之間的依賴關系,組裝成一個個包含多個子產品的

      Chunk

      ,再把每個 

      Chunk

       轉換成一個單獨的檔案加入到輸出清單,這步是可以修改輸出内容的最後機會;
    7. 輸出完成:在确定好輸出内容後,根據配置确定輸出的路徑和檔案名,把檔案内容寫入到檔案系統。

    優化項目的webpack打包編譯過程

    1.建構打點:建構過程中,每一個

    Loader

     和 

    Plugin

     的執行時長,在編譯 JS、CSS 的 Loader 以及對這兩類代碼執行壓縮操作的 Plugin上消耗時長 。一款工具:speed-measure-webpack-plugin

    2.緩存:大部分 Loader 都提供了

    cache

     配置項。

    cache-loader

     ,将 loader 的編譯結果寫入硬碟緩存

    3.多核編譯,

    happypack

    項目接入多核編譯,了解為

    happypack

     将編譯工作灌滿所有線程

    4.抽離,

    webpack-dll-plugin

     将這些靜态依賴從每一次的建構邏輯中抽離出去,靜态依賴單獨打包,

    Externals

    将不需要打包的靜态資源從建構邏輯中剔除出去,使用

    CDN 引用

    5.

    tree-shaking

    ,雖然依賴了某個子產品,但其實隻使用其中的某些功能。通過 

    tree-shaking

    ,将沒有使用的子產品剔除,來達到删除無用代碼的目的。

    首屏加載優化

    路由懶加載:改為用

    import

    引用,以函數的形式動态引入,可以把各自的路由檔案分别打包,隻有在解析給定的路由時,才會下載下傳路由元件;

    element-ui

    按需加載:引用實際上用到的元件 ;

    元件重複打包:

    CommonsChunkPlugin

    配置來拆包,把使用2次及以上的包抽離出來,放進公共依賴檔案,首頁也有複用的元件,也會下載下傳這個公共依賴檔案;

    gzip: 拆完包之後,再用

    gzip

    做一下壓縮,關閉sourcemap。

    UglifyJsPlugin:  生産環境,壓縮混淆代碼,移除console代碼

    CDN部署靜态資源:靜态請求打在nginx時,将擷取靜态資源的位址進行重定向CDN内容分發網絡

    移動端首屏加載可以使用骨架屏,自定義loading,首頁單獨做服務端渲染。

    如何進行前端性能優化(21種優化+7種定位方式)

    webpack 熱更新機制

    熱更新流程總結:
    • 啟動本地

      server

      ,讓浏覽器可以請求本地的靜态資源
    • 頁面首次打開後,服務端與用戶端通過 websocket建立通信管道,把下一次的 hash 傳回前端
    • 用戶端擷取到hash,這個hash将作為下一次請求服務端 hot-update.js 和 hot-update.json的hash
    • 修改頁面代碼後,Webpack 監聽到檔案修改後,開始編譯,編譯完成後,發送 build 消息給用戶端
    • 用戶端擷取到hash,成功後用戶端構造hot-update.js script連結,然後插入主文檔
    • hot-update.js 插入成功後,執行hotAPI 的 createRecord 和 reload方法,擷取到 Vue 元件的 render方法,重新 render 元件, 繼而實作 UI 無重新整理更新。

    webpack的 loader和plugin介紹,css-loader,style-loader的差別

    loader 它就是一個轉換器,将A檔案進行編譯形成B檔案,

    plugin ,它就是一個擴充器,來操作的是檔案,針對是loader結束後,webpack打包的整個過程,它并不直接操作檔案,會監聽webpack打包過程中的某些節點(run, build-module, program)

    Babel 能把ES6/ES7的代碼轉化成指定浏覽器能支援的代碼。

    css-loader

     的作用是把 css檔案進行轉碼

    style-loader

    : 使用

    <style>

    css-loader

    内部樣式注入到我們的HTML頁面

    先使用 

    css-loader

    轉碼,然後再使用 

    style-loader

    插入到檔案

    如何編寫一個webpack的plugin?

    https://segmentfault.com/a/1190000037513682

    webpack 插件的組成:

    • 一個 JS 命名函數或一個類(可以想下我們平時使用插件就是 

      new XXXPlugin()

      的方式)
    • 在插件類/函數的 (prototype) 上定義一個 apply 方法。
    • 通過 apply 函數中傳入 compiler 并插入指定的事件鈎子,在鈎子回調中取到 compilation 對象
    • 通過 compilation 處理 webpack 内部特定的執行個體資料
    • 如果是插件是異步的,在插件的邏輯編寫完後調用 webpack 提供的 callback

    為什麼 Vite 啟動這麼快

    Webpack 會

    先打包

    ,然後啟動開發伺服器,請求伺服器時直接給予打包結果。

    而 Vite 是

    直接啟動

    開發伺服器,請求哪個子產品再對該子產品進行

    實時編譯

    Vite 将開發環境下的子產品檔案,就作為浏覽器要執行的檔案,而不是像 Webpack 那樣進行

    打包合并

    由于 Vite 在啟動的時候

    不需要打包

    ,也就意味着

    不需要分析子產品的依賴

    不需要編譯

    。是以啟動速度非常快。當浏覽器請求某個子產品時,再根據需要對子產品内容進行編譯。

    你的腳手架是怎麼做的

    使用 

    download-git-repo

     下載下傳倉庫代碼demo

    commander

    :完整的 

    node.js

     指令行解決方案。聲明

    program

    ,使用

    .option()

     方法來定義選項

    Inquirer.js

    :指令行使用者界面的集合。

    前端監控

    前端監控通常包括行為監控(PV/UV,埋點接口統計)、異常監控、性能監控。

    一個監控系統,大緻可以分為四個階段:日志采集、日志存儲、統計與分析、報告和警告。

    錯誤監控

    Vue專門的錯誤警告的方法 

    Vue.config.errorHandler

    ,(Vue提供隻能捕獲其頁面生命周期内的函數,比如created,mounted)
    Vue.config.errorHandler = function (err) {
    console.error(‘Vue.error’,err.stack)
    // 邏輯處理
    };
               
    架構:betterjs,fundebug(收費) 捕獲錯誤的腳本要放置在最前面,確定可以收集到錯誤資訊 方法:
    1. window.onerror()

      當有js運作時錯誤觸發時,onerror可以接受多個參數(message, source, lineno, colno, error)。
    2. window.addEventListener('error'), function(e) {}, true

       會比window.onerror先觸發,不能阻止預設事件處理函數的執行,但可以全局捕獲資源加載異常的錯誤
    前端JS錯誤捕獲--sourceMap

    如何監控網頁崩潰?**崩潰和卡頓有何差别?**監控錯誤

    1. Service Worker 有自己獨立的工作線程,與網頁區分開,網頁崩潰了,Service Worker 一般情況下不會崩潰;
    2. Service Worker 生命周期一般要比網頁還要長,可以用來監控網頁的狀态;

      卡頓:加載中,渲染遇到阻塞

    性能監控 && 性能優化

    性能名額:
    • FP

      (首次繪制)
    • FCP

      (首次内容繪制 First contentful paint)
    • LCP

      (最大内容繪制時間 Largest contentful paint)
    • FPS

      (每秒傳輸幀數)
    • TTI

      (頁面可互動時間 Time to Interactive)
    • HTTP

       請求響應時間
    • DNS

       解析時間
    • TCP

       連接配接時間
    性能資料采集需要使用 

    window.performance API

     ,   JS庫 

    web-vitals

    import {getLCP} from 'web-vitals'

    ;
        // 重定向耗時
        redirect: timing.redirectEnd - timing.redirectStart,
        // DOM 渲染耗時
        dom: timing.domComplete - timing.domLoading,
        // 頁面加載耗時
        load: timing.loadEventEnd - timing.navigationStart,
        // 頁面解除安裝耗時
        unload: timing.unloadEventEnd - timing.unloadEventStart,
        // 請求耗時
        request: timing.responseEnd - timing.requestStart,
        // 擷取性能資訊時目前時間
        time: new Date().getTime(),
        // DNS查詢耗時
        domainLookupEnd - domainLookupStart
     // TCP連結耗時
        connectEnd - connectStart
     // request請求耗時
        responseEnd - responseStart
     // 解析dom樹耗時
        domComplete - domInteractive
     // 白屏時間
        domloadng - fetchStart
     // onload時間
        loadEventEnd - fetchStart
               
    性能優化常用手段:緩存技術、   預加載技術、   渲染方案。
    1. 緩存 :主要有 cdn、浏覽器緩存、本地緩存以及應用離線包
    2. 預加載 :資源預拉取(prefetch)則是另一種性能優化的技術。通過預拉取可以告訴浏覽器使用者在未來可能用到哪些資源。
    • prefetch支援預拉取圖檔、腳本或者任何可以被浏覽器緩存的資源。

      在head裡 添加 

      <linkrel="prefetch"href="image.png" target="_blank" rel="external nofollow" >

    • prerender是一個重量級的選項,它可以讓浏覽器提前加載指定頁面的所有資源。
    • subresource可以用來指定資源是最高優先級的。目前頁面需要,或者馬上就會用到時。
    1. 渲染方案:
    • 靜态渲染(SR)
    • 前端渲染(CSR)
    • 服務端渲染(SSR)
    • 用戶端渲染(NSR):NSR 資料請求,首屏資料請求和資料線上與 webview 的一個初始化和架構 JS 初始化并行了起來,大大縮短了首屏時間。
    2021年7月 蝦皮、OPPO、富途等十幾家公司面經總結JS相關架構 Vue | Reactwebpack 及工程化Http 及浏覽器相關CSS 及 HTML常見場景及問題代碼程式設計相關
    640.png

    常見的六種設計模式以及應用場景

    https://www.cnblogs.com/whu-2017/p/9471670.html

    觀察者模式的概念

    觀察者模式模式,屬于行為型模式的一種,它定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主體對象在狀态變化時,會通知所有的觀察者對象。

    釋出訂閱者模式的概念

    釋出-訂閱模式,消息的發送方,叫做釋出者(publishers),消息不會直接發送給特定的接收者,叫做訂閱者。意思就是釋出者和訂閱者不知道對方的存在。需要一個第三方元件,叫做資訊中介,它将訂閱者和釋出者串聯起來,它過濾和配置設定所有輸入的消息。換句話說,釋出-訂閱模式用來處理不同系統元件的資訊交流,即使這些元件不知道對方的存在。

    需要一個第三方元件,叫做資訊中介,它将訂閱者和釋出者串聯起來

    工廠模式  主要是為建立對象提供了接口。場景:在編碼時不能預見需要建立哪種類的執行個體。

    代理模式 指令模式

    單例模式

    保證一個類僅有一個執行個體,并提供一個通路它的全局通路點。(window)

    Http 及浏覽器相關

    七層網絡模型

    應用層、表示層、會話層、傳輸層、網絡層、資料鍊路層、實體層

    TCP:面向連接配接、傳輸可靠(保證資料正确性,保證資料順序)、用于傳輸大量資料(流模式)、速度慢,建立連接配接需要開銷較多(時間,系統資源) 。(應用場景:HTP,HTTP,郵件)

    UDP:面向非連接配接、傳輸不可靠、用于傳輸少量資料(資料包模式)、速度快 ,可能丢包(應用場景:即時通訊)

    是否連接配接     面向連接配接     面向非連接配接
    傳輸可靠性   可靠        不可靠
    應用場合    少量資料    傳輸大量資料
               

    https

    用戶端先向伺服器端索要公鑰,然後用公鑰加密資訊,伺服器收到密文後,用自己的私鑰解密。伺服器公鑰放在數字證書中。

    url到加載渲染全過程

    1. DNS域名解析。
    2. TCP三向交握,建立接連。
    3. 發送HTTP請求封包。
    4. 伺服器處理請求傳回響應封包。
    5. 浏覽器解析渲染頁面。
    6. 四次揮手,斷開連接配接。
    DNS 協定提供通過

    域名查找 IP位址

    ,或逆向從 

    IP位址反查域名

    的服務。DNS 是一個網絡伺服器,我們的域名解析簡單來說就是在 DNS 上記錄一條資訊記錄。

    TCP 三次握手,四次揮手:握手揮手都是用戶端發起,用戶端結束。三次握手與四次揮手詳解

    負載均衡:請求在進入到真正的應用伺服器前,可能還會先經過負責負載均衡的機器,它的作用是将請求

    合理地配置設定到多個伺服器上

    ,轉發HTTP請求;同時具備具備防攻擊等功能。可分為DNS負載均衡,HTTP負載均衡,IP負載均衡,鍊路層負載均衡等。

    Web Server:請求經過前面的負載均衡後,将進入到對應伺服器上的 Web Server,比如 

    Apache

    Tomcat

    反向代理是工作在 HTTP 上的,一般都是 

    Nginx

    。全國各地通路baidu.com就肯定要通過代理通路,不可能都通路百度的那台伺服器。 (VPN正向代理,代理用戶端)

    浏覽器解析渲染過程:傳回的html傳遞到浏覽器後,如果有gzip會先解壓,找出檔案編碼格式,外鍊資源的加載 html從上往下解析,遇到js,css停止解析渲染,直到js執行完成。解析HTML,建構DOM樹 解析CSS,生成CSS規則樹 合并DOM樹和CSS規則,生成render樹去渲染

    不會引起DOM樹變化,頁面布局變化,改變元素樣式的行為叫重繪

    引起DOM樹結構變化,頁面布局變化的行為叫回流

    GUI渲染線程

    負責渲染浏覽器界面HTML元素,當界面需要 

    重繪(Repaint)

     或由于某種操作引發 

    回流(reflow)

     時,該線程就會執行。在Javascript引擎運作腳本期間,GUI渲染線程都是處于挂起狀态的,也就是說被”當機”了. 直到JS程式執行完成,才會接着執行。是以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導緻頁面渲染加載阻塞的感覺。JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面,渲染前後元素資料可能不一緻

    GPU繪制多程序的浏覽器:主要程序,插件程序,GPU,tab頁(浏覽器核心)多線程的浏覽器核心:每一個tab頁面可以看作是浏覽器核心程序,然後這個程序是多線程的。

    它有幾大類子線程:

    • GUI線程
    • JS引擎線程
    • 事件觸發線程
    • 定時器線程
    • HTTP請求線程

    http1 跟HTTP2

    http2

    多路複用:相同域名多個請求,共享同一個TCP連接配接,降低了延遲

    請求優先級:給每個request設定優先級

    二進制傳輸;之前是用純文字傳輸

    資料流:資料包不是按順序發送,對資料包做标記。每個請求或回應的所有資料包成為一個資料流,

    服務端推送:可以主動向用戶端發送消息。

    頭部壓縮:減少包的大小跟數量

    HTTP/1.1 中的管道( pipeline)傳輸中如果有一個

    請求阻塞

    了,那麼隊列後請求也統統被阻塞住了 HTTP/2 多請求複用一個TCP連接配接,一旦發生丢包,就會阻塞住所有的 HTTP 請求。HTTP/3 把 HTTP 下層的 TCP 協定改成了 UDP!http1 keep alive 串行傳輸

    http 中的 keep-alive 有什麼作用

    響應頭中設定 

    keep-alive

     可以在一個 TCP 連接配接上發送多個 http 請求

    浏覽器緩存政策

    強緩存:cache-control;no-cache max-age=<10000000>;expires;其中Cache-Conctrol的優先級比Expires高;

    控制強制緩存的字段分别是Expires和Cache-Control,如果用戶端的時間小于Expires的值時,直接使用緩存結果。

    協商緩存:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的優先級比Last-Modified / 首次請求,伺服器會在傳回的響應頭中加上Last-Modified字段,表示資源最後修改的時間。

    浏覽器再次請求時,請求頭中會帶上If-Modified-Since字段,比較兩個字段,一樣則證明資源未修改,傳回304,否則重新傳回資源,狀态碼為200;

    垃圾回收機制:

    标記清除

    :進入執行環境的變量都被标記,然後執行完,清除這些标記跟變量。檢視變量是否被引用。

    引用計數

    :會記錄每個值被引用的次數,當引用次數變成0後,就會被釋放掉。

    前端安全

    同源政策:如果兩個 URL 的協定、域名和端口都相同,我們就稱這兩個 URL 同源。因為浏覽器有

    cookies

    • XSS:跨站腳本攻擊(Cross Site Scripting) input, textarea等所有可能輸入文本資訊的區域,輸入

      <script src="http://惡意網站"></script>

      等,送出後資訊會存在伺服器中 。
    • CSRF:跨站請求僞造 。引誘使用者打開黑客的網站,在黑客的網站中,利用使用者的登入狀态發起的跨站請求。

      A

      站點

      img

      src=B

      站點的請求接口,可以通路;解決:

      referer

      攜帶請求來源

      通路該頁面後,表單自動送出, 模拟完成了一次POST操作,發送post請求

      解決:後端注入一個

      随機串

      Cookie

      ,前端請求取出随機串添加傳給後端。
    • http 劫持:電信營運商劫持
    • SQL注入
    • 點選劫持:誘使使用者點選看似無害的按鈕(實則點選了透明 

      iframe

      中的按鈕) ,解決後端請求頭加一個字段 

      X-Frame-Options

    • 檔案上傳漏洞 :伺服器未校驗上傳的檔案

    CSS 及 HTML

    什麼是BFC(塊級格式化上下文)、IFC(内聯格式化上下文 )、FFC(彈性盒模型)

    BFC(Block formatting context)

    ,即

    塊級格式化上下文

    ,它作為HTML頁面上的一個

    獨立渲染區域

    ,隻有區域内元素參與渲染,且不會影響其外部元素。簡單來說,可以将 BFC 看做是一個“圍城”,外面的元素進不來,裡面的元素出不去(互不幹擾)。

    一個決定如何渲染元素的容器 ,渲染規則 :

    • 1、内部的塊級元素會在垂直方向,一個接一個地放置。
    • 2、塊級元素垂直方向的距離由margin決定。屬于同一個BFC的兩個相鄰塊級元素的margin會發生重疊。
    • 3、對于從左往右的格式化,每個元素(塊級元素與行内元素)的左邊緣,與包含塊的左邊緣相接觸,(對于從右往左的格式化則相反)。即使包含塊中的元素存在浮動也是如此,除非其中元素再生成一個BFC。
    • 4、BFC的區域不會與浮動元素重疊。
    • 5、BFC是一個隔離的獨立容器,容器裡面的子元素和外面的元素互不影響。
    • 6、計算BFC容器的高度時,浮動元素也參與計算。

    形成BFC的條件:

    1、浮動元素,float 除 none 以外的值;

    2、定位元素,position(absolute,fixed);

    3、display 為以下其中之一的值 inline-block,table-cell,table-caption;

    4、overflow 除了 visible 以外的值(hidden,auto,scroll);

    BFC 一般用來解決以下幾個問題

    • 邊距重疊問題
    • 消除浮動問題
    • 自适應布局問題

    flex: 0 1 auto;

     是什麼意思?

    元素會根據自身寬高設定尺寸。它會縮短自身以适應 

    flex

     容器,但不會伸長并吸收 

    flex

     容器中的額外自由空間來适應 

    flex

     容器 。水準的主軸(

    main axis

    )和垂直的交叉軸(

    cross axis

    )幾個屬性決定按哪個軸的排列方向
    • flex-grow

      :    一個無機關數(): 它會被當作

      <flex-grow>的值。

    • flex-shrink

      1

        一個有效的**寬度(width)**值: 它會被當作 

      <flex-basis>的值。

    • flex-basis

      auto

        關鍵字

      none

      auto

      initial

      .
    放大比例、縮小比例、配置設定多餘空間之前占據的主軸空間。

    避免CSS全局污染

    1. scoped 屬性
    2. css in js
    const styles = {
      bar: {
        backgroundColor: '#000'
      }
    }
    const example = (props)=>{
      <div style={styles.bar} />
    }
               
    1. CSS Modules
    2. 使用less,盡量少使用全局對選擇器
    // 選擇器上>要記得寫,免得污染所有ul下面的li
    ul{
      >li{
        color:red;
      }
    }
               

    CSS Modules

    阮一峰 CSS Modules

    CSS Modules是一種建構步驟中的一個程序。通過建構工具來使

    指定class

    達到

    scope

    的過程。

    CSS Modules

     允許使用:

    :global(.className)

    的文法,聲明一個全局規則。凡是這樣聲明的

    class

    ,都不會被編譯成

    哈希字元串

    :local(className)

    : 做 localIdentName 規則處理,編譯唯一哈希類名。

    CSS Modules使用特點:

    • 不使用選擇器,隻使用 class 名來定義樣式
    • 不層疊多個 class,隻使用一個 class 把所有樣式定義好
    • 不嵌套class

    盒子模型和 

    box-sizing

     屬性

    width: 160px; padding: 20px; border: 8px solid orange;

    标準 box-sizing: 

    content-box;

     元素的總寬度 = 160 + 202 + 82; IE的 

    border-box

    :總寬度

    160

    margin/padding

    取百分比的值時 ,基于父元素的寬度和高度的。

    css繪制三角形

    1. 通過border 處理
    // border 處理
    .class {
        width: 0;
        height: 0;
        border-left: 50px solid transparent;
        border-right: 50px solid transparent;
        border-bottom: 100px solid red;
    }
    // 寬高+border
    div {
        width: 50px;
        height: 50px;
        border: 2px solid orange;
    }
               
    1. clip-path裁剪獲得
    div{
     clip-path: polygon(0 100%, 50% 0, 100% 100%);
    }
               
    1. 漸變linear-gradient 實作
    div {
      width: 200px;
      height: 200px;
      background:linear-gradient(to bottom right, #fff 0%, #fff 49.9%, rgba(148,88,255,1) 50%,rgba(185,88,255,1) 100%);
    }
               

    CSS實作了三角形後如何給三角形添加陰影

    ???

    CSS兩列布局的N種實作

    兩列布局分為兩種,一種是左側定寬、右側自适應,另一種是兩列都自适應(即左側寬度由子元素決定,右側補齊剩餘空間)。
    1. 左側定寬、右側自适應如何實作
    // 兩個元素都設定dislpay:inline-block
    .left {
        display: inline-block;
        width: 100px;
        height: 200px;
        background-color: red;
        vertical-align: top;
    }
    .right {
        display: inline-block;
        width: calc(100% - 100px);
        height: 400px;
        background-color: blue;
        vertical-align: top;
    }
    // 兩個元素設定浮動,右側自适應元素寬度使用calc函數計算
    .left{
        float: left;
        width: 100px;
        height: 200px;
        background-color: red;
    }
    .right{
        float: left;
        width: calc(100% - 100px);
        height: 400px;
        background-color: blue;
    }
    // 父元素設定display:flex,自适應元素設定flex:1
    .box{
        height: 600px;
        width: 100%;
        display: flex;
    }
    .left{
        width: 100px;
        height: 200px;
        background-color: red;
    }
    .right{
        flex: 1;
        height: 400px;
        background-color: blue;
    }
    // 父元素相對定位,左側元素絕對定位,右側自适應元素設定margin-left的值大于定寬元素的寬度
    .left{
        position: absolute;
        width: 100px;
        height: 200px;
        background-color: red;
    }
    .right{
        margin-left: 100px;
        height: 400px;
        background-color: blue;
    }
               
    1. 左右兩側元素都自适應
    // flex布局 同上
    // 父元素設定display:grid; grid-template-columns:auto 1fr;(這個屬性定義列寬,auto關鍵字表示由浏覽器自己決定長度。fr是一個相對尺寸機關,表示剩餘空間做等分)grid-gap:20px(行間距)
    .parent{
        display:grid;
        grid-template-columns:auto 1fr;
        grid-gap:20px
    } 
    .left{
        background-color: red;
        height: 200px;
    }
    .right{
        height:300px;
        background-color: blue;
    }
    // 浮動+BFC   父元素設定overflow:hidden,左側定寬元素浮動,右側自适應元素設定overflow:auto建立BFC
    .box{
        height: 600px;
        width: 100%;
        overflow: hidden;
    }
    .left{
        float: left;
        width: 100px;
        height: 200px;
        background-color: red;
    }
    .right{
        overflow: auto;
        height: 400px;
        background-color: blue;
    }
               

    CSS三列布局

    1. float布局:左邊左浮動,右邊右浮動,中間margin:0 100px;
    2. Position布局: 左邊left:0; 右邊right:0; 中間left: 100px; right: 100px;
    3. table布局: 父元素 display: table; 左右 width: 100px; 三個元素display: table-cell;
    4. 彈性(flex)布局:父元素 display: flex; 左右 width: 100px;
    5. 網格(gird)布局:
    // gird提供了 gird-template-columns、grid-template-rows屬性讓我們設定行和列的高、寬
    .div{
        width: 100%;
        display: grid;
        grid-template-rows: 100px;
        grid-template-columns: 300px auto 300px;
    }
               

    常見場景及問題

    app與H5 如何通訊互動的?

    // 相容IOS和安卓
    callMobile(parameters,messageHandlerName) {
      //handlerInterface由iOS addScriptMessageHandler與andorid addJavascriptInterface 代碼注入而來。
      if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
        // alert('ios')
        window.webkit.messageHandlers[messageHandlerName].postMessage(JSON.stringify(parameters))
      } else {
        // alert('安卓')
        //安卓傳輸不了js json對象,隻能傳輸string
        window.webkit[messageHandlerName](JSON.stringify(parameters))
      }
    }
               
    由app将原生方法注入到window上供js調用

    messageHandlerName

     約定的通信方法

    parameters

     需要傳入的參數

    移動端适配方案

    rem

    是相對于HTML的根元素

    em

    相對于父級元素的字型大小。

    VW,VH

     螢幕寬度高度的高分比
    //按照寬度375圖算, 1rem = 100px;
    (function (win, doc) {
       function changeSize() {
         doc.documentElement.style.fontSize = doc.documentElement.clientWidth / 3.75 + 'px';
        console.log(100 * doc.documentElement.clientWidht / 3.75)
       }
       changeSize();
       win.addEventListener('resize', changeSize, false);
    
    })(window, document);
               

    代碼程式設計相關

    實作釋出訂閱

    /* Pubsub */
    function Pubsub(){
      //存放事件和對應的處理方法
      this.handles = {};
    }
    
    Pubsub.prototype = {
      //傳入事件類型type和事件處理handle
      on: function (type, handle) {
        if(!this.handles[type]){
          this.handles[type] = [];
        }
        this.handles[type].push(handle);
      },
      emit: function () {
        //通過傳入參數擷取事件類型
        //将arguments轉為真數組
        var type = Array.prototype.shift.call(arguments);
        if(!this.handles[type]){
          return false;
        }
        for (var i = 0; i < this.handles[type].length; i++) {
          var handle = this.handles[type][i];
          //執行事件
          handle.apply(this, arguments);
        }
      },
      off: function (type, handle) {
        handles = this.handles[type];
        if(handles){
          if(!handle){
            handles.length = 0;//清空數組
          }else{
          for (var i = 0; i < handles.length; i++) {
            var _handle = handles[i];
            if(_handle === handle){
              //從數組中删除
              handles.splice(i,1);
            }
          }
        }
      }  
    }
               

    promise怎麼實作鍊式調用跟傳回不同的狀态

    // MyPromise.js
    
    // 先定義三個常量表示狀态
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';
    
    // 建立 MyPromise 類
    class MyPromise {
      constructor(executor){
        // executor 是一個執行器,進入會立即執行
        // 并傳入resolve和reject方法
        executor(this.resolve, this.reject)
      }
    
      // 儲存狀态的變量,初始值是 pending
      status = PENDING;
    
      // resolve和reject為什麼要用箭頭函數?
      // 如果直接調用的話,普通函數this指向的是window或者undefined
      // 用箭頭函數就可以讓this指向目前執行個體對象
      // 成功之後的值
      value = null;
      // 失敗之後的原因
      reason = null;
    
      // 更改成功後的狀态
      resolve = (value) => {
        // 隻有狀态是等待,才執行狀态修改
        if (this.status === PENDING) {
          // 狀态修改為成功
          this.status = FULFILLED;
          // 儲存成功之後的值
          this.value = value;
        }
      }
    
      // 更改失敗後的狀态
      reject = (reason) => {
        // 隻有狀态是等待,才執行狀态修改
        if (this.status === PENDING) {
          // 狀态成功為失敗
          this.status = REJECTED;
          // 儲存失敗後的原因
          this.reason = reason;
        }
      }
    
        then(onFulfilled, onRejected) {
        // 判斷狀态
        if (this.status === FULFILLED) {
          // 調用成功回調,并且把值傳回
          onFulfilled(this.value);
        } else if (this.status === REJECTED) {
          // 調用失敗回調,并且把原因傳回
          onRejected(this.reason);
        }
      }
    
    }
               

    實作Promise.all

    // Promise.all
    function all(promises) {
      let len = promises.length, res = []
      if (len) {
        return new Promise(function (resolve, reject) {
            for(let i=0; i < len; i++){
                let promise = promises[i];
                promise.then(response => {
                    res[i] = response
    
                    // 當傳回結果為最後一個時
                    if (res.length === len) {
                        resolve(res)
                    }
    
                }, error => {
                    reject(error)
                })
    
            }
        })
    }
               

    對象數組轉換成tree數組

    > 将entries 按照 level 轉換成 result 資料結構
    
    const entries = [
        {
            "province": "浙江", "city": "杭州", "name": "西湖"
        }, {
            "province": "四川", "city": "成都", "name": "錦裡"
        }, {
            "province": "四川", "city": "成都", "name": "方所"
        }, {
            "province": "四川", "city": "阿壩", "name": "九寨溝"
        }
    ];
     
    const level = ["province", "city", "name"];
    
    const  result = [
     {
      value:'浙江',
      children:[
       {
        value:'杭州',
        children:[
         {
          value:'西湖'
         }
        ]
       }
      ]
     },
     {
      value:'四川',
      children:[
       {
        value:'成都',
        children:[
         {
          value:'錦裡'
         },
         {
          value:'方所'
         }
        ]
       },
       {
        value:'阿壩',
        children:[
         {
          value:'九寨溝'
         }
        ]
       }
      ]
     },
    ]
               
    思路:涉及到樹形數組,采用遞歸周遊的方式
    function transfrom(list, level) {
      const res = [];
      list.forEach(item => {
        pushItem(res, item, 0);
      });
    
      function pushItem(arr, obj, i) {
        const o = {
          value: obj[level[i]],
          children: [],
        };
        // 判斷傳入數組裡是否有value等于要傳入的項
        const hasItem = arr.find(el => el.value === obj[level[i]]);
        let nowArr;
        if(hasItem) {
          // 存在,則下一次周遊傳入存在項的children
          nowArr = hasItem.children;
        }else{
          // 不存在 壓入arr,下一次周遊傳入此項的children
          arr.push(o);
          nowArr = o.children;
        }
        if(i === level.length - 1) delete o.children;
        i++;
        if(i < level.length) {
          // 遞歸進行層級的周遊
          pushItem(nowArr, obj, i);
        }
      }
    }
    
    transfrom(entries, level);
               

    JS instanceof 方法原生實作

    簡單用法
    function Fn () {}
    const fn = new Fn()
    fn instanceof Fn  // true
               
    實作如下:
    // left instanceof right
    function _instanceof(left, right) {
      // 構造函數原型
      const prototype = right.prototype
      // 實列對象屬性,指向其構造函數原型
      left = left.__proto__
      // 查實原型鍊
      while (true) {
        // 如果為null,說明原型鍊已經查找到最頂層了,真接傳回false
        if (left === null) {
          return false
        }
        // 查找到原型
        if (prototype === left){
          return true
        }
        // 繼續向上查找
        left = left.__proto__
      }
    }
    
    const str = "abc"
    _instanceof(str, String) // true
               

    程式設計題

    将有同樣元素的數組進行合并
    // 例如:
    const arr = [
        ['a', 'b', 'c'],
        ['a', 'd'],
        ['d', 'e'],
        ['f', 'g'],
        ['h', 'g'],
        ['i']
    ]
    // 運作後的傳回結果是:
    [
        ['a', 'b', 'c', 'd', 'e'],
        ['f', 'g', 'h'],
        ['i']
    ]
    // 思路一:
    const arr = [['a', 'b', 'c'], ['a', 'd'], ['d', 'e'], ['f', 'g'], ['h', 'g'], ['i']]
    function transform(arr){
        let res = []
        arr = arr.map(el => el.sort()).sort()
        const item = arr.reduce((pre, cur) => {
          if (cur.some(el => pre && pre.includes(el))) {
            pre = pre.concat(cur)
          } else {
            res.push(pre)
            pre = cur
          }
          return [...new Set(pre)]
        })
        res.push(item)
        return res;
    }
    transform(arr)
    // console.log(transform(arr));
    
    // 思路二: 
    
    function r (arr) {
      const map = new Map()
      arr.forEach((array, index) => {
        const findAlp = array.find((v) => map.get(v))
        if (findAlp) {
          const set = map.get(findAlp)
          array.forEach((alp) => {
            set.add(alp)
            const findAlp2 = map.get(alp)
            if (findAlp2 && findAlp2 !== set) {
              for(const v of findAlp2.values()){
                set.add(v)
                map.set(v, set)
              }
            }
            map.set(alp, set)
          })
        } else {
          const set = new Set(arr[index])
          array.forEach((alp) => map.set(alp, set))
        }
      })
      const set = new Set()
      const ret = []
      for (const [key, value] of map.entries()) {
        if (set.has(value)) continue
        set.add(value)
        ret.push([...value])
      }
      return ret
    }
               
    關于本文

    作者@幾米陽光 

    https://juejin.cn/post/6991724298197008421

    推薦閱讀

    2021 阿裡位元組快手面經 & 個人成長經驗分享

    記一次進階前端開發工程師面經

    關注下方「前端開發部落格」,回複 “加群”

    加入我們一起學習,天天進步

    2021年7月 蝦皮、OPPO、富途等十幾家公司面經總結JS相關架構 Vue | Reactwebpack 及工程化Http 及浏覽器相關CSS 及 HTML常見場景及問題代碼程式設計相關
    如果覺得這篇文章還不錯,來個【分享、點贊、在看】三連吧,讓更多的人也看到~

繼續閱讀