操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    前言

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

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

    JavaScript

    數據類型

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

    堆、棧

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

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

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

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

    隱式類型轉換

    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. 令牌化。從輸入流中提取可識別的子串和標記符號??梢岳斫鉃閷TML解析,進行詞法分析,匹配標簽生成令牌結構。
    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中增加“多路復用”的機制,不再受限于瀏覽器的連接數限制?;诙M制分幀,客戶端發送的數據會被分割成帶有編號的碎片(二進制幀),然后將這些碎片同時發送給服務端,服務端接收到數據后根據編號再合并成完整的數據。服務端返回數據也同樣遵循這個過程。

    三次握手

    過程

    第一次握手:客戶端向服務端發起連接請求報文,報文中帶有一個連接標識(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

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

    超級熱鍵可通過簡單編程 —— 自動化完成復雜操作,提升效率。

    快速上手

    本教程需要一個很小的開源軟件 ImTip ( 體積 639 KB ),了解 ImTip 請點擊:電腦必備:通用輸入法狀態提示,再也不怕按錯,再也不用看右下角

    請右鍵點開 ImTip 托盤菜單,然后點擊「管理超級熱鍵」

    然后將熱鍵配置改為如下代碼,并且勾選「啟用超級熱鍵」,再點擊「保存」按鈕使熱鍵生效。

    //大寫金額、日期、時間 
    ["Ctrl+$"]=function(hFocus){  
    	win.dlg.chineseNumber().show();
    };
    
    //打開調色器
    ["Ctrl+#"]=function(hFocus){  
    	
    	//創建選色器
    	var dlg=win.ui.ctrl.pick();
    	dlg.show();
    	
    	//置頂
    	win.setTopmost(dlg.hwnd,true); 
    };

    上面的配置定義了 "Ctrl+$","Ctrl+#" 這兩個熱鍵。中括號里是按鍵名字符串,等號的后面指定要輸入的文本或者需要執行的函數對象。

    以上熱鍵配置將自動轉換為以下 aardio 代碼,然后編譯執行:

    //導入超級熱鍵
    import key.hotkey; 
    
    //創建超級熱鍵
    var superHotkey=key.hotkey(winform);
    
    //批量加載熱鍵配置表
    superHotkey.loadTable({
    
    	["Ctrl+$"]=function(hFocus){  
    		win.dlg.chineseNumber().show();
    	};
    	
    	["Ctrl+#"]=function(hFocus){  
    		
    		var dlg=win.ui.ctrl.pick();
    		dlg.show();
    		
    		win.setTopmost(dlg.hwnd,true); 
    	};
    });

    超級熱鍵配置其實就是一個表對象 ( table )。每個熱鍵配置由等號分隔的鍵值對組成,等號前面指定熱鍵,等號后面用一個函數指定要執行的代碼。多個熱鍵配置使用分號隔開。

    如果不會編程沒關系,下面會提供可直接復制粘貼的范例,建議先看幾個范例 —— 再看一下:aardio 編程語言快速入門——語法速覽

    下面看一下 Ctrl + $ 熱鍵運行效果:

    Ctrl + # 熱鍵運行效果:


    超級熱鍵檢測規則

    1、如果首個按下的鍵不是控制鍵,則不必同時按住多個鍵。如果按下的鍵是已注冊的熱鍵前半部分,則阻止當前按鍵繼續發送。如果繼續按鍵已不符合任何熱鍵,則釋放已捕獲的按鍵并按原順序重新發送。

    2、如果首次按住的是控制鍵( CTRL,ALT,SHIFT,WIN 之一),則必須同時按住多個鍵才算已完成熱鍵。如果這樣同時按住的多個鍵是一個已完成的熱鍵,但同時又是其他熱鍵的前半部分,則必須放開所有鍵才會生效。

    3、如果注冊單個控制鍵熱鍵,并且加上 @ 前綴,則放開該鍵(且中間沒有按其他鍵)才算完成熱鍵。

    4、超鍵熱鍵中任何鍵名都只表示該鍵名所在的按鍵,不區分上檔鍵。例如熱鍵 "~hi" 指連續按 3 個鍵,其中的 ~ 鍵不必按 Shift + ~。

    5、超級熱鍵會檢測按鍵順序,["Ctrl+Shift"] 與 ["Shift+Ctrl"] 是不同的熱鍵。如果希望不同順序觸發同一個回調函數,則需要添加多個熱鍵配置。

    示例:

    //按連續按 3 個鍵,每個鍵都要放開。
    ["~AA"]=function(){  
    	
    };
    
    //按下Shift不放,再按2下Q。
    ["SHIFT+Q,Q"]=function(){  
    	
    };
    
    //按下Ctrl不放,再按K, 然后都放開。
    ["Ctrl+K"]=function(){  
    	
    };
    
    //按下Ctrl不放,再按2次K。
    //因為不是其他熱鍵的前半部分,不需要等待放開。
    ["Ctrl+K,K"]=function(){  
    	
    };
    
    //表示按下 Shift 鍵再放開,中間不按其他鍵,
    //通常不會阻止 Shift 切換輸入法狀態的默認熱鍵。
    ["@Shift"]=function(){  
    	
    };

    運行 「aardio 自帶范例 > Windows 窗口 > 快捷鍵 > 超級熱鍵」看下效果:

    超級熱鍵回調函數

    熱鍵回調函數返回 true 表示允許系統繼續發送按鍵,否則取消該按鍵,不再繼續發送。

    例如把斜杠“/”改為頓號的熱鍵配置:

    ["/"]=function(hFocus){
    	var openState,mode=key.ime.state();//
    	if( !openState /*&&(mode==3) */ ) return true; 
    	key.sendString("、")
    };

    上面的代碼檢測到當前不是中文標點就執行 return true 允許系統繼續發送按鍵。

    如果熱鍵回調函數返回一個函數對象,則取消該按鍵不再發送,并在返回函數以后異步執行返回的函數對象。

    例如輸入法糾錯熱鍵配置就是這樣做的:

    ["Ctrl+,"]=function(hFocus){    
    	
    	//... 省略其他代碼
    	
    	//通過返回函數異步執行耗時操作
    	return function(){ 
    		key.combine("SHIFT","HOME");
    		key.combine("CTRL","C");
    		
    		//... 省略其他代碼
    	};  
    };

    超級熱鍵基于低級鍵盤鉤子,在熱鍵回調中不允許做耗時操作(在熱鍵回調中返回異步執行的函數則不受限制)。

    超級熱鍵配置范例:運行指定程序

    //運行計算器
    ["~calc"]=function(hFocus){
    	process.execute("calc.exe")
    };
    
    //運行word
    ["~word"]=function(hFocus){
    	var word=com.CreateObject("Word.Application")
    	if(word)word.Visible=true; 
    };

    超級熱鍵配置范例:微軟全拼雙拼切換

    ["Ctrl+P"]=function(){ 
    	var dpSchemes=tsfInput.getDoublePinyinSchemes(); 
    	tsfInput.enableoublePinyinScheme(dpSchemes.default===null); 
    }

    超級熱鍵配置范例:改鍵演示

    ["Ctrl+."]=function(hFocus){  
    	key.up("Ctrl"); //先把已經按下的鍵彈起來
    	key.combine("CTRL","A"); //換成別的鍵,具體看 aardio 庫函數文檔
    	return false; //阻止按鍵事件
    };

    超級熱鍵配置范例:取消次選鍵

    [";"]=function(hFocus){
    	if( winex.msCandidate.isVisible() ){
    		key.send(" ;") 
    	} 
    	else return true;
    };

    超級熱鍵配置范例:按 Shift + Back 變 Ctrl + Z

    ["SHIFT+BACK"]=function(hFocus){
    	key.up("SHIFT"); //先把已經按下的鍵彈起來
    	key.combine("CTRL","Z")
    };

    超級熱鍵配置范例:斜杠“/”改為頓號

    ["/"]=function(hFocus){
    	var openState,mode=key.ime.state();//
    	if( !openState /*&&(mode==3) */ ) return true; 
    	key.sendString("、")
    };

    超級熱鍵配置范例:調整音量

    //增加音量
    ["Ctrl+F6"]=function(hFocus){
    	key.press("VOLUME_UP");
    }
    
    //降低音量
    ["Ctrl+F7"]=function(hFocus){
    	key.press("VOLUME_DOWN");
    }
    
    //切換靜音
    ["Ctrl+F8"]=function(){
    	key.press("VOLUME_MUTE");
    }

    超級熱鍵配置范例:依次按 ~date 輸入大寫的當前日期

    ["~date"]=function(hFocus){ 
    	var zh=string.chineseNumber('〇一二三四五六七八九');
    	key.sendString(zh.date()); //改為 zh.time() 輸出大寫的當前時間
    }; 

    注意超鍵熱鍵中任何鍵名都只表示該鍵名所在的按鍵,不區分上檔鍵。所以 ~ 鍵不需要同時按 Shift + ~ 。

    超級熱鍵配置范例:按大寫自動切換到英文輸入

    ["CAPSLK"]=function(hFocus){  
    	key.ime.setOpenStatus(false);
    	key.ime.setConversionMode(0); 
    	
    	return true;
    };

    超級熱鍵配置范例:右 Shift 鍵切換為英文

    ["RSHIFT"]=function(hFocus){  
    	key.ime.setOpenStatus(false);
    	key.ime.setConversionMode(0); 
    };

    超級熱鍵配置范例:左 SHIFT 鍵切換為中文

    ["LSHIFT"]=function(hFocus){    
    	var openState,mode=key.ime.state();
    	if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
    	
    	key.up("SHIFT");//先放開 SHIFT 鍵
    
    	//如果是大寫狀態,切換為小寫
    	if(key.ime.capital())    key.press("CAPSLK") 
    
    	//英文直接切中文 + 中文標點
    	key.ime.setOpenStatus(true); //打開輸入法
    	key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
    	
    	//再次嘗試用鍵盤切換中文標點,這一步不能省略
    	key.combine("CTRL",".");
    	
    	//現在再次檢測中文標點狀態
    	var openState,mode=key.ime.state();
    	if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
    		//說明切換到了英文標點,再切換回去
    		key.combine("CTRL",".")
    	}  
    };

    超級熱鍵配置范例:按 Ctrl + . 切換到中文輸入 + 中文標點 + 小寫

    ["Ctrl+."]=function(hFocus){    
    	var openState,mode=key.ime.state();
    	if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
    	
    	key.up("Ctrl");//先放開 Ctrl 鍵
    
    	//如果是大寫狀態,切換為小寫
    	if(key.ime.capital())    key.press("CAPSLK") 
    
    	//英文直接切中文 + 中文標點
    	key.ime.setOpenStatus(true); //打開輸入法
    	key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
    	
    	//再次嘗試用鍵盤切換中文標點,這一步不能省略
    	key.combine("CTRL",".");
    	
    	//現在再次檢測中文標點狀態
    	var openState,mode=key.ime.state();
    	if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
    		//說明切換到了英文標點,再切換回去
    		key.combine("CTRL",".")
    	}  
    };

    超級熱鍵配置范例:輸入法糾錯,已輸出英文自動轉為中文輸入

    ["Ctrl+,"]=function(hFocus){    
    	var openState,mode=key.ime.state();
    	if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
    	
    	key.up("Ctrl");//先放開 Ctrl 鍵
    
    	//如果是大寫狀態,切換為小寫
    	if(key.ime.capital())    key.press("CAPSLK") 
    
    	//英文直接切中文 + 中文標點
    	key.ime.setOpenStatus(true); //打開輸入法
    	key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
    	
    	//再次嘗試用鍵盤切換中文標點,這一步不能省略
    	key.combine("CTRL",".");
    	
    	//現在再次檢測中文標點狀態
    	var openState,mode=key.ime.state();
    	if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
    		//說明切換到了英文標點,再切換回去
    		key.combine("CTRL",".")
    	}  
    	
    	//通過返回函數異步執行耗時操作(避免系統刪除熱鍵鉤子)
    	return function(){ 
    		key.combine("SHIFT","HOME");//選中當前行 
    		key.combine("CTRL","C");//復制當前行
    		
    		var line=win.clip.read(); //讀取剪貼板文本
    		var str=string.match(line,"[a-zA-Z]+$"); //查找尾部連續字母
    		key.press("RIGHT");//按右方向鍵,取消選區
    		
    		//如果字符串非空
    		if(#str){ 
    			key.repeat("BACK",#str);//按退格鍵指定次數(取字符串長度)
    			key.send(str);//字符串轉換為發送按鍵
    			//key.press("SPACE");//發果需要按空格,請取消前面的注釋符號
    		}
    	};  
    };

    超級熱鍵配置范例:粘貼時替換指定的字符

    ["Ctrl+V"]=function(){
    	var str=win.clip.read();
    	 
    	if(str && string.find(str,"abcd")){
    		str=string.replace(str,"abcd","");
    		win.clip.write(str);  
    	}
    	
    	return true; //執行默認操作 
    }

    超級熱鍵配置范例:引號配對

    [`SHIFT+"`]=function(hFocus){  
    	
    	if( checkImeProcess(hFocus,"Code.exe") ){
    		return true;//允許繼續發送按鍵
    	}
    
    	var o,s=key.ime.state();
    	key.sendString(s==3 ? `“”` : `""`); 
    
    	//與目標窗口共享輸入狀態
    	winex.attach(hFocus,true);
    	
    	//設置LSHIFT,RSHIFT 為彈起狀態
    	key.up("RSHIFT","LSHIFT","SHIFT");
    	key.setState(false,"RSHIFT","LSHIFT","SHIFT"); 
    
    	//移動光標
    	key.press("LEFT");
    	
    	//取消共享輸入狀態
    	winex.attach(hFocus,false);
    }

    超級熱鍵配置范例:微軟五筆打開或關閉拼音混輸

    ["Ctrl+,"]=function(hFocus){  
    	import win.reg;
    	var reg=win.reg("HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS");
    	var mode=!reg.queryValue("PinyinMixEnable") ? 1 : 0
    	reg.setDwValue("PinyinMixEnable",mode)	
    	
    	key.ime.changeRequest(0x4090409)
    	key.ime.changeRequest(0x8040804)
    };

    超級熱鍵配置范例:切換鼠標左右鍵

    ["Ctrl+SHIFT+RIGHT"]=function(hFocus){  
    	::User32.SwapMouseButton(!::User32.GetSystemMetrics(23));
    }

    超級熱鍵配置范例:微軟五筆疊字鍵

    ["`"]=function(hFocus){  
    	var openState,mode=key.ime.state();//用法請查看 aardio 文檔
    	if(!openState 
    			||  mode !=3 || key.getState("Shift")  
    			|| key.getState("Ctrl")  
    			|| key.getState("CAPSLK")  ) {
    			return true; //允許此按鍵繼續發送
    	}
    	
    	key.combine("SHIFT","LEFT"); //向后選一個字
    	key.combine("CTRL","C"); //復制
    	key.press("RIGHT"); //取消選中
    	key.combine("CTRL","V"); //粘貼
    };

    實現更多功能 ……

    ImTip 體積雖然小,但已自帶了大量 #aardio# 庫。您也可以下載 aardio ,將 aardio 目錄下的 /lib/ 目錄復制到 ImTip.exe 所在目錄 —— 超級熱鍵就可以使用全部的 aardio 庫了。

    也可以在 /lib/ 目錄下新建用戶庫,然后通過 import 語句導入超級熱鍵。

    超級熱鍵配置本質是一個表對象 (table),不但可以包含鍵值對,也可以包含數組成員。利用這個特性可在超級熱鍵配置內直接執行代碼。例如添加一個檢測當前窗口啟動程序名的全局函數 checkImeProcess,然后使用該函數檢測目標窗口的啟動程序文件名:

    (function(){
    	var lastFocus,lastPath,lastClass;
    	checkImeProcess=function(hFocus,exeFile){
    		if(lastFocus !=hFocus){
    			lastClass=win.getClass(hFocus);
    			var tid,pid=win.getThreadProcessId(hFocus);
    			lastPath=process.getPath(pid);
    			lastFocus=hFocus
    		}
    	
    		return (lastPath && io.splitpath(lastPath).file==exeFile )
    	}	
    })();
    
    //引號配對
    [`SHIFT+"`]=function(hFocus){  
    	
    	if( checkImeProcess(hFocus,"Code.exe") ){
    		return true;//允許繼續發送按鍵
    	}
    
    	var o,s=key.ime.state();
    	key.sendString(s==3 ? `“”` : `""`); 
    
    	//與目標窗口共享輸入狀態
    	winex.attach(hFocus,true);
    	
    	//設置LSHIFT,RSHIFT 為彈起狀態
    	key.up("RSHIFT","LSHIFT","SHIFT");
    	key.setState(false,"RSHIFT","LSHIFT","SHIFT"); 
    
    	//移動光標
    	key.press("LEFT");
    	
    	//取消共享輸入狀態
    	winex.attach(hFocus,false);
    }

    比較常用的按鍵、文本輸入函數:

    //發送字符串
    key.sendString("這里是要發送的字符串")
    
    //發送按鍵
    key.send("/")
    
    //發送按鍵
    key.press("ENTER")
    
    //判斷中文輸入法是否打開,打開則執行花括號中的語句
    if( key.ime.state() ){ }

    更多庫函數用法請參考 aardio 自帶的《庫函數文檔》,或者 aardio 自帶的范例:

    aardio 也支持很多第三方編程語言,例如 C語言、C++、C#、Java、Python、R、Javascript、Node.Js、Fortran、VB、PHP、VBScript、PowerShell、NewLISP、AutoLISP、Delphi、FreeBASIC、Ruby、Rust、Julia、Nim、Go 語言、批處理 ...... 用法請參考 aardio 自帶范例。

    如果您對超級熱鍵還有任何疑問,歡迎在評論區留言,我會盡快解答。

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有