主頁 > 前端設計 > 這2萬字的前端基礎知識查漏補缺,請你收藏好 (本人目前想換一份前端作業)

這2萬字的前端基礎知識查漏補缺,請你收藏好 (本人目前想換一份前端作業)

2021-10-10 17:39:49 前端設計

思維導圖

一,JS基礎

1.如何在es5環境下實作let

對于這個問題,我們可以直接查看babel轉換前后的結果,看一下在回圈中通過let定義的變數是如何解決變數提升的問題
  • 原始碼
//原始碼
for(let i=0; i<10; i++){
	console.log(i)
}
	console.log(i)

babel轉碼

for(var _i = 0; _i < 10; _i++){
	console.log(_i)
}
	console.log(i)

babel在let定義的變數前加了道下劃線,避免在塊級作用域外訪問到該變數,除了對變數名的轉換,我們也可以通過自執行函式來模擬塊級作用域,
(function(){ 
 for(var i = 0; i < 5; i ++){ 
    console.log(i)  // 0 1 2 3 4 
  }
 })();
 console.log(i)      // Uncaught ReferenceError: i is not defined

2、如何在ES5環境下實作const

實作const的關鍵在于Object.defineProperty()這個API,這個API用于在一個物件上增加或修改屬性,通過配置屬性描述符,可以精確地控制屬性行為,Object.defineProperty() 接收三個引數:

Object.defineProperty(obj, prop, desc)

在這里插入圖片描述

  • 對于const不可修改的特性,我們通過設定writable屬性來實作
function _const(key, value) {    
   const desc = {        
        value,        
        writable: false
   }    
  Object.defineProperty(window, key, desc)
}

_const('obj', {a: 1})   //定義obj
obj.b = 2               //可以正常給obj的屬性賦值
obj = {}                //拋出錯誤,提示物件read-only
參考資料:如何在 ES5 環境下實作一個const ?

手寫call()

call() 方法使用一個指定的 this 值和單獨給出的一個或多個引數來呼叫一個函式

語法:function.call(thisArg, arg1, arg2, …)

call()的原理比較簡單,由于函式的this指向它的直接呼叫者,我們變更呼叫者即完成this指向的變更:

//變更函式呼叫者示例
function foo() {
 console.log(this.name)
}

// 測驗
const obj = {
name: '寫代碼像蔡徐抻'
}
obj.foo = foo   // 變更foo的呼叫者
obj.foo()       // '寫代碼像蔡徐抻'
  • 基于以上原理, 我們兩句代碼就能實作call()
Function.prototype.myCall = function(thisArg, ...args) {
    thisArg.fn = this              // this指向呼叫call的物件,即我們要改變this指向的函式
    return thisArg.fn(...args)     // 執行函式并return其執行結果
}
但是我們有一些細節需要處理:
Function.prototype.myCall = function(thisArg, ...args) {  
  const fn = Symbol('fn')  // 宣告一個獨有的Symbol屬性, 防止fn覆寫已有屬性   
   thisArg = thisArg || window   // 若沒有傳入this, 默認系結window物件    
    thisArg[fn] = this // this指向呼叫call的物件,即我們要改變this指向的函式    
    const result = thisArg[fn](...args)  // 執行當前函式    
    delete thisArg[fn]          // 洗掉我們宣告的fn屬性    
    return result       // 回傳函式執行結果
  }
    //測驗
    foo.myCall(obj)   // 輸出'寫代碼像蔡徐抻'

4. 手寫apply()

apply() 方法呼叫一個具有給定this值的函式,以及作為一個陣列(或類似陣列物件)提供的引數,
apply()和call()類似,區別在于call()接收引數串列,而apply()接收一個引數陣列,所以我們在call()的實作上簡單改一下入參形式即可
Function.prototype.myApply = function(thisArg, args) {
   const fn = Symbol('fn')        // 宣告一個獨有的Symbol屬性, 防止fn覆寫已有屬性
    thisArg = thisArg || window    // 若沒有傳入this, 默認系結window物件
    thisArg[fn] = this              // this指向呼叫call的物件,即我們要改變this指向的函式
    const result = thisArg[fn](...args)  // 執行當前函式
    delete thisArg[fn]              // 洗掉我們宣告的fn屬性
    return result                  // 回傳函式執行結果
}

//測驗
foo.myApply(obj, [])     // 輸出'寫代碼像蔡徐抻'

5、手寫bind()

bind() 方法創建一個新的函式,在 bind() 被呼叫時,這個新函式的 this 被指定為 bind() 的第一個引數,而其余引數將作為新函式的引數,供呼叫時使用,
語法: function.bind(thisArg, arg1, arg2, …)

從用法上看,似乎給call/apply包一層function就實作了bind():

Function.prototype.myBind = function(thisArg, ...args) {
 return () => {
   this.apply(thisArg, args)
  }
}

但我們忽略了三點:

  • bind()除了this還接收其他引數,bind()回傳的函式也接收引數,這兩部分的引數都要傳給回傳的函式
  • new的優先級:如果bind系結后的函式被new了,那么此時this指向就發生改變,此時的this就是當前函式的實體
  • 沒有保留原函式在原型鏈上的屬性和方法
Function.prototype.myBind = function (thisArg, ...args) {
  var self = this
  // new優先級
  var fbound = function () {
        self.apply(this instanceof self ? this : thisArg, 		args.concat(Array.prototype.slice.call(arguments)))
    }
// 繼承原型上的屬性和方法
    fbound.prototype = Object.create(self.prototype);

return fbound;
}

//測驗
const obj = { name: '寫代碼像蔡徐抻' }
function foo() {
console.log(this.name)
console.log(arguments)
}

foo.myBind(obj, 'a', 'b', 'c')()    //輸出寫代碼像蔡徐抻 ['a', 'b', 'c']

6、手寫一個防抖函式

防抖和節流的概念都比較簡單,所以我們就不在“防抖節流是什么”這個問題上浪費過多篇幅了,簡單點一下:
防抖,即短時間內大量觸發同一事件,只會執行一次函式,實作原理為設定一個定時器,約定在xx毫秒后再觸發事件處理,每次觸發事件都會重新設定計時器,直到xx毫秒內無第二次操作,防抖常用于搜索框/滾動條的監聽事件處理,如果不做防抖,每輸入一個字/滾動螢屏,都會觸發事件處理,造成性能浪費,
function debounce(func, wait) {
 let timeout = null
 return function() {
  let context = this
  let args = arguments
  if (timeout) clearTimeout(timeout)
   timeout = setTimeout(() => {
            func.apply(context, args)
    }, wait)
   }
}

7、手寫一個節流函式

防抖是延遲執行,而節流是間隔執行,函式節流即每隔一段時間就執行一次,實作原理為設定一個定時器,約定xx毫秒后執行事件,如果時間到了,那么執行函式并重置定時器,和防抖的區別在于,防抖每次觸發事件都重置定時器,而節流在定時器到時間后再清空定時器

方式一 延時器

function throttle(func, wait) {
let timeout = null
return function() {
let context = this
let args = arguments
if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null
                func.apply(context, args)
            }, wait)
        }

    }
}

實作方式2:使用兩個時間戳prev舊時間戳和now新時間戳,每次觸發事件都判斷二者的時間差,如果到達規定時間,執行函式并重置舊時間戳

function throttle(func, wait) {
var prev = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - prev > wait) {
            func.apply(context, args);
            prev = now;
        }
    }
}

陣列扁平化

對于[1, [1,2], [1,2,3]]這樣多層嵌套的陣列,我們如何將其扁平化為[1, 1, 2, 1, 2, 3]這樣的一維陣列呢:
  • 1.ES6的flat()
const arr = [1, [1,2], [1,2,3]]
arr.flat(Infinity)  // [1, 1, 2, 1, 2, 3]
  • 2.序列化后正則
const arr = [1, [1,2], [1,2,3]]
const str = `[${JSON.stringify(arr).replace(/(\[|\])/g, '')}]`
JSON.parse(str)   // [1, 1, 2, 1, 2, 3]
  • 3.遞回
    對于樹狀結構的資料,最直接的處理方式就是遞回
const arr = [1, [1,2], [1,2,3]]
function flat(arr) {
let result = []
for (const item of arr) {
    item instanceof Array ? result = result.concat(flat(item)) : result.push(item)
  }
return result
}

flat(arr) // [1, 1, 2, 1, 2, 3]
  • 4.reduce()遞回
const arr = [1, [1,2], [1,2,3]]
function flat(arr) {
return arr.reduce((prev, cur) => {
return prev.concat(cur instanceof Array ? flat(cur) : cur)
  }, [])
}

flat(arr)  // [1, 1, 2, 1, 2, 3]
  • 5.迭代+展開運算子
let arr = [1, [1,2], [1,2,3]]
while (arr.some(Array.isArray)) {
  arr = [].concat(...arr);
}

console.log(arr)  // [1, 1, 2, 1, 2, 3]

9. 手寫一個Promise

異步編程二三事 | Promise/async/Generator實作原理決議 | 9k字

二、JS面向物件

在JS中一切皆物件,但JS并不是一種真正的面向物件(OOP)的語言,因為它缺少類(class)的概念,雖然ES6引入了class和extends,使我們能夠輕易地實作類和繼承,但JS并不存在真實的類,JS的類是通過函式以及原型鏈機制模擬的,本小節的就來探究如何在ES5環境下利用函式和原型鏈實作JS面向物件的特性,

在開始之前,我們先回顧一下原型鏈的知識,后續new和繼承等實作都是基于原型鏈機制,很多介紹原型鏈的資料都能寫上洋洋灑灑幾千字,但我覺得讀者們不需要把原型鏈想太復雜,容易把自己繞進去,其實在我看來,原型鏈的核心只需要記住三點:

  • 每個物件都有__proto__屬性,該屬性指向其原型物件,在呼叫實體的方法和屬性時,如果在實體物件上找不到,就會往原型物件上找
  • 建構式的prototype屬性也指向實體的原型物件
  • 原型物件的constructor屬性指向建構式
    原型

1、模擬實作new

首先我們要知道new做了什么
  • 創建一個新物件,并繼承其建構式的prototype,這一步是為了繼承建構式原型上的屬性和方法
  • 執行建構式,方法內的this被指定為該新實體,這一步是為了執行建構式內的賦值操作
  • 回傳新實體(規范規定,如果構造方法回傳了一個物件,那么回傳該物件,否則回傳第一步創建的新物件)
// new是關鍵字,這里我們用函式來模擬,new Foo(args) <=> myNew(Foo, args)
function myNew(foo, ...args) {
// 創建新物件,并繼承構造方法的prototype屬性, 這一步是為了把obj掛原型鏈上, 相當于obj.__proto__ = Foo.prototype
let obj = Object.create(foo.prototype)

// 執行構造方法, 并為其系結新this, 這一步是為了讓構造方法能進行this.name = name之類的操作, args是構造方法的入參, 因為這里用myNew模擬, 所以入參從myNew傳入
let result = foo.apply(obj, args)

// 如果構造方法已經return了一個物件, 那么就回傳該物件, 一般情況下,構造方法不會回傳新實體,但使用者可以選擇回傳新實體來覆寫new創建的物件 否則回傳myNew創建的新物件
return typeof result === 'object' && result !== null ? result : obj
}

function Foo(name) {
this.name = name
}
const newObj = myNew(Foo, 'zhangsan')
console.log(newObj)                 // Foo {name: "zhangsan"}
console.log(newObj instanceof Foo)  // true

2、ES5如何實作繼承

說到繼承,最容易想到的是ES6的extends,當然如果只回答這個肯定不合格,我們要從函式和原型鏈的角度上實作繼承,下面我們一步步地、遞進地實作一個合格的繼承
  • 1). 原型鏈繼承
    原型鏈繼承的原理很簡單,直接讓子類的原型物件指向父類實體,當子類實體找不到對應的屬性和方法時,就會往它的原型物件,也就是父類實體上找,從而實作對父類的屬性和方法的繼承
// 父類
function Parent() {
this.name = '寫代碼像蔡徐抻'
}
// 父類的原型方法
Parent.prototype.getName = function() {
return this.name
}
// 子類
function Child() {}

// 讓子類的原型物件指向父類實體, 這樣一來在Child實體中找不到的屬性和方法就會到原型物件(父類實體)上尋找
Child.prototype = new Parent()
Child.prototype.constructor = Child // 根據原型鏈的規則,順便系結一下constructor, 這一步不影響繼承, 只是在用到constructor時會需要

// 然后Child實體就能訪問到父類及其原型上的name屬性和getName()方法
const child = new Child()
child.name          // '寫代碼像蔡徐抻'
child.getName()     // '寫代碼像蔡徐抻'

原型繼承的缺點:

  • 由于所有Child實體原型都指向同一個Parent實體, 因此對某個Child實體的父類參考型別變數修改會影響所有的Child實體
  • 在創建子類實體時無法向父類構造傳參, 即沒有實作super()的功能
// 示例:
function Parent() {
 this.name = ['寫代碼像蔡徐抻']
}
Parent.prototype.getName = function() {
 return this.name
}
function Child() {}

Child.prototype = new Parent()
Child.prototype.constructor = Child

// 測驗
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['foo'] (預期是['寫代碼像蔡徐抻'], 對child1.name的修改引起了所有child實體的變化
  • 2)、 建構式繼承
    建構式繼承,即在子類的建構式中執行父類的建構式,并為其系結子類的this,讓父類的建構式把成員屬性和方法都掛到子類的this上去,這樣既能避免實體之間共享一個原型實體,又能向父類構造方法傳參
function Parent(name) {
 this.name = [name]
}
Parent.prototype.getName = function() {
 return this.name
}
function Child() {
    Parent.call(this, 'zhangsan')   // 執行父類構造方法并系結子類的this, 使得父類中的屬性能夠賦到子類的this上
}

//測驗
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // 報錯,找不到getName(), 建構式繼承的方式繼承不到父類原型上的屬性和方法

建構式繼承的缺點:

繼承不到父類原型上的屬性和方法
  • 3)、 組合式繼承

既然原型鏈繼承和建構式繼承各有互補的優缺點, 那么我們為什么不組合起來使用呢, 所以就有了綜合二者的組合式繼承

function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 建構式繼承
    Parent.call(this, 'zhangsan')
}
//原型鏈繼承
Child.prototype = new Parent()
Child.prototype.constructor = Child

//測驗
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // ['zhangsan']
組合式繼承的缺點:
  • 每次創建子類實體都執行了兩次建構式(Parent.call()和new Parent()),雖然這并不影響對父類的繼承,但子類創建實體時,原型中會存在兩份相同的屬性和方法,這并不優雅

  • 4)、寄生式組合繼承
    為了解決建構式被執行兩次的問題, 我們將指向父類實體改為指向父類原型, 減去一次建構式的執行

function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 建構式繼承
    Parent.call(this, 'zhangsan')
}
//原型鏈繼承
// Child.prototype = new Parent()
Child.prototype = Parent.prototype  //將`指向父類實體`改為`指向父類原型`
Child.prototype.constructor = Child

//測驗
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name)          // ['foo']
console.log(child2.name)          // ['zhangsan']
child2.getName()                  // ['zhangsan']

但這種方式存在一個問題,由于子類原型和父類原型指向同一個物件,我們對子類原型的操作會影響到父類原型,例如給Child.prototype增加一個getName()方法,那么會導致Parent.prototype也增加或被覆寫一個getName()方法,為了解決這個問題,我們給Parent.prototype做一個淺拷貝

function Parent(name) {
    this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 建構式繼承
Parent.call(this, 'zhangsan')
}
//原型鏈繼承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype)  //將`指向父類實體`改為`指向父類原型`
Child.prototype.constructor = Child

//測驗
const child = new Child()
const parent = new Parent()
child.getName()                  // ['zhangsan']
parent.getName()                 // 報錯, 找不到getName()

到這里我們就完成了ES5環境下的繼承的實作,這種繼承方式稱為寄生組合式繼承,是目前最成熟的繼承方式,babel對ES6繼承的轉化也是使用了寄生組合式繼承

我們回顧一下實作程序:

一開始最容易想到的是原型鏈繼承,通過把子類實體的原型指向父類實體來繼承父類的屬性和方法,但原型鏈繼承的缺陷在于對子類實體繼承的參考型別的修改會影響到所有的實體物件以及無法向父類的構造方法傳參,
因此我們引入了建構式繼承, 通過在子類建構式中呼叫父類建構式并傳入子類this來獲取父類的屬性和方法,但建構式繼承也存在缺陷,建構式繼承不能繼承到父類原型鏈上的屬性和方法,
所以我們綜合了兩種繼承的優點,提出了組合式繼承,但組合式繼承也引入了新的問題,它每次創建子類實體都執行了兩次父類構造方法,我們通過將子類原型指向父類實體改為子類原型指向父類原型的淺拷貝來解決這一問題,也就是最終實作 —— 寄生組合式繼承,
js繼承

三、V8引擎機制

v8

  • 預決議:檢查語法錯誤但不生成AST
  • 生成AST:經過詞法/語法分析,生成抽象語法樹
  • 生成位元組碼:基線編譯器(Ignition)將AST轉換成位元組碼
  • 生成機器碼:優化編譯器(Turbofan)將位元組碼轉換成優化過的機器碼,此外在逐行執行位元組碼的程序中,如果一段代碼經常被執行,那么V8會將這段代碼直接轉換成機器碼保存起來,下一次執行就不必經過位元組碼,優化了執行速度
上面幾點只是V8執行機制的極簡總結,建議閱讀參考資料:

2、介紹一下參考計數和標記清除

  • 參考計數:給一個變數賦值參考型別,則該物件的參考次數+1,如果這個變數變成了其他值,那么該物件的參考次數-1,垃圾回收器會回收參考次數為0的物件,但是當物件回圈參考時,會導致參考次數永遠無法歸零,造成記憶體無法釋放,
  • 標記清除:垃圾收集器先給記憶體中所有物件加上標記,然后從根節點開始遍歷,去掉被參考的物件和運行環境中物件的標記,剩下的被標記的物件就是無法訪問的等待回收的物件,

3、 V8如何進行垃圾回收

JS引擎中對變數的存盤主要有兩種位置,堆疊記憶體和堆記憶體,堆疊記憶體存盤基本型別資料以及參考型別資料的記憶體地址,堆記憶體儲存參考型別的資料
在這里插入圖片描述
堆疊記憶體的回收:

  • 堆疊記憶體呼叫堆疊背景關系切換后就被回收,比較簡單
    V8的堆記憶體分為新生代記憶體和老生代記憶體,新生代記憶體是臨時分配的記憶體,存在時間短,老生代記憶體存在時間長,
    在這里插入圖片描述
  • 新生代記憶體回識訓制:
    新生代記憶體容量小,64位系統下僅有32M,新生代記憶體分為From、To兩部分,進行垃圾回收時,先掃描From,將非存活物件回收,將存活物件順序復制到To中,之后調換From/To,等待下一次回收
  • 老生代記憶體回識訓制
  • 晉升:如果新生代的變數經過多次回收依然存在,那么就會被放入老生代記憶體中
  • 標記清除:老生代記憶體會先遍歷所有物件并打上標記,然后對正在使用或被強參考的物件取消標記,回收被標記的物件
  • 整理記憶體碎片:把物件挪到記憶體的一端
參考資料:聊聊V8引擎的垃圾回收

4. JS相較于C++等語言為什么慢,V8做了哪些優化

1 JS的問題:
  • 動態型別:導致每次存取屬性/尋求方法時候,都需要先檢查型別;此外動態型別也很難在編譯階段進行優化
  • 屬性存取:C++/Java等語言中方法、屬性是存盤在陣列中的,僅需陣列位移就可以獲取,而JS存盤在物件中,每次獲取都要進行哈希查詢
2 V8的優化:
  • 優化JIT(即時編譯):相較于C++/Java這類編譯型語言,JS一邊解釋一邊執行,效率低,V8對這個程序進行了優化:如果一段代碼被執行多次,那么V8會把這段代碼轉化為機器碼快取下來,下次運行時直接使用機器碼,
  • 隱藏類:對于C++這類語言來說,僅需幾個指令就能通過偏移量獲取變數資訊,而JS需要進行字串匹配,效率低,V8借用了類和偏移位置的思想,將物件劃分成不同的組,即隱藏類
  • 內嵌快取:即快取物件查詢的結果,常規查詢程序是:獲取隱藏類地址 -> 根據屬性名查找偏移值 -> 計算該屬性地址,內嵌快取就是對這一程序結果的快取
  • 垃圾回收管理:上文已介紹
    在這里插入圖片描述
參考資料:為什么V8引擎這么快?
瀏覽器渲染機制
    1. 瀏覽器的渲染程序是怎樣的,
      在這里插入圖片描述
      大體流程如下:
      1 HTML和CSS經過各自決議,生成DOM樹和CSSOM樹
      2 合并成為渲染樹
      3 根據渲染樹進行布局
      4 最后呼叫GPU進行繪制,顯示在螢屏上
2、 如何根據瀏覽器渲染機制加快首屏速度
  • 優化檔案大小:HTML和CSS的加載和決議都會阻塞渲染樹的生成,從而影響首屏展示速度,因此我們可以通過優化檔案大小、減少CSS檔案層級的方法來加快首屏速度
  • 避免資源下載阻塞檔案決議:瀏覽器決議到
3、什么是回流(重排),什么情況下會觸發回流
  • 當元素的尺寸或者位置發生了變化,就需要重新計算渲染樹,這就是回流
  • DOM元素的幾何屬性(width/height/padding/margin/border)發生變化時會觸發回流
  • DOM元素移動或增加會觸發回流
  • 讀寫offset/scroll/client等屬性時會觸發回流
  • 呼叫window.getComputedStyle會觸發回流
4、什么是重繪,什么情況下會觸發重繪

DOM樣式發生了變化,但沒有影響DOM的幾何屬性時,會觸發重繪,而不會觸發回流,重繪由于DOM位置資訊不需要更新,省去了布局程序,因而性能上優于回流

5、 什么是GPU加速,如何使用GPU加速,GPU加速的缺點
  • 優點:使用transform、opacity、filters等屬性時,會直接在GPU中完成處理,這些屬性的變化不會引起回流重繪
  • 缺點:GPU渲染字體會導致字體模糊,過多的GPU處理會導致記憶體問題
6、 如何減少回流
  • 使用class替代style,減少style的使用
  • 使用resize、scroll時進行防抖和節流處理,這兩者會直接導致回流
  • 使用visibility替換display: none,因為前者只會引起重繪,后者會引發回流
  • 批量修改元素時,可以先讓元素脫離檔案流,等修改完畢后,再放入檔案流
  • 避免觸發同步布局事件,我們在獲取offsetWidth這類屬性的值時,可以使用變數將查詢結果存起來,避免多次查詢,每次對offset/scroll/client等屬性進行查詢時都會觸發回流
  • 對于復雜影片效果,使用絕對定位讓其脫離檔案流,復雜的影片效果會頻繁地觸發回流重繪,我們可以將影片元素設定絕對定位從而脫離檔案流避免反復回流重繪,

在這里插入圖片描述
參考資料:必須明白的瀏覽器渲染機制

四、瀏覽器快取策略

1、介紹一下瀏覽器快取位置和優先級

1 Service Worker
2 Memory Cache(記憶體快取)
3 Disk Cache(硬碟快取)
4 Push Cache(推送快取)
5 以上快取都沒命中就會進行網路請求

2、 說說不同快取間的差別

  • Service Worker
    和Web Worker類似,是獨立的執行緒,我們可以在這個執行緒中快取檔案,在主執行緒需要的時候讀取這里的檔案,Service Worker使我們可以自由選擇快取哪些檔案以及檔案的匹配、讀取規則,并且快取是持續性的
  • Memory Cache
    即記憶體快取,記憶體快取不是持續性的,快取會隨著行程釋放而釋放
  • Disk Cache
    即硬碟快取,相較于記憶體快取,硬碟快取的持續性和容量更優,它會根據HTTP header的欄位判斷哪些資源需要快取
  • Push Cache
    即推送快取,是HTTP/2的內容,目前應用較少

強快取(不要向服務器詢問的快取)

設定Expires

即過期時間,例如「Expires: Thu, 26 Dec 2019 10:30:42 GMT」表示快取會在這個時間后失效,這個過期日期是絕對日期,如果修改了本地日期,或者本地日期與服務器日期不一致,那么將導致快取過期時間錯誤,

設定Cache-Control

HTTP/1.1新增欄位,Cache-Control可以通過max-age欄位來設定過期時間,例如「Cache-Control:max-age=3600」除此之外Cache-Control還能設定private/no-cache等多種欄位

協商快取(需要向服務器詢問快取是否已經過期)

Last-Modified

即最后修改時間,瀏覽器第一次請求資源時,服務器會在回應頭上加上Last-Modified ,當瀏覽器再次請求該資源時,瀏覽器會在請求頭中帶上If-Modified-Since 欄位,欄位的值就是之前服務器回傳的最后修改時間,服務器對比這兩個時間,若相同則回傳304,否則回傳新資源,并更新Last-Modified

ETag

HTTP/1.1新增欄位,表示檔案唯一標識,只要檔案內容改動,ETag就會重新計算,快取流程和 Last-Modified 一樣:服務器發送 ETag 欄位 -> 瀏覽器再次請求時發送 If-None-Match -> 如果ETag值不匹配,說明檔案已經改變,回傳新資源并更新ETag,若匹配則回傳304

兩者對比
  • ETag 比 Last-Modified 更準確:如果我們打開檔案但并沒有修改,Last-Modified 也會改變,并且 Last-Modified 的單位時間為一秒,如果一秒內修改完了檔案,那么還是會命中快取
  • 如果什么快取策略都沒有設定,那么瀏覽器會取回應頭中的 Date 減去 Last-Modified 值的 10% 作為快取時間

在這里插入圖片描述

參考資料:瀏覽器快取機制剖析

五、網路相關

1、講講網路OSI七層模型,TCP/IP和HTTP分別位于哪一層

在這里插入圖片描述
在這里插入圖片描述

2、 常見HTTP狀態碼有哪些

  • 2xx 開頭(請求成功)
    200 OK:客戶端發送給服務器的請求被正常處理并回傳
  • 3xx 開頭(重定向)
    301 Moved Permanently:永久重定向,請求的網頁已永久移動到新位置,服務器回傳此回應時,會自動將請求者轉到新位置
    302 Moved Permanently:臨時重定向,請求的網頁已臨時移動到新位置,服務器目前從不同位置的網頁回應請求,但請求者應繼續使用原有位置來進行以后的請求
    304 Not Modified:未修改,自從上次請求后,請求的網頁未修改過,服務器回傳此回應時,不會回傳網頁內容,
  • 4xx 開頭(客戶端錯誤)
    400 Bad Request:錯誤請求,服務器不理解請求的語法,常見于客戶端傳參錯誤
    401 Unauthorized:未授權,表示發送的請求需要有通過 HTTP 認證的認證資訊,常見于客戶端未登錄
    403 Forbidden:禁止,服務器拒絕請求,常見于客戶端權限不足
    404 Not Found:未找到,服務器找不到對應資源
  • 5xx 開頭(服務端錯誤)
    500 Inter Server Error:服務器內部錯誤,服務器遇到錯誤,無法完成請求
    501 Not Implemented:尚未實施,服務器不具備完成請求的功能
    502 Bad Gateway:作為網關或者代理作業的服務器嘗試執行請求時,從上游服務器接收到無效的回應,
    503 service unavailable:服務不可用,服務器目前無法使用(處于超載或停機維護狀態),通常是暫時狀態,

3、GET請求和POST請求有何區別

標準答案:
GET請求引數放在URL上,POST請求引數放在請求體里
GET請求引數長度有限制,POST請求引數長度可以非常大
POST請求相較于GET請求安全一點點,因為GET請求的引數在URL上,且有歷史記錄
GET請求能快取,POST不能
更進一步:
其實HTTP協議并沒有要求GET/POST請求引數必須放在URL上或請求體里,也沒有規定GET請求的長度,目前對URL的長度限制,是各家瀏覽器設定的限制,GET和POST的根本區別在于:GET請求是冪等性的,而POST請求不是
冪等性,指的是對某一資源進行一次或多次請求都具有相同的副作用,例如搜索就是一個冪等的操作,而洗掉、新增則不是一個冪等操作,

由于GET請求是冪等的,在網路不好的環境中,GET請求可能會重復嘗試,造成重復操作資料的風險,因此,GET請求用于無副作用的操作(如搜索),新增/洗掉等操作適合用POST

4、HTTP的請求報文由哪幾部分組成

一個HTTP請求報文由請求行(request line)、請求頭(header)、空行和請求資料4個部分組成
在這里插入圖片描述
回應報文和請求報文結構類似,不再贅述

5、HTTP常見請求/回應頭及其含義

  • 通用頭(請求頭和回應頭都有的首部)
    在這里插入圖片描述
  • 請求頭
    在這里插入圖片描述
  • 回應頭
    在這里插入圖片描述
    物體頭(針對請求報文和回應報文的物體部分使用首部)
    在這里插入圖片描述
    HTTP首部當然不止這么幾個,但為了避免寫太多大家記不住(主要是別的我也沒去看),這里只介紹了一些常用的,詳細的可以看MDN的檔案,

6、HTTP/1.0和HTTP/1.1有什么區別

  • 長連接: HTTP/1.1支持長連接和請求的流水線,在一個TCP連接上可以傳送多個HTTP請求,避免了因為多次建立TCP連接的時間消耗和延時
  • 快取處理: HTTP/1.1引入Entity tag,If-Unmodified-Since, If-Match, If-None-Match等新的請求頭來控制快取,詳見瀏覽器快取小節
  • 帶寬優化及網路連接的使用: HTTP1.1則在請求頭引入了range頭域,支持斷點續傳功能
  • Host頭處理: 在HTTP/1.0中認為每臺服務器都有唯一的IP地址,但隨著虛擬主機技術的發展,多個主機共享一個IP地址愈發普遍,HTTP1.1的請求訊息和回應訊息都應支持Host頭域,且請求訊息中如果沒有Host頭域會400錯誤

7、 介紹一下HTTP/2.0新特性

  • 多路復用: 即多個請求都通過一個TCP連接并發地完成
  • 服務端推送: 服務端能夠主動把資源推送給客戶端
  • 新的二進制格式: HTTP/2采用二進制格式傳輸資料,相比于HTTP/1.1的文本格式,二進制格式具有更好的決議性和拓展性
  • header壓縮: HTTP/2壓縮訊息頭,減少了傳輸資料的大小,

8、 說說HTTP/2.0多路復用基本原理以及解決的問題

HTTP/2解決的問題,就是HTTP/1.1存在的問題:

  • TCP慢啟動: TCP連接建立后,會經歷一個先慢后快的發送程序,就像汽車啟動一般,如果我們的網頁檔案(HTML/JS/CSS/icon)都經過一次慢啟動,對性能是不小的損耗,另外慢啟動是TCP為了減少網路擁塞的一種策略,我們是沒有辦法改變的,
  • 多條TCP連接競爭帶寬: 如果同時建立多條TCP連接,當帶寬不足時就會競爭帶寬,影響關鍵資源的下載,
  • HTTP/1.1隊頭阻塞: 盡管HTTP/1.1長鏈接可以通過一個TCP連接傳輸多個請求,但同一時刻只能處理一個請求,當前請求未結束前,其他請求只能處于阻塞狀態,

為了解決以上幾個問題,HTTP/2一個域名只使用一個TCP?連接來傳輸資料,而且請求直接是并行的、非阻塞的,這就是多路復用

實作原理: HTTP/2引入了一個二進制分幀層,客戶端和服務端進行傳輸時,資料會先經過二進制分幀層處理,轉化為一個個帶有請求ID的幀,這些幀在傳輸完成后根據ID組合成對應的資料,

9、 說說HTTP/3.0

盡管HTTP/2解決了很多1.1的問題,但HTTP/2仍然存在一些缺陷,這些缺陷并不是來自于HTTP/2協議本身,而是來源于底層的TCP協議,我們知道TCP鏈接是可靠的連接,如果出現了丟包,那么整個連接都要等待重傳,HTTP/1.1可以同時使用6個TCP連接,一個阻塞另外五個還能作業,但HTTP/2只有一個TCP連接,阻塞的問題便被放大了,
由于TCP協議已經被廣泛使用,我們很難直接修改TCP協議,基于此,HTTP/3選擇了一個折衷的方法——UDP協議,HTTP/2在UDP的基礎上實作多路復用、0-RTT、TLS加密、流量控制、丟包重傳等功能,

參考資料:http發展史(http0.9、http1.0、http1.1、http2、http3)梳理筆記 (推薦閱讀)

10、 HTTP和HTTPS有何區別

HTTPS使用443埠,而HTTP使用80
HTTPS需要申請證書
HTTP是超文本傳輸協議,是明文傳輸;HTTPS是經過SSL加密的協議,傳輸更安全
HTTPS比HTTP慢,因為HTTPS除了TCP握手的三個包,還要加上SSL握手的九個包,

11、 HTTPS是如何進行加密的

我們通過分析幾種加密方式,層層遞進,理解HTTPS的加密方式以及為什么使用這種加密方式:

  • 對稱加密
    客戶端和服務器公用一個密匙用來對訊息加解密,這種方式稱為對稱加密,客戶端和服務器約定好一個加密的密匙,客戶端在發訊息前用該密匙對訊息加密,發送給服務器后,服務器再用該密匙進行解密拿到訊息,
    在這里插入圖片描述
    這種方式一定程度上保證了資料的安全性,但密鑰一旦泄露(密鑰在傳輸程序中被截獲),傳輸內容就會暴露,因此我們要尋找一種安全傳遞密鑰的方法,
  • 非對稱加密
    采用非對稱加密時,客戶端和服務端均擁有一個公鑰和私鑰,公鑰加密的內容只有對應的私鑰能解密,私鑰自己留著,公鑰發給對方,這樣在發送訊息前,先用對方的公鑰對訊息進行加密,收到后再用自己的私鑰進行解密,這樣攻擊者只拿到傳輸程序中的公鑰也無法破解傳輸的內容,
    在這里插入圖片描述
    盡管非對稱加密解決了由于密鑰被獲取而導致傳輸內容泄露的問題,但中間人仍然可以用篡改公鑰的方式來獲取或篡改傳輸內容,而且非對稱加密的性能比對稱加密的性能差了不少
    在這里插入圖片描述
  • 第三方認證
    上面這種方法的弱點在于,客戶端不知道公鑰是由服務端回傳,還是中間人回傳的,因此我們再引入一個第三方認證的環節:即第三方使用私鑰加密我們自己的公鑰,瀏覽器已經內置一些權威第三方認證機構的公鑰,瀏覽器會使用第三方的公鑰來解開第三方私鑰加密過的我們自己的公鑰,從而獲取公鑰,如果能成功解密,就說明獲取到的自己的公鑰是正確的
    但第三方認證也未能完全解決問題,第三方認證是面向所有人的,中間人也能申請證書,如果中間人使用自己的證書掉包原證書,客戶端還是無法確認公鑰的真偽
    在這里插入圖片描述
  • 數字簽名

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/307691.html

標籤:其他

上一篇:如何檢查潘達斯系列串列中的元素是否都是另一個串列的一部分?

下一篇:是否可以用一個xpath查詢來組合兩個屬性值?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more