「乾貨滿滿」1.5w字初中級前端面試複習總結

2021-01-08 紙鶴視界

前言

金九銀十,又是一波跑路。趁著有空把前端基礎和面試相關的知識點都系統的學習一遍,參考一些權威的書籍和優秀的文章,最後加上自己的一些理解,總結出來這篇文章。適合複習和準備面試的同學,其中的知識點包括:

JavsScript設計模式Vue模塊化瀏覽器HTTP前端安全JavaScript

數據類型

String、Number、Boolean、Null、Undefined、Symbol、BigInt、Object

堆、棧

兩者都是存放數據的地方。

棧(stack)是自動分配的內存空間,它存放基本類型的值和引用類型的內存地址。

堆(heap)是動態分配的內存空間,它存放引用類型的值。

JavaScript 不允許直接操作堆空間的對象,在操作對象時,實際操作是對象的引用,而存放在棧空間中的內存地址就起到指向的作用,通過內存地址找到堆空間中的對應引用類型的值。

隱式類型轉換

JavaScript 作為一個弱類型語言,因使用靈活的原因,在一些場景中會對類型進行自動轉換。

常見隱式類型轉換場景有3種:運算、取反、比較

運算

運算的隱式類型轉換會將運算的成員轉換為 number 類型。

基本類型轉換:

true + false // 1null + 10 // 10false + 20 // 20undefined + 30 // NaN1 + '2' // "12"NaN + '' // "NaN"undefined + '' // "undefined"null + '' // "null"'' - 3 // -3null、false、'' 轉換 number 類型都是 0undefined 轉換 number 類型是 NaN,所以 undefined 和其他基本類型運算都會輸出 NaN字符串在加法運算(其實是字符串拼接)中很強勢,和任何類型相加都會輸出字符串(symbol除外),即使是 NaN、undefined。其他運算則正常轉為 number 進行運算。引用類型轉換:

[1] + 10 // "110"[] + 20 // "20"[1,2] + 20 // "1,220"[20] - 10 // 10[1,2] - 10 // NaN({}) + 10 // "[object Object]10"({}) - 10 // NaN引用類型運算時,會默認調用 toString 先轉換為 string同上結論,除了加法都會輸出字符串外,其他情況都是先轉 string 再轉 number解析引用類型轉換過程:

[1,2] + 20// 過程:[1,2].toString() // '1,2''1,2' + 20 // '1,220'[20] - 10// 過程[20].toString() // '20'Number('20') // 2020 - 10 // 10取反

取反的隱式類型轉換會將運算的成員轉換為 boolean 類型。

這個隱式類型轉換比較簡單,就是將值轉為布爾值再取反:

![] // false!{} // false!false // true通常為了快速獲得一個值的布爾值類型,可以取反兩次:

!![] // true!!0 // false比較

比較分為 嚴格比較=== 和 非嚴格比較==,由於 === 會比較類型,不會進行類型轉換。這裡只討論 ==。

比較的隱式類型轉換基本會將運算的成員轉換為 number 類型。

undefined == null // true'' == 0 // truetrue == 1 // true'1' == true // true[1] == '1' // true[1,2] == '1,2' // true({}) == '[object Object]' // trueundefined 等於 null字符串、布爾值、null比較時,都會轉 number引用類型在隱式轉換時會先轉成 string 比較,如果不相等然再轉成 number 比較預編譯

預編譯發生在 JavaScript 代碼執行前,對代碼進行語法分析和代碼生成,初始化的創建並存儲變量,為執行代碼做好準備。

預編譯過程:

創建GO/AO對象(GO是全局對象,AO是活動對象)將形參和變量聲明賦值為 undefined實參形參相統一函數聲明提升(將變量賦值為函數體)例子:

function foo(x, y) { console.log(x) var x = 10 console.log(x) function x(){} console.log(x)}foo(20, 30)// 1. 創建AO對象AO {}// 2. 尋找形參和變量聲明賦值為 undefinedAO { x: undefined y: undefined}// 3. 實參形參相統一AO { x: 20 y: 30}// 4. 函數聲明提升AO { x: function x(){} y: 30}編譯結束後代碼開始執行,第一個 x 從 AO 中取值,輸出是函數x;x 被賦值為 10,第二個 x 輸出 10;函數x 已被聲明提升,此處不會再賦值 x,第三個 x 輸出 10。

作用域

作用域能保證對有權訪問的所有變量和函數的有序訪問,是代碼在運行期間查找變量的一種規則。

函數作用域

函數在運行時會創建屬於自己的作用域,將內部的變量和函數定義「隱藏」起來,外部作用域無法訪問包裝函數內部的任何內容。

塊級作用域

在ES6之前創建塊級作用域,可以使用 with 或 try/catch。而在ES6引入 let 關鍵字後,讓塊級作用域聲明變得更簡單。let 關鍵字可以將變量綁定到所在的任意作用域中(通常是{...}內部)。

{ let num = 10}console.log(num) // ReferenceError: num is not defined參數作用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域。等到初始化結束,這個作用域就會消失。這種語法行為,在不設置參數默認值時,是不會出現的。

let x = 1;function f(x, y = x) { console.log(y);}f(2) // 2參數y的默認值等於變量x。調用函數f時,參數形成一個單獨的作用域。在這個作用域裡面,默認值變量x指向第一個參數x,而不是全局變量x,所以輸出是2。

let x = 1;function foo(x, y = function() { x = 2; }) { x = 3; y(); console.log(x);}foo() // 2x // 1y 的默認是一個匿名函數,匿名函數內的x指向同一個作用域的第一個參數x。函數foo的內部變量x就指向第一個參數x,與匿名函數內部的x是一致的。y函數執行對參數x重新賦值,最後輸出的就是2,而外層的全局變量x依然不受影響。

閉包

閉包的本質就是作用域問題。當函數可以記住並訪問所在作用域,且該函數在所處作用域之外被調用時,就會產生閉包。

簡單點說,一個函數內引用著所在作用域的變量,並且它被保存到其他作用域執行,引用變量的作用域並沒有消失,而是跟著這個函數。當這個函數執行時,就可以通過作用域鏈查找到變量。

let barfunction foo() { let a = 10 // 函數被保存到了外部 bar = function () { // 引用著不是當前作用域的變量a console.log(a) }}foo()// bar函數不是在本身所處的作用域執行bar() // 10優點:私有變量或方法、緩存

缺點:閉包讓作用域鏈得不到釋放,會導致內存洩漏

原型鏈

JavaScript 中的對象有一個特殊的內置屬性 prototype(原型),它是對於其他對象的引用。當查找一個變量時,會優先在本身的對象上查找,如果找不到就會去該對象的 prototype 上查找,以此類推,最終以 Object.prototype 為終點。多個 prototype 連接在一起被稱為原型鏈。

原型繼承

原型繼承的方法有很多種,這裡不會全部提及,只記錄兩種常用的方法。

聖杯模式

function inherit(Target, Origin){ function F() {}; F.prototype = Origin.prototype; Target.prototype = new F(); // 還原 constuctor Target.prototype.constuctor = Target; // 記錄繼承自誰 Target.prototype.uber = Origin.prototype; }聖杯模式的好處在於,使用中間對象隔離,子級添加屬性時,都會加在這個對象裡面,不會對父級產生影響。而查找屬性是沿著 __proto__ 查找,可以順利查找到父級的屬性,實現繼承。

使用:

function Person() { this.name = 'people'}Person.prototype.sayName = function () { console.log(this.name) }function Child() { this.name = 'child'}inherit(Child, Person)Child.prototype.age = 18let child = new Child()ES6 Class

class Person { constructor() { this.name = 'people' } sayName() { console.log(this.name) }}class Child extends Person { constructor() { super() this.name = 'child' }}Child.prototype.age = 18let child = new Child()Class 可以通過 extends 關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

基本包裝類型

let str = 'hello'str.split('')基本類型按道理說是沒有屬性和方法,但是在實際操作時,我們卻能從基本類型調用方法,就像一個字符串能調用 split 方法。

為了方便操作基本類型值,每當讀取一個基本類型值的時候,後臺會創建一個對應的基本包裝類型的對象,從而讓我們能夠調用方法來操作這些數據。大概過程如下:

創建String類型的實例在實例上調用指定的方法銷毀這個實例let str = new String('hello')str.split('')str = nullthis

this是函數被調用時發生的綁定,它指向什麼完全取決於函數在哪裡被調用。我理解的this是函數的調用者對象,當在函數內使用this,可以訪問到調用者對象上的屬性和方法。

this綁定的四種情況:

new 綁定。new實例化顯示綁定。call、apply、bind手動更改指向隱式綁定。由上下文對象調用,如 obj.fn(),this 指向 obj默認綁定。默認綁定全局對象,在嚴格模式下會綁定到undefined優先級new綁定最高,最後到默認綁定。

new的過程

創建一個空對象設置原型,將對象的 __proto__ 指向構造函數的 prototype構造函數中的 this 執行對象,並執行構造函數,為空對象添加屬性和方法返回實例對象注意點:構造函數內出現return,如果返回基本類型,則提前結束構造過程,返回實例對象;如果返回引用類型,則返回該引用類型。

// 返回基本類型function Foo(){ this.name = 'Joe' return 123 this.age = 20}new Foo() // Foo {name: "Joe"}// 返回引用類型function Foo(){ this.name = 'Joe' return [123] this.age = 20}new Foo() // [123]call、apply、bind

三者作用都是改變this指向的。

call 和 apply 改變 this 指向並調用函數,它們兩者區別就是傳參形式不同,前者的參數是逐個傳入,後者傳入數組類型的參數列表。

bind 改變 this 並返回一個函數引用,bind 多次調用是無效的,它改變的 this 指向只會以第一次調用為準。

手寫call

Function.prototype.mycall = function () { if(typeof this !== 'function'){ throw 'caller must be a function' } let othis = arguments[0] || window othis._fn = this let arg = [...arguments].slice(1) let res = othis._fn(...arg) Reflect.deleteProperty(othis, '_fn') //刪除_fn屬性 return res}apply 實現同理,修改傳參形式即可

手寫bind

Function.prototype.mybind = function (oThis) { if(typeof this != 'function'){ throw 'caller must be a function' } let fThis = this //Array.prototype.slice.call 將類數組轉為數組 let arg = Array.prototype.slice.call(arguments,1) let NOP = function(){} let fBound = function(){ let arg_ = Array.prototype.slice.call(arguments) // new 綁定等級高於顯式綁定 // 作為構造函數調用時,保留指向不做修改 // 使用 instanceof 判斷是否為構造函數調用 return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_)) } // 維護原型 if(this.prototype){ NOP.prototype = this.prototype fBound.prototype = new NOP() } return fBound}對ES6語法的了解

常用:let、const、擴展運算符、模板字符串、對象解構、箭頭函數、默認參數、Promise

數據結構:Set、Map、Symbol

其他:Proxy、Reflect

Set、Map、WeakSet、WeakMap

Set:

成員的值都是唯一的,沒有重複的值,類似於數組可以遍歷WeakSet:

成員必須為引用類型成員都是弱引用,可以被垃圾回收。成員所指向的外部引用被回收後,該成員也可以被回收不能遍歷Map:

鍵值對的集合,鍵值可以是任意類型可以遍歷WeakMap:

只接受引用類型作為鍵名鍵名是弱引用,鍵值可以是任意值,可以被垃圾回收。鍵名所指向的外部引用被回收後,對應鍵名也可以被回收不能遍歷箭頭函數和普通函數的區別

箭頭函數的this指向在編寫代碼時就已經確定,即箭頭函數本身所在的作用域;普通函數在調用時確定this。箭頭函數沒有arguments箭頭函數沒有prototype屬性Promise

Promise 是ES6中新增的異步編程解決方案,避免回調地獄問題。Promise 對象是通過狀態的改變來實現通過同步的流程來表示異步的操作, 只要狀態發生改變就會自動觸發對應的函數。

Promise對象有三種狀態,分別是:

pending: 默認狀態,只要沒有告訴 promise 任務是成功還是失敗就是 pending 狀態fulfilled: 只要調用 resolve 函數, 狀態就會變為fulfilled, 表示操作成功rejected: 只要調用 rejected 函數, 狀態就會變為 rejected, 表示操作失敗狀態一旦改變既不可逆,可以通過函數來監聽 Promise 狀態的變化,成功執行 then 函數的回調,失敗執行 catch 函數的回調

淺拷貝

淺拷貝是值的複製,對於對象是內存地址的複製,目標對象的引用和源對象的引用指向的是同一塊內存空間。如果其中一個對象改變,就會影響到另一個對象。

常用淺拷貝的方法:

Array.prototype.slicelet arr = [{a:1}, {b:2}]let newArr = arr1.slice()擴展運算符let newArr = [...arr1]深拷貝

深拷貝是將一個對象從內存中完整的拷貝一份出來,對象與對象間不會共享內存,而是在堆內存中新開闢一個空間去存儲,所以修改新對象不會影響原對象。

常用的深拷貝方法:

JSON.parse(JSON.stringify())JSON.parse(JSON.stringify(obj))手寫深拷貝function deepClone(obj, map = new WeakMap()) { if (obj === null || typeof obj !== "object") return obj; const type = Object.prototype.toString.call(obj).slice(8, -1) let strategy = { Date: (obj) => new Date(obj), RegExp: (obj) => new RegExp(obj), Array: clone, Object: clone } function clone(obj){ // 防止循環引用,導致棧溢出,相同引用的對象直接返回 if (map.get(obj)) return map.get(obj); let target = new obj.constructor(); map.set(obj, target); for (let key in obj) { if (obj.hasOwnProperty(key)) { target[key] = deepClone(obj[key], map); } } return target; } return strategy[type] && strategy[type](obj)}事件委託

事件委託也叫做事件代理,是一種dom事件優化的手段。事件委託利用事件冒泡的機制,只指定一個事件處理程序,就可以管理某一類型的所有事件。

假設有個列表,其中每個子元素都會有個點擊事件。當子元素變多時,事件綁定佔用的內存將會成線性增加,這時候就可以使用事件委託來優化這種場景。代理的事件通常會綁定到父元素上,而不必為每個子元素都添加事件。

<ul @click="clickHandler"> <li>1</li> <li>2</li> <li>3</li></ul>clickHandler(e) { // 點擊獲取的子元素 let target = e.target // 輸出子元素內容 consoel.log(target.textContent)}防抖

防抖用於減少函數調用次數,對於頻繁的調用,只執行這些調用的最後一次。

/** * @param {function} func - 執行函數 * @param {number} wait - 等待時間 * @param {boolean} immediate - 是否立即執行 * @return {function} */function debounce(func, wait = 300, immediate = false){ let timer, ctx; let later = (arg) => setTimeout(()=>{ func.apply(ctx, arg) timer = ctx = null }, wait) return function(...arg){ if(!timer){ timer = later(arg) ctx = this if(immediate){ func.apply(ctx, arg) } }else{ clearTimeout(timer) timer = later(arg) } }}節流

節流用於減少函數請求次數,與防抖不同,節流是在一段時間執行一次。

/** * @param {function} func - 執行函數 * @param {number} delay - 延遲時間 * @return {function} */function throttle(func, delay){ let timer = null return function(...arg){ if(!timer){ timer = setTimeout(()=>{ func.apply(this, arg) timer = null }, delay) } }}柯裡化

Currying(柯裡化)是把接受多個參數的函數變換成接受一個單一參數的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

通用柯裡化函數:

function currying(fn, arr = []) { let len = fn.length return (...args) => { let concatArgs = [...arr, ...args] if (concatArgs.length < len) { return currying(fn, concatArgs) } else { return fn.call(this, ...concatArgs) } }}使用:

let sum = (a,b,c,d) => { console.log(a,b,c,d)}let newSum = currying(sum)newSum(1)(2)(3)(4)優點:

參數復用,由於參數可以分開傳入,我們可以復用傳入參數後的函數延遲執行,就跟 bind 一樣可以接收參數並返回函數的引用,而沒有調用垃圾回收

堆分為新生代和老生代,分別由副垃圾回收器和主垃圾回收器來負責垃圾回收。

新生代

一般剛使用的對象都會放在新生代,它的空間比較小,只有幾十MB,新生代裡還會劃分出兩個空間:form空間和to空間。

對象會先被分配到form空間中,等到垃圾回收階段,將form空間的存活對象複製到to空間中,對未存活對象進行回收,之後調換兩個空間,這種算法稱之為 「Scanvage」。

新生代的內存回收頻率很高、速度也很快,但空間利用率較低,因為讓一半的內存空間處於「閒置」狀態。

老生代

老生代的空間較大,新生代經過多次回收後還存活的對象會被送到老生代。

老生代使用「標記清除」的方式,從根元素開始遍歷,將存活對象進行標記。標記完成後,對未標記的對象進行回收。

經過標記清除之後的內存空間會產生很多不連續的碎片空間,導致一些大對象無法存放進來。所以在回收完成後,會對這些不連續的碎片空間進行整理。

JavaScript設計模式

單例模式

定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

JavaScript 作為一門無類的語言,傳統的單例模式概念在 JavaScript 中並不適用。稍微轉換下思想:單例模式確保只有一個對象,並提供全局訪問。

常見的應用場景就是彈窗組件,使用單例模式封裝全局彈窗組件方法:

import Vue from 'vue'import Index from './index.vue'let alertInstance = nulllet alertConstructor = Vue.extend(Index)let init = (options)=>{ alertInstance = new alertConstructor() Object.assign(alertInstance, options) alertInstance.$mount() document.body.appendChild(alertInstance.$el)}let caller = (options)=>{ // 單例判斷 if(!alertInstance){ init(options) } return alertInstance.show(()=>alertInstance = null)}export default { install(vue){ vue.prototype.$alert = caller }}無論調用幾次,組件也只實例化一次,最終獲取的都是同一個實例。

策略模式

定義:定義一系列的算法,把它們一個個封裝起來,並且使它們可以相互替換。

策略模式是開發中最常用的設計模式,在一些場景下如果存在大量的 if/else,且每個分支點的功能獨立,這時候就可以考慮使用策略模式來優化。

就像就上面手寫深拷貝就用到策略模式來實現:

function deepClone(obj, map = new WeakMap()) { if (obj === null || typeof obj !== "object") return obj; const type = Object.prototype.toString.call(obj).slice(8, -1) // 策略對象 let strategy = { Date: (obj) => new Date(obj), RegExp: (obj) => new RegExp(obj), Array: clone, Object: clone } function clone(obj){ // 防止循環引用,導致棧溢出,相同引用的對象直接返回 if (map.get(obj)) return map.get(obj); let target = new obj.constructor(); map.set(obj, target); for (let key in obj) { if (obj.hasOwnProperty(key)) { target[key] = deepClone(obj[key], map); } } return target; } return strategy[type] && strategy[type](obj)}這樣的代碼看起來會更簡潔,只需要維護一個策略對象,需要新功能就添加一個策略。由於策略項是單獨封裝的方法,也更易於復用。

代理模式

定義:為一個對象提供一個代用品,以便控制對它的訪問。

當不方便直接訪問一個對象或者不滿足需要的時候,提供一個代理對象來控制對這個對象的訪問,實際訪問的是代理對象,代理對象對請求做出處理後,再轉交給本體對象。

使用緩存代理請求數據:

function getList(page) { return this.$api.getList({ page }).then(res => { this.list = res.data return res })}// 代理getListlet proxyGetList = (function() { let cache = {} return async function(page) { if (cache[page]) { return cache[page] } let res = await getList.call(this, page) return cache[page] = res.data }})()上面的場景是常見的分頁需求,同一頁的數據只需要去後臺獲取一次,並將獲取到的數據緩存起來,下次再請求同一頁時,便可以直接使用之前的數據。

發布訂閱模式

定義:它定義對象間的一種一對多的依賴關係,當一個對象的狀態發送改變時,所有依賴於它的對象都將得到通知。

發布訂閱模式主要優點是解決對象間的解耦,它的應用非常廣泛,既可以用在異步編程中,也可以幫助我們完成鬆耦合的代碼編寫。像 eventBus 的通信方式就是發布訂閱模式。

let event = { events: [], on(key, fn){ if(!this.events[key]) { this.events[key] = [] } this.events[key].push(fn) }, emit(key, ...arg){ let fns = this.events[key] if(!fns || fns.length == 0){ return false } fns.forEach(fn => fn.apply(this, arg)) }}上面只是發布訂閱模式的簡單實現,還可以為其添加 off 方法來取消監聽事件。在 Vue 中,通常是實例化一個新的 Vue 實例來做發布訂閱中心,解決組件通信。而在小程序中可以手動實現發布訂閱模式,用於解決頁面通信的問題。

裝飾器模式

定義:動態地為某個對象添加一些額外的職責,而不會影響對象本身。

裝飾器模式在開發中也是很常用的設計模式,它能夠在不影響原始碼的情況下,很方便的擴展屬性和方法。比如以下應用場景是提交表單。

methods: { submit(){ this.$api.submit({ data: this.form }) }, // 為提交表單添加驗證功能 validateForm(){ if(this.form.name == ''){ return } this.submit() }}想像一下,如果你剛接手一個項目,而 submit 的邏輯很複雜,可能還會牽扯到很多地方。冒然的侵入原始碼去擴展功能會有風險,這時候裝飾器模式就幫上大忙了。

Vue

對MVVM模式的理解

MVVM 對應 3個組成部分,Model(模型)、View(視圖) 和 ViewModel(視圖模型)。

View 是用戶在屏幕上看到的結構、布局和外觀,也稱UI。ViewModel 是一個綁定器,能和 View 層和 Model 層進行通信。Model 是數據和邏輯。View 不能和 Model 直接通信,它們只能通過 ViewModel 通信。Model 和 ViewModel 之間的交互是雙向的,ViewModel 通過雙向數據綁定把 View 層和 Model 層連接起來,因此 View 數據的變化會同步到 Model 中,而 Model 數據的變化也會立即反應到 View 上。

題外話,你可能不知道 Vue 不完全是 MVVM 模式:

嚴格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 在組件提供了 $refs 這個屬性,讓 Model 可以直接操作 View,違反了這一規定。

Vue的渲染流程

流程主要分為三個部分:

模板編譯,parse 解析模板生成抽象語法樹(AST);optimize 標記靜態節點,在後續頁面更新時會跳過靜態節點;generate 將AST轉成 render 函數,render 函數用於構建 VNode。構建VNode(虛擬dom),構建過程使用 createElement 構建 VNode,createElement 也是自定義 render 函數時接受到的第一個參數。VNode轉真實dom,patch 函數負責將 VNode 轉換成真實dom,核心方法是createElm,遞歸創建真實dom樹,最終渲染到頁面上。

data為什麼要求是函數

當一個組件被定義,data 必須聲明為返回一個初始數據對象的函數,因為組件可能被用來創建多個實例。如果 data 仍然是一個純粹的對象,則所有的實例將共享引用同一個數據對象!通過提供 data 函數,每次創建一個新實例後,我們能夠調用 data 函數,從而返回初始數據的一個全新副本數據對象。

JavaScript 中的對象作為引用類型,如果是創建多個實例,直接使用對象會導致實例的共享引用。而這裡創建多個實例,指的是組件復用的情況。因為在編寫組件時,是通過 export 暴露出去的一個對象,如果組件復用的話,多個實例都是引用這個對象,就會造成共享引用。使用函數返回一個對象,由於是不同引用,自然可以避免這個問題發生。

Vue生命周期

beforeCreate: 在實例創建之前調用,由於實例還未創建,所以無法訪問實例上的 data、computed、method等。created: 在實例創建完成後調用,這時已完成數據的觀測,可以獲取數據和更改數據,但還無法與dom進行交互,如果想要訪問dom,可以使用 vm.$nextTick。此時可以對數據進行更改,不會觸發 updated。beforeMount: 在掛載之前調用,這時的模板已編譯完成並生成render函數,準備開始渲染。在此時也可以對數據進行更改,不會觸發 updated。mounted: 在掛載完成後調用,真實的dom掛載完畢,可以訪問到dom節點,使用 $refs 屬性對dom進行操作。beforeUpdate: 在更新之前調用,也就是響應式數據發生更新,虛擬dom重新渲染之前被觸發,在當前階段進行更改數據,不會造成重渲染。updated: 在更新完成之後調用,組件dom已完成更新。要注意的是避免在此期間更改數據,這可能會導致死循環。beforeDestroy: 在實例銷毀之前調用,這時實例還可以被使用,一般這個周期內可以做清除計時器和取消事件監聽的工作。destroyed: 在實例銷毀之後調用,這時已無法訪問實例。當前實例從父實例中被移除,觀測被卸載,所有事件監聽器唄移除,子實例也統統被銷毀。請說出 Vue 的5種指令

v-ifv-forv-showv-htmlv-modelcomputed 和 watch 的區別

computed 依賴 data 的改變而改變,computed 會返回值;watch 觀察 data,執行對應的函數。computed 有緩存功能,重複取值不會執行求值函數。computed 依賴收集在頁面渲染時觸發,watch 收集依賴在頁面渲染前觸發。computed 更新需要「渲染Watcher」的配合,computed 更新只是設置 dirty,需要頁面渲染觸發 get 重新求值Vue 中的 computed 是如何實現緩存的

「計算屬性Watcher」會帶有一個 dirty 的屬性,在初始化取值完成後,會將 dirty 設置為 false。只要依賴屬性不更新,dirty 永遠為 false,重複取值也不會再去執行求值函數,而是直接返回結果,從而實現緩存。相反,依賴屬性更新會將「計算屬性 Watcher」的 dirty 設置為 true,在頁面渲染對計算屬性取值時,再次觸發求值函數更新計算屬性。

Object.defineProperty(target, key, { get() { const watcher = this._computedWatchers && this._computedWatchers[key] // 計算屬性緩存 if (watcher.dirty) { // 計算屬性求值 watcher.evaluate() } return watcher.value }})組件通信方式

props/emit$children/$parentref$attrs/$listenersprovide/injecteventBusvuex雙向綁定原理

雙向綁定是視圖變化會反映到數據,數據變化會反映到視圖,v-model 就是個很好理解的例子。其實主要考查的還是響應式原理,響應式原理共包括3個主要成員,Observer 負責監聽數據變化,Dep 負責依賴收集,Watcher 負責數據或視圖更新,我們常說的收集依賴就是收集 Watcher。

響應式原理主要工作流程如下:

Observer 內使用 Object.defineProperty 劫持數據,為其設置 set 和 get。每個數據都會有自己的 dep。數據取值觸發 get 函數,調用 dep.depend 收集依賴;數據更新觸發 set 函數,調用 dep.notify 通知 Watcher 更新。Watcher 接收到更新的通知,將這些通知加入到一個異步隊列中,並且進行去重處理,等到所有同步操作完成後,再一次性更新視圖。Vue如何檢測數組變化

Vue 內部重寫數組原型鏈,當數組發生變化時,除了執行原生的數組方法外,還會調用 dep.notify 通知 Watcher 更新。觸發數組更新的方法共7種:

pushpopshiftunshiftsplicesortreversekeep-alive

keep-alive 是 Vue 的內置組件,同時也是一個抽象組件,它主要用於組件緩存。當組件切換時會將組件的VNode緩存起來,等待下次重新激活時,再將緩存的組件VNode渲染出來,從而實現緩存。

常用的兩個屬性 include 和 exclude,支持字符串、正則和數組的形式,允許組件有條件的進行緩存。還有 max 屬性,用於設置最大緩存數。

兩個生命周期 activated 和 deactivated,在組件激活和失活時觸發。

keep-alive 的緩存機制運用LRU(Least Recently Used)算法,

nextTick

在下次 dom 更新結束之後執行延遲回調。nextTick 主要使用了宏任務和微任務。根據執行環境分別嘗試採用:

PromiseMutationObserversetImmediatesetTimeoutnextTick 主要用於內部 Watcher 的異步更新,對外我們可以使用 Vue.nextTick 和 vm.$nextTick。在 nextTick 中可以獲取更新完成的 dom。

如何理解單向數據流

所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。

單向數據流只允許數據由父組件傳遞給子組件,數據只能由父組件更新。當數據傳遞到多個子組件,而子組件能夠在其內部更新數據時,在主觀上很難知道是哪個子組件更新了數據,導致數據流向不明確,從而增加應用調試的難度。

但子組件更新父組件數據的場景確實存在,有3種方法可以使用:

子組件emit,父組件接受自定義事件。這種方法最終還是由父組件進行修改,子組件只是起到一個通知的作用。子組件自定義雙向綁定,設置組件的 model 選項為組件添加自定義雙向綁定。.sync 屬性修飾符,它是第一種方法的語法糖,在傳遞屬性添加上該修飾符,子組件內可調用 this.$emit('update:屬性名', value) 更新屬性。Vue3 和 Vue2.x 的差異

使用 Proxy 代替 Object.defineProperty新增 Composition API模板允許多個根節點Vue3 為什麼使用 Proxy 代替 Object.definedProperty

Object.definedProperty 只能檢測到屬性的獲取和設置,對於新增和刪除是沒辦法檢測的。在數據初始化時,由於不知道哪些數據會被用到,Vue 是直接遞歸觀測全部數據,這會導致性能多餘的消耗。

Proxy 劫持整個對象,對象屬性的增加和刪除都能檢測到。Proxy 並不能監聽到內部深層的對象變化,因此 Vue 3.0 的處理方式是在 getter 中去遞歸響應式,只有真正訪問到的內部對象才會變成響應式,而不是無腦遞歸,在很大程度上提升了性能。

路由懶加載是如何實現的

路由懶加載是性能優化的一種手段,在編寫代碼時可以使用 import() 引入路由組件,使用懶加載的路由會在打包時單獨出來成一個 js 文件,可以使用 webpackChunkName 自定義包名。在項目上線後,懶加載的 js 文件不會在第一時間加載,而是在訪問到對應的路由時,才會動態創建 script 標籤去加載這個 js 文件。

{ path:'users', name:'users', component:()=> import(/*webpackChunkName: "users"*/ '@/views/users'),}Vue路由鉤子函數

全局鉤子

beforeEach路由進入前調用

const router = new VueRouter({ ... })router.beforeEach((to, from, next) => { // ...})beforeResolve (2.5.0 新增)在所有組件內守衛和異步組件被解析之後調用

router.beforeResolve((to, from, next) => { // ...})afterEach路由在確認後調用

router.afterEach((to, from) => { // ...})路由獨享鉤子

beforeEnter路由進入前調用,beforeEnter 在 beforeEach 之後執行

const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ]})組件鉤子

beforeRouteEnter路由確認前調用,組件實例還沒被創建,不能獲取組件實例 this

beforeRouteEnter (to, from, next) { // ... // 可以通過回調訪問實例 next(vm => { // vm 為組件實例 })},beforeRouteUpdate (2.2 新增)路由改變時調用,可以訪問組件實例

beforeRouteUpdate (to, from, next) { // ...},beforeRouteLeave離開該組件的對應路由時調用,可以訪問組件實例 this

beforeRouteLeave (to, from, next) { // ...}vue-router的原理

vue-router原理是更新視圖而不重新請求頁面。vue-router共有3種模式:hash模式、history模式、abstract模式。

hash模式

hash模式使用 hashchange 監聽地址欄的hash值的變化,加載對應的頁面。每次的hash值變化後依然會在瀏覽器留下歷史記錄,可以通過瀏覽器的前進後退按鈕回到上一個頁面。

history模式

history模式基於History Api實現,使用 popstate 監聽地址欄的變化。使用 pushState 和 replaceState 修改url,而無需加載頁面。但是在刷新頁面時還是會向後端發起請求,需要後端配合將資源定向回前端,交由前端路由處理。

abstract

不涉及和瀏覽器地址的相關記錄。通過數組維護模擬瀏覽器的歷史記錄棧。

vuex 怎麼跨模塊調用

跨模塊調用是指當前命名空間模塊調用全局模塊或者另一個命名空間模塊。在調用 dispatch 和 commit 時設置第三個參數為 {root:true}。

modules: { foo: { namespaced: true, actions: { someAction ({ dispatch, commit, getters, rootGetters }) { // 調用自己的action dispatch('someOtherAction') // -> 'foo/someOtherAction' // 調用全局的action dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' // 調用其他模塊的action dispatch('user/someOtherAction', null, { root: true }) // -> 'user/someOtherAction' }, someOtherAction (ctx, payload) { ... } } }}vuex 如何實現持久化

vuex存儲的狀態在頁面刷新後會丟失,使用持久化技術能保證頁面刷新後狀態依然存在。

使用本地存儲配合,設置 state 同時設置 storage,在刷新後再初始化 vuexvuex-persistedstate 插件模塊化

這裡只記錄常用的兩種模塊:CommonJS模塊、ES6模塊。

CommonJS模塊

Node.js 採用 CommonJS 模塊規範,在服務端運行時是同步加載,在客戶端使用需要編譯後才可以運行。

特點

模塊可以多次加載。但在第一次加載時,結果會被緩存起來,再次加載模塊,直接獲取緩存的結果模塊加載的順序,按照其在代碼中出現的順序語法

暴露模塊:module.exports = value 或 exports.xxx = value引入模塊:require('xxx'),如果是第三方模塊,xxx為模塊名;如果是自定義模塊,xxx為模塊文件路徑清楚模塊緩存:delete require.cache[moduleName];,緩存保存在 require.cache 中,可操作該屬性進行刪除模塊加載機制

加載某個模塊,其實是加載該模塊的 module.exports 屬性exports 是指向 module.exports 的引用module.exports 的初始值為一個空對象,exports 也為空對象,module.exports 對象不為空的時候 exports 對象就被忽略模塊加載的是值的拷貝,一旦輸出值,模塊內的變化不會影響到值,引用類型除外module.exports 不為空:

// nums.jsexports.a = 1module.exports = { b: 2}exports.c = 3let nums = require('./nums.js') // { b: 2 }module.exports 為空:

// nums.jsexports.a = 1exports.c = 3let nums = require('./nums.js') // { a: 1, c: 3 }值拷貝的體現:

// nums.jslet obj = { count: 10}let count = 20function addCount() { count++}function getCount() { return count}function addObjCount() { obj.count++}module.exports = { count, obj, addCount, getCount, addObjCount }let { count, obj, addCount, getCount, addObjCount } = require('./nums.js')// 原始類型不受影響console.log(count) // 20addCount()console.log(count) // 20// 如果想獲取到變化的值,可以使用函數返回console.log(getCount()) // 21// 引用類型會被改變console.log(obj) // { count: 10 }addObjCount()console.log(obj) // { count: 11 }ES6模塊

ES6 模塊的設計思想是儘量的靜態化,使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量。

特點

由於靜態分析的原因,ES6模塊加載只能在代碼頂層使用模塊不能多次加載同一個變量語法

暴露模塊:export 或 export default引入模塊:import模塊加載機制

模塊加載的是引用的拷貝,模塊內的變化會影響到值// nums.jsexport let count = 20export function addCount() { count++}export default { other: 30}// 同時引入 export default 和 export 的變量import other, { count, addCount } from './async.js'console.log(other) // { other: 30 }console.log(count) // 20addCount()console.log(count) // 21ES6 模塊與 CommonJS 模塊的差異

CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。瀏覽器

頁面渲染流程

字節流解碼。瀏覽器獲得字節數據,根據字節編碼將字節流解碼,轉換為代碼。輸入流預處理。字符數據進行統一格式化。令牌化。從輸入流中提取可識別的子串和標記符號。可以理解為對HTML解析,進行詞法分析,匹配標籤生成令牌結構。構建DOM樹、構建CSSOM樹。DOM樹和CSSOM樹的構建過程是同時進行的,在 HTML 解析過程中如果遇到 script 標籤,解析會暫停並將執行權限交給 JavaScript 引擎,等到 JavaScript 腳本執行完畢後再交給渲染引擎繼續解析。(補充:如果腳本中調用了改變 DOM 結構的 document.write() 函數,此時渲染引擎會回到第二步,將這些代碼加入字符流,重新進行解析。)構建渲染樹。DOM樹負責結構內容,CSSOM樹負責樣式規則,為了渲染,需要將它們合成渲染樹。布局。布局階段根據渲染樹的節點和節點的CSS定義以及節點從屬關係,計算元素的大小和位置,將所有相對值轉換為屏幕上的絕對像素。繪製。繪製就是將渲染樹中的每個節點轉換成屏幕上的實際像素的過程。在繪製階段,瀏覽器會遍歷渲染樹,調用渲染器的paint方法在屏幕上顯示其內容。實際上,繪製過程是在多個層上完成的,這些層稱為渲染層(RenderLayer)。渲染層合成。多個繪製後的渲染層按照恰當的重疊順序進行合併,而後生成位圖,最終通過顯卡展示到屏幕上。數據變化過程:字節 → 字符 → 令牌 → 樹 → 頁面

回流、重繪

回流(Reflow)

在布局完成後,對DOM布局進行修改(比如大小或位置),會引起頁面重新計算布局,這個過程稱為「回流」。

重繪(Repaint)

對DOM進行不影響布局的修改引起的屏幕局部繪製(比如背景顏色、字體顏色),這個過程稱為「重繪」。

小結

回流一定會引起重繪,而重繪不一定會引起回流。由於回流需要重新計算節點布局,回流的渲染耗時會高於重繪。

對於回流重繪,瀏覽器本身也有優化策略,瀏覽器會維護一個隊列,將回流重繪操作放入隊列中,等隊列到達一定時間,再按順序去一次性執行隊列的操作。

但是也有例外,有時我們需要獲取某些樣式信息,例如:offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop/Left/Width/Height,clientTop/Left/Width/Height,getComputedStyle(),或者 IE 的 currentStyle。

這時,瀏覽器為了反饋準確的信息,需要立即回流重繪一次,所以可能導致隊列提前執行。

事件循環(Event Loop)

在瀏覽器的實現上,諸如渲染任務、JavaScript 腳本執行、User Interaction(用戶交互)、網絡處理都跑在同一個線程上,當執行其中一個類型的任務的時候意味著其他任務的阻塞,為了有序的對各個任務按照優先級進行執行瀏覽器實現了我們稱為 Event Loop 調度流程。

簡單來說,Event Loop 就是執行代碼、收集和處理事件以及執行隊列中子任務的一個過程。

宏任務

在一次新的事件循環的過程中,遇到宏任務時,宏任務將被加入任務隊列,但需要等到下一次事件循環才會執行。

常見宏任務:setTimeout、setInterval、requestAnimationFrame

微任務

當前事件循環的任務隊列為空時,微任務隊列中的任務就會被依次執行。在執行過程中,如果遇到微任務,微任務被加入到當前事件循環的微任務隊列中。簡單來說,只要有微任務就會繼續執行,而不是放到下一個事件循環才執行。

微任務隊列屬於任務運行環境內的一員,並非處於全局的位置。也就是說,每個任務都會有一個微任務隊列。

常見微任務:Promise.then、Promise.catch、MutationObserver

流程

取出一個宏任務執行,如果碰到宏任務,將其放入任務隊列,如果碰到微任務,將其放入微任務隊列檢查微任務隊列是否有可執行的微任務,如果有則執行微任務。微任務執行過程中,如果碰到宏任務,將其放入任務隊列。如果碰到微任務,繼續將其放入當前的微任務隊列,直到微任務全部執行。更新渲染階段,判斷是否需要渲染,也就是說不一定每一輪 Event Loop 都會對應一次瀏覽器渲染。對於需要渲染的文檔,執行requestAnimationFrame幀動畫回調。對於需要渲染的文檔,重新渲染繪製用戶界面。判斷任務隊列和微任務隊列是否為空,如果是,則進行 Idle 空閒周期的算法,判斷是否要執行 requestIdleCallback 的回調函數。小結

在當前任務運行環境內,微任務總是先於宏任務執行;

requestAnimationFrame 回調在頁面渲染之前調用,適合做動畫;

requestIdleCallback 在渲染屏幕之後調用,可以使用它來執行一些不太重要的任務。

同源策略(Same origin policy)

源是由 URL 中協議、主機名(域名)以及埠共同組成的部分。

同源策略是瀏覽器的行為,為了保護本地數據不被JavaScript代碼獲取回來的數據汙染,它是存在於瀏覽器最核心也最基本的安全功能。

所謂同源指的是:協議、域名、埠號必須一致,只要有一個不相同,那麼就是「跨源」。

最常見的同源策略是因為域名不同,也就是常說的「跨域」。一般分為請求跨域和頁面跨域。

請求跨域解決方案

跨域資源共享(CORS)。服務端設置HTTP響應頭(Access-Control-Allow-Origin)代理轉發。同源策略只存在於瀏覽器,使用服務端設置代理轉發沒有同源策略的限制。JSONP。依賴的是 script 標籤跨域引用 js 文件不會受到瀏覽器同源策略的限制。Websocket。HTML5 規範提出的一個應用層的全雙工協議,適用於瀏覽器與伺服器進行實時通信場景。常用方法是CORS和代理轉發。

頁面跨域解決方案

postMessage。HTML5 的 postMessage 方法可用於兩個頁面之間通信,而且不論這兩個頁面是否同源。document.domain。對於主域名相同,子域名不同的情況,可以通過修改 document.domain 的值來進行跨域。window.location.hash,通過 url 帶 hash ,通過一個非跨域的中間頁面來傳遞數據。window. name,當 window 的 location 變化,然後重新加載,它的 name 屬性可以依然保持不變。通過 iframe 的 src 屬性由外域轉向本地域,跨域數據即由 iframe 的 window. name 從外域傳遞到本地域。CORS請求

對於CORS請求,瀏覽器將其分成兩個類型:簡單請求和非簡單請求。

簡單請求

簡單請求符合下面 2 個特徵:

請求方法為 GET、POST、HEAD。請求頭只能使用以下規定的安全欄位:Accept(瀏覽器能夠接受的響應內容類型)Accept-Language(瀏覽器能夠接受的自然語言列表)Content-Type (請求對應的類型,只限於 text/plain、multipart/form-data、application/x-www-form-urlencode)Content-Language(瀏覽器希望採用的自然語言)Save-DataDPRDownLinkViewport-WidthWidth非簡單請求

任意一條要求不符合的即為非簡單請求。常見是自定義 header,例如將token 設置到請求頭。

在處理非簡單請求時,瀏覽器會先發出「預檢請求」,預檢請求為OPTIONS方法,以獲知伺服器是否允許該實際請求,避免跨域請求對伺服器產生預期外的影響。如果預檢請求返回200允許通過,才會發真實的請求。

預檢請求並非每次都需要發送,可以使用 Access-Control-Max-Age 設置緩存時間進行優化,減少請求發送。

HTTP

HTTP 1.0、HTTP 1.1、HTTP 2.0的區別

HTTP1.0

增加頭部設定,頭部內容以鍵值對的形式設置。請求頭部通過 Accept 欄位來告訴服務端可以接收的文件類型,響應頭部再通過 Content-Type 欄位來告訴瀏覽器返回文件的類型。

HTTP1.1

HTTP1.0中每次通信都需要經歷建立連接、傳輸數據和斷開連接三個階段,這會增加大量網絡開銷。

HTTP1.1增加持久化連接,即連接傳輸完畢後,TCP連接不會馬上關閉,而是其他請求可以復用連接。這個連接保持到瀏覽器或者伺服器要求斷開連接為止。

HTTP2.0

HTTP1.1雖然減少連接帶來的性能消耗,但是請求最大並發受到限制,同一域下的HTTP連接數根據瀏覽器不同有所變化,一般是6 ~ 8個。而且一個TCP連接同一時刻只能處理一個請求,當前請求未結束之前,其他請求只能處於阻塞狀態。

HTTP2.0中增加「多路復用」的機制,不再受限於瀏覽器的連接數限制。基於二進位分幀,客戶端發送的數據會被分割成帶有編號的碎片(二進位幀),然後將這些碎片同時發送給服務端,服務端接收到數據後根據編號再合併成完整的數據。服務端返回數據也同樣遵循這個過程。

三次握手

過程

第一次握手:客戶端向服務端發起連接請求報文,報文中帶有一個連接標識(SYN);

第二次握手:服務端接收到客戶端的報文,發現報文中有連接標識,服務端知道是一個連接請求,於是給客戶端回復確認報文(帶有SYN標識);

第三次握手:客戶端收到服務端回復確認報文,得知服務端允許連接,於是客戶端回復確認報文給服務端,服務端收到客戶端的回覆報文後,正式建立TCP連接;

為什麼需要三次握手,兩次可以嗎?

如果是兩次握手,在第二次握手出現確認報文丟失,客戶端不知道服務端是否準備好了,這種情況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。

如果是三次握手,在第三次握手出現確認報文丟失,服務端在一段時間沒有收到客戶端的回覆報文就會重新第二次握手,客戶端收到重複的報文會再次給服務端發送確認報文。

三次握手主要考慮是丟包重連的問題。

四次揮手

過程

第一次揮手:客戶端向服務端發出連接釋放報文,報文中帶有一個連接釋放標識(FIN)。此時客戶端不能再發送數據,但是可以正常接收數據;

第二次揮手:服務端接收到客戶端的報文,知道是一個連接釋放請求。服務端給客戶端回復確認報文,但要注意這個回復報文未帶有FIN標識。此時服務端處於關閉等待狀態,這個狀態還要持續一段時間,因為服務端可能還有數據沒發完;

第三次揮手:服務端將最後的數據發送完畢後,給客戶端回復確認報文(帶有FIN標識),這個才是通知客戶端可以釋放連接的報文;

第四次揮手:客戶端收到服務端回復確認報文後,於是客戶端回復確認報文給服務端。而服務端一旦收到客戶端發出的確認報文就會立馬釋放TCP連接,所以服務端結束TCP連接的時間要比客戶端早一些。

為什麼握手需要三次,而揮手需要四次

服務端需要確保數據完整性,只能先回復客戶端確認報文告訴客戶端我收到了報文,進入關閉等待狀態。服務端在數據發送完畢後,才回復FIN報文告知客戶端數據發送完了,可以斷開了,由此多了一次揮手過程。

HTTPS

HTTPS之所以比HTTP安全,是因為對傳輸內容加密。HTTPS加密使用對稱加密和非對稱加密。

對稱加密:雙方共用一把鑰匙,可以對內容雙向加解密。但是只要有人和伺服器通信就能獲得密鑰,也可以解密其他通信數據。所以相比非對稱加密,安全性較低,但是它的效率比非對稱加密高。

非對稱加密:非對稱加密會生成公鑰和私鑰,一般是服務端持有私鑰,公鑰向外公開。非對稱加密對內容單向加解密,即公鑰加密只能私鑰解,私鑰加密只能公鑰解。非對稱加密安全性雖然高,但是它的加解密效率很低。

CA證書:由權威機構頒發,用於驗證服務端的合法性,其內容包括頒發機構信息、公鑰、公司信息、域名等。

對稱加密不安全主要是因為密鑰容易洩露,那只要保證密鑰的安全,就可以得到兩全其美的方案,加解密效率高且安全性好。所以HTTPS在傳輸過程中,對內容使用對稱加密,而密鑰使用非對稱加密。

過程

客戶端向服務端發起HTTPS請求服務端返回HTTPS證書客戶端驗證證書是否合法,不合法會提示告警證書驗證合法後,在本地生成隨機數用公鑰加密隨機數並發送到服務端服務端使用私鑰對隨機數解密服務端使用隨機數構造對稱加密算法,對內容加密後傳輸客戶端收到加密內容,使用本地存儲的隨機數構建對稱加密算法進行解密HTTP 緩存

HTTP 緩存包括強緩存和協商緩存,強緩存的優先級高於協商緩存。緩存優點在於使用瀏覽器緩存,對於某些資源服務端不必重複發送,減小服務端的壓力,使用緩存的速度也會更快,從而提高用戶體驗。

強緩存

強緩存在瀏覽器加載資源時,先從緩存中查找結果,如果不存在則向服務端發起請求。

Expirss

HTTP/1.0 中可以使用響應頭部欄位 Expires 來設置緩存時間。

客戶端第一次請求時,服務端會在響應頭部添加 Expirss 欄位,瀏覽器在下一次發送請求時,會對比時間和Expirss的時間,沒有過期使用緩存,過期則發送請求。

Cache-Control

HTTP/1.1 提出了 Cache-Control 響應頭部欄位。

一般會設置 max-age 的值,表示該資源需要緩存多長時間。Cache-Control 的 max-age 優先級高於 Expires。

協商緩存

協商緩存的更新策略是不再指定緩存的有效時間,而是瀏覽器直接發送請求到服務端進行確認緩存是否更新,如果請求響應返回的 HTTP 狀態為 304,則表示緩存仍然有效。

Last-Modified 和 If-Modified-Since

Last-Modified 和 If-Modified-Since 對比資源最後修改時間來實現緩存。

瀏覽器第一次請求資源,服務端在返回資源的響應頭上添加 Last-Modified 欄位,值是資源在服務端的最後修改時間;瀏覽器再次請求資源,在請求頭上添加 If-Modified-Since,值是上次服務端返回的最後修改時間;服務端收到請求,根據 If-Modified-Since 的值進行判斷。若資源未修改過,則返回 304 狀態碼,並且不返回內容,瀏覽器使用緩存;否則返回資源內容,並更新 Last-Modified 的值;ETag 和 If-None-Match

ETag 和 If-None-Match 對比資源哈希值,哈希值由資源內容計算得出,即依賴資源內容實現緩存。

瀏覽器第一次請求資源,服務端在返回資源的響應頭上添加 ETag 欄位,值是資源的哈希值瀏覽器再次請求資源,在請求頭上添加 If-None-Match,值是上次服務端返回的資源哈希值;服務端收到請求,根據 If-None-Match 的值進行判斷。若資源內容沒有變化,則返回 304 狀態碼,並且不返回內容,瀏覽器使用緩存;否則返回資源內容,並計算哈希值放到 ETag;TCP 和 UDP 的區別

TCP

面向連接一對一通信面向字節流可靠傳輸,使用流量控制和擁塞控制報頭最小20位元組,最大60位元組UDP

無連接支持一對一,一對多,多對一和多對多的通信面向報文不可靠傳輸,不使用流量控制和擁塞控制報頭開銷小,僅8位元組正向代理

代理客戶;隱藏真實的客戶,為客戶端收發請求,使真實客戶端對伺服器不可見;一個區域網內的所有用戶可能被一臺伺服器做了正向代理,由該臺伺服器負責 HTTP 請求;意味著同伺服器做通信的是正向代理伺服器;

反向代理

代理伺服器;隱藏了真實的伺服器,為伺服器收發請求,使真實伺服器對客戶 端不可見;負載均衡伺服器,將用戶的請求分發到空閒的伺服器上;意味著用戶和負載均衡伺服器直接通信,即用戶解析伺服器域名時得到的是負載均衡伺服器的 IP ;

前端安全

跨站腳本攻擊(XSS)

跨站腳本(Cross Site Scripting,XSS)指攻擊者在頁面插入惡意代碼,當其他用戶訪問時,瀏覽會器解析並執行這些代碼,達到竊取用戶身份、釣魚、傳播惡意代碼等行為。一般我們把 XSS 分為反射型、存儲型、DOM 型3 種類型。

反射型 XSS

反射型 XSS 也叫「非持久型 XSS」,是指攻擊者將惡意代碼通過請求提交給服務端,服務端返回的內容,也帶上了這段 XSS 代碼,最後導致瀏覽器執行了這段惡意代碼。

反射型 XSS 攻擊方式需要誘導用戶點擊連結,攻擊者會偽裝該連結(例如短連結),當用戶點擊攻擊者的連結後,攻擊者便可以獲取用戶的 cookie 身份信息。

案例:

服務端直接輸出參數內容:

<? php$input = $_GET["param"];echo "<div>".$input."</div>";惡意代碼連結:

http://www.a.com/test.php?param=<srcipt src="xss.js"></script>存儲型 XSS

存儲型 XSS 也叫「持久型XSS」,會把用戶輸入的數據存儲在服務端,這種XSS具有很強的穩定性。

案例:

比如攻擊者在一篇博客下留言,留言包含惡意代碼,提交到服務端後被存儲到資料庫。所有訪問該博客的用戶,在加載出這條留言時,會在他們的瀏覽器中執行這段惡意的代碼。

DOM 型 XSS

DOM 型 XSS 是一種特殊的反射型 XSS,它也是非持久型 XSS。相比於反射型 XSS,它不需要經過服務端,而是改變頁面 DOM 來達到攻擊。同樣,這種攻擊方式也需要誘導用戶點擊。

案例:

目標頁面:

<html> <body>hello</body></html><script> let search = new URLSearchParams(location.search) document.write("hello, " + search.get('name') + '!')</script>惡意代碼連結:

http://www.a.com/test.index?name=<srcipt src="xss.js"></script>防禦手段

參數驗證,不符合要求的數據不要存入資料庫對特殊字符轉義,如"<"、">"、"/"、"&"等避免使用eval、new Function動態執行字符串的方法避免使用 innerHTML、document.write 直接將字符串輸出到HTML把一些敏感的 cookie 設置為 http only,避免前端訪問 cookie跨站請求偽造(CSRF)

CSRF 攻擊就是在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊站點,從而在並未授權的情況下執行在權限保護之下的操作。CSRF 並不需要直接獲取用戶信息,只需要「借用」用戶的登錄信息相關操作即可,隱蔽性更強。

案例:

假設現在有一個博客網站,得知刪除博文的 URL 為:

http://blog.com?m=delete&id=123攻擊者構造一個頁面,內容為:

<img src="http://blog.com?m=delete&id=123"></img>攻擊者偽裝該網站連結並誘導用戶進行點擊,用戶恰好訪問過 blog.com,與該網站的 cookie 身份驗證信息還未過期。這時進入攻擊者的網站,img 發起請求,請求裡攜帶上cookie,成功刪除博文。但是對於用戶是無感知的,當用戶返回到博客時會發現博文不見了,而這個請求是屬於合法請求,因為攻擊者借用受害者的身份信息進行操作。

防禦手段

設置 Cookie 的 SameSite服務端驗證 Refer 欄位,Refer 是請求源網址,對於不合法的 Refer 拒絕請求添加 token,讓連結變得不可預測,攻擊者無法構造一個完整的 URL 實施 CSRF 攻擊添加驗證碼,強制用戶必須與應用交互,但會降低用戶體驗,只能作為輔助手段點擊劫持(ClickJacking)

攻擊者創建一個網頁利用 iframe 包含目標網站,然後通過設置透明度等方式隱藏目標網站,使用戶無法察覺目標網站的存在,並且把它遮罩在網頁上。在網頁中誘導用戶點擊特定的按鈕,而這個按鈕的位置和目標網站的某個按鈕重合,當用戶點擊網頁上的按鈕時,實際上是點擊目標網站的按鈕。

防禦手段

frame busting,通常可以寫一段JavaScript,以禁止 iframe 的嵌套。if (top.location != location) { top.location = self.location}添加 HTTP 頭 X-Frame-Options參考資料

《JavaScript高級程序設計(第3版)》《你不知道的JavaScript(上卷)》《JavaScript設計模式與開發實踐》《白帽子講Web安全》ES6 入門教程Vue.js 技術揭秘前端模塊化詳解(完整版)HTML規範 - 解析HTML文檔瀏覽器層合成與頁面渲染優化10種跨域解決方案(附終極大招)MDN - HTTP訪問控制(CORS)HTML規範 - 事件循環MDN - 深入:微任務與Javascript運行時環境深入解析你不知道的 EventLoop 和瀏覽器渲染、幀動畫、空閒回調(動圖演示)Tasks, microtasks, queues and schedules臥槽!牛皮了,頭一次見有大佬把TCP三次握手四次揮手解釋的這麼明白你連 HTTPS 原理都不懂,還講「中間人攻擊」?進階 · 那些你必須搞懂的網絡基礎

相關焦點

  • 面試官說我「可塑性強」是什麼意思?
    「引子」小A說昨天面試結束,在電梯裡遇到主面試官,她很和藹的說「你的可塑性還是很強的,回去等複試通知吧。」01面試官說我「可塑性強」是什麼意思?「可塑性」指一個人被繼續培養、改造的可行性及上升空間。「可塑性強」就是說這個人有學習的能力,有改變的可行性,包括思想,行為,技能等等。因此,並不是所有人都擁有可塑性的。
  • 「高中數學」16模塊知識點:分類總結+針對性提分!高三複習必備
    童鞋們好,這麼快就周日啦,學姐給大家帶來乾貨之前,想和泥萌分享一部劇《知否知否,應是肥紅瘦》真的是學姐認為的唯一一部百看不膩的劇,學習的空閒時間可以看看(女主和男主的三觀很正,能為我們帶來正確的人生觀)本文學姐就為大家帶來了「高中數學」16模塊知識點!
  • 「國家公務員考試網官網分數」國考面試形式
    「國家公務員考試網官網分數」國考面試形式由國家公務員考試網考試快訊欄目由提供,更多關於國考面試,國家公務員考試網,國家公務員考試考試快訊的內容,請關注國家公務員考試網/廣東公務員考試網!   2021國家公務員考試面試名單已發布!
  • 面試800人後,我總結了20條識人技巧
    他們不僅會提前做一些面試題回答的準備,還會預判企業要這個崗位的人是希望他外向還是內向、成熟還是純真、獨特還是合群... ...網上甚至還有「反面試技巧」的相關培訓。當下HR的一場面試,想要通過面試題來獲取的信息量,已經越來越少。
  • 教師資格證英語學科面試——你必須要看的實用備考乾貨
    教師資格證英語學科面試——你必須要看的實用備考乾貨 http://www.hteacher.net 2019-04-15 13:40 中國教師資格網 [您的教師考試網]
  • 「最美應用」MindNode 5:要寫年終總結了吧?用最強腦圖梳理下,說不...
    今天推薦的這個:MindNode 5 ,是 Mac 和 IOS平臺上盛譽滿滿的種子選手,從 4 升級到 5,不僅僅是整個 UI 系統大換血,而且還和蘋果原生系統 IOS 11 保持統一設計,這就是非常大的加分項了,用著視覺感更舒服。再加上臨近年終,麵包君覺得更有必要讓大家用一款極致的思維導圖,用邏輯感超強的腦圖梳理自己一年的成績,隨年終總結一起交給 Boss ,還怕不能升職加薪嗎?
  • 20條靠譜的面試識人技巧,乾貨內容大分享
    說實在話,招人用人是HR在招聘路上一輩子的修行,如果我們沒有辦法做到像美劇《讀心神探》那樣通過一個微表情就有見面識人的本領,下面我們總結的20條比較精準的看人識人技巧,也一樣可以幫你在常規面試題以外,助你看清一位候選人。
  • 化學「必修一」超全知識點整理,滿滿的乾貨!
    對於初學者來說,高中化學進入了一個新的高度,化學不僅需要反覆記憶,更重要的是學習方法,要學會總結歸納。高中化學必修一在高考中佔的分值也比較大,不要等到高三總複習回頭撿高一的內容,會耽誤你整個總複習的進度,而且效果不好。建議在高一、高二把所學的知識點吃透,等到高三總複習時,你會很輕鬆的查漏補缺,快速上分。
  • 宮崎駿認了龍貓「是可怕生物」!吉卜力工作室面試過程曝光
    他近日在受訪時分享當年10幾歲的他到吉卜力工作室面試的過程,當時由宮崎駿和鈴木敏夫親自進行團體面試,宮崎駿為了緩解一位女生的緊張,便問對方:「喜歡看什麼作品?」得到「龍貓」這個回答後,宮崎駿語出驚人的表示:「聽妳這樣說我是很高興啦,但是我自己不覺得龍貓是很可愛的生物耶……」▲宮崎駿曾說不覺得龍貓是可愛的生物。
  • 產品面試中「費米問題」怎麼答?四步就能搞定!
    在產品經理面試中,常常會問到這樣的問題:「北京有多少輛公交車?"「煎餅攤一天能賣出多少煎餅?"本文將以「估算上海自行車數量」為例,給出費米問題的答題思路和解題方法。話題復盤Q:如何估算上海有多少自行車?
  • 別誤會,日本網友對你說「草」時,其實想表達是一種植物
    (日本語)」、「草(一種植物)」等梗。因為「笑」在日語中是笑い「わらい」,羅馬音是(wa ra i ),網友通常會直接打首字母「w」作為簡寫,來表示笑的含義。又因為一排「w」看起來很像草叢,便演變出用「草」來表達笑的含義。
  • 新技能Get:面試中常見的「費米估算」要怎麼破?
    在面試中,Fermi Problem往往能間接反映出一個人的綜合素質。你是否有在面試中遇到過這樣的奇怪問題?一個正常成年人有多少根頭髮?北京有多少個加油站?胡同口的煎餅攤子一年能賣多少個煎餅?正如這段引用所說,「費米問題」能夠鍛鍊一個人的多方位思考分析能力。你不但需要有不錯的思維邏輯,同時需要有較為廣泛的知識面涉及。所以在面試中,Fermi Problem往往能間接反映出一個人的綜合素質。同樣地,在日常工作中。估算同樣發揮著它的作用。大到資本市場對青睞的公司或行業的潛力預測,小到某場運營活動的前期市場調研。
  • 「面試技巧」英文面試難題,怎麼回答你的優點缺點和離職原因?
    求職面試中,很多公司喜歡加上英文面試的環節,來考驗面試者的英語表達能力。今天我們來看五個英文面試中最常被問到的問題:1. Why do you want to work here? 為什麼你想來這裡工作?2.
  • 初中物理公式換算大總結,滿滿的乾貨 !
    原標題:初中物理公式換算大總結,滿滿的乾貨 ! 很多同學說初中物理很難,太多繁瑣的公式以及複雜的理論了,要記憶、理解還得做題!!都不知道怎麼學好物理了…… 可能這就是很多同學們物理成績比較薄弱的主要原因。
  • 「國家公務員考試總是不進面」國家公務員面試比例是什麼意思
    「國家公務員考試總是不進面」國家公務員面試比例是什麼意思由國家公務員考試網考試快訊欄目由提供,更多關於國考面試,國家公務員考試網,國家公務員考試考試快訊的內容,請關注國家公務員考試網/廣東公務員考試網!   2021國家公務員考試面試名單已發布!
  • 「python opencv視覺零基礎」十、圖片效果毛玻璃
    一、學習目標了解高斯模糊的使用方法了解毛玻璃的圖片效果添加了解如何自己做一個噪聲圖片目錄「python opencv 計算機視覺零基礎實戰」 第一節「python opencv視覺入門到實戰」二、格式與攝像頭「python opencv 視覺入門到實戰」 三、圖像編輯「python opencv視覺入門到實戰
  • 行測備考誤區之四:無意義的「概念詞」
    關於「基期」和「現期」的基礎公式為:增長率=增長量/基期量=(現期量/基期量)-1=(現期量-基期量)/基期量。下面我給大家講一下這個基礎公式的原理……搞不搞笑,有沒有意思?上面一段話念完,兩分鐘就過去了。很多考生交了幾千塊錢的課,就只能學幾十個小時。
  • 通過「AI得賢招聘官」實現90%+精準度面試判定,「近嶼智能」獲黑馬...
    36氪獲悉,AI視頻面試SaaS服務商「近嶼智能」獲得來自黑馬基金數千萬元的股權融資。本輪融資將主要用於AI研發和工程交付,通過升級產品持續提升用戶體驗;另一部分將用於搭建服務團隊,在業務發展初期找到具有先進思維最適合的客戶從而提升服務能力和市場佔有率。
  • 能幫你記住一切的「記憶神器」:Anki 使用指南
    而我將這個 Supermemo 2在 Anki 裡的實現粗略的總結為 「3-2-1」 來幫助理解。三種狀態Anki 中學習以牌組為單位,畢竟一個牌組意味著你在某個領域中的知識積累。而在牌組中,卡片被分為三類狀態:新建。從未學習過或者 Anki 認為你已經遺忘了的卡片。正在進行的課程。近期學習過的卡片,但仍需要學習。待覆習。
  • 「python opencv視覺零到實戰」八、圖片選區操作
    一、學習目標了解什麼是ROI了解floodFill的使用方法如有錯誤歡迎指出~目錄「python opencv 計算機視覺零基礎實戰」 第一節「python opencv視覺入門到實戰」二、格式與攝像頭「python opencv 視覺入門到實戰」 三、圖像編輯「python opencv視覺入門到實戰