整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

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

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

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

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

          JavaScript

          數據類型

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

          堆、棧

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

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

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

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

          隱式類型轉換

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

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

          運算

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

          基本類型轉換:

          true + false   // 1
          null + 10      // 10
          false + 20     // 20
          undefined + 30 // NaN
          1 + '2'        // "12"
          NaN + ''       // "NaN"
          undefined + '' // "undefined"
          null + ''      // "null"
          '' - 3         // -3
          
          • null、false、'' 轉換 number 類型都是 0
          • undefined 轉換 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')     // 20
          20 - 10          // 10
          

          取反

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

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

          ![]     // false
          !{}     // false
          !false  // true
          

          通常為了快速獲得一個值的布爾值類型,可以取反兩次:

          !![]  // true
          !!0   // false
          

          比較

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

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

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

          預編譯

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

          預編譯過程:

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

          例子:

          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. 尋找形參和變量聲明賦值為 undefined
          AO {
              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() // 2
          x // 1
          

          y 的默認是一個匿名函數,匿名函數內的x指向同一個作用域的第一個參數x。函數foo的內部變量x就指向第一個參數x,與匿名函數內部的x是一致的。y函數執行對參數x重新賦值,最后輸出的就是2,而外層的全局變量x依然不受影響。

          閉包

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

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

          let bar
          function 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=18
          let 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=18
          let child=new Child()
          

          Class 可以通過 extends 關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

          基本包裝類型

          let str='hello'
          str.split('')
          

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

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

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

          this

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

          this綁定的四種情況:

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

          優先級new綁定最高,最后到默認綁定。

          new的過程

          1. 創建一個空對象
          2. 設置原型,將對象的 __proto__ 指向構造函數的 prototype
          3. 構造函數中的 this 執行對象,并執行構造函數,為空對象添加屬性和方法
          4. 返回實例對象

          注意點:構造函數內出現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

          • 只接受引用類型作為鍵名
          • 鍵名是弱引用,鍵值可以是任意值,可以被垃圾回收。鍵名所指向的外部引用被回收后,對應鍵名也可以被回收
          • 不能遍歷

          箭頭函數和普通函數的區別

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

          Promise

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

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

          • pending: 默認狀態,只要沒有告訴 promise 任務是成功還是失敗就是 pending 狀態
          • fulfilled: 只要調用 resolve 函數, 狀態就會變為fulfilled, 表示操作成功
          • rejected: 只要調用 rejected 函數, 狀態就會變為 rejected, 表示操作失敗

          狀態一旦改變既不可逆,可以通過函數來監聽 Promise 狀態的變化,成功執行 then 函數的回調,失敗執行 catch 函數的回調

          淺拷貝

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

          常用淺拷貝的方法:

          • Array.prototype.slice
          let 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 class="item">1</li>
              <li class="item">2</li>
              <li class="item">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)
          

          優點:

          1. 參數復用,由于參數可以分開傳入,我們可以復用傳入參數后的函數
          2. 延遲執行,就跟 bind 一樣可以接收參數并返回函數的引用,而沒有調用

          垃圾回收

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

          新生代

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

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

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

          老生代

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

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

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

          JavaScript設計模式

          單例模式

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

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

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

          import Vue from 'vue'
          import Index from './index.vue'
          
          let alertInstance=null
          let 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
              })
          }
          
          // 代理getList
          let 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的渲染流程

          流程主要分為三個部分:

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

          data為什么要求是函數

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

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

          Vue生命周期

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

          請說出 Vue 的5種指令

          1. v-if
          2. v-for
          3. v-show
          4. v-html
          5. v-model

          computed 和 watch 的區別

          1. computed 依賴 data 的改變而改變,computed 會返回值;watch 觀察 data,執行對應的函數。
          2. computed 有緩存功能,重復取值不會執行求值函數。
          3. computed 依賴收集在頁面渲染時觸發,watch 收集依賴在頁面渲染前觸發。
          4. 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
              }
          })
          

          組件通信方式

          1. props/emit
          2. $children/$parent
          3. ref
          4. $attrs/$listeners
          5. provide/inject
          6. eventBus
          7. vuex

          雙向綁定原理

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

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

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

          Vue如何檢測數組變化

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

          • push
          • pop
          • shift
          • unshift
          • splice
          • sort
          • reverse

          keep-alive

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

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

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

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

          nextTick

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

          • Promise
          • MutationObserver
          • setImmediate
          • setTimeout

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

          如何理解單向數據流

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

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

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

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

          Vue3 和 Vue2.x 的差異

          1. 使用 Proxy 代替 Object.defineProperty
          2. 新增 Composition API
          3. 模板允許多個根節點

          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存儲的狀態在頁面刷新后會丟失,使用持久化技術能保證頁面刷新后狀態依然存在。

          1. 使用本地存儲配合,設置 state 同時設置 storage,在刷新后再初始化 vuex
          2. vuex-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.js
          exports.a=1
          module.exports={
              b: 2
          }
          exports.c=3
          
          let nums=require('./nums.js') // { b: 2 }
          

          module.exports 為空:

          // nums.js
          exports.a=1
          exports.c=3
          
          let nums=require('./nums.js') // { a: 1, c: 3 }
          

          值拷貝的體現:

          // nums.js
          let obj={
              count: 10
          }
          let count=20
          function 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) // 20
          addCount()
          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.js
          export let count=20
          export 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) // 20
          addCount()
          console.log(count) // 21
          

          ES6 模塊與 CommonJS 模塊的差異

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

          瀏覽器

          頁面渲染流程

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

          數據變化過程:字節 → 字符 → 令牌 → 樹 → 頁面

          回流、重繪

          回流(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

          流程

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

          小結

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

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

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

          同源策略(Same origin policy)

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

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

          所謂同源指的是:協議、域名、端口號必須一致,只要有一個不相同,那么就是“跨源”。

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

          請求跨域解決方案

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

          常用方法是CORS和代理轉發。

          頁面跨域解決方案

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

          CORS請求

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

          簡單請求

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

          1. 請求方法為 GET、POST、HEAD。
          2. 請求頭只能使用以下規定的安全字段: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在傳輸過程中,對內容使用對稱加密,而密鑰使用非對稱加密。

          過程

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

          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 對比資源最后修改時間來實現緩存。

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

          ETag 和 If-None-Match

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

          1. 瀏覽器第一次請求資源,服務端在返回資源的響應頭上添加 ETag 字段,值是資源的哈希值
          2. 瀏覽器再次請求資源,在請求頭上添加 If-None-Match,值是上次服務端返回的資源哈希值;
          3. 服務端收到請求,根據 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>
          

          防御手段

          1. 參數驗證,不符合要求的數據不要存入數據庫
          2. 對特殊字符轉義,如"<"、">"、"/"、"&"等
          3. 避免使用eval、new Function動態執行字符串的方法
          4. 避免使用 innerHTML、document.write 直接將字符串輸出到HTML
          5. 把一些敏感的 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,成功刪除博文。但是對于用戶是無感知的,當用戶返回到博客時會發現博文不見了,而這個請求是屬于合法請求,因為攻擊者借用受害者的身份信息進行操作。

          防御手段

          1. 設置 Cookie 的 SameSite
          2. 服務端驗證 Refer 字段,Refer 是請求源網址,對于不合法的 Refer 拒絕請求
          3. 添加 token,讓鏈接變得不可預測,攻擊者無法構造一個完整的 URL 實施 CSRF 攻擊
          4. 添加驗證碼,強制用戶必須與應用交互,但會降低用戶體驗,只能作為輔助手段

          點擊劫持(ClickJacking)

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

          防御手段

          1. frame busting,通常可以寫一段JavaScript,以禁止 iframe 的嵌套。
          if (top.location !=location) {
              top.location=self.location
          }
          
          1. 添加 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 原理都不懂,還講“中間人攻擊”?
          • 進階 · 那些你必須搞懂的網絡基礎


          轉自 https://www.cnblogs.com/chanwahfung/p/13616849.html

          喜歡的朋友點一下關注 點關注不迷路

          么理解H5與小程序這兩種不同的載體?這篇文章里,作者嘗試從產品經理角度對二者做了分析解讀,一起來看看,或許可以幫你更好地理清這兩個概念。

          01

          今天與大家聊聊H5和小程序。還記得小吳在剛成為產品經理時,經常會聽到技術同學甚至設計同學提到“這是個H5”、“用H5做就行了”之類的話,但那時的我并不理解H5是個什么東西,聽起來覺得很高大上,但實際上總覺得H5與優惠、活動、打折掛鉤,而且看上去千篇一律,辨識度很高。

          H5和小程序是兩種不同的產品形態的載體,而且是當下比較流行的兩種技術解決方案。以下是小吳用文心一言4.0生成的對H5和小程序的本質與技術原理的解讀:

          H5:跨平臺的網絡解決方案

          HTML5,通常稱為H5,是最新的HTML標準。它的核心優勢在于跨平臺性。無論是在桌面還是移動設備上,只要有瀏覽器,H5應用就可以運行。技術上,H5依賴于瀏覽器作為其運行環境,通過HTML、CSS和JavaScript等語言實現頁面布局、樣式設計和交互邏輯。

          小程序:依托特定平臺的輕量級應用

          小程序,則是一種不需要下載安裝即可使用的應用,它實現了“觸手可及”的輕量級體驗。小程序通常依托于大型平臺(如微信、支付寶)的生態系統,運行在這些平臺的專門環境中。它們通過各自的開發框架,允許開發者快速構建應用。

          相信大家看得一頭霧水,小吳用稍微簡單一點的表達方式再和大家解釋一下。

          簡單來說,H5(HTML5)是一種制作網頁的標準,它就像是網頁的“建筑材料”,包含了文本、圖片、視頻和各種互動元素的排版和設計,也就是說H5就是一個網頁。

          正常來說我們使用百度、谷歌等瀏覽器上網,訪問的都是一個個網頁,也就是說,只要你的設備上有瀏覽器,就可以訪問H5頁面。想象一下,如果每次要看一篇新聞或購物就需要先下載一個程序,是不是很麻煩?而且現在的APP想體驗服務都得注冊、登錄、輸入驗證碼,但H5就解決了這個問題,你直接在瀏覽器上打開網頁,無需下載安裝任何額外的應用,就可以享受其服務。

          再來想像這樣一種場景,如果沒有H5,你想告訴你的朋友“年貨節開始啦,快去囤貨吧,打折力度真的好大”,恐怕你只能通過微信等其他聯系方式告知你的朋友,然后你的朋友還得下載京東或淘寶去享受年貨節。有了H5以后,京東等電商平臺都會將自己的促銷活動做成H5頁面的形式,這樣一來,你就可以將這個H5頁面分享給你的朋友,而你的朋友只需要點擊進去,就可以參與到這個活動當中。

          所以H5頁面是可以通過鏈接分享的,這就像分享一個有趣的故事一樣簡單,用戶可以通過社交媒體、消息類應用輕松分享。

          而小程序大家可以想象成一個迷你版的應用,它通常嵌入在像微信或支付寶這樣的大型應用中。使用小程序時,你不需要像普通應用那樣去應用商店下載,只需在微信或支付寶里搜索或掃碼就可以直接使用。比方說支付寶的螞蟻森林,微信的同城旅行買火車票,都是支付寶、微信生態下的小程序。

          02

          接下來我們來聊聊H5和小程序的區別,以及為什么有的業務選擇H5開發,而有些業務選擇基于小程序開發。

          首先從運行環境的差異角度來說,我們經常從微信里點了一個鏈接分享,然后解析出來是一個商城的優惠活動,這基本上都是H5頁面。但我們應該沒在微信里面用過螞蟻森林或者其他支付寶系的小程序吧。這是因為它的核心優勢在于跨平臺性。無論是在桌面還是移動設備上,只要有瀏覽器,H5應用就可以運行。

          而小程序則運行在特定平臺(如微信、支付寶)的專用容器中,這些容器為小程序提供了一套統一的API和界面標準。例如微信小程序的運行環境是微信應用本身,它為小程序提供了一系列微信特有的功能和接口,如微信支付、朋友圈等。這種專用環境確保了小程序在特定平臺上有更好的性能和用戶體驗,但同時也限制了它們在其他平臺上的運行。

          從開發成本角度來說,H5相對來說較低一些。微信、支付寶這些互聯網巨頭都是移動互聯網時代的幸運兒,在移動互聯網時代之前,大多數朋友們都在通過電腦瀏覽器進行網上沖浪。而H5的開發依賴于傳統的Web技術棧,如HTML、CSS和JavaScript,這些技術廣泛應用且成熟,因此有大量現成的工具和框架可供使用,這些工具和框架可以顯著提升開發效率。

          同時,H5項目通常只需要一套代碼即可在多個平臺上運行,這進一步減少了開發和維護的工作量。

          小程序的開發則需要遵循特定平臺的開發框架和標準。例如微信小程序需要使用微信提供的開發工具和API,感興趣的各位可以搜索一下微信開放平臺。雖然這些平臺提供了一些便利的開發工具,但開發者仍需學習和適應每個平臺的特有規范和接口。此外,如果需要在多個平臺上推出小程序,開發者可能需要為每個平臺單獨開發和維護代碼,這無疑增加了工作量和成本。

          雖然在開發成本方面H5要顯得比較有優勢,但是小程序的能力還是要更強大一些。如果我們把APP理解為程序,那小程序就是比程序小一點兒的程序而已,這變相地說明小程序是一種接近原生應用的技術。相較于H5,小程序能更深層次地訪問系統資源和權限。

          例如小程序可以利用手機硬件(如攝像頭、GPS)、實現更豐富的功能(如掃一掃、即時支付)。這些深度整合的功能為小程序提供了更強大的能力,比如微信生態下的順豐快遞小程序,一打開就可以自動定位到我們的當前位置,此外我們也可以直接通過微信支付將快遞費轉給快遞小哥,甚至可以在小程序中直接打開我們的手機相機,進行快遞貨物的拍攝。

          這些功能都是得益于小程序可以獲取我們手機的系統權限,通常我們在使用時都會收到手機的系統提示,比方說是否同意獲取相冊中全部照片,是否同意獲取當前位置等。雖然現代瀏覽器允許H5應用訪問一些設備功能,如地理位置、攝像頭等,但對于一些高級功能,如后臺運行、推送通知等,在H5應用中要么無法實現,要么實現起來較為復雜。

          其實現在小程序還是很火爆的,無論是從產品開發的技術選型角度,還是從用戶體驗角度,小程序都完勝H5一籌,甚至比APP還要吃香。任何行業發展到中后期,都是幾家獨大,而群英薈萃百花齊放的場面通常是在行業初期。互聯網行業更是如此。

          目前大家都知道的這幾家互聯網巨頭,都在強調所謂的生態概念。廣義來說,所謂的生態就是盡可能地覆蓋多的業務,盡可能地搶占市場以及用戶的時間,終極愿景是用戶手機里只需要安裝我的APP,就可以高度便捷化衣食住行,不再需要安裝其他APP了。

          而從技術角度來理解生態,這么多的功能如何搭建在我的APP中呢?大家可以把這種生態模式理解為樂高積木,這些巨頭公司會把積木搭建方法提供給所有開發者并歡迎各位開發者來我的地盤上構建五花八門的樂高積木作品。但樂高的積木不能用在其他品牌的積木中。久而久之,樂高的積木越堆越大,這也便是生態的形成。

          由于這些互聯網巨頭掌握著巨大的流量,很多互聯網公司已經將小程序業務作為主力業務,畢竟參天大樹好乘涼,寄居蟹也有流量吃。不知道在這個移動互聯網時代還會不會有新的生態出現,也不知道這種繁榮的生態下腐蝕的是不是互聯網從業者的創新能力。

          最后希望這篇文章對各位有所幫助,祝各位睡個好覺。

          作者:產品小吳,公眾號:產品小吳

          本文由 @產品小吳 原創發布于人人都是產品經理,未經作者許可,禁止轉載。

          題圖來自Unsplash,基于CC0協議。

          該文觀點僅代表作者本人,人人都是產品經理平臺僅提供信息存儲空間服務。

          讀:本文將從全國公路客運票務智慧管理系統的開發目的、開發背景、目標用戶、系統設計、系統架構、主要功能模塊等方面進行分析,軟件主要功能包括:班次管理、部門管理、車輛管理、車站管理、訂單管理、公告管理、角色管理、積分管理、路線管理、票務管理、評論管理、權限管理、日志管理、數據報表、投訴管理、用戶管理、優惠券管理、站內信管理、折扣管理,全文約5404字,需要10分鐘左右。感謝閱讀,如有建議和意見歡迎評論交流。

          一、引言

          1.1、開發目的和背景

          軟件開發目的與背景

          全國公路客運票務智慧管理系統的設計初衷是為了徹底革新我國公路客運行業的服務質量和運營效率。隨著科技的飛速發展和數字化轉型的需求,傳統的票務管理方式已無法滿足日益增長的業務復雜性和用戶體驗的要求。本系統應運而生,旨在構建一個集智能化、集成化于一體的信息化平臺。

          在過去的交通行業中,班次調度、部門協作、車輛維護等各個環節存在著信息孤島的問題,數據難以實時共享,不僅降低了工作效率,還可能導致服務失誤。我們的目標是通過這款系統,實現全程的信息化管理,消除信息壁壘,提升決策支持的精確度。

          系統的關鍵模塊如班次管理、車輛管理、車站管理等,旨在優化線路規劃和資源調度,提高運輸效率;訂單管理和票務管理則致力于簡化購票流程,提供便捷的在線支付方式,提升乘客滿意度;角色管理和權限管理確保了數據安全和操作合規性。

          此外,積分管理、優惠券管理、折扣管理等功能將激勵用戶重復使用服務,增強用戶粘性;投訴管理模塊能快速響應并解決用戶問題,體現我們對服務質量的高度關注;數據報表和日志管理為管理者提供了有力的數據分析工具,便于他們進行持續改進。

          全國公路客運票務智慧管理系統的開發,不僅是技術進步的體現,更是推動我國公路客運行業向現代化、智能化轉型的重要一步。我們期待這款系統能助力整個行業步入全新的發展階段,實現服務升級、運營高效和用戶體驗的全面提升。

          1.2、軟件的目標用戶

          全國公路客運票務智慧管理系統是一款專為公路運輸行業的管理部門和運營商設計的高效信息化工具。它的主要應用場景是全國范圍內的長途汽車公司、客運站、旅行社等,這些單位需要管理和優化他們的運營服務,提升票務銷售效率,同時滿足乘客的便捷需求。

          該系統的主要目標用戶是各級公路客運公司的管理層,如總經理、運營管理部、售票中心、客戶服務等部門負責人;售票員、客服人員、車輛駕駛員以及乘客。對于管理層,系統提供班次調度、部門協作、車輛維護、車站運營等全方位的管理平臺,幫助他們實時監控運營狀態,提高決策效率。售票員和客服人員可以通過訂單管理、票務查詢等功能,為乘客提供快速、準確的服務,同時也能通過積分管理、優惠券發放吸引和留住客戶。

          對于乘客,系統則提供了在線購票、查詢班車時刻、路線選擇、評價反饋等一站式的服務,使得購票過程更為簡單快捷,同時還能通過站內信獲取最新資訊和優惠政策。投訴管理模塊能及時處理并解決乘客的問題,保障服務質量。

          總而言之,全國公路客運票務智慧管理系統旨在通過智能化的技術手段,簡化業務流程,提升行業效率,優化用戶體驗,從而推動整個公路客運行業的數字化轉型和升級。

          二、軟件總體架構

          2.1、系統概述

          全國公路客運票務智慧管理系統是一款專為公路客運行業設計的全面信息化解決方案。該系統基于Java開發語言,采用了高效穩定的SpringMVC架構,數據庫則選用成熟可靠的MySQL,旨在提升行業運營效率和管理水平。

          系統的核心功能模塊豐富多樣,涵蓋了業務流程的關鍵環節。班次管理模塊允許管理員靈活創建、修改和查詢各類班車信息,確保乘客查詢的準確性。部門、車輛和車站管理模塊實現資源的集中化管理,便于追蹤和維護。訂單管理模塊支持在線預訂、支付及實時狀態查詢,簡化購票流程。

          公告管理、角色管理和權限管理模塊提供了一套完整的權限控制體系,確保信息安全與合規。積分管理鼓勵乘客積極參與,增強忠誠度。路線管理和票務管理模塊緊密結合,方便用戶了解線路信息和票務價格。評論管理模塊收集用戶反饋,提升服務質量。數據報表模塊提供詳盡的業務分析,幫助決策者做出明智選擇。

          投訴管理模塊確保問題快速解決,維護用戶權益。用戶管理和優惠券管理模塊關注用戶體驗,優化營銷策略。站內信管理和折扣管理則進一步提升了用戶交互和促銷效果。日志管理確保系統的穩定運行,而數據安全通過嚴格的加密措施得以保障。

          總之,全國公路客運票務智慧管理系統是一套全面、智能的管理工具,旨在通過數字化手段,提升公路客運行業的運營效率和服務質量,推動行業向現代化、智能化轉型。

          2.2、系統架構

          全國公路客運票務智慧管理系統采用先進的B/S(Browser/Server)架構設計,這是一種基于互聯網的分布式應用模型,用戶通過瀏覽器訪問,服務器端負責數據處理和業務邏輯。以下是詳細的系統架構描述:

          1. 前端界面:使用HTML5、CSS3及JavaScript技術構建,提供用戶友好的圖形化界面,包括各類模塊如班次查詢、訂單操作等,支持響應式設計,適應不同設備的訪問。

          2. 后端服務:核心部分由Java語言開發,利用Spring MVC框架進行結構設計,實現了模塊間的松耦合。它負責處理用戶的請求,調用相應的業務邏輯,然后返回處理結果。

          3. 數據庫管理:數據庫采用關系型數據庫,如MySQL或Oracle,存儲用戶信息、訂單記錄、車輛數據等關鍵信息,通過ORM工具如MyBatis進行數據交互。

          4. 模塊化設計:系統分為多個獨立模塊,如票務管理、用戶管理等,每個模塊都有自己明確的功能,有利于團隊協作和代碼維護。

          5. 權限與角色管理:通過Spring Security實現用戶權限控制,根據不同的角色賦予不同的操作權限,保證數據安全。

          6. 日志與審計:系統集成日志管理模塊,記錄關鍵操作和異常情況,便于問題追蹤和審計。

          7. 報表與數據分析:利用大數據處理技術,生成各類報表,幫助決策者分析運營狀況和優化策略。

          8. 服務調用與接口:采用RESTful API設計,與其他系統和服務進行無縫對接,提高系統的靈活性和擴展性。

          9. 安全措施:通過HTTPS協議保證數據傳輸安全,同時采用加密算法保護敏感信息,防止數據泄露。

          總之,全國公路客運票務智慧管理系統通過這種高效的B/S架構設計,實現了高效、穩定、易用的運營管理,為公路客運行業提供了強大而智能的解決方案。

          三、軟件功能操作

          3.1、系統登錄

          在瀏覽器中輸入系統網址,打開登錄界面后輸入登錄賬號、登錄密碼、驗證碼即可登錄。

          3.2、工作臺

          工作臺包含:班次管理、部門管理、車輛管理、車站管理、訂單管理、公告管理、角色管理、積分管理、路線管理、票務管理、評論管理、權限管理、日志管理、數據報表、投訴管理、用戶管理、優惠券管理、站內信管理、折扣管理,根據不同角色權限菜單展示會有所區別。

          3.2.1、班次管理

          管理功能主要字段信息包含:管理編碼、班次名稱、車輛型號、開車時間、到達時間、班次狀態等。使用表格形式展示數據信息,方便用戶查看和編輯。

          管理設置新增、編輯、刪除、條件搜索、查看詳情等操作,可按照頁面提示進行操作執行,界面結構設計簡單,操作流程簡潔明了,可提升用戶操作體驗。


          主站蜘蛛池模板: 鲁丝片一区二区三区免费| 国产在线精品一区二区夜色 | 日韩动漫av在线播放一区| 蜜臀Av午夜一区二区三区| 国产一区二区三区在线视頻| 色老头在线一区二区三区| 精产国品一区二区三产区| 日本在线一区二区| 日韩免费无码一区二区视频| 亲子乱AV视频一区二区| 3D动漫精品啪啪一区二区下载 | 亚洲综合av一区二区三区| 国产伦精品一区二区三区无广告| 无码日韩精品一区二区免费暖暖| 日韩aⅴ人妻无码一区二区| 色狠狠AV一区二区三区| 精品视频一区二区三区在线观看| 色婷婷亚洲一区二区三区| 精品欧洲av无码一区二区| 少妇激情av一区二区| 成人无码AV一区二区| 日韩有码一区二区| 丰满人妻一区二区三区视频| 日本一区免费电影| 精品免费国产一区二区| 国产香蕉一区二区精品视频| 秋霞午夜一区二区| 亚洲av不卡一区二区三区| 久久无码人妻一区二区三区 | 久久久久人妻一区精品| 亚洲视频一区二区| 亚洲成AV人片一区二区密柚 | 国产一区二区免费在线| chinese国产一区二区| 国产成人av一区二区三区在线观看| 午夜视频一区二区| 日本韩国黄色一区二区三区| 国产美女口爆吞精一区二区| 日韩精品成人一区二区三区| 亚洲一区二区三区在线网站| 无码福利一区二区三区|