記錄--閉包,沙箱,防抖節流,函數柯里化,數據劫持......

来源:https://www.cnblogs.com/smileZAZ/archive/2023/11/29/17865577.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 函數創建與定義的過程 函數定義階段 在堆記憶體中開闢一段空間 把函數體內的代碼一模一樣的存儲在這段空間內 把空間賦值給棧記憶體的變數中 函數調用階段 按照變數名內的存儲地址找到堆記憶體中對應的存儲空間 在調用棧中開闢一個新的函數執行空間 在執行 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

函數創建與定義的過程

  • 函數定義階段
    • 在堆記憶體中開闢一段空間
    • 把函數體內的代碼一模一樣的存儲在這段空間內
    • 把空間賦值給棧記憶體的變數中
  • 函數調用階段
    • 按照變數名內的存儲地址找到堆記憶體中對應的存儲空間
    • 在調用棧中開闢一個新的函數執行空間
    • 在執行空間中進行形參賦值
    • 在執行空間中進行預解析
    • 在執行空間中完整執行一遍函數內的代碼
    • 銷毀在調用棧創建的執行空間

不會銷毀的函數執行空間

  1. 當函數內返回一個引用數據類型
  2. 並且函數外部有變數接收這個引用數據類型
  3. 函數執行完畢的執行空間不會銷毀
  4. 如果後續不需要這個空間了,只要讓變數指向別的位置即可
function fn() {
    const obj = {
        a: 1,
        b: 2
    }
    return obj
}

const res = fn()
console.log(res)

// 如果後續不需要這個空間了, 只需要讓 res 指向別的位置即可
res = 100

閉包

  • 需要一個不會被銷毀的執行空間
  • 需要直接或間接返回一個函數
  • 內部函數使用外部函數的私有變數
  • 概念 : 函數里的函數
  • 優點:
    • 可以在函數外訪問函數內的變數
    • 延長了變數的生命周期
  • 缺點:
    • 閉包函數不會銷毀空間,大量使用會造成記憶體溢出
function outer () {
    let a = 100
    let b = 200

    // 我們說 inner 是 outer 的閉包函數
    function inner () {
        /**
         * 我使用了一個 a 變數, 但是 inner 自己沒有
         * 所以我用的是 外部函數 outer 內部的變數 a
        */
        // console.log(a)
        return a
    }
    return inner
}

// 我們說 res 是 outer 的閉包函數
let res = outer()

let outerA = res()
console.log(outerA)

沙箱模式

  • 利用了函數內間接返回了一個函數
  • 外部函數返回一個對象,對象內書寫多個函數
function outer () {
    let a = 100
    let b = 200

    // 創建一個 沙箱, "間接的返回一個函數"
    const obj = {
        getA: function () {
            return a
        },
        getB: function () {
            return b
        },
        setA: function (val) {
            a = val
        }
    }
    return obj
}

// 得到一個沙箱
const res1 = outer()

console.log(res1.getA())    // 100
console.log(res1.getB())    // 200

res1.setA(999)
console.log(res1.getA())    // 999

// 重新得到一個沙箱
const res2 = outer()
console.log(res2.getA())    // 100

沙箱小案例

<button class="sub">-</button>
<input class="inp" type="text" value="1">
<button class="add">+</button>
<br>
<button class="sub1">-</button>
<input class="inp1" type="text" value="1">
<button class="add1">+</button>
// 準備一個沙箱
function outer() {
    let a = 1
    return {
        getA() {
            return a
        },
        setA(val) {
            a = val
        }
    }
}

// 0. 獲取元素
const subBtn = document.querySelector('.sub')
const addBtn = document.querySelector('.add')
const inp = document.querySelector('.inp')

// 0. 準備變數
// let count = 1
let res = outer()
subBtn.onclick = function () {
    let count = res.getA()
    res.setA(count - 1)
    inp.value = res.getA()
}
addBtn.onclick = function () {
    // count++
    let count = res.getA()
    res.setA(count + 1)
    inp.value = res.getA()
}

// 0. 獲取元素
const subBtn1 = document.querySelector('.sub1')
const addBtn1 = document.querySelector('.add1')
const inp1 = document.querySelector('.inp1')

// 0. 準備變數
let res1 = outer()
subBtn1.onclick = function () {
    let count = res1.getA()
    res1.setA(count - 1)
    inp1.value = res1.getA()
}
addBtn1.onclick = function () {
    let count = res1.getA()
    res1.setA(count + 1)
    inp1.value = res1.getA()
}

沙箱語法糖

  • 儘可能的簡化沙箱模式的語法
  • 利用 get 和 set 進行操作數據
  • 語法糖:
    • 在不影響功能的情況下提供一點更適合操作的語法
function outer() {
    let a = 100
    let b = 200

    return {
        get a() { return a },
        get b() { return b },
        set a(val) { a = val }
    }
}

let res = outer()
console.log(res.a)
console.log(res.b)
res.a = 999
console.log(res.a)   // 999 

閉包面試題!!!!

function fun(n, o) {
    console.log(o)

    const obj = {
        fun: function (m) {
            return fun(m, n)
        }
    }

    return obj
}

var a = fun(0)    // undefined
a.fun(1)    // 0
a.fun(2)    // 0
a.fun(3)    // 0

/**
 *  var a = fun(0)
 *  a.fun(1)
 *  a.fun(2)
 *  a.fun(3)
 *
 *  1. var a = fun(0)
 *          調用 fun(QF001) 函數(QF001) 傳遞一個 參數 0
 *              全局函數 fun (QF001) 的 形參 n == 0     形參 o == undefined
 *          調用 fun 函數後, 會返回一個對象 存儲在 變數 a 中, 這個對象內部有一個屬性叫做 fun, 屬性值為 一個函數(QF002),
 *              所以我們可以通過 a.fun()   去調用這個函數
 *
 *  2. a.fun(1)
 *      2.1 調用這個函數 會 return 一個函數 fun (為全局函數 QF001) 的調用結果,   
 *      2.2 調用全局函數 fun(m, n)       m 此時 傳遞的是 1, n 傳遞的是 0
 *      2.3 執行全局函數 fun(m, n) 內部會輸出第二個形參
 *
 *  3. a.fun(2)
 *      2.1 調用這個函數 會 return 一個函數 fun(為全局函數 QF001) 的調用結果
 *      2.2 調用全局函數 fun(m, n)  m 此時傳遞的是 2, n 傳遞的是 0
 *      2.3 執行全局函數 fun(m, n) 內部會輸出第二個形參
 *
*/

防抖與節流

防抖

  • 解釋:在短時間內觸發一件事,每次都用上一次的時間替代,也就是只執行最後一次
        box.oninput = ((timerID) => {
            return function (e) {
                clearInterval(timerID)
                timerID = setTimeout(() => {
                    console.log('搜索了: ', e.target.value)
                }, 300)
            }
        })(0)

節流

  • 解釋:短時間內快速觸發一件事,當一個事件處理函數開始執行的時候,不允許重覆執行(瀑布流)
        box.oninput = ((flag) => {
            return function (e) {
                if (!flag) return
                flag = false
                setTimeout(() => {
                    console.log('搜索了: ', e.target.value)
                    flag = true
                }, 300)
            }
        })(true)

柯里化函數

  • 定義:本質上還是一個函數,只不過將原本接收多個參數才能正常執行的函數拆分成多個只接收一個的函數
// 原本函數
        function reg (reg, str) {
            return reg.test(str)
        }
        // 柯里化後
        function reg(reg) {
            return (str) => {
                return reg.test(str)
            }
        }
        const res = reg(/^\w{3,5}$/)
        console.log(res('123asd'));   // false
        console.log(res('123'));      // true

封裝柯里化函數案例

        /**
         *  函數柯里化封裝
         * 
         *  fn 函數能夠幫助我們拼接一個 完整的網路地址
         *      a --- 傳輸協議: http      https
         *      b --- 功能變數名稱:   localhost   127.0.0.1
         *      c --- 埠號: 0-65535
         *      d --- 地址:   /index.html     /a/b/c/index.html
         *
         *
         *  現在只有我們正確的傳遞了參數的數量才能夠實現最好的拼接, 如果傳遞的參數數量不夠也會運行函數, 但是字元串不太對
         *
         *  需求:
         *      將當前函數處理成柯里化函數, 只有傳遞的參數數量足夠的時候, 在執行函數內容
        */
        // 功能函數
        function fn(a, b, c, d) {
            return a + '://' + b + ':' + c + d
        }
        // 通過柯里化解決
        function keli (callBack, ...args) {
            return function (..._args) {
                _args = [...args, ..._args]
                if (_args.length >= callBack.length) {
                    return callBack(..._args)
                } else {
                    return keli(callBack, ..._args)
                }
            }
         }

數據劫持(代理)

將來在框架中我們通常都是 數據驅動視圖 也就是說: 修改完畢數據, 視圖自動更新

  • 數據劫持:以原始數據為基礎,對數據進行一份複製
  • 複製出來的數據是不允許被修改的,值是從原始數據裡面獲取的
  • 語法:Object.defineproperty(哪一個對象,屬性名,{配置項})
  • 配置項:
    • value:該屬性對應值
    • writable:該屬性確定是否允許被重寫,預設值是false
    • emunerable:該屬性是否可被枚舉(遍歷), 預設是 false
    • get:是一個函數,叫做getter獲取器,可以用來決定改屬性的屬性值
      • get屬性的返回值就是當前屬性的屬性值
    • set:是一個函數,叫做setter設置器,當修改屬性值的時候會觸發函數
    • set和get不能和其他三個屬性一起用
    <h1>姓名: <span class="name">預設值</span> </h1>
    <h1>年齡: <span class="age">預設值</span> </h1>
    <h1>性別: <span class="sex">預設值</span> </h1>
    請輸入年齡:<input type="text" name="" id="name">
    <br>
    請輸入年齡:<input type="text" name="" id="age">
    <br>
    請輸入性別:<input type="text" name="" id="sex">


        const nameEl = document.querySelector('.name')
        const ageEl = document.querySelector('.age')
        const sexEl = document.querySelector('.sex')
        const inp1 = document.querySelector('#name')
        const inp2 = document.querySelector('#age')
        const inp3 = document.querySelector('#sex')
        const obj = {
            name:'張三',
            age:18,
            sex:'男'
        }
        function bindHtml(res) {
            nameEl.innerHTML = res.name
            ageEl.innerHTML = res.age
            sexEl.innerHTML = res.sex
        }
        const app =  observer(obj, bindHtml)

        inp1.oninput = function () {
            app.name = this.value
        }
        inp2.oninput = function () {
            app.age = this.value
        }
        inp3.oninput = function () {
            app.sex = this.value
        }

    function observer (origin, callBack) {
    const target = {}
    // 數據劫持
    for (let key in origin) {
        Object.defineProperty(target, key, {
            get () {
                return origin[key]
            },
            set (val) {
                origin[key] = val
                callBack(target)
            }
        })
    }
    // 首次調用 
    callBack(target)
    return target
}

數據劫持 + 渲染 (vue功能實現)

<div id="app">
        <p>姓名:{{name}}</p>
        <p>年齡:{{age}}</p>
        <p>性別:{{sex}}</p>
        <p>用戶:{{id}}</p>
    </div>

    用戶id:<input type="text" name="" id="">

    <script src="js/vue.js"></script>

    <script>
        const app = createApp({
            el:'#app',
            data:{
                name:'張三',
                age:20,
                sex:'男',
                id:'0001'
            }
        })
        const inp = document.querySelector('input')
        inp.oninput = function () {
            app.id = this.value
        }

    </script>

function createApp(options) {
    // 安全判斷(傳參判斷)
    // 1.1 el
    if (options.el === undefined) {
        throw new Error('el選項必須傳遞')
    }
    // 1.2 data
    if (Object.prototype.toString.call(options.data) !== '[object Object]') {
        throw new Error('data 屬性必須是個對象')
    }
    // 1.3 el 不能為空
    const root = document.querySelector(options.el)
    if (root === null) throw new Error('el 必須傳入,且root為有效元素')
    // 2 數據劫持
    const target = {}
    for (let key in options.data) {
        Object.defineProperty(target, key, {
            get() {
                return options.data[key]
            },
            set(val) {
                options.data[key] = val
                // 每次修改數據調用渲染函數
                bindHtml(root, target, rootStr)
            }
        })
    }
    // 拿到根元素下麵的結構(字元串形式)
    const rootStr = root.innerHTML
    // 首次調用
    bindHtml(root, target, rootStr)
    return target
}
function bindHtml(root, _data, _str) {
    // 定義一個正則拿到{{......}}
    const reg = /{{ *(\w+) *}}/g
    const arr = _str.match(reg)
    arr.forEach(item => {
        const key = /{{ *(\w+) *}}/.exec(item)[1]
        _str = _str.replace(item, _data[key]) 
    });
    root.innerHTML = _str
}

數據劫持升級

自己劫持自己

  • 語法:Object.defineProperties(想要劫持得對象,配置項)
        const obj = {
            username:'admin',
            password:'123456',
            id:'0001'
        }
        for (let key in obj) {
            Object.defineProperties(obj, {
                // 對象裡面預設把key當作字元串,通過[]語法實現將其當作變數
                ['_' + key]:{
                    value:obj[key],
                    writable:true
                },
                [key]:{
                    get () {
                        // 如果returnobj[key],每次return都會訪問,
                        // 然後觸發get方法會形成死迴圈
                        return obj['_' + key]
                    },
                    set (val) {
                        obj['_' + key] = val
                    }
                }
            })
        }

數據代理(ES6)

  • 通過內置構造函數代理
  • 語法:new Proxy(想要代理得對象,)
  • 數據代理完成後,在向對象中添加屬性,也可以自動完成代理
        const obj = {
            name:'zhangsan',
            age:18
        }
        const res = new Proxy(obj, {
            get (target, property) {
                return target[property]
            },
            set (target, property, val) {
                target[property] = val
            }
        })
        res.age = 20
        console.log(res.age);
        console.log(res.name);
        // 數據代理後添加的數據也可以被代理
        res.abc = 123
        console.log(res);

本文轉載於:

https://juejin.cn/post/7293341446924468243

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 硬體:一個樹莓派4B、一臺筆記本電腦(以win10系統為例,做樹莓派顯示屏) 1 下載工具軟體 1.1 下載樹莓派鏡像燒錄器Raspberry Pi Imager。 該軟體是把Raspberry Pi OS安裝(燒錄)到TD卡上的工具。 樹莓派官網鏈接。根據下載Raspberry Pi Imager ...
  • 飛騰E2000板載乙太網適配e20005.4.18macbyt85211. 設備樹移植2. MACB驅動移植3. 網路連通性調試3-1. MACB驅動環回測試3-2. YT8521 PHY工作模式及電壓確認3-3. YT8521 PHY環回測試3-4. YT8521 PHY tx delay調整 飛 ...
  • 只是記錄一些我認為比較有用而且容易忘記的操作,主要系統為CentOS CentOS yum使用鏡像源 sudo yum install epel-release (虛擬機)使用主機代理 使用ip route show查看預設路由埠 設置代理: #添加Proxy代理信息(其中username和pas ...
  • SQL HAVING子句 HAVING子句被添加到SQL中,因為WHERE關鍵字不能與聚合函數一起使用。 HAVING語法 SELECT column_name(s) FROM table_name WHERE condition GROUP BY column_name(s) HAVING con ...
  • 稀疏索引 密集索引:文件中的每個搜索碼值都對應一個索引值,就是葉子節點保存了整行. 稀疏索引:文件只為索引碼的某些值建立索引項. 稀疏索引的創建過程包括將集合中的元素分段,並給每個分段中的最小元素創建索引。在搜索時,先定位到第一個大於搜索值的索引的前一個索引,然後從該索引所在的分段中從前向後順序遍歷 ...
  • WebSocket 是一種用於實現持久連接的通信協議,它的原理和工作方式相對複雜,但我們可以嘗試以儘可能簡單和清晰的方式來解釋它。 WebSocket 的原理 在理解 WebSocket 的工作原理之前,我們首先要瞭解 HTTP 協議的短連接性質。在傳統的 HTTP 通信中,客戶端發送一個請求到服務 ...
  • 一個變數如果聲明為聯合類型,而後續操作需要針對其具體的單一類型做不同處理,這個過程就叫做類型收窄(`Narrowing`) ...
  • 項目代碼同步至碼雲 weiz-vue3-template Vue Router 是 Vue.js 的官方路由。它與 Vue.js 核心深度集成,讓用 Vue.js 構建單頁應用變得輕而易舉。 1. 安裝 npm i vue-router@4 2. 集成 1. 新建兩頁面進行示例 在src/view下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...