前端20個靈魂拷問 完全搞明白你就是中級前端工程師 【中篇】

圖片描述

前端20個靈魂拷問,完全搞明白你就是中級前端工程師 上篇javascript

感受你們比較喜歡看這種類型的文章,之後會多一些。

歡迎加入咱們的前端交流二羣 目前一羣人數有點多 因此開放了二羣 ~ 歡迎加入前端

裏面不少小姐姐哦~~(包括思否小姐姐) 個人微信號在最後·~java

前端越往深度發展,越須要瞭解底層實現原理,借鑑他們的思想去實現業務需求,去實現性能優化,並且去學習新的東西時候也是在這些知識基礎上去學習~ 事半功倍node

爲何我會將這些問題放在中篇,本文會在介紹完這些問題後在後面給出理由react

問題來了

1.爲何會出現模塊化,以及各類模塊化標準

移動端React開源項目,從零搭建的webpack腳手架jquery

前端模塊化出現是一定的,一個很複雜的應用不可能全部的內容都在一個文件中~webpack

模塊化的歷程:git

傳統的命令空間github

代碼實現:web

index.js

(function(w){
    w.a = 1 
})(window)

原理: 在window這個全局對象下面,掛載屬性,那麼全局均可以拿到這個屬性的值,原則上一個js文件做爲一個模塊,就是一個IIFE函數

-> require.js 基於AMD規範

AMD規範採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。這裏介紹用require.js實現AMD規範的模塊化:用require.config()指定引用路徑等,用define()定義模塊,用require()加載模塊。

代碼實現:

// 簡單的對象定義
define({
    color: "black",
    size: "unisize"
});

// 當你須要一些邏輯來作準備工做時能夠這樣定義:
define(function () {
    //這裏能夠作一些準備工做
    return {
        color: "black",
        size: "unisize"
    }
});

// 依賴於某些模塊來定義屬於你本身的模塊
define(["./cart", "./inventory"], function(cart, inventory) {
        //經過返回一個對象來定義你本身的模塊
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

-> sea.js基於CMD規範

CMD是另外一種js模塊化方案,它與AMD很相似,不一樣點在於:AMD 推崇依賴前置、提早執行,CMD推崇依賴就近、延遲執行。此規範實際上是在sea.js推廣過程當中產生的。

代碼實現:

define(function(require, exports, module) {
  var $ = require('jquery');

  exports.sayHello = function() {
    $('#hello').toggle('slow');
  };
});


seajs.config({
  alias: {
    'jquery': 'http://modules.seajs.org/jquery/1.7.2/jquery.js'
  }
});

seajs.use(['./hello', 'jquery'], function(hello, $) {
  $('#beautiful-sea').click(hello.sayHello);
});

原理:頂部引入sea.js的源碼文件,運行時轉換代碼,一開始指定入口文件,根據入口文件定義的數組(或者引入的依賴),去繼續尋找對應的依賴。

-> commonJs

Node.js原生環境支持commonJs模塊化規範

先簡單實現一個require

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // Module code here. In this example, define a function.
    // 模塊代碼在這裏,在這個例子中,咱們定義了一個函數
    function someFunc() {}
    exports = someFunc;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    // 當代碼運行到這裏時,exports 再也不是 module.exports 的引用,而且當前的
    // module 仍舊會導出一個空對象(就像上面聲明的默認對象那樣)
    module.exports = someFunc;
    // At this point, the module will now export someFunc, instead of the
    // default object.
    // 當代碼運行到這時,當前 module 會導出 someFunc 而不是默認的對象
  })(module, module.exports);
  return module.exports;
}

require 就至關於把被引用的 module 拷貝了一份到當前 module

exportmodule.exports暴露出來接口

exportmodule.exports的區別:

export 是 module.exports 的引用。做爲一個引用,若是咱們修改它的值,實際上修改的是它對應的引用對象的值。

commonJS用同步的方式加載模塊。在服務端,模塊文件都存在本地磁盤,讀取很是快,因此這樣作不會有問題。可是在瀏覽器端,限於網絡緣由,更合理的方案是使用異步加載。

一句話簡單總結就是,exports-> {} <- module.exports同時指向一個對象

-> ES6模塊化

目前最經常使用的模塊化規範:
clipboard.png

ES6模塊化規範原生的瀏覽器環境和Node.js環境都不識別,可是要使用,就必需要使用babel編譯成瀏覽器或者Node.js能夠識別的代碼,爲了節省時間,那麼就會出現自動化一鍵打包編譯代碼的工具, - webpack.

ES6最牛逼的地方,不只支持了靜態校驗,能夠同步異步加載,並且統一了先後端的模塊化規範,Node和傳統前端,均可以用這套規範。

ES6 模塊與 CommonJS 模塊的差別
  1. CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用 (首次require不一樣路徑的文件,會在require.cache中保存一份緩存,下次讀取的時候就直接從緩存中讀取了)
  2. CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。

CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成

這也是爲何TypeScript 支持靜態類型檢查的緣由 由於他使用的是ES6模塊化方案

特別提示:如今 Node也能夠用 ES6模塊化方案的 用 experimental 便可

看看commonJs

index.js 
const a = require('./test1.js');
const func = require('./test2');
a.a = 2;
console.log(a.a,'test1');
func()


test2.js
const a = require('./test1')
module.exports = function(){
    console.log(a.a,'test2')
}

test1.js
let a={
    a:1
}
module.exports=a

運行node index.js

輸出結果

clipboard.png

看看ES6

// math.js
export let val = 1
export function add () {
    val++
}
// test.js
import { val, add } from './math.js'
console.log(val) // 1
add()
console.log(val) // 2

React Vue框架實現基本原理以及設計思想~

設計思想和基本原理:

  • 1.由傳統的直接DOM操做改爲了數據驅動的方式去間接替咱們操做DOM
  • 2.每次數據改變須要從新渲染時,只對存在差別對那個部分DOM進行操做。 --diff算法
  • 有一系列對生命週期,其實就是代碼執行順序中給定了一部分的特定函數名稱進行執行,一種約定。

常見的diff算法,有上一個虛擬dom和此次更新後的虛擬dom去對比,而後給真實dom打補丁的方式,也有用真實dom和虛擬dom直接對比的方式。

從零本身編寫一個React框架 我這篇文章附帶了源碼,從零本身實現了一個React框架

前端須要瞭解的常見的算法和數據結構

常見的數據結構:棧,隊列,樹,圖,數組,單鏈表,雙鏈表,圖等...

clipboard.png

clipboard.png

冒泡排序

比較相鄰的兩個元素,若是前一個比後一個大,則交換位置。
第一輪的時候最後一個元素應該是最大的一個。
按照步驟一的方法進行相鄰兩個元素的比較,這個時候因爲最後一個元素已是最大的了,因此最後一個元素不用比較

function bubble_sort(arr){
  for(var i=0;i<arr.length-1;i++){
    for(var j=0;j<arr.length-i-1;j++){
      if(arr[j]>arr[j+1]){
        var swap=arr[j];
        arr[j]=arr[j+1];
        arr[j+1]=swap;
      }
    }
  }
}

var arr=[3,1,5,7,2,4,9,6,10,8];
bubble_sort(arr);
console.log(arr);

快速排序

js代碼實現 解析:快速排序是對冒泡排序的一種改進,第一趟排序時將數據分紅兩部分,一部分比另外一部分的全部數據都要小。而後遞歸調用,在兩邊都實行快速排序。

function quick_sort(arr){
  if(arr.length<=1){
    return arr;
  }
  var pivotIndex=Math.floor(arr.length/2);
  var pivot=arr.splice(pivotIndex,1)[0];

  var left=[];
  var right=[];
  for(var i=0;i<arr.length;i++){
    if(arr[i]<pivot){
      left.push(arr[i]);
    }else{
      right.push(arr[i]);
    }
  }

  return quick_sort(left).concat([pivot],quick_sort(right));
}

var arr=[5,6,2,1,3,8,7,1,2,3,4,7];
console.log(quick_sort(arr));

時間複雜度概念:

一個算法的時間複雜度反映了程序運行從開始到結束所須要的時間。

空間複雜度概念:

一個程序的空間複雜度是指運行完一個程序所需內存的大小。利用程序的空間複雜度,能夠對程序的運行所須要的內存多少有個預先估計。

具體能夠看這篇文章:

JavaScript 算法與數據結構

Node.js的底層fs,net,pathstream等模塊以及express框架使用和操做數據庫

注意, Node.js中不少回調函數的首個參數都是 err

根據路徑同步讀取文件流:

// 在 macOS、Linux 和 Windows 上:
fs.readFileSync('<目錄>');
// => [Error: EISDIR: illegal operation on a directory, read <目錄>]

異步地讀取文件的所有內容:

fs.readFile('路徑', (err, data) => {
  if (err) throw err;
  console.log(data);
});
上面讀取到的 data都是 buffer數據 ,Buffer 類是一個全局變量,用於直接處理二進制數據。

若是路徑存在,則返回 true,不然返回 false。:

fs.existsSync(path)
Node.js中通常同步的 API都是 sync結尾,不帶的通常是異步的,咱們通常都用異步 API

Node.js 中有四種基本的流類型:

Writable - 可寫入數據的流(例如 fs.createWriteStream())。
Readable - 可讀取數據的流(例如 fs.createReadStream())。
Duplex - 可讀又可寫的流(例如 net.Socket)。
Transform - 在讀寫過程當中能夠修改或轉換數據的 Duplex 流(例如 zlib.createDeflate() )。

使用Node.js編寫的靜態資源服務器 這是個人本身編寫的靜態資源服務器

裏面有大量的Buffer操做

Node裏面這些經常使用的模塊,是走向全棧工程師的基礎。越是複雜的應用,對二進制操做會越多,好比本身定義的即時通信協議,你須要把數據一點點的從 Buffer裏切出來。若是是 prob協議,那麼還要反序列化。可是原理大都相似,還有涉及音視頻等。

使用Node.js做爲中間件,同構服務端渲染單頁面應用,以及作轉發請求等操做

爲了解決單頁面應用的SEO問題

傳統的SSR渲染是在服務端把代碼都運行好了而後經過字符串都形式傳給前端渲染

如今都單頁面應用是隻傳輸一個空的HTML文件和不少個js文件 給前端,而後拿到文件後動態生成頁面。這就致使搜索引擎的爬蟲沒法爬到網頁的信息,全部有了同構。

同構就是把單頁面應用,React和Vue這樣框架寫的代碼,在服務端運行一遍(並非運行所有),而後返回字符串給前端渲染,這個時候搜索引擎就能夠爬取到關鍵字了。前端根據服務端返回的字符串渲染生成頁面後,js文件接管後續的邏輯。這樣就是一套完整的同構

React服務端渲染源碼 這個是個人React服務端渲染源碼

客戶端入口文件:

//client/index. js
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { getClientStore } from '../containers/redux-file/store';
import {renderRoutes} from 'react-router-config'
import routers from '../Router';
const store = getClientStore();
const App = () => {
  return (
    <Provider store={store}>
      <BrowserRouter>{renderRoutes(routers)}</BrowserRouter>
    </Provider>
  );
};
ReactDom.hydrate(<App />, document.getElementById('root'));

同構的入口代碼:

// server/index.js
import express from 'express';
import { render } from '../utils';
import { serverStore } from '../containers/redux-file/store';
const app = express();
app.use(express.static('public'));
app.get('*', function(req, res) {
  if (req.path === '/favicon.ico') {
    res.send();
    return;
  }
  const store = serverStore();
  res.send(render(req, store));
});
const server = app.listen(3000, () => {
  var host = server.address().address;
  var port = server.address().port;
  console.log(host, port);
  console.log('啓動鏈接了');
});

render函數:

import Routes from '../Router';
import { renderToString } from 'react-dom/server';
import { StaticRouter, Link, Route } from 'react-router-dom';
import React from 'react';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import routers from '../Router';
import { matchRoutes } from 'react-router-config';
export const render = (req, store) => {
  const matchedRoutes = matchRoutes(routers, req.path);
  matchedRoutes.forEach(item => {
    //若是這個路由對應的組件有loadData方法
    if (item.route.loadData) {
      item.route.loadData(store);
    }
  });
  console.log(store.getState(),Date.now())
  const content = renderToString(
    <Provider store={store}>
      <StaticRouter location={req.path}>{renderRoutes(routers)}</StaticRouter>
    </Provider>
  );
看起來眼花繚亂 其實就是把代碼運行在服務端,而後拼接成字符串給前端

惟一有點特別的地方:

服務端代碼注水:

<script>window.context={state:${JSON.stringify(store.getState())}}</script>

客戶端代碼脫水:

store.js

import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';

export const getClientStore = () => {
   //下面這行代碼就是代碼脫水,createStore是能夠傳入第二個參數的,去閱讀源碼能夠了解
  const defaultState = window.context ? window.context.state : {};
  return createStore(reducers, defaultState, applyMiddleware(thunk));
};


export const serverStore = () => {
  return createStore(reducers, applyMiddleware(thunk));
};

跟我一塊兒默唸:

同構的祕訣:

1.代碼如今服務端運行
2.返回字符串和注水後的數據給前端
3.前端拿到字符串和注水數據後,脫水渲染,而後js文件接管,這時候又是單頁面應用的邏輯了~

通過好久考慮才以爲應該寫這5個問題,接下來的5個問題會在下週更新。

爲何要挑選這五個問題

模塊化規範的學習,是爲了擁有改造舊輪子的能力

數據結構和算法是爲了擁有編寫輕量級框架和性能優化打基礎

Node.js的使用是爲了向全棧發展打基礎

同構是爲了走向高併發場景打基礎

框架的實現原理,是爲了讓咱們學習這種設計思想,在平時業務代碼書寫時候,考慮時間複雜度和空間度的同時也要考慮框架底層實現。

以爲寫得不錯,能夠給個 star

歡迎加入咱們的二羣哦~

個人我的微信號:CALASFxiaotan

相關文章
相關標籤/搜索