天天看點

中進階前端大廠面試秘籍,直通大廠(下)屏蔽 192.168.1.1 的通路;允許 192.168.1.2 ~ 10 的通路;

引言

本篇文章會繼續沿着前面兩篇的腳步,繼續梳理前端領域一些比較主流的進階知識點,力求能讓大家在橫向層面有個全面的概念。能在面試時有限的時間裡,能夠快速抓住重點與面試官交流。這些知識點屬于加分項,如果能在面試時從容侃侃而談,想必面試官會記憶深刻,為你折服的~🤤

另外有許多童鞋提到: 面試造火箭,實踐全不會,對這種應試政策表達一些擔憂。其實我是覺得面試或者這些知識點,也僅僅是個初級的 開始。能幫助在初期的快速成長,但這種政策并沒辦法讓你達到更高的水準,隻有後續不斷地真正實踐和深入研究,才能突破自己的瓶頸,繼續成長。面試,不也隻是一個開始而已嘛。~😋

建議各位小夥從基礎入手,先看

  • (上篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠
  • (中篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠
  • (下篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠

進階知識

Hybrid

随着 Web技術 和 移動裝置 的快速發展,在各家大廠中,Hybrid 技術已經成為一種最主流最不可取代的架構方案之一。一套好的 Hybrid 架構方案能讓 App 既能擁有 極緻的體驗和性能,同時也能擁有 Web技術 靈活的開發模式、跨平台能力以及熱更新機制。是以,相關的 Hybrid 領域人才也是十分的吃香,精通Hybrid 技術和相關的實戰經驗,也是面試中一項大大的加分項。

1. 混合方案簡析

Hybrid App,俗稱 混合應用,即混合了 Native技術 與 Web技術 進行開發的移動應用。現在比較流行的混合方案主要有三種,主要是在UI渲染機制上的不同:

  • Webview UI:
    • 通過 JSBridge 完成 H5 與 Native 的雙向通訊,并 基于 Webview 進行頁面的渲染;
    • 優勢: 簡單易用,架構門檻/成本較低,适用性與靈活性極強;
    • 劣勢: Webview 性能局限,在複雜頁面中,表現遠不如原生頁面;
  • Native UI:
    • 通過 JSBridge 賦予 H5 原生能力,并進一步将 JS 生成的虛拟節點樹(Virtual DOM)傳遞至 Native 層,并使用 原生系統渲染。
    • 優勢: 使用者體驗基本接近原生,且能發揮 Web技術 開發靈活與易更新的特性;
    • 劣勢: 上手/改造門檻較高,最好需要掌握一定程度的用戶端技術。相比于正常 Web開發,需要更高的開發調試、問題排查成本;
  • 小程式
    • 通過更加定制化的 JSBridge,賦予了 Web 更大的權限,并使用雙 WebView 雙線程的模式隔離了 JS邏輯 與 UI渲染,形成了特殊的開發模式,加強了 H5 與 Native 混合程度,屬于第一種方案的優化版本;
    • 優勢: 使用者體驗好于正常 Webview 方案,且通常依托的平台也能提供更為友好的開發調試體驗以及功能;
    • 劣勢: 需要依托于特定的平台的規範限定

2. Webviev

Webview 是 Native App 中内置的一款基于 Webkit核心 的浏覽器,主要由兩部分組成:

  • WebCore 排版引擎;
  • JSCore 解析引擎;

在原生開發 SDK 中 Webview 被封裝成了一個元件,用于作為 Web頁面 的容器。是以,作為宿主的用戶端中擁有更高的權限,可以對 Webview 中的 Web頁面 進行配置和開發。

Hybrid技術中雙端的互動原理,便是基于 Webview 的一些 API 和特性。

3. 互動原理

Hybrid技術 中最核心的點就是 Native端 與 H5端 之間的 雙向通訊層,其實這裡也可以了解為我們需要一套 跨語言通訊方案,便是我們常聽到的 JSBridge。

  • JavaScript 通知 Native
    • API注入,Native 直接在 JS 上下文中挂載資料或者方法
      • 延遲較低,在安卓4.1以下具有安全性問題,風險較高
    • WebView URL Scheme 跳轉攔截
      • 相容性好,但延遲較高,且有長度限制
    • WebView 中的 prompt/console/alert攔截(通常使用 prompt)
  • Native 通知 Javascript:
    • IOS:

      stringByEvaluatingJavaScriptFromString

      // Swift

      webview.stringByEvaluatingJavaScriptFromString(“alert(‘NativeCall’)”)

      複制代碼

    • Android:

      loadUrl

      (4.4-)

      // 調用js中的JSBridge.trigger方法

      // 該方法的弊端是無法擷取函數傳回值;

      webView.loadUrl(“javascript:JSBridge.trigger(‘NativeCall’)”)

      複制代碼

    • Android:

      evaluateJavascript

      (4.4+)

      // 4.4+後使用該方法便可調用并擷取函數傳回值;

      mWebView.evaluateJavascript(“javascript:JSBridge.trigger(‘NativeCall’)”, new ValueCallback() {

      @Override

      public void onReceiveValue(String value) {

      //此處為 js 傳回的結果

      }

      });

      複制代碼

4. 接入方案

整套方案需要 Web 與 Native 兩部分共同來完成:

  • Native: 負責實作URL攔截與解析、環境資訊的注入、拓展功能的映射、版本更新等功能;
  • JavaScirpt: 負責實作功能協定的拼裝、協定的發送、參數的傳遞、回調等一系列基礎功能。

接入方式:

  • 線上H5: 直接将項目部署于線上伺服器,并由用戶端在 HTML 頭部注入對應的 Bridge。
    • 優勢: 接入/開發成本低,對 App 的侵入小;
    • 劣勢: 重度依賴網絡,無法離線使用,首屏加載慢;
  • 内置離線包: 将代碼直接内置于 App 中,即本地存儲中,可由 H5 或者 用戶端引用 Bridge。
    • 優勢: 首屏加載快,可離線化使用;
    • 劣勢: 開發、調試成本變高,需要多端合作,且會增加 App 包體積

5. 優化方案簡述

  • Webview 預加載: Webview 的初始化其實挺耗時的。我們測試過,大概在100~200ms之間,是以如果能前置做好初始化于記憶體中,會大大加快渲染速度。
  • 更新機制: 使用離線包的時候,便會涉及到本地離線代碼的更新問題,是以需要建立一套雲端下發包的機制,由用戶端下載下傳雲端最新代碼包 (zip包),并解壓替換本地代碼。
    • 增量更新: 由于下發包是一個下載下傳的過程,是以包的體積越小,下載下傳速度越快,流量損耗越低。隻打包改變的檔案,用戶端下載下傳後覆寫式替換,能大大減小每次更新包的體積。
    • 條件分發: 雲平台下發更新包時,可以配合用戶端設定一系列的條件與規則,進而實作代碼的條件更新:
      • 單 地區 更新: 例如一個隻有中國地區才能更新的版本;
      • 按 語言 更新: 例如隻有中文版本會更新;
      • 按 App 版本 更新: 例如隻有最新版本的 App 才會更新;
      • 灰階 更新: 隻有小比例使用者會更新;
      • AB測試: 隻有命中的使用者會更新;
  • 降級機制: 當使用者下載下傳或解壓代碼包失敗時,需要有套降級方案,通常有兩種做法:
    • 本地内置: 随着 App 打包時内置一份線上最新完整代碼包,保證本地代碼檔案的存在,資源加載均使用本地化路徑;
    • 域名攔截: 資源加載使用線上域名,通過攔截域名映射到本地路徑。當本地不存在時,則請求線上檔案,當存在時,直接加載;
  • 跨平台部署: Bridge層 可以做一套浏覽器适配,在一些無法适配的功能,做好降級處理,進而保證代碼在任何環境的可用性,一套代碼可同時運作于 App内 與 普通浏覽器;
  • 環境系統: 與用戶端進行統一配合,搭建出 正式 / 預上線 / 測試 / 開發環境,能大大提高項目穩定性與問題排查;
  • 開發模式:
    • 能連接配接PC Chrome/safari 進行代碼調試;
    • 具有開發調試入口,可以使用同樣的 Webview 加載開發時的本地代碼;
    • 具備日志系統,可以檢視 Log 資訊;

詳細内容由興趣的童鞋可以看文章:

  • Hybrid App技術解析 – 原理篇
  • Hybrid App技術解析 – 實戰篇

Webpack

1. 原理簡述

Webpack 已經成為了現在前端工程化中最重要的一環,通過

Webpack

Node

的配合,前端領域完成了不可思議的進步。通過預編譯,将軟體程式設計中先進的思想和理念能夠真正運用于生産,讓前端開發領域告别原始的蠻荒階段。深入了解

Webpack

,可以讓你在程式設計思維及技術領域上産生質的成長,極大拓展技術邊界。這也是在面試中必不可少的一個内容。

  • 核心概念
    • JavaScript 的 子產品打包工具 (module bundler)。通過分析子產品之間的依賴,最終将所有子產品打包成一份或者多份代碼包 (bundler),供 HTML 直接引用。實質上,Webpack 僅僅提供了 打包功能 和一套 檔案處理機制,然後通過生态中的各種 Loader 和 Plugin 對代碼進行預編譯和打包。是以 Webpack 具有高度的可拓展性,能更好的發揮社群生态的力量。
      • Entry: 入口檔案,Webpack 會從該檔案開始進行分析與編譯;
      • Output: 出口路徑,打包後建立 bundler 的檔案路徑以及檔案名;
      • Module: 子產品,在 Webpack 中任何檔案都可以作為一個子產品,會根據配置的不同的 Loader 進行加載和打包;
      • Chunk: 代碼塊,可以根據配置,将所有子產品代碼合并成一個或多個代碼塊,以便按需加載,提高性能;
      • Loader: 子產品加載器,進行各種檔案類型的加載與轉換;
      • Plugin: 拓展插件,可以通過 Webpack 相應的事件鈎子,介入到打包過程中的任意環節,進而對代碼按需修改;
  • 工作流程 (加載 - 編譯 - 輸出)
    • 1、讀取配置檔案,按指令 初始化 配置參數,建立 Compiler 對象;
    • 2、調用插件的 apply 方法 挂載插件 監聽,然後從入口檔案開始執行編譯;
    • 3、按檔案類型,調用相應的 Loader 對子產品進行 編譯,并在合适的時機點觸發對應的事件,調用 Plugin 執行,最後再根據子產品 依賴查找 到所依賴的子產品,遞歸執行第三步;
    • 4、将編譯後的所有代碼包裝成一個個代碼塊 (Chuck), 并按依賴和配置确定 輸出内容。這個步驟,仍然可以通過 Plugin 進行檔案的修改;
    • 5、最後,根據 Output 把檔案内容一一寫入到指定的檔案夾中,完成整個過程;
  • 子產品包裝:

    (function(modules) {

    // 模拟 require 函數,從記憶體中加載子產品;

    function webpack_require(moduleId) {

    // 緩存子產品

    if (installedModules[moduleId]) {

    return installedModules[moduleId].exports;

    }

    var module = installedModules[moduleId] = {
    		i: moduleId,
    		l: false,
    		exports: {}
    	};
    	
    	// 執行代碼;
    	modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    	
    	// Flag: 标記是否加載完成;
    	module.l = true;
    	
    	return module.exports;
    }
    
    // ...
    
    // 開始執行加載入口檔案;
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
               

    })({

    “./src/index.js”: function (module, webpack_exports, webpack_require) {

    // 使用 eval 執行編譯後的代碼;

    // 繼續遞歸引用子產品内部依賴;

    // 實際情況并不是使用模闆字元串,這裡是為了代碼的可讀性;

    eval(

    __webpack_require__.r(__webpack_exports__); // var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("test", ./src/test.js");

    );

    },

    “./src/test.js”: function (module, webpack_exports, webpack_require) {

    // …

    },

    })

    複制代碼

  • 總結:
    • 子產品機制: webpack 自己實作了一套模拟子產品的機制,将其包裹于業務代碼的外部,進而提供了一套子產品機制;
    • 檔案編譯: webpack 規定了一套編譯規則,通過 Loader 和 Plugin,以管道的形式對檔案字元串進行處理;

2. Loader

由于 Webpack 是基于 Node,是以 Webpack 其實是隻能識别 js 子產品,比如 css / html / 圖檔等類型的檔案并無法加載,是以就需要一個對 不同格式檔案轉換器。其實 Loader 做的事,也并不難了解: 對 Webpack 傳入的字元串進行按需修改。例如一個最簡單的 Loader:

// html-loader/index.js
module.exports = function(htmlSource) {
	// 傳回處理後的代碼字元串
	// 删除 html 檔案中的所有注釋
	return htmlSource.replace(/<!--[\w\W]*?-->/g, '')
}
複制代碼
           

當然,實際的 Loader 不會這麼簡單,通常是需要将代碼進行分析,建構 AST (抽象文法樹), 周遊進行定向的修改後,再重新生成新的代碼字元串。如我們常用的 Babel-loader 會執行以下步驟:

  • babylon 将 ES6/ES7 代碼解析成 AST
  • babel-traverse 對 AST 進行周遊轉譯,得到新的 AST
  • 新 AST 通過 babel-generator 轉換成 ES5

Loader 特性:

  • 鍊式傳遞,按照配置時相反的順序鍊式執行;
  • 基于 Node 環境,擁有 較高權限,比如檔案的增删查改;
  • 可同步也可異步;

常用 Loader:

  • file-loader: 加載檔案資源,如 字型 / 圖檔 等,具有移動/複制/命名等功能;
  • url-loader: 通常用于加載圖檔,可以将小圖檔直接轉換為 Date Url,減少請求;
  • babel-loader: 加載 js / jsx 檔案, 将 ES6 / ES7 代碼轉換成 ES5,抹平相容性問題;
  • ts-loader: 加載 ts / tsx 檔案,編譯 TypeScript;
  • style-loader: 将 css 代碼以

    <style>

    标簽的形式插入到 html 中;
  • css-loader: 分析

    @import

    url()

    ,引用 css 檔案與對應的資源;
  • postcss-loader: 用于 css 的相容性處理,具有衆多功能,例如 添加字首,機關轉換 等;
  • less-loader / sass-loader: css預處理器,在 css 中新增了許多文法,提高了開發效率;

編寫原則:

  • 單一原則: 每個 Loader 隻做一件事;
  • 鍊式調用: Webpack 會按順序鍊式調用每個 Loader;
  • 統一原則: 遵循 Webpack 制定的設計規則和結構,輸入與輸出均為字元串,各個 Loader 完全獨立,即插即用;

3. Plugin

插件系統是 Webpack 成功的一個關鍵性因素。在編譯的整個生命周期中,Webpack 會觸發許多事件鈎子,Plugin 可以監聽這些事件,根據需求在相應的時間點對打包内容進行定向的修改。

  • 一個最簡單的 plugin 是這樣的:

    class Plugin{

    // 注冊插件時,會調用 apply 方法

    // apply 方法接收 compiler 對象

    // 通過 compiler 上提供的 Api,可以對事件進行監聽,執行相應的操作

    apply(compiler){

    // compilation 是監聽每次編譯循環

    // 每次檔案變化,都會生成新的 compilation 對象并觸發該事件

    compiler.plugin(‘compilation’,function(compilation) {})

    }

    }

    複制代碼

  • 注冊插件:

    // webpack.config.js

    module.export = {

    plugins:[

    new Plugin(options),

    ]

    }

    複制代碼

  • 事件流機制:

Webpack 就像工廠中的一條産品流水線。原材料經過 Loader 與 Plugin 的一道道處理,最後輸出結果。

  • 通過鍊式調用,按順序串起一個個 Loader;
  • 通過事件流機制,讓 Plugin 可以插入到整個生産過程中的每個步驟中;

Webpack 事件流程式設計範式的核心是基礎類 Tapable,是一種 觀察者模式 的實作事件的訂閱與廣播:

const { SyncHook } = require("tapable")

const hook = new SyncHook(['arg'])

// 訂閱
hook.tap('event', (arg) => {
	// 'event-hook'
	console.log(arg)
})

// 廣播
hook.call('event-hook')
複制代碼
           

Webpack 中兩個最重要的類 Compiler 與 Compilation 便是繼承于 Tapable,也擁有這樣的事件流機制。

  • Compiler: 可以簡單的了解為 Webpack 執行個體,它包含了目前 Webpack 中的所有配置資訊,如 options, loaders, plugins 等資訊,全局唯一,隻在啟動時完成初始化建立,随着生命周期逐一傳遞;
  • Compilation: 可以稱為 編譯執行個體。當監聽到檔案發生改變時,Webpack 會建立一個新的 Compilation 對象,開始一次新的編譯。它包含了目前的輸入資源,輸出資源,變化的檔案等,同時通過它提供的 api,可以監聽每次編譯過程中觸發的事件鈎子;
  • 差別:
    • Compiler 全局唯一,且從啟動生存到結束;
    • Compilation 對應每次編譯,每輪編譯循環均會重新建立;
  • 常用 Plugin:
    • UglifyJsPlugin: 壓縮、混淆代碼;
    • CommonsChunkPlugin: 代碼分割;
    • ProvidePlugin: 自動加載子產品;
    • html-webpack-plugin: 加載 html 檔案,并引入 css / js 檔案;
    • extract-text-webpack-plugin / mini-css-extract-plugin: 抽離樣式,生成 css 檔案;
    • DefinePlugin: 定義全局變量;
    • optimize-css-assets-webpack-plugin: CSS 代碼去重;
    • webpack-bundle-analyzer: 代碼分析;
    • compression-webpack-plugin: 使用 gzip 壓縮 js 和 css;
    • happypack: 使用多程序,加速代碼建構;
    • EnvironmentPlugin: 定義環境變量;

4. 編譯優化

  • 代碼優化:
    • 無用代碼消除,是許多程式設計語言都具有的優化手段,這個過程稱為 DCE (dead code elimination),即 删除不可能執行的代碼;
      • 例如我們的 UglifyJs,它就會幫我們在生産環境中删除不可能被執行的代碼,例如:

        var fn = function() {

        return 1;

        // 下面代碼便屬于 不可能執行的代碼;

        // 通過 UglifyJs (Webpack4+ 已内置) 便會進行 DCE;

        var a = 1;

        return a;

        }

        複制代碼

    • 搖樹優化 (Tree-shaking),這是一種形象比喻。我們把打包後的代碼比喻成一棵樹,這裡其實表示的就是,通過工具 “搖” 我們打包後的 js 代碼,将沒有使用到的無用代碼 “搖” 下來 (删除)。即 消除那些被 引用了但未被使用 的子產品代碼。
      • 原理: 由于是在編譯時優化,是以最基本的前提就是文法的靜态分析,ES6的子產品機制 提供了這種可能性。不需要運作時,便可進行代碼字面上的靜态分析,确定相應的依賴關系。
      • 問題: 具有 副作用 的函數無法被 tree-shaking。
        • 在引用一些第三方庫,需要去觀察其引入的代碼量是不是符合預期;
        • 盡量寫純函數,減少函數的副作用;
        • 可使用 webpack-deep-scope-plugin,可以進行作用域分析,減少此類情況的發生,但仍需要注意;
  • code-spliting: 代碼分割 技術,将代碼分割成多份進行 懶加載 或 異步加載,避免打包成一份後導緻體積過大,影響頁面的首屏加載;
    • Webpack 中使用 SplitChunksPlugin 進行拆分;
    • 按 頁面 拆分: 不同頁面打包成不同的檔案;
    • 按 功能 拆分:
      • 将類似于播放器,計算庫等大子產品進行拆分後再懶加載引入;
      • 提取複用的業務代碼,減少備援代碼;
    • 按 檔案修改頻率 拆分: 将第三方庫等不常修改的代碼單獨打包,而且不改變其檔案 hash 值,能最大化運用浏覽器的緩存;
  • scope hoisting: 作用域提升,将分散的子產品劃分到同一個作用域中,避免了代碼的重複引入,有效減少打包後的代碼體積和運作時的記憶體損耗;
  • 編譯性能優化:
    • 更新至 最新 版本的 webpack,能有效提升編譯性能;
    • 使用 dev-server / 子產品熱替換 (HMR) 提升開發體驗;
      • 監聽檔案變動 忽略 node_modules 目錄能有效提高監聽時的編譯效率;
    • 縮小編譯範圍:
      • modules: 指定子產品路徑,減少遞歸搜尋;
      • mainFields: 指定入口檔案描述字段,減少搜尋;
      • noParse: 避免對非子產品化檔案的加載;
      • includes/exclude: 指定搜尋範圍/排除不必要的搜尋範圍;
      • alias: 緩存目錄,避免重複尋址;
    • babel-loader

      :
      • 忽略

        node_moudles

        ,避免編譯第三方庫中已經被編譯過的代碼;
      • 使用

        cacheDirectory

        ,可以緩存編譯結果,避免多次重複編譯;
    • 多程序并發:
      • webpack-parallel-uglify-plugin: 可多程序并發壓縮 js 檔案,提高壓縮速度;
      • HappyPack: 多程序并發檔案的 Loader 解析;
    • 第三方庫子產品緩存:
      • DLLPlugin 和 DLLReferencePlugin 可以提前進行打包并緩存,避免每次都重新編譯;
    • 使用分析:
      • Webpack Analyse / webpack-bundle-analyzer 對打包後的檔案進行分析,尋找可優化的地方;
      • 配置

        profile:true

        ,對各個編譯階段耗時進行監控,尋找耗時最多的地方;
    • source-map

      :
      • 開發:

        cheap-module-eval-source-map

      • 生産:

        hidden-source-map

項目性能優化

1. 編碼優化

編碼優化,指的就是 在代碼編寫時的,通過一些 最佳實踐,提升代碼的執行性能。通常這并不會帶來非常大的收益,但這屬于 程式猿的自我修養,而且這也是面試中經常被問到的一個方面,考察自我管理與細節的處理。

  • 資料讀取:
    • 通過作用域鍊 / 原型鍊 讀取變量或方法時,需要更多的耗時,且越長越慢;
    • 對象嵌套越深,讀取值也越慢;
    • 最佳實踐:
      • 盡量在局部作用域中進行 變量緩存;
      • 避免嵌套過深的資料結構,資料扁平化 有利于資料的讀取和維護;
  • 循環: 循環通常是編碼性能的關鍵點;
    • 代碼的性能問題會再循環中被指數倍放大;
    • 最佳實踐:
      • 盡可能 減少循環次數;
        • 減少周遊的資料量;
        • 完成目的後馬上結束循環;
      • 避免在循環中執行大量的運算,避免重複計算,相同的執行結果應該使用緩存;
      • js 中使用 倒序循環 會略微提升性能;
      • 盡量避免使用 for-in 循環,因為它會枚舉原型對象,耗時大于普通循環;
  • 條件流程性能: Map / Object > switch > if-else

    // 使用 if-else

    if(type === 1) {

    } else if (type === 2) {

    } else if (type === 3) {

    }

    // 使用 switch

    switch (type) {

    case 1:

    break;4

    case 2:

    break;

    case 3:

    break;

    default:

    break;

    }

    // 使用 Map

    const map = new Map([

    [1, () => {}],

    [2, () => {}],

    [3, () => {}],

    ])

    map.get(type)()

    // 使用 Objext

    const obj = {

    1: () => {},

    2: () => {},

    3: () => {},

    }

    objtype

    複制代碼

  • 減少 cookie 體積: 能有效減少每次請求的體積和響應時間;
    • 去除不必要的 cookie;
    • 壓縮 cookie 大小;
    • 設定 domain 與 過期時間;
  • dom 優化:
    • 減少通路 dom 的次數,如需多次,将 dom 緩存于變量中;
    • 減少重繪與回流:
      • 多次操作合并為一次;
      • 減少對計算屬性的通路;
        • 例如 offsetTop, getComputedStyle 等
        • 因為浏覽器需要擷取最新準确的值,是以必須立即進行重排,這樣會破壞了浏覽器的隊列整合,盡量将值進行緩存使用;
      • 大量操作時,可将 dom 脫離文檔流或者隐藏,待操作完成後再重新恢複;
      • 使用

        DocumentFragment / cloneNode / replaceChild

        進行操作;
    • 使用事件委托,避免大量的事件綁定;
  • css 優化:
    • 層級扁平,避免過于多層級的選擇器嵌套;
    • 特定的選擇器 好過一層一層查找: .xxx-child-text{} 優于 .xxx .child .text{}
    • 減少使用通配符與屬性選擇器;
    • 減少不必要的多餘屬性;
    • 使用 動畫屬性 實作動畫,動畫時脫離文檔流,開啟硬體加速,優先使用 css 動畫;
    • 使用

      <link>

      替代原生 @import;
  • html 優化:
    • 減少 dom 數量,避免不必要的節點或嵌套;
    • 避免``空标簽,能減少伺服器壓力,因為 src 為空時,浏覽器仍然會發起請求
      • IE 向頁面所在的目錄發送請求;
      • Safari、Chrome、Firefox 向頁面本身發送請求;
      • Opera 不執行任何操作。
    • 圖檔提前 指定寬高 或者 脫離文檔流,能有效減少因圖檔加載導緻的頁面回流;
    • 語義化标簽 有利于 SEO 與浏覽器的解析時間;
    • 減少使用 table 進行布局,避免使用

      <br />

      <hr />

2. 頁面基礎優化

  • 引入位置: css 檔案

    <head>

    中引入, js 檔案

    <body>

    底部引入;
    • 影響首屏的,優先級很高的 js 也可以頭部引入,甚至内聯;
  • 減少請求 (http 1.0 - 1.1),合并請求,正确設定 http 緩存;
  • 減少檔案體積:
    • 删除多餘代碼:
      • tree-shaking
      • UglifyJs
      • code-spliting
    • 混淆 / 壓縮代碼,開啟 gzip 壓縮;
    • 多份編譯檔案按條件引入:
      • 針對現代浏覽器直接給 ES6 檔案,隻針對低端浏覽器引用編譯後的 ES5 檔案;
      • 可以利用

        <script type="module"> / <script type="module">

        進行條件引入用
    • 動态 polyfill,隻針對不支援的浏覽器引入 polyfill;
  • 圖檔優化:
    • 根據業務場景,與UI探讨選擇 合适品質,合适尺寸;
    • 根據需求和平台,選擇 合适格式,例如非透明時可用 jpg;非蘋果端,使用 webp;
    • 小圖檔合成 雪碧圖,低于 5K 的圖檔可以轉換成 base64 内嵌;
    • 合适場景下,使用 iconfont 或者 svg;
  • 使用緩存:
    • 浏覽器緩存: 通過設定請求的過期時間,合理運用浏覽器緩存;
    • CDN緩存: 靜态檔案合理使用 CDN 緩存技術;
      • HTML 放于自己的伺服器上;
      • 打包後的圖檔 / js / css 等資源上傳到 CDN 上,檔案帶上 hash 值;
      • 由于浏覽器對單個域名請求的限制,可以将資源放在多個不同域的 CDN 上,可以繞開該限制;
    • 伺服器緩存: 将不變的資料、頁面緩存到 記憶體 或 遠端存儲(redis等) 上;
    • 資料緩存: 通過各種存儲将不常變的資料進行緩存,縮短資料的擷取時間;

3. 首屏渲染優化

  • css / js 分割,使首屏依賴的檔案體積最小,内聯首屏關鍵 css / js;
  • 非關鍵性的檔案盡可能的 異步加載和懶加載,避免阻塞首頁渲染;
  • 使用

    dns-prefetch / preconnect / prefetch / preload

    等浏覽器提供的資源提示,加快檔案傳輸;
  • 謹慎控制好 Web字型,一個大字型包足夠讓你功虧一篑;
    • 控制字型包的加載時機;
    • 如果使用的字型有限,那盡可能隻将使用的文字單獨打包,能有效減少體積;
  • 合理利用 Localstorage / server-worker 等存儲方式進行 資料與資源緩存;
  • 厘清輕重緩急:
    • 重要的元素優先渲染;
    • 視窗内的元素優先渲染;
  • 服務端渲染(SSR):
    • 減少首屏需要的資料量,剔除備援資料和請求;
    • 控制好緩存,對資料/頁面進行合理的緩存;
    • 頁面的請求使用流的形式進行傳遞;
  • 優化使用者感覺:
    • 利用一些動畫 過渡效果,能有效減少使用者對卡頓的感覺;
    • 盡可能利用 骨架屏(Placeholder) / Loading 等減少使用者對白屏的感覺;
    • 動畫幀數盡量保證在 30幀 以上,低幀數、卡頓的動畫甯願不要;
    • js 執行時間避免超過 100ms,超過的話就需要做:
      • 尋找可 緩存 的點;
      • 任務的 分割異步 或 web worker 執行;

全棧基礎

其實我覺得并不能講前端的天花闆低,隻是說前端是項更多元化的工作,它需要涉及的知識面很廣。你能發現,從最開始的簡單頁面到現在,其實整個領域是在不斷地往外拓張。在許多的大廠的面試中,具備一定程度的 服務端知識、運維知識,甚至數學、圖形學、設計 等等,都可能是你占得先機的法寶。

Nginx

輕量級、高性能的 Web 伺服器,在現今的大型應用、網站基本都離不開 Nginx,已經成為了一項必選的技術;其實可以把它了解成 入口網關,這裡我舉個例子可能更好了解:

當你去銀行辦理業務時,剛走進銀行,需要到入門處的機器排隊取号,然後按指令到對應的櫃台辦理業務,或者也有可能告訴你,今天不能排号了,回家吧!

這樣一個場景中,取号機器就是 Nginx(入口網關)。一個個櫃台就是我們的業務伺服器(辦理業務);銀行中的保險箱就是我們的資料庫(存取資料);🤣

中進階前端大廠面試秘籍,直通大廠(下)屏蔽 192.168.1.1 的通路;允許 192.168.1.2 ~ 10 的通路;
  • 特點:
    • 輕量級,配置友善靈活,無侵入性;
    • 占用記憶體少,啟動快,性能好;
    • 高并發,事件驅動,異步;
    • 熱部署,修改配置熱生效;
  • 架構模型:
    • 基于 socket 與 Linux epoll (I/O 事件通知機制),實作了 高并發;
      • 使用子產品化、事件通知、回調函數、計時器、輪詢實作非阻塞的異步模式;
      • 磁盤不足的情況,可能會導緻阻塞;
    • Master-worker 程序模式:
      • Nginx 啟動時會在記憶體中常駐一個 Master 主程序,功能:
        • 讀取配置檔案;
        • 建立、綁定、關閉 socket;
        • 啟動、維護、配置 worker 程序;
        • 編譯腳本、打開日志;
      • master 程序會開啟配置數量的 worker 程序,比如根據 CPU 核數等:
        • 利用 socket 監聽連接配接,不會新開程序或線程,節約了建立與銷毀程序的成本;
        • 檢查網絡、存儲,把新連接配接加入到輪詢隊列中,異步處理;
        • 能有效利用 cpu 多核,并避免了線程切換和鎖等待;
    • 熱部署模式:
      • 當我們修改配置熱重新開機後,master 程序會以新的配置新建立 worker 程序,新連接配接會全部交給新程序處理;
      • 老的 worker 程序會在處理完之前的連接配接後被 kill 掉,逐漸全替換成新配置的 worker 程序;
  • 配置:
    • 官網下載下傳;
    • 配置檔案路徑:

      /usr/local/etc/nginx/nginx.conf

    • 啟動: 終端輸入

      nginx

      ,通路

      localhost:8080

      就能看到

      Welcome...

    • nginx -s stop

      : 停止服務;
    • nginx -s reload

      : 熱重新開機服務;
    • 配置代理:

      proxy_pass

      • 在配置檔案中配置即可完成;

        server {

        listen 80;

        location / {

        proxy_pass http://xxx.xxx.xx.xx:3000;

        }

        }

        複制代碼

  • 常用場景:
    • 代理:
      • 其實 Nginx 可以算一層 代理伺服器,将用戶端的請求處理一層後,再轉發到業務伺服器,這裡可以分成兩種類型,其實實質就是 請求的轉發,使用 Nginx 非常合适、高效;
    • 正向代理:
      • 即使用者通過通路這層正向代理伺服器,再由代理伺服器去到原始伺服器請求内容後,再傳回給使用者;
      • 例如我們常使用的 VPN 就是一種常見的正向代理模式。通常我們無法直接通路谷歌伺服器,但是通過通路一台國外的伺服器,再由這台伺服器去請求谷歌傳回給使用者,使用者即可通路谷歌;
      • 特點:
        • 代理伺服器屬于 用戶端層,稱之為正向代理;
        • 代理伺服器是 為使用者服務,對于使用者是透明的,使用者知道自己通路代理伺服器;
        • 對内容伺服器來說是 隐藏 的,内容伺服器并無法厘清通路是來自使用者或者代理;
    中進階前端大廠面試秘籍,直通大廠(下)屏蔽 192.168.1.1 的通路;允許 192.168.1.2 ~ 10 的通路;
    • 反向代理:
      • 使用者通路頭條的反向代理網關,通過網關的一層處理和排程後,再由網關将通路轉發到内部的伺服器上,傳回内容給使用者;
      • 特點:
        • 代理伺服器屬于 服務端層,是以稱為反向代理。通常代理伺服器與内部内容伺服器會隸屬于同一内網或者叢集;
        • 代理伺服器是 為内容伺服器服務 的,對使用者是隐藏的,使用者不清楚自己通路的具體是哪台内部伺服器;
        • 能有效保證内部伺服器的 穩定與安全;
    中進階前端大廠面試秘籍,直通大廠(下)屏蔽 192.168.1.1 的通路;允許 192.168.1.2 ~ 10 的通路;
    • 反向代理的好處:
      • 安全與權限:
        • 使用者通路必須通過反向代理伺服器,也就是便可以在做這層做統一的請求校驗,過濾攔截不合法、危險的請求,進而就能更好的保證伺服器的安全與穩定;
      • 負載均衡: 能有效配置設定流量,最大化叢集的穩定性,保證使用者的通路品質;
    • 負載均衡:
      • 負載均衡是基于反向代理下實作的一種 流量配置設定 功能,目的是為了達到伺服器資源的充分利用,以及更快的通路響應;
      • 其實很好了解,還是以上面銀行的例子來看: 通過門口的取号器,系統就可以根據每個櫃台的業務排隊情況進行使用者的分,使每個櫃台都保持在一個比較高效的運作狀态,避免出現配置設定不均的情況;
      • 由于使用者并不知道内部伺服器中的隊列情況,而反向代理伺服器是清楚的,是以通過 Nginx,便能很簡單地實作流量的均衡配置設定;
      • Nginx 實作:

        Upstream

        子產品, 這樣當使用者通路

        http://xxx

        時,流量便會被按照一定的規則配置設定到

        upstream

        中的3台伺服器上;

        http {

        upstream xxx {

        server 1.1.1.1:3001;

        server 2.2.2.2:3001;

        server 3.3.3.3:3001;

        }

        server {

        listen 8080;

        location / {

        proxy_pass http://xxx;

        }

        }

        }

        複制代碼

      • 配置設定政策:
        • 伺服器權重(

          weight

          ):
          • 可以為每台伺服器配置通路權重,傳入參數

            weight

            ,例如:

            upstream xxx {

            server 1.1.1.1:3001 weight=1;

            server 2.2.2.2:3001 weight=1;

            server 3.3.3.3:3001 weight=8;

            }

            複制代碼

        • 時間順序(預設): 按使用者的通路的順序逐一的配置設定到正常運作的伺服器上;
        • 連接配接數優先(

          least_conn

          ): 優先将通路配置設定到清單中連接配接數隊列最短的伺服器上;
        • 響應時間優先(

          fair

          ): 優先将通路配置設定到清單中通路響應時間最短的伺服器上;
        • ip_hash: 通過 ip_hash 指定,使每個 ip 使用者都通路固定的伺服器上,有利于使用者特異性資料的緩存,例如本地 session 服務等;
        • url_hash: 通過 url_hash 指定,使每個 url 都配置設定到固定的伺服器上,有利于緩存;
    • Nginx 對于前端的作用:
      • 1. 快速配置靜态伺服器,當通路

        localhost:80

        時,就會預設通路到

        /Users/files/index.html

        server {

        listen 80;

        server_name localhost;

        location / {
        	root   /Users/files;
        	index  index.html;
        }
                   

        }

        複制代碼

      • 2. 通路限制: 可以制定一系列的規則進行通路的控制,例如直接通過 ip 限制:

        屏蔽 192.168.1.1 的通路;

        允許 192.168.1.2 ~ 10 的通路;

        location / {

        deny 192.168.1.1;

        allow 192.168.1.2/10;

        deny all;

        }

        複制代碼

      • 3. 解決跨域: 其實跨域是 浏覽器的安全政策,這意味着隻要不是通過浏覽器,就可以繞開跨域的問題。是以隻要通過在同域下啟動一個 Nginx 服務,轉發請求即可;

        location ^~/api/ {

        # 重寫請求并代理到對應域名下

        rewrite ^/api/(.*)$ /$1 break;

        proxy_pass https://www.cross-target.com/;

        }

        複制代碼

      • 4. 圖檔處理: 通過 ngx_http_image_filter_module 這個子產品,可以作為一層圖檔伺服器的代理,在通路的時候 對圖檔進行特定的操作,例如裁剪,旋轉,壓縮等;
      • 5. 本地代理,繞過白名單限制: 例如我們在接入一些第三方服務時經常會有一些域名白名單的限制,如果我們在本地通過

        localhost

        進行開發,便無法完成功能。這裡我們可以做一層本地代理,便可以直接通過指定域名通路本地開發環境;

        server {

        listen 80;

        server_name www.toutiao.com;

        location / {

        proxy_pass http://localhost:3000;

        }

        }

        複制代碼

Docker

Docker,是一款現在最流行的 軟體容器平台,提供了軟體運作時所依賴的環境。

  • 實體機:
    • 硬體環境,真實的 計算機實體,包含了例如實體記憶體,硬碟等等硬體;
  • 虛拟機:
    • 在實體機上 模拟出一套硬體環境和作業系統,應用軟體可以運作于其中,并且毫無感覺,是一套隔離的完整環境。本質上,它隻是實體機上的一份 運作檔案。
  • 為什麼需要虛拟機?
    • 環境配置與遷移:
      • 在軟體開發和運作中,環境依賴一直是一個很頭疼的難題,比如你想運作 node 應用,那至少環境得安裝 node 吧,而且不同版本,不同系統都會影響運作。解決的辦法,就是我們的包裝包中直接包含運作環境的安裝,讓同一份環境可以快速複制到任意一台實體機上。
    • 資源使用率與隔離:
      • 通過硬體模拟,并包含一套完整的作業系統,應用可以獨立運作在虛拟機中,與外界隔離。并且可以在同一台實體機上,開啟多個不同的虛拟機啟動服務,即一台伺服器,提供多套服務,且資源完全互相隔離,互不影響。不僅能更好提高資源使用率率,降低成本,而且也有利于服務的穩定性。
  • 傳統虛拟機的缺點:
    • 資源占用大:
      • 由于虛拟機是模拟出一套 完整系統,包含衆多系統級别的檔案和庫,運作也需要占用一部分資源,單單啟動一個空的虛拟機,可能就要占用 100+MB 的記憶體了。
    • 啟動緩慢:
      • 同樣是由于完整系統,在啟動過程中就需要運作各種系統應用和步驟,也就是跟我們平時啟動電腦一樣的耗時。
    • 備援步驟多:
      • 系統有許多内置的系統操作,例如使用者登入,系統檢查等等,有些場景其實我們要的隻是一個隔離的環境,其實也就是說,虛拟機對部分需求痛點來說,其實是有點過重的。
  • Linux 容器:
    • Linux 中的一項虛拟化技術,稱為 Linux 容器技術(LXC)。
    • 它在 程序層面 模拟出一套隔離的環境配置,但并沒有模拟硬體和完整的作業系統。是以它完全規避了傳統虛拟機的缺點,在啟動速度,資源利用上遠遠優于虛拟機;
  • Docker:
    • Docker 就是基于 Linux 容器的一種上層封裝,提供了更為簡單易用的 API 用于操作 Docker,屬于一種 容器解決方案。
    • 基本概念: 在 Docker 中,有三個核心的概念:
      • 鏡像 (Image):
        • 從原理上說,鏡像屬于一種 root 檔案系統,包含了一些系統檔案和環境配置等,可以将其了解成一套 最小作業系統。為了讓鏡像輕量化和可移植,Docker 采用了 Union FS 的分層存儲模式。将檔案系統分成一層一層的結構,逐漸從底層往上層建構,每層檔案都可以進行繼承和定制。這裡從前端的角度來了解: 鏡像就類似于代碼中的 class,可以通過繼承與上層封裝進行複用。
        • 從外層系統看來,一個鏡像就是一個 Image 二進制檔案,可以任意遷移,删除,添加;
    • 容器 (Container):
      • 鏡像是一份靜态檔案系統,無法進行運作時操作,就如

        class

        ,如果我們不進行執行個體化時,便無法進行操作和使用。是以 容器可以了解成鏡像的執行個體,即

        new 鏡像()

        ,這樣我們便可以建立、修改、操作容器;一旦建立後,就可以簡單了解成一個輕量級的作業系統,可以在内部進行各種操作,例如運作 node 應用,拉取 git 等;
      • 基于鏡像的分層結構,容器是 以鏡像為基礎底層,在上面封裝了一層 容器的存儲層;
        • 存儲空間的生命周期與容器一緻;
        • 該層存儲層會随着容器的銷毀而銷毀;
        • 盡量避免往容器層寫入資料;
      • 容器中的資料的持久化管理主要由兩種方式:
        • 資料卷 (Volume): 一種可以在多個容器間共享的特殊目錄,其處于容器外層,并不會随着容器銷毀而删除;
        • 挂載主機目錄: 直接将一個主機目錄挂載到容器中進行寫入;
    • 倉庫 (Repository):
      • 為了便于鏡像的使用,Docker 提供了類似于 git 的倉庫機制,在倉庫中包含着各種各樣版本的鏡像。官方服務是 Docker Hub;
      • 可以快速地從倉庫中拉取各種類型的鏡像,也可以基于某些鏡像進行自定義,甚至釋出到倉庫供社群使用;

結語

不知不覺,一個月又過去了,也終于完成了整個系列。其實下篇涉及的許多知識點都是有比較深的拓展空間,部落客自己也水準有限,無法面面俱到,也許甚至會有些争議或者錯誤的見解,還望小夥伴們共同指出和糾正。希望這個面試系列能幫助到大家,好好地将這些知識點進行消化和了解,閉關修煉雖然辛苦,但現在已經是時候出山征戰江湖,收割 Offer 啦~

整個系列其實仍然是屬于淺嘗辄止的階段,後續如果大家想要繼續提升,可以往自己感興趣的方向進行深挖,例如:

  • 全棧: 那可能得更多的去了解 Node / Nginx / 反向代理 / 負載均衡 / PM2 / Docker 等服務端或者運維知識;
  • 跨平台: 可以學習 Hybrid / Flutter / React Native / Swift 等;
  • 視覺遊戲: WebGL / 動畫 / Three.js / Canvas / 遊戲引擎 / VR / AR 等;
  • 底層架構: 浏覽器引擎 / 架構底層 / 機器學習 / 算法等;

總之,學無止境呐。造火箭無止境呐。😂。感謝各位小夥伴的觀看,共同進步,一起成長!

  • (上篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠
  • (中篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠
  • (下篇)中進階前端大廠面試秘籍,寒冬中為您保駕護航,直通大廠

繼續閱讀