金九銀十衝刺大廠,你須要知道的性能優化和手寫源碼

寫在開頭

  • 今天這些只是基礎知識,面試時候若是面試很高級的崗位,只靠背面試題是很容易被識破,建議你們只是做爲一個學習的點,不斷去深刻、實踐在項目中。有一些同窗說前端很難,不少東西,學不完,我想你多是走錯了方向,畢竟人不是萬能,不要太深刻那些對你目前來講沒有意義的東西,若是你有對現狀不是很滿意,能夠在下面評論留言,我能夠給你一些學習建議.
  • 個人手寫源碼教程集合提供給你們學習,地址是:倉庫地址,但願你們給個star~

正式開始

  • 本文爲上篇-純前端,不涉及Node.jsElectronReact-nativeLinuxDocker、數據庫、redis、消息隊列和操做系統等知識.根據經驗,看上篇的人最多,由於畢竟這部分的前端佔比大。有必定技術基礎的,能夠看看後面文章。

如何讓display出現「動畫

  • 初始化APPdisplay爲"none"
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
          #app {
            width: 200px;
            height: 200px;
            background-color: red;
            display: none;
            transition: all 1s;
        }
    </style>
</head>

<body>
    <div id="app">
    </div>
    <button id="test">測試</button>
</body>

</html>
  • 初始化界面:

  • 此時我將app的display初始化爲none,而且寫入腳本文件
<style>
        #app {
            width: 200px;
            height: 200px;
            background-color: red;
            display: none;
        }
    </style>
    。。。
<script>
     test.onclick = function () {
        const app = document.querySelector('#app')
        console.log(app, 'app')
        app.style.transform = "translateX(200px)"
        app.style.display = "block"
    }
</script>
  • 初始化界面變成了這樣:

  • 此時,我點擊測試按鈕

  • 並無出現動畫,很是生硬的出來了,有一些場景我又要性能,好比初始化不渲染,可是當它出現又要有動畫的時候,就有可能使用這行代碼
test.onclick = function () {
        const app = document.querySelector('#app')
        console.log(app, 'app')
        app.style.display = "block"
        const height = app.offsetHeight
        app.style.transform = "translateX(200px)"
    }
  • 當我加入 const height = app.offsetHeight這行代碼的時候,再點擊測試按鈕,display切換就順帶出來了「動畫」,有了過分效果
  • 爲何會出現動畫了呢? 由於我讀取dom的這些特殊屬性時,瀏覽器就會強制清空渲染隊列一次,讓我拿到最新的值。也就是說讀取的時候,其實已是display爲"block"了,所以。咱們出現了過渡動畫

  • 完整代碼
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #app {
            width: 200px;
            height: 200px;
            background-color: red;
            display: none;
            transition: all 1s;
        }
    </style>
</head>

<body>
    <div id="app">
    </div>
    <button id="test">測試</button>
</body>
<script>
    test.onclick = function () {
        const app = document.querySelector('#app')
        console.log(app, 'app')
        app.style.display = "block"
        const height = app.offsetHeight
        app.style.transform = "translateX(200px)"
    }
</script>

</html>
  • 要注意的一點是,除了手動讀取特殊屬性清空瀏覽器渲染隊列外,瀏覽器也會有本身的一個隊列閥值,當達到後,會自動清空。這就是爲何在一個for循環裏面屢次操做DOM,可是它不會真的渲染那麼屢次的緣由,由於瀏覽器幫咱們維護了一個隊列,擇機渲染。

前端出現性能問題可能有哪些層面的緣由?

  • 答:html

    • 網絡傳輸層面
    • 前端渲染機制層面
    • js代碼執行層面的
    • 部署層面
    • 產品設計層面
    • 前端緩存機制層面
    • 功能實現方式層面
    • 等...
  • 逐個擊破:
  • 網絡傳輸層面:對於這塊,應該跟前端緩存機制層面的問題一塊兒解決,咱們看掘金的代碼分割:
  • 不須要看掘金的工程化環境配置,我就大概知道他們配置是怎樣的。webpack/或其餘工具打包,精細化分割了代碼。將node_modules(vendor開頭)和本身寫的src(default開頭)源碼分開打包了.這樣最小的限度的縮小了hash值改變帶來的http緩存失效問題.在webpack中配置splitChunks.
splitChunks: {
    // 表示選擇哪些 chunks 進行分割,可選值有:async,initial和all
    chunks: "async",
    // 表示新分離出的chunk必須大於等於minSize,默認爲30000,約30kb。
    minSize: 30000,
    // 表示一個模塊至少應被minChunks個chunk所包含才能分割。默認爲1。
    minChunks: 1,
    // 表示按需加載文件時,並行請求的最大數目。默認爲5。
    maxAsyncRequests: 5,
    // 表示加載入口文件時,並行請求的最大數目。默認爲3。
    maxInitialRequests: 3,
    // 表示拆分出的chunk的名稱鏈接符。默認爲~。如chunk~vendors.js
    automaticNameDelimiter: '~',
    // 設置chunk的文件名。默認爲true。當爲true時,splitChunks基於chunk和cacheGroups的key自動命名。
    name: true,
    // cacheGroups 下能夠能夠配置多個組,每一個組根據test設置條件,符合test條件的模塊,就分配到該組。模塊能夠被多個組引用,但最終會根據priority來決定打包到哪一個組中。默認將全部來自 node_modules目錄的模塊打包至vendors組,將兩個以上的chunk所共享的模塊打包至default組。
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        // 
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}
  • 有人會疑問,爲何會把這個問題,放在第一個?由於這是拋磚引玉,不少性能問題來源於此,小項目感知很小,可是對於一個百萬行,千萬行級別的巨無霸項目,提高就會很是大,這都是我實踐過的。(首屏性能可能會由於精細化分割代碼提高十倍,我強烈推薦你們使用精細化分割手段)
  • 代碼分割完了,把依賴拆解開了,可是一個組件代碼有一萬多行,傳輸的js文件仍是有幾MB大小,這個問題能忽略嗎?固然不能夠。咱們通常生產環境部署時候,會藉助CDN,在各類雲廠商提供/本身搭建的CDN服務配置相應的解析。掘金如今已由頭條系接手,我看靜態資源方案的域名開頭使用的是s3.pstatp.com,我查詢大概是跟抖音/相關的域名。或許是本身搭建的CDN服務(千萬不要忽略CDN,靜態資源服務對於服務器來講是很是佔帶寬的,速度和體驗確定會比CDN差不少,越大越重的系統提高越大
  • 網絡傳輸層面, 僅僅是靜態資源嗎?前端

    • 還有前端發送ajax請求
    • 對於一些比較大的數據,例如表格、列表,咱們若是一次性拉取數據,那麼就可能後端須要查詢很長時間數據庫、傳輸也須要很長時間,若是又是弱網,那就要可能用戶點擊一下,就要等上好幾秒甚至十幾秒才能拿到數據繼續操做(前不久剛設計了一套混合APP改造方案,弱網/無網絡狀況擁有正常的體驗,以爲分塊傳輸就是核心,特別在APP端)
    • 對於ws/wss協議、TCP等私有協議雙工通訊的node

      • 心跳檢測、發送隊列持久化是必要的,手機端常常出現網絡切換、弱網狀況,那就避免不了心跳檢測保活,咱們能夠參考一種監控實現手段,他們爲了不監控數據的重複上傳,後端經過統一hash算法和接收到的錯誤上報,生成對應的後綴空文件,發佈到CDN。這樣前端每次上報時候,都會用統一hash算法生成對應後綴,請求那個地址,若是存在,就說明後端已經接受到了這個上報,能夠丟棄這個上報。同理,咱們能夠參考這個,能夠用空的CDN文件作網絡檢測質量檢測(由於一些跨平臺APP框架沒法精確獲取當前網絡質量),而後再將發送請求隊列持久化便可保證請求不丟失。(對於IM場景的APP,那就要考慮更多,這裏不對IM展開講)
  • Js代碼執行層面的,先看一段代碼:
// 真奧義之阻塞JS主線程之休眠
const sleep = (delay = 500) => { 
 let t = Date.now(); 
 while(Date.now() - t <= delay) { 
 continue; 
 } 
};
  • 上面這段代碼,會阻塞JS主解析線程,默認是500ms,這意味着,這500ms,用戶操做咱們的系統,是不會有任何相應的,由於此時JS主解析線程一直被阻塞。(若是是10秒,那麼會怎樣?若是把這段代碼換成其餘計算的函數/循環,效果也是同樣)。這就是由於JS代碼致使的性能問題,如何解決?固然是分片分片分片!不要寫太耗時的JS同步代碼.
  • 這裏又會有人說 用web worker wasm PWA等技術能夠解決一些問題啊.可是我勸你在技術選型的時候,要考慮一個複雜度和收益的問題。如今仍是有人用JQ和原生JSCanvas開發項目,由於開發最複雜的功能,每每都須要他們。(例如富文本編輯器、可視化編輯等)
  • 對於純粹計算佔用耗時的,能夠考慮web worker技術,使用事件通訊,將大量計算工做交給web worker。Worker 也能夠建立新的 Worker,固然,全部 Worker 必須與其建立者同源(注意:Blink暫時不支持嵌套 Worker)。
var myWorker = new Worker('worker.js'); 
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');

first.onchange = function() {
  myWorker.postMessage([first.value,second.value]);
  console.log('Message posted to worker');
}
  • web worker兼容性很好,若是是webpack環境,須要對應作一些工程化環境處理,web worker在我看來,適用於純粹的大量計算場景。我記得有團隊是用於3D數據可視化處理的,他們使用web worker後提高就很大

  • webAssembly技術,更適用於音視頻領域,更底層的功能處理,例如瀏覽器端要音視頻編解碼或者圖片壓縮處理之類就很適合用它.它有點像瀏覽器版的Node.js的c++插件。Node.js能夠調用C# Go Object-c等語言的插件&通訊,同理,webAssembly也是能夠用各類語言編寫。關於webAssembly這個技術,我寫過一篇文章方便你們入門。
self.importScripts('ffmpeg.js');
onmessage = function(e) {
  console.log('ffmpeg_run', ffmpeg_run);
  var files = e.data;
  console.log(files);
  ffmpeg_run({
    arguments: ['-i', '/input/' + files[0].name, '-b:v', '64k', '-bufsize', '64k', '-vf', 'showinfo', '-strict', '-2', 'out.mp4'],
    files: files,
  }, function(results) {
    console.log('result',results);
    self.postMessage(results[0].data, [results[0].data]);
  });
}
  • 這裏講到了這些技術,不得不提到V8,因爲它是自動垃圾回收機制,那麼有一可能下一次回收的內存垃圾達到500MB,這個時候就會產生卡頓,有人會莫名其妙,爲何會卡?由於v8垃圾回收也會阻塞主解析線程,因此形成了讓你卡的假象.
這裏留下一個問題,若是使用 Node.js c++插件或者 wasm技術,調用它們模塊暴露的方法,它們的方法很是耗時,此時 js主解析線程掛起嗎?
  • 渲染層面的性能問題:webpack

    • 頻繁觸發的更新,能夠合併爲一幀更新,異步調度處理渲染任務
function defer(fn) {
  //requestIdleCallback的兼容性很差,對於用戶交互頻繁屢次合併更新來講,requestAnimation更有及時性高優先級,requestIdleCallback則適合處理能夠延遲渲染的任務~
  //   if (window.requestIdleCallback) {
  //     return requestIdleCallback(fn);
  //   }
  //高優先級任務 異步的 先掛起 
  return requestAnimationFrame(fn);
}

export function enqueueSetState(stateChange, component) {
  //第一次進來確定會先調用defer函數
  if (setStateQueue.length === 0) {
    //清空隊列的辦法是異步執行 
    defer(flush);
  }
 
  //向隊列中添加對象 key:stateChange value:component
  setStateQueue.push({
    stateChange,
    component
  });

  //若是渲染隊列中沒有這個組件 那麼添加進去
  if (!renderQueue.some(item => item === component)) {
    renderQueue.push(component);
  }
}
  • 對於首屏渲染壓力比較大的,能夠採起webpack的代碼分割&懶加載js文件

  • 以及圖片懶加載,下拉加載更多等手段處理


  • 對於一次性渲染壓力比較大並且不分頁的,能夠考慮使用虛擬列表,像原生APP同樣,只渲染可視區域,可是給用戶無感知的滾動體驗。
  • 對於說很大的超複雜項目,使用可變數據,致使內存使用一直很是高的狀況,可使用不可變數據,減小內存使用,我是真的使用在項目中,確實優化很大。可是前提你的項目用得上,否則就是增長複雜度(特別注意,對比跟渲染,其實都耗時,不要天真覺得用了不可變數據去作對比,而後不從新渲染組件就會提高很大性能)

  • 對於一次性渲染壓力比較大並且不分頁的,能夠考慮使用虛擬列表,像原生APP同樣,只渲染可視區域,可是給用戶無感知的滾動體驗。
  • 對於對於SEO和首屏要求很是高的,能夠考慮作SSR,SSR有成熟的框架和庫,能夠考慮用nuxt,next等直接生成靜態資源文件
性能是一個很是大的學問,每每都看重這方面,可是真正有對性能要求很高的項目,每每都是環境很複雜的,例如使用 JSC++插件去作解析播放同一個格式的音頻,解析速度哪一個快,這些都是須要實際項目和團隊支撐你去實踐,這裏我以爲也寫得比較淺,雖然說核心技術不外泄,可是這些大部分人確定都夠用了

手寫Promise核心原理

  • .then實現鏈式調用,每次都是返回一個新的Promise,跟JQuery的鏈式調用類似(返回this
MyPromise.prototype.then = function (onResolved, onRejected) { // 定義then      
    const self = this;
    // 指定回調函數的默認值(必須是函數)
    onResolved = typeof onResolved==='function' ?  onResolved : value => value;
    onRejected = typeof onRejected==='function' ?  onRejected : reason => {throw reason};

    return new MyPromise((resolve,reject)=>{                    // 返回一個新的MyPromise對象
      function handle(callback) {
          // 返回的MyPromise結果由onResolved/onRejected的結果決定
        // 一、拋出異常MyPromise結果爲失敗      reason爲結果
        // 二、返回的是MyPromise               MyPromise爲當前的結果
        // 三、返回的不是MyPromise             value爲結果

        // 須要經過捕獲獲取才能知道有沒有異常
        try{
          const result = callback(self.data)
          // 判斷是否是MyPromise
          if ( result instanceof MyPromise){
            // 只有then才知道結果
            result.then(value=>resolve(value),reason=>reject(reason))
          }else{
            resolve(result)
          }
        }catch(error){
          reject(error)                                          // 返回的結果爲reject(error)  上面第一點
        }
      }
    
      // 判斷當前的status
      if (self.status === FULFILLED){                           // 狀態爲 fulfilled
        setTimeout(()=>{                                        // 當即執行異步回調
          handle(onResolved);
        })                                                        
      } else if (self.status === REJECTED){                     // 狀態爲 rejected
        setTimeout(()=>{                                        // 當即執行異步回調
          handle(onRejected);
        })
      }else{                                                    // pendding將成功和失敗保存到callbacks裏緩存起來
        self.callbacks.push({
          onResolved(value){                                    //函數裏面調用回調函數 而且根據回調函數的結果改變MyPromise的結果
            handle(onResolved)                                  //爲何這裏沒有setTimeout,由於上面已經寫到過改變狀態後回去callbacks裏循環待執行的回調函數
          },
          onRejected(reason){
            handle(onRejected)
          }
        })
      }
    })
  }

手寫redux核心原理

  • createStore裏的實現,根據是否傳入了中間件作處理
export default function createStore(reducer, enhancer) {
    if (typeof enhancer !== 'undefined') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}
  • 中間件實現,經過reduce,將上次的結果逐個傳入,核心在於compose,支持了多箇中間件使用.
import compose from './compose';
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer)
        let dispatch = store.dispatch
        let chain = []

        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)

        return {
            ...store,
            dispatch
        }
    }
}

export default function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

手寫微前端框架原理

  • 微前端的模式:

  • 微前端原理:c++

    • 經過fetch請求,經過配置的entry入口,去對應的地址拉取index.html文件,獲取他們所需的資源和全部標籤、DOM節點
    • 拉取他們的資源所有字符串化.
    • 把資源生成對應DOM節點和標籤塞入基座中以及用key-value形式緩存內存中(避免重複發送請求拉取)
    • 像子應用同樣渲染和交互
  • 代碼實現:
  • 加載子應用
async function loadApp() {
  const shouldMountApp = Apps.filter(shouldBeActive);
  const App = shouldMountApp.pop();
  fetch(App.entry)
    .then(function (response) {
      return response.text();
    })
    .then(async function (text) {
      const dom = document.createElement('div');
      dom.innerHTML = text;
      const entryPath = App.entry;
      const subapp = document.querySelector('#subApp-content');
      subapp.appendChild(dom);
      handleScripts(entryPath, subapp, dom);
      handleStyles(entryPath, subapp, dom);
    });
}
  • 拉取&生成資源標籤等
async function handleScripts(entryPath, subapp, dom) {
  const scripts = dom.querySelectorAll('script');
  const paromiseArr =
    scripts &&
    Array.from(scripts).map((item) => {
      if (item.src) {
        const url = window.location.protocol + '//' + window.location.host;
        return fetch(`${entryPath}/${item.src}`.replace(url, '')).then(
          function (response) {
            return response.text();
          }
        );
      } else {
        return Promise.resolve(item.textContent);
      }
    });
  const res = await Promise.all(paromiseArr);
  if (res && res.length > 0) {
    res.forEach((item) => {
      const script = document.createElement('script');
      script.innerText = item;
      subapp.appendChild(script);
    });
  }
}

手寫webpack原理

  • webpack打包過程git

    • 1.識別入口文件
    • 2.經過逐層識別模塊依賴。(Commonjs、amd或者es6的import,webpack都會對其進行分析。來獲取代碼的依賴)
    • 3.webpack作的就是分析代碼。轉換代碼,編譯代碼,輸出代碼
    • 4.最終造成打包後的代碼
  • webpack打包原理es6

    • 1.先逐級遞歸識別依賴,構建依賴圖譜
    • 2.將代碼轉化成AST抽象語法樹 ,一個AST抽象語法樹以下所示:
    Node {
    type: 'File',
    start: 0,
    end: 32,
    loc:
     SourceLocation {
       start: Position { line: 1, column: 0 },
       end: Position { line: 1, column: 32 } },
    program:
     Node {
       type: 'Program',
       start: 0,
       end: 32,
       loc: SourceLocation { start: [Position], end: [Position] },
       sourceType: 'module',
       interpreter: null,
       body: [ [Node] ],
       directives: [] },
    comments: [] }
    • 3.在AST階段中去處理代碼
    • 4.把AST抽象語法樹變成瀏覽器能夠識別的代碼, 而後輸出
  • 核心實現過程
  • 將代碼轉化成AST,而且收集依賴
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default; 
// traverse 採用的 ES Module 導出,咱們經過 requier 引入的話就加個 .default
const babel = require('@babel/core');

const read = fileName => {
  const buffer = fs.readFileSync(fileName, 'utf-8');
  const AST = parser.parse(buffer, { sourceType: 'module' });
  console.log(AST);
  // 依賴收集
    const dependencies = {};

    // 使用 traverse 來遍歷 AST
    traverse(AST, {
        ImportDeclaration({ node }) { // 函數名是 AST 中包含的內容,參數是一些節點,node 表示這些節點下的子內容
            const dirname = path.dirname(filename); // 咱們從抽象語法樹裏面拿到的路徑是相對路徑,而後咱們要處理它,在 bundler.js 中才能正確使用
            const newDirname = './' + path.join(dirname, node.source.value).replace('\\', '/'); // 將dirname 和 獲取到的依賴聯合生成絕對路徑
            dependencies[node.source.value] = newDirname; // 將源路徑和新路徑以 key-value 的形式存儲起來
        }
    })

    // 將抽象語法樹轉換成瀏覽器能夠運行的代碼
    const { code } = babel.transformFromAst(AST, null, {
        presets: ['@babel/preset-env']
    })

    return {
        filename,
        dependencies,
        code
    }

};
read('./test1.js');
  • 繪製依賴圖譜
// 建立依賴圖譜函數, 遞歸遍歷全部依賴模塊
const makeDependenciesGraph = (entry) => {
    const entryModule = read(entry)
    const graghArray = [ entryModule ]; // 首先將咱們分析的入口文件結果放入圖譜數組中
    for (let i = 0; i < graghArray.length; i ++) {
    const item = graghArray[i];
        const { dependencies } = item; // 拿到當前模塊所依賴的模塊
        if (dependencies) {
            for ( let j in dependencies ) { // 經過 for-in 遍歷對象
                graghArray.push(read(dependencies[j])); // 若是子模塊又依賴其它模塊,就分析子模塊的內容
            }
        }
    }

    const gragh = {}; // 將圖譜的數組形式轉換成對象形式
    graghArray.forEach( item => {
        gragh[item.filename] = {
            dependencies: item.dependencies,
            code: item.code
        }
  })
  console.log(gragh)
    return gragh;
}
  • 打印gragh獲得的對象:
{ './app.js':
   { dependencies: { './test1.js': './test1.js' },
     code:
      '"use strict";\n\nvar _test = _interopRequireDefault(require("./test1.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(test
1);' },
  './test1.js':
   { dependencies: { './test2.js': './test2.js' },
     code:
      '"use strict";\n\nvar _test = _interopRequireDefault(require("./test2.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(\'th
is is test1.js \', _test["default"]);' },
  './test2.js':
   { dependencies: {},
     code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports["default"] = void 0;\n\nfunction test2() {\n  console.log(\'this is test2 \');\n}\n\nvar _default = tes
t2;\nexports["default"] = _default;' } }
  • 獲取編譯後的代碼
const generateCode = (entry) => {
    // 注意:咱們的 gragh 是一個對象,key是咱們全部模塊的絕對路徑,須要經過 JSON.stringify 來轉換
    const gragh = JSON.stringify(makeDependenciesGraph(entry));
    // 咱們知道,webpack 是將咱們的全部模塊放在閉包裏面執行的,因此咱們寫一個自執行的函數
    // 注意: 咱們生成的代碼裏面,都是使用的 require 和 exports 來引入導出模塊的,而咱們的瀏覽器是不認識的,因此須要構建這樣的函數
    return `
        (function( gragh ) {
            function require( module ) {
                // 相對路徑轉換成絕對路徑的方法
                function localRequire(relativePath) {
                    return require(gragh[module].dependencies[relativePath])
                }
                const exports = {};
                (function( require, exports, code ) {
                    eval(code)
                })( localRequire, exports, gragh[module].code )

                return exports;
            }
            require('${ entry }')
        })(${ gragh })
    `;
}

const code = generateCode('./app.js');

console.log(code)
  • 獲得編譯輸出的代碼code以下:
(function( gragh ) {
                        function require( module ) {
                                // 相對路徑轉換成絕對路徑的方法
                                function localRequire(relativePath) {
                                        return require(gragh[module].dependencies[relativePath])
                                }
                                const exports = {};
                                (function( require, exports, code ) {
                                        eval(code)
                                })( localRequire, exports, gragh[module].code )

                                return exports;
                        }
                        require('./app.js')
                })({"./app.js":{"dependencies":{"./test1.js":"./test1.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test1.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log(_test[\"default\"]);"},"./test1.js":{"dependencies":{"./test2.js":"./test2.js"},"code":"\"use strict\";\n\nvar _test = _interopRequireDefault(require(\"./test2.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nconsole.log('this is test1.js ', _test[\"default\"]);"},"./test2.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports[\"default\"] = void 0;\n\nfunction test2() {\n  console.log('this is test2 ');\n}\n\nvar _default = test2;\nexports[\"default\"] = _default;"}})
  • 複製這段代碼到瀏覽器中運行便可

最後

  • 紙上得來終覺淺,這些手寫框架&庫源碼,都須要一些時間去鑽研,當你理解了源碼以後,就會發現,源碼其實大都差很少,因此你要抓住每一個框架&庫源碼實現最重要的點,但沒有必要所有去搞懂它,抓重點學習是很重要的
  • 若是感受對你有幫助,能夠點個,讓更多人看到這篇文章
  • 同時,歡迎你關注個人公衆號:[前端巔峯],個人gitHub源碼地址是:https://github.com/JinJieTan/Peter-,記得Star
相關文章
相關標籤/搜索