Web前端開發必看的100道大廠面試題

可愛的小鋒發表於2023-04-06

1. 說說gulp和webpack的區別

開放式題目

Gulp強調的是前端開發的工作流程。我們可以透過配置一系列的task,定義task處理的事務(例如檔案壓縮合並、雪碧圖、啟動server、版本控制等),然後定義執行順序,來讓Gulp執行這些task,從而構建專案的整個前端開發流程。通俗一點來說,“Gulp就像是一個產品的流水線,整個產品從無到有,都要受流水線的控制,在流水線上我們可以對產品進行管理。”

Webpack是一個前端模組化方案,更側重模組打包。我們可以把開發中的所有資源(圖片、js檔案、css檔案等)都看成模組,透過loader(載入器)和plugins(外掛)對資源進行處理,打包成符合生產環境部署的前端資源。 Webpack就是需要透過其配置檔案(Webpack.config.js)中 entry 配置的一個入口檔案(JS檔案),然後在解析過程中,發現其他的模組,如scss等檔案,再呼叫配置的loader或者外掛對相關檔案進行解析處理。

雖然Gulp 和 Webpack都是前端自動化構建工具,但看2者的定位就知道不是對等的。Gulp嚴格上講,模組化不是他強調的東西,旨在規範前端開發流程。Webpack更明顯的強調模組化開發,而那些檔案壓縮合並、預處理等功能,不過是他附帶的功能。

2. 小程式路由跳轉

  1. 透過元件navigator跳轉,設定url屬性指定跳轉的路徑,設定open-type屬性指定跳轉的型別(可選),open-type的屬性有 redirect, switchTab, navigateBack
// redirect 對應 API 中的 wx.redirect 方法
<navigator url="/page/redirect/redirect?title=redirect" open-type="redirect">在當前頁開啟</navigator>

// navigator 元件預設的 open-type 為 navigate 
<navigator url="/page/navigate/navigate?title=navigate">跳轉到新頁面</navigator>

// switchTab 對應 API 中的 wx.switchTab 方法
<navigator url="/page/index/index" open-type="switchTab">切換 Tab</navigator>

// reLanch 對應 API 中的 wx.reLanch 方法
<navigator url="/page/redirect/redirect?title=redirect" open-type="redirect">//關閉所有頁面,開啟到應用內的某個頁面

// navigateBack 對應 API 中的 wx.navigateBack 方法
<navigator url="/page/index/index" open-type="navigateBack">關閉當前頁面,返回上一級頁面或多級頁面</navigator>
  1. 透過api跳轉,wx.navigateTo() , wx.navigateBack(), wx.redirectTo() , wx.switchTab(), wx.reLanch()
wx.navigateTo({
  url: 'page/home/home?user_id=1'  // 頁面 A
})
wx.navigateTo({
  url: 'page/detail/detail?product_id=2'  // 頁面 B
})
// 跳轉到頁面 A
wx.navigateBack({
  delta: 2  //返回指定頁面
})

// 關閉當前頁面,跳轉到應用內的某個頁面。
wx.redirectTo({
url: 'page/home/home?user_id=111'
})

// 跳轉到tabBar頁面(在app.json中註冊過的tabBar頁面),同時關閉其他非tabBar頁面。
wx.switchTab({
url: 'page/index/index'
})

// 關閉所有頁面,開啟到應用內的某個頁面。
wx.reLanch({
url: 'page/home/home?user_id=111'
})

3. 闡述一下http1.0與http2.0的區別,及http和https區別

1、HTTP1.0和HTTP1.1的一些區別

快取處理,HTTP1.0中主要使用Last-Modified,Expires 來做為快取判斷的標準,HTTP1.1則引入了更多的快取控制策略:ETag,Cache-Control…

頻寬最佳化及網路連線的使用,HTTP1.1支援斷點續傳,即返回碼是206(Partial Content)
錯誤通知的管理,在HTTP1.1中新增了24個錯誤狀態響應碼,如409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示伺服器上的某個資源被永久性的刪除…

Host頭處理,在HTTP1.0中認為每臺伺服器都繫結一個唯一的IP地址,因此,請求訊息中的URL並沒有傳遞主機名(hostname)。但隨著虛擬主機技術的發展,在一臺物理伺服器上可以存在多個虛擬主機(Multi-homed Web Servers),並且它們共享一個IP地址。HTTP1.1的請求訊息和響應訊息都應支援Host頭域,且請求訊息中如果沒有Host頭域會報告一個錯誤(400 Bad Request)

長連線,HTTP1.1中預設開啟Connection: keep-alive,一定程度上彌補了HTTP1.0每次請求都要建立連線的缺點

2、HTTP2.0和HTTP1.X相比的新特性

新的二進位制格式(Binary Format),HTTP1.x的解析是基於文字,基於文字協議的格式解析存在天然缺陷,文字的表現形式有多樣性,要做到健壯性考慮的場景必然很多,二進位制則不同,只認0和1的組合,基於這種考慮HTTP2.0的協議解析決定採用二進位制格式,實現方便且健壯

header壓縮,HTTP1.x的header帶有大量資訊,而且每次都要重複傳送,HTTP2.0使用encoder來減少需要傳輸的header大小,通訊雙方各自cache一份header fields表,既避免了重複header的傳輸,又減小了需要傳輸的大小

服務端推送(server push),例如我的網頁有一個sytle.css的請求,在客戶端收到sytle.css資料的同時,服務端會將sytle.js的檔案推送給客戶端,當客戶端再次嘗試獲取sytle.js時就可以直接從快取中獲取到,不用再發請求了

4. 談談宏任務與微任務的理解,舉一個宏任務與微任務的api

要理解宏任務(macrotask)與微任務(microtask),就必須瞭解javascript中的事件迴圈機制(event loop)以及js程式碼的執行方式

要了解js程式碼的執行方式,得先搞懂以下幾個概念:

JS是單執行緒執行

單執行緒指的是JS引擎執行緒

宿主環境

JS執行的環境,一般為瀏覽器或者Node

執行棧

是一個儲存函式呼叫的棧結構,遵循先進後出的原則

JS引擎常駐於記憶體中,等待宿主將JS程式碼或函式傳遞給它執行,如何傳遞,這就是事件迴圈(event loop)所做的事情:當js執行棧空閒時,事件迴圈機制會從任務佇列中提取第一個任務進入到執行棧執行,優先提取微任務(microtask),待微任務佇列清空後,再提取宏任務(macrotask),並不斷重複該過程

在實際應用中,宏任務(macrotask)與微任務(microtask)的API分別如下:

宏任務
setTimeout/setInterval
ajax
setImmediate (Node 獨有)
requestAnimationFrame (瀏覽器獨有)
I/O
UI rendering (瀏覽器獨有)
微任務
process.nextTick (Node 獨有)
Promise
Object.observe
MutationObserver

5. 如何在TS中對函式的返回值進行型別約束

ts中函式引數的型別定義

函式的引數可能是一個,也可能是多個,有可能是一個變數,一個物件,一個函式,一個陣列等等。

1.函式的引數為單個或多個單一變數的型別定義

function fntA(one, two, three) {
    // 引數 "two" 隱式具有 "any" 型別,但可以從用法中推斷出更好的型別。
    return one + two + three
}
const aResult = fntA(1, '3', true)

修改後:

function fntA(one: number, two: string, three: boolean) {
    return one + two + three
}
const aResult1 = fntA(1, '3', true)
// 如果函式的引數為單個或者多個變數的時候,只需要為這些引數進行靜態型別下的基礎型別定義就行

2. 函式的引數為陣列的型別定義

function fntB(arr) {
    //引數 "arr" 隱式具有 "any" 型別,但可以從用法中推斷出更好的型別。
    return arr[0]
}
const bResult = fntB([1, 3, 5])

修改後:

function fntB(arr: number[]) {
    return arr[0]
}
const bResult1 = fntB([1, 3, 5])
// 如果引數是陣列時,只需要為這些變數進行物件型別下的陣列型別定義

3.函式的引數為物件的型別定義

function fntC({ one, two }) {
    return one + two
}
const cResult = fntC({ one: 6, two: 10 })

修改後:

function fntC({ one, two }: { one: number, two: number }) {
    return one + two
}
const cResult1 = fntC({ one: 6, two: 10 })
// 如果引數是物件,只需要為這些變數進行物件型別下的物件型別定義

4.函式的引數為函式的型別定義

function fntD(callback) {
    //引數 "callback" 隱式具有 "any" 型別,但可以從用法中推斷出更好的型別
    callback(true)
}
function callback(bl: boolean): boolean {
    console.log(bl)
    return bl
}
const dResult = fntD(callback)

修改後:

function fntD(callback: (bl: boolean) => boolean) {
    callback(true)
}
function callback(bl: boolean): boolean {
    console.log(bl)
    return bl
}
const dResult = fntD(callback)
// 如果引數是函式,只需要為引數進行物件型別下的函式型別定義即可

ts中函式返回值的型別定義

當函式有返回值時,根據返回值的型別在相應的函式位置進行靜態型別定義即可

返回數字:

function getTotal2(one: number, two: number): number {
    return one + two;
}
const total2 = getTotal(1, 2);
// 返回值為數字型別

返回布林值

function getTotal2(one: number, two: number): boolean {
    return Boolean(one + two);
}
const total2 = getTotal(1, 2);
// 返回值為布林型別

返回字串

function getTotal2(one: string, two: string): string{
    return Bone + two;
}
const total2 = getTotal('1', '2');
// 返回值為字串

返回物件

function getObj(name: string, age: number): { name: string, age: number } {
    return {name,age}
}
getObj('小紅',16)
// 返回值為物件

返回陣列

function getArr(arr: number[]) :number[]{
    let newArr = [...arr]
    return newArr
}
getArr([1,2,3,4])
// 返回值為陣列

函式返回值為underfinde,僅僅時為了在內部實現某個功能,我們就可以給他一個型別註解void,代表沒有任何返回值,

function sayName() {
    console.log('hello,world')
}

修改後:

function sayName1(): void {
    console.log('無返回值')
}

當函式沒有返回值時

// 因為總是丟擲異常,所以 error 將不會有返回值
// never 型別表示永遠不會有值的一種型別
function error(message: string): never {
    throw new Error(message);
}

6. 平時工作中有是否有接觸linux系統?說說常用到linux命令?

1、常用的目錄操作命令

建立目錄 mkdir <目錄名稱>
刪除目錄 rm <目錄名稱>
定位目錄 cd <目錄名稱>
檢視目錄檔案 ls ll
修改目錄名 mv <目錄名稱> <新目錄名稱>
複製目錄 cp <目錄名稱> <新目錄名稱>

2、常用的檔案操作命令

建立檔案 touch <檔名稱> vi <檔名稱>
刪除檔案 rm <檔名稱>
修改檔名 mv <檔名稱> <新檔名稱>
複製檔案 cp <檔名稱> <新檔名稱>

3、常用的檔案內容操作命令

檢視檔案 cat <檔名稱> head <檔名稱> tail <檔名稱>
編輯檔案內容 vi <檔名稱>
查詢檔案內容 grep '關鍵字' <檔名稱>

7. 常見的 HTTP Method 有哪些? GET/POST 區別?

1、常見的HTTP方法

GET:獲取資源
POST:傳輸資源
PUT:更新資源
DELETE:刪除資源
HEAD:獲得報文首部

2、GET/POST的區別

GET在瀏覽器回退時是無害的,而POST會再次提交請求
GET請求會被瀏覽器主動快取,而POST不會,除非手動設定
GET請求引數會被完整保留在瀏覽器的歷史記錄裡,而POST中的引數不會被保留
GET請求在URL中傳送的引數是有長度限制的,而POST沒有限制
GET引數透過URL傳遞,POST放在Request body中
GET請求只能進行 url 編碼,而POST支援多種編碼方式
GET產生的URL地址可以被收藏,而POST不可以
對引數的資料型別,GET只接受ASCII字元,而POST沒有限制
GET比POST更不安全,因為引數直接暴露在URL上,所以不能用來傳遞敏感資訊

8. vue打包記憶體過大,怎麼使用webpack來進行最佳化

開放式題目

打包最佳化的目的

1、最佳化專案啟動速度,和效能

2、必要的清理資料

3、效能最佳化的主要方向

cdn載入

-壓縮js

減少專案在首次載入的時長(首屏載入最佳化)

4、目前的解決方向

cdn載入不比多說,就是改為引入外部js路徑

首屏載入最佳化方面主要其實就兩點

第一:
儘可能的減少首次載入的檔案體積,和進行分佈載入

第二:
首屏載入最好的解決方案就是ssr(服務端渲染),還利於seo
但是一般情況下沒太多人選擇ssr,因為只要不需要seo,ssr更多的是增加了專案開銷和技術難度的。

1、路由懶載入

在 Webpack 中,我們可以使用動態 import語法來定義程式碼分塊點 (split point): import(’./Fee.vue’) // 返回 Promise如果您使用的是 Babel,你將需要新增 syntax-dynamic-import 外掛,才能使 Babel 可以正確地解析語法。
結合這兩者,這就是如何定義一個能夠被 Webpack 自動程式碼分割的非同步元件。

const Fee = () => import('./Fee.vue')
在路由配置中什麼都不需要改變,只需要像往常一樣使用 Foo:

const router = new VueRouter({
  routes: [
    { path: '/fee', component: Fee }
  ]
})

2、伺服器和webpack打包同時配置Gzip

Gzip是GNU zip的縮寫,顧名思義是一種壓縮技術。它將瀏覽器請求的檔案先在伺服器端進行壓縮,然後傳遞給瀏覽器,瀏覽器解壓之後再進行頁面的解析工作。在服務端開啟Gzip支援後,我們前端需要提供資源壓縮包,透過Compression-Webpack-Plugin外掛build提供壓縮

需要後端配置,這裡提供nginx方式:

http:{ 
      gzip on; #開啟或關閉gzip on off
      gzip_disable "msie6"; #不使用gzip IE6
      gzip_min_length 100k; #gzip壓縮最小檔案大小,超出進行壓縮(自行調節)
      gzip_buffers 4 16k; #buffer 不用修改
      gzip_comp_level 8; #壓縮級別:1-10,數字越大壓縮的越好,時間也越長
      gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; #  壓縮檔案型別 
}

// 安裝外掛

  $ cnpm i --save-dev compression-webpack-plugin

// 在vue-config.js 中加入

  const CompressionWebpackPlugin = require('compression-webpack-plugin');
  const productionGzipExtensions = [
    "js",
    "css",
    "svg",
    "woff",
    "ttf",
    "json",
    "html"
  ];
  const isProduction = process.env.NODE_ENV === 'production';
  .....
  module.exports = {
  ....
   // 配置webpack
   configureWebpack: config => {
    if (isProduction) {
     // 開啟gzip壓縮
     config.plugins.push(new CompressionWebpackPlugin({
      algorithm: 'gzip',
      test: /\.js$|\.html$|\.json$|\.css/,
      threshold: 10240,
      minRatio: 0.8
     }))
    }
   }
  }

3、最佳化打包chunk-vendor.js檔案體積過大

當我們執行專案並且打包的時候,會發現chunk-vendors.js這個檔案非常大,那是因為webpack將所有的依賴全都壓縮到了這個檔案裡面,這時我們可以將其拆分,將所有的依賴都打包成單獨的js。

  // 在vue-config.js 中加入

  .....
  module.exports = {
  ....
   // 配置webpack
   configureWebpack: config => {
    if (isProduction) {
      // 開啟分離js
      config.optimization = {
        runtimeChunk: 'single',
        splitChunks: {
          chunks: 'all',
          maxInitialRequests: Infinity,
          minSize: 20000,
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name (module) {
                // get the name. E.g. node_modules/packageName/not/this/part.js
                // or node_modules/packageName
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                // npm package names are URL-safe, but some servers don't like @ symbols
                return `npm.${packageName.replace('@', '')}`
              }
            }
          }
        }
      };
    }
   }
  }

// 至此,你會發現原先的vender檔案沒有了,同時多了好幾個依賴的js檔案

4、啟用CDN加速

用Gzip已把檔案的大小減少了三分之二了,但這個還是得不到滿足。那我們就把那些不太可能改動的程式碼或者庫分離出來,繼續減小單個chunk-vendors,然後透過CDN載入進行加速載入資源。

  // 修改vue.config.js 分離不常用程式碼庫
  // 如果不配置webpack也可直接在index.html引入

  module.exports = {
   configureWebpack: config => {
    if (isProduction) {
     config.externals = {
      'vue': 'Vue',
      'vue-router': 'VueRouter',
      'moment': 'moment'
     }
    }
   }
  }

// 在public資料夾的index.html 載入

  <script src="https://cdn.bootcss.com/vue/2.5.17-beta.0/vue.runtime.min.js"></script>
  <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>

5、完整vue.config.js程式碼

  const path = require('path')

  // 在vue-config.js 中加入
  // 開啟gzip壓縮
  const CompressionWebpackPlugin = require('compression-webpack-plugin');
  // 判斷開發環境
  const isProduction = process.env.NODE_ENV === 'production';

  const resolve = dir => {
    return path.join(__dirname, dir)
  }

  // 專案部署基礎
  // 預設情況下,我們假設你的應用將被部署在域的根目錄下,
  // 例如:https://www.my-app.com/
  // 預設:'/'
  // 如果您的應用程式部署在子路徑中,則需要在這指定子路徑
  // 例如:https://www.foobar.com/my-app/
  // 需要將它改為'/my-app/'
  // iview-admin線上演示打包路徑: https://file.iviewui.com/admin-dist/
  const BASE_URL = process.env.NODE_ENV === 'production'
    ? '/'
    : '/'

  module.exports = {
    //webpack配置
    configureWebpack:config => {
      // 開啟gzip壓縮
      if (isProduction) {
        config.plugins.push(new CompressionWebpackPlugin({
          algorithm: 'gzip',
          test: /\.js$|\.html$|\.json$|\.css/,
          threshold: 10240,
          minRatio: 0.8
        }));
        // 開啟分離js
        config.optimization = {
          runtimeChunk: 'single',
          splitChunks: {
            chunks: 'all',
            maxInitialRequests: Infinity,
            minSize: 20000,
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name (module) {
                  // get the name. E.g. node_modules/packageName/not/this/part.js
                  // or node_modules/packageName
                  const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                  // npm package names are URL-safe, but some servers don't like @ symbols
                  return `npm.${packageName.replace('@', '')}`
                }
              }
            }
          }
        };
        // 取消webpack警告的效能提示
        config.performance = {
          hints:'warning',
              //入口起點的最大體積
              maxEntrypointSize: 50000000,
              //生成檔案的最大體積
              maxAssetSize: 30000000,
              //只給出 js 檔案的效能提示
              assetFilter: function(assetFilename) {
            return assetFilename.endsWith('.js');
          }
        }
      }
    },
    // Project deployment base
    // By default we assume your app will be deployed at the root of a domain,
    // e.g. https://www.my-app.com/
    // If your app is deployed at a sub-path, you will need to specify that
    // sub-path here. For example, if your app is deployed at
    // https://www.foobar.com/my-app/
    // then change this to '/my-app/'
    publicPath: BASE_URL,
    // tweak internal webpack configuration.
    // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
    devServer: {
      host: 'localhost',
      port: 8080, // 埠號
      hotOnly: false,
      https: false, // https:{type:Boolean}
      open: true, //配置自動啟動瀏覽器
      proxy:null // 配置跨域處理,只有一個代理

    },
    // 如果你不需要使用eslint,把lintOnSave設為false即可
    lintOnSave: true,
    css:{
      loaderOptions:{
        less:{
          javascriptEnabled:true
        }
      },
      extract: true,// 是否使用css分離外掛 ExtractTextPlugin
      sourceMap: false,// 開啟 CSS source maps
      modules: false// 啟用 CSS modules for all css / pre-processor files.
    },
    chainWebpack: config => {
      config.resolve.alias
        .set('@', resolve('src')) // key,value自行定義,比如.set('@@', resolve('src/components'))
        .set('@c', resolve('src/components'))
    },
    // 打包時不生成.map檔案
    productionSourceMap: false
    // 這裡寫你呼叫介面的基礎路徑,來解決跨域,如果設定了代理,那你本地開發環境的axios的baseUrl要寫為 '' ,即空字串
    // devServer: {
    //   proxy: 'localhost:3000'
    // }
  }

9. git經常用哪些指令

產生程式碼庫

新建一個git程式碼庫

git init

下載遠端專案和它的整個程式碼歷史

git clone 遠端倉庫地址

配置

顯示配置

git config --list [--global]

編輯配置

git config -e [--global]

設定使用者資訊

git config [--global] user.name "名"
git config [--global] user.email "郵箱地址"

暫存區檔案操作

增加檔案到暫存區

# 1.新增當前目錄的所有檔案到暫存區
git add .
# 2.新增指定目錄到暫存區,包括子目錄
git add [dir]
# 3.新增指定檔案到暫存區
git add [file1] [file2] ...

在暫存區中刪除檔案

# 刪除工作區檔案,並且將這次刪除放入暫存區
git rm [file1] [file2] ...
# 停止追蹤指定檔案,但該檔案會保留在工作區
git rm --cached [file]

重新命名暫存區檔案

# 改名檔案,並且將這個改名放入暫存區
git mv [file-original] [file-renamed]

程式碼提交

# 提交暫存區到倉庫區
git commit -m [message]

分支操作

# 列出所有本地分支
git branch

# 列出所有遠端分支
git branch -r

# 列出所有本地分支和遠端分支
git branch -a

# 新建一個分支,但依然停留在當前分支
git branch [branch-name]

# 新建一個分支,並切換到該分支
git checkout -b [branch]

# 新建一個分支,指向指定commit
git branch [branch] [commit]

# 新建一個分支,與指定的遠端分支建立追蹤關係
git branch --track [branch] [remote-branch]

# 切換到指定分支,並更新工作區
git checkout [branch-name]

# 切換到上一個分支
git checkout -

# 建立追蹤關係,在現有分支與指定的遠端分支之間
git branch --set-upstream [branch] [remote-branch]

# 合併指定分支到當前分支
git merge [branch]

# 選擇一個commit,合併進當前分支
git cherry-pick [commit]

# 刪除分支
git branch -d [branch-name]

# 刪除遠端分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]

資訊檢視

# 顯示有變更的檔案
git status

# 顯示當前分支的版本歷史
git log

# 顯示commit歷史,以及每次commit發生變更的檔案
git log --stat

# 搜尋提交歷史,根據關鍵詞
git log -S [keyword]

# 顯示某個commit之後的所有變動,每個commit佔據一行
git log [tag] HEAD --pretty=format:%s

# 顯示某個commit之後的所有變動,其"提交說明"必須符合搜尋條件
git log [tag] HEAD --grep feature

# 顯示過去5次提交
git log -5 --pretty --oneline

同步操作

# 增加一個新的遠端倉庫,並命名
git remote add [shortname] [url]

# 取回遠端倉庫的變化,並與本地分支合併
git pull [remote] [branch]

# 上傳本地指定分支到遠端倉庫
git push [remote] [branch]

# 強行推送當前分支到遠端倉庫,即使有衝突
git push [remote] --force

# 推送所有分支到遠端倉庫
git push [remote] --all

撤銷操作

# 恢復暫存區的指定檔案到工作區
git checkout [file]

# 恢復某個commit的指定檔案到暫存區和工作區
git checkout [commit] [file]

# 恢復暫存區的所有檔案到工作區
git checkout .

# 重置暫存區的指定檔案,與上一次commit保持一致,但工作區不變
git reset [file]

# 重置暫存區與工作區,與上一次commit保持一致
git reset --hard

# 重置當前分支的指標為指定commit,同時重置暫存區,但工作區不變
git reset [commit]

# 重置當前分支的HEAD為指定commit,同時重置暫存區和工作區,與指定commit一致
git reset --hard [commit]

# 重置當前HEAD為指定commit,但保持暫存區和工作區不變
git reset --keep [commit]

# 新建一個commit,用來撤銷指定commit
# 後者的所有變化都將被前者抵消,並且應用到當前分支
git revert [commit]

10. 協商快取和強快取

瀏覽器快取主要分為強強快取(也稱本地快取)和協商快取(也稱弱快取)。瀏覽器在第一次請求發生後,再次傳送請求時:

  • 瀏覽器請求某一資源時,會先獲取該資源快取的header資訊,然後根據header中的Cache-Control和Expires來判斷是否過期。若沒過期則直接從快取中獲取資源資訊,包括快取的header的資訊,所以此次請求不會與伺服器進行通訊。這裡判斷是否過期,則是強快取相關。後面會講Cache-Control和Expires相關。
  • 如果顯示已過期,瀏覽器會向伺服器端傳送請求,這個請求會攜帶第一次請求返回的有關快取的header欄位資訊,比如客戶端會透過If-None-Match頭將先前伺服器端傳送過來的Etag傳送給伺服器,服務會對比這個客戶端發過來的Etag是否與伺服器的相同,若相同,就將If-None-Match的值設為false,返回狀態304,客戶端繼續使用本地快取,不解析伺服器端發回來的資料,若不相同就將If-None-Match的值設為true,返回狀態為200,客戶端重新機械伺服器端返回的資料;客戶端還會透過If-Modified-Since頭將先前伺服器端發過來的最後修改時間戳傳送給伺服器,伺服器端透過這個時間戳判斷客戶端的頁面是否是最新的,如果不是最新的,則返回最新的內容,如果是最新的,則返回304,客戶端繼續使用本地快取。

強快取

強快取是利用http頭中的Expires和Cache-Control兩個欄位來控制的,用來表示資源的快取時間。強快取中,普通重新整理會忽略它,但不會清除它,需要強制重新整理。瀏覽器強制重新整理,請求會帶上Cache-Control:no-cache和Pragma:no-cache

Expires

Expires是http1.0的規範,它的值是一個絕對時間的GMT格式的時間字串。如我現在這個網頁的Expires值是:expires:Fri, 14 Apr 2017 10:47:02 GMT。這個時間代表這這個資源的失效時間,只要傳送請求時間是在Expires之前,那麼本地快取始終有效,則在快取中讀取資料。所以這種方式有一個明顯的缺點,由於失效的時間是一個絕對時間,所以當伺服器與客戶端時間偏差較大時,就會導致快取混亂。如果同時出現Cache-Control:max-age和Expires,那麼max-age優先順序更高。如我主頁的response headers部分如下:

cache-control:max-age=691200
expires:Fri, 14 Apr 2017 10:47:02 GMT

Cache-Control

Cache-Control是在http1.1中出現的,主要是利用該欄位的max-age值來進行判斷,它是一個相對時間,例如Cache-Control:max-age=3600,代表著資源的有效期是3600秒。cache-control除了該欄位外,還有下面幾個比較常用的設定值:

  • no-cache:不使用本地快取。需要使用快取協商,先與伺服器確認返回的響應是否被更改,如果之前的響應中存在ETag,那麼請求的時候會與服務端驗證,如果資源未被更改,則可以避免重新下載。

  • no-store:直接禁止遊覽器快取資料,每次使用者請求該資源,都會向伺服器傳送一個請求,每次都會下載完整的資源。

  • public:可以被所有的使用者快取,包括終端使用者和CDN等中間代理伺服器。

  • private:只能被終端使用者的瀏覽器快取,不允許CDN等中繼快取伺服器對其快取。

  • Cache-Control與Expires可以在服務端配置同時啟用,同時啟用的時候Cache-Control優先順序高。

協商快取

協商快取就是由伺服器來確定快取資源是否可用,所以客戶端與伺服器端要透過某種標識來進行通訊,從而讓伺服器判斷請求資源是否可以快取訪問。

普通重新整理會啟用弱快取,忽略強快取。只有在位址列或收藏夾輸入網址、透過連結引用資源等情況下,瀏覽器才會啟用強快取, 這也是為什麼有時候我們更新一張圖片、一個js檔案,頁面內容依然是舊的,但是直接瀏覽器訪問那個圖片或檔案,看到的內容卻是新的。

這個主要涉及到兩組header欄位:Etag和If-None-Match、Last-Modified和If-Modified-Since。上面以及說得很清楚這兩組怎麼使用啦~複習一下:

Etag和If-None-Match

Etag/If-None-Match返回的是一個校驗碼。ETag可以保證每一個資源是唯一的,資源變化都會導致ETag變化。伺服器根據瀏覽器上送的If-None-Match值來判斷是否命中快取。

與Last-Modified不一樣的是,當伺服器返回304 Not Modified的響應時,由於ETag重新生成過,response header中還會把這個ETag返回,即使這個ETag跟之前的沒有變化。

Last-Modify/If-Modify-Since

瀏覽器第一次請求一個資源的時候,伺服器返回的header中會加上Last-Modify,Last-modify是一個時間標識該資源的最後修改時間,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。

當瀏覽器再次請求該資源時,request的請求頭中會包含If-Modify-Since,該值為快取之前返回的Last-Modify。伺服器收到If-Modify-Since後,根據資源的最後修改時間判斷是否命中快取。

如果命中快取,則返回304,並且不會返回資源內容,並且不會返回Last-Modify。

為什麼要有Etag

你可能會覺得使用Last-Modified已經足以讓瀏覽器知道本地的快取副本是否足夠新,為什麼還需要Etag呢?HTTP1.1中Etag的出現主要是為了解決幾個Last-Modified比較難解決的問題:

  • 一些檔案也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個檔案被修改了,而重新GET;
  • 某些檔案修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
  • 某些伺服器不能精確的得到檔案的最後修改時間。

Last-Modified與ETag是可以一起使用的,伺服器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最後才決定是否返回304。

11. 對Event loop的瞭解?

Javascript是單執行緒的,那麼各個任務程式是按照什麼樣的規範來執行的呢?這就涉及到Event Loop的概念了,EventLoop是在html5規範中明確定義的;

何為eventloop,javascript中的一種執行機制,用來解決瀏覽器單執行緒的問題

Event Loop是一個程式結構,用於等待和傳送訊息和事件。同步任務、非同步任務、微任務、宏任務

javascript單執行緒任務從時間上分為同步任務和非同步任務,而非同步任務又分為宏任務(macroTask)和微任務(microTask)

宏任務:主程式碼塊、setTimeOut、setInterval、script、I/O操作、UI渲染

微任務:promise、async/await(返回的也是一個promise)、process.nextTick

在執行程式碼前,任務佇列為空,所以會優先執行主程式碼塊,再執行主程式碼塊過程中,遇到同步任務則立即將任務放到呼叫棧執行任務,遇到宏任務則將任務放入宏任務佇列中,遇到微任務則將任務放到微任務佇列中。

主執行緒任務執行完之後,先檢查微任務佇列是否有任務,有的話則將按照先入先出的順序將先放進來的微任務放入呼叫棧中執行,並將該任務從微任務佇列中移除,執行完該任務後繼續檢視微任務佇列中是否還有任務,有的話繼續執行微任務,微任務佇列null時,檢視宏任務佇列,有的話則按先進先出的順序執行,將任務放入呼叫棧並將該任務從宏任務佇列中移除,該任務執行完之後繼續檢視微任務佇列,有的話則執行微任務佇列,沒有則繼續執行宏任務佇列中的任務。

需要注意的是:每次呼叫棧執行完一個宏任務之後,都會去檢視微任務佇列,如果microtask queue不為空,則會執行微任務佇列中的任務,直到微任務佇列為空再返回去檢視執行宏任務佇列中的任務

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')

最終輸出

script start
VM70:8 async2 end
VM70:17 Promise
VM70:27 script end
VM70:5 async1 end
VM70:21 promise1
VM70:24 promise2
undefined
VM70:13 setTimeout

解析:

在說返回結果之前,先說一下async/await,async 返回的是一個promise,而await則等待一個promise物件執行,await之後的程式碼則相當於promise.then()

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end') 
}

//等價於
new Promise(resolve => 
resolve(console.log('async2 end'))
).then(res => {
console.log('async1 end')
})

整體分析:先執行同步任務 script start -> async2 end -> await之後的console被放入微任務佇列中 -> setTimeOut被放入宏任務佇列中 ->promise -> promise.then被放入微任務佇列中 -> script end -> 同步任務執行完畢,檢視微任務佇列 --> async1 end -> promise1 -> promise2 --> 微任務佇列執行完畢,執行宏任務佇列列印setTimeout

12. node.js如何匯出頁面資料形成報表

node.js如何匯出頁面資料形成報表

生成報表並下載是作為web應用中的一個傳統功能,在實際專案中有廣範的應用,實現原理也很簡單,以NodeJS匯出資料的方法為例,就是拿到頁面資料對應id,然後根據id查詢資料庫,拿到所需資料後格式化為特點格式的資料,最後匯出檔案。

在nodejs中,也提供了很多的第三方庫來實現這一功能,以node-xlsx匯出excel檔案為例,實現步驟如下:

  1. 下載node-xlsx
    npm install node-xlsx
  1. 編寫介面

以下程式碼使用express編寫,呼叫介面實現下載功能

    const fs = require('fs');
    const path = require('path');
    const xlsx = require('node-xlsx');
    const express = require('express');
    const router = express.Router();
    const mongo = require('../db');

    router.post('/export', async (req, res) => {
        const { ids } = req.body;
        const query = {
            _id: { $in: ids }
        }
        // 查詢資料庫獲取資料
        let result = await mongo.find(colName, query);
     // 設定表頭
     const keys = Object.keys(Object.assign({}, ...result));
     rows[0] = keys;
 
     // 設定表格資料
     const rows = []
     result.map(item => {
         const values = []
         keys.forEach((key, idx) => {
             if (item[key] === undefined) {
                 values[idx] = null;
             } else {
                 values[idx] = item[key];
             }
         })
         rows.push(values);
     });
 
     let data = xlsx.build([{ name: "商品列表", data: rows }]);
     const downloadPath = path.join(__dirname, '../../public/download');
     const filePath = `${downloadPath}/goodslist.xlsx`;
     fs.writeFileSync(filePath, data);
     res.download(filePath, `商品列表.xlsx`, (err) => {
         console.log('download err', err);
     });
 })

13. 說說你對nodejs的瞭解

我們從以下幾方面來看nodejs.

什麼是nodejs?

Node.js 是一個開源與跨平臺的 JavaScript 執行時環境, 在瀏覽器外執行 V8 JavaScript 引擎(Google Chrome 的核心),利用事件驅動、非阻塞和非同步輸入輸出模型等技術提高效能.

可以理解為 Node.js 就是一個伺服器端的、非阻塞式I/O的、事件驅動的JavaScript執行環境

非阻塞非同步

Nodejs採用了非阻塞型I/O機制,在做I/O操作的時候不會造成任何的阻塞,當完成之後,以時間的形式通知執行操作

例如在執行了訪問資料庫的程式碼之後,將立即轉而執行其後面的程式碼,把資料庫返回結果的處理程式碼放在回撥函式中,從而提高了程式的執行效率

事件驅動

事件驅動就是當進來一個新的請求的時,請求將會被壓入一個事件佇列中,然後透過一個迴圈來檢測佇列中的事件狀態變化,如果檢測到有狀態變化的事件,那麼就執行該事件對應的處理程式碼,一般都是回撥函式

優缺點

優點:

處理高併發場景效能更佳
適合I/O密集型應用,指的是應用在執行極限時,CPU佔用率仍然比較低,大部分時間是在做 I/O硬碟記憶體讀寫操作

因為Nodejs是單執行緒

缺點:

不適合CPU密集型應用
只支援單核CPU,不能充分利用CPU
可靠性低,一旦程式碼某個環節崩潰,整個系統都崩潰

應用場景

藉助Nodejs的特點和弊端,其應用場景分類如下:

善於I/O,不善於計算。因為Nodejs是一個單執行緒,如果計算(同步)太多,則會阻塞這個執行緒
大量併發的I/O,應用程式內部並不需要進行非常複雜的處理
與 websocket 配合,開發長連線的實時互動應用程式

具體場景可以表現為如下:

第一大類:使用者表單收集系統、後臺管理系統、實時互動系統、考試系統、聯網軟體、高併發量的web應用程>序
第二大類:基於web、canvas等多人聯網遊戲
第三大類:基於web的多人實時聊天客戶端、聊天室、圖文直播
第四大類:單頁面瀏覽器應用程式
第五大類:運算元據庫、為前端和移動端提供基於json的API


持續更新ing

相關文章