-防抖与节流-数据劫持-函数柯里化-数据代理

41 阅读7分钟

防抖:

  • 在短时间内快速的触发一件事, 每次都用新的事件替代上一次, 也就是说那么我们只会执行最后一次触发的事件 * 短时间内快速触发一件事, 只会执行最后一次
        let timerID = 0 
        box.oninput = function (e) {
            clearInterval(timerID)
            timerID = setTimeout(() => {
                console.log('搜索了: ', e.target.value)
            }, 300)
        }

        // box.oninput = (() => {
        //     let timerID = 0
        //     return function (e) {
        //         clearInterval(timerID)
        //         timerID = setTimeout(() => {
        //             console.log('搜索了: ', e.target.value)
        //         }, 300)
        //     }
        // })()

        // box.oninput = ((timerID) => {
        //     return function (e) {
        //         clearInterval(timerID)
        //         timerID = setTimeout(() => {
        //             console.log('搜索了: ', e.target.value)
        //         }, 300)
        //     }
        // })(0)

节流: 在短时间内快速的触发一件事, 当一事件处理函数开始执行的时候, 在此期间, 不允许重复触发 (以前的瀑布流/轮播图 都有使用节流处理)

        
        let flag = true // 当前变量是开关变量, 默认值为 true 表示能够处理事件
        box.oninput = function (e) {
            if (!flag) return
            flag = false
            setTimeout(() => {
                console.log('搜索了: ', e.target.value)
                flag = true
            }, 300)
        }

        优化1
        box.oninput = (() => {
            let flag = true
            return function (e) {
                if (!flag) return
                flag = false
                setTimeout(() => {
                    console.log('搜索了: ', e.target.value)
                    flag = true
                }, 300)
            }
        })()

        优化2
        box.oninput = ((flag) => {
            return function (e) {
                if (!flag) return
                flag = false
                setTimeout(() => {
                    console.log('搜索了: ', e.target.value)
                    flag = true
                }, 300)
            }
        })(true)

封装柯里化函数

  • 柯里化函数, 其实还是一个函数, 只不过是将原本接收多个参数才能正常使用的函数
    • 拆分为多个函数, 每个函数只接受一个参数
function test(reg) {
            return function (str) {
                return reg.test(str)
            }
        }

        const res_1 = test(/^\w{4,6}$/)
        console.log(res_1('QF001'))
        console.log(res_1('qwe123'))
        console.log(res_1('!@#$%^&*(*&^%$#@!#$%^&)'))
 /**
         *  函数柯里化封装
         * 
         *  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
        }

        // console.log(fn('https', 'www.baidu.com', '8080', '/index.html'))
        // console.log(fn('http', '127.0.0.1', '7777', '/a/b/c/index.html'))


        /**
         *  封装一个新的函数, 这个函数只有收集够指定的参数后, 再去执行指定的函数, 否则就一直收集参数
        */


        // function keli(callback, ...args) {
        //     return function (..._args) {
        //         _args = [...args, ..._args]
        //         if (_args.length >= callback.length) {
        //             return callback(..._args)
        //         } else {
        //             return keli(callback, ..._args)
        //         }
        //     }
        // }




        // 外层函数需要收集 我们的执行函数, 以及一些基础参数
        function keli(callback, ...args) {


            // 内层函数负责收集参数
            return function (..._args) {

                // 将收集到的所有参数, 合并到一个数组中, 方便统一的维护与管理
                _args = [...args, ..._args]


                if (_args.length >= callback.length) {
                    // 判断 如果 我们收集到的参数的数量, 正好符合 callback 函数需要的参数数量, 那么我们直接执行函数即可
                    return callback(..._args)
                } else {
                    // 否则说明参数数量不够, 我们应该继续收集参数
                    // console.log('现在参数数量不够, 我们应该继续收集参数')
                    // return keli()

                    /**
                     *  注意: 此时返回的不是外层函数, 而是外层函数的调用结果
                     * 
                     *  所以实际上, 我们返回的是 内层函数
                    */
                    // return keli(callback, _args)     // return keli(callback, ['http', '127.0.0.1', '8080'])
                    return keli(callback, ..._args)     // return keli(callback, 'http', '127.0.0.1', '8080')
                }

            }

        }

        /**
         *  调用了 keli 函数, 传递一个 fn 函数, 以及两个 字符串, 会返回一个内层函数
        */
        const res_1 = keli(fn, 'http', '127.0.0.1')

        /**
         *  调用了 内层函数, 并且又重新传递了一个字符串, 现在结合之前的两个字符串, 一共收集到了 3 个 字符串
         * 
         *  所以根据内层函数里边的逻辑, 我们此时运行的 是 else 分支
         * 
         *  内层函数中 返回了一个 keli 函数的调用结果
         * 
         *  所以实际上我们相当于是拿到了 内层函数
        */
        const res_2 = res_1('8081')
        // console.log(res_2)    // 内层函数

        /**
         *  刚才分析得出 res_2 其实就是我们的内层函数, 目前结合我现在传递的一个参数, 一共有 4 个参数了
         * 
         *  所以此时就回调用函数, 而不是继续返回一个内层函数收集参数
        */
        const str = res_2('/a.html', 10086)
        console.log(str)


        // const res = keli(fn, 'http', '127.0.0.1')
        // const str = res('8080', '/a.html')
        // console.log(str)    //  http://127.0.0.1:8080/a.html






        // keli(fn)    // []
        // keli(fn, 'http')    // ['http']
        // keli(fn, 'http', '127.0.0.1')    // ['http', '127.0.0.1']
        // keli(fn, 'http', '127.0.0.1', '8989')    // ['http', '127.0.0.1', '8989']
        // keli(fn, 'http', '127.0.0.1', '8989', '/index.html')    // ['http', '127.0.0.1', '8989', '/index.html']

数据劫持

  • 于一个原本的数据, 劫持出来一个数据
  • 利用这个方法, 向对象 obj 中添加一个属性
  • Object.defineProperty('向那个对象中添加', '要添加的属性名是什么', '关于当前属性的一些配置项')
const obj = {}
       obj.name = '张三'
       obj.info = '详情'
       // 数据劫持 配置项补充
       // const obj = {}
       // obj.name = '张三'
       // obj.info = '详情'

       /**
        *  当前方法 添加的属性, 默认不允许被修改, 也不允许被遍历到
        *
        *  除非添加配置项
       */

       Object.defineProperty(obj, 'age', {

           // value: 18,          // 你访问这个属性的时候会得到的值
           // // writable: false
           // writable: true, // 当前选项决定当前 属性能否被修改, 默认为 false, 不允许修改
           // // enumerable: false
           // enumerable: true,    // 当前选项决定当前属性能否被枚举(遍历)到, 默认为 false, 不允许被枚举到
           /**4
            *  正常开发中我们都会选用 get 和 set 因为相比上边的我们多了一个 函数能够出发
            *
            *  有了这个函数之后, 我们可以做任何事情
           */

           get () {
               // 当你要访问这个属性的时候, 会执行, 内部的返回值就是你这个属性实际的值
               console.log('你访问了 age 属性, 我就会触发')
               return 'age 属性实际的值'
           },

           set (val) {
               console.log('set触发~~~', val)
           }
       })

       console.log(obj.age)

       obj.age = 999
       console.log(obj)

       for (let key in obj) {
           console.log(key, '==>', obj[key])
       }

  • 封装数据劫持
function observer (origin, cb) {
    // 1. 创建一个对象用于存储数据劫持后的一些内容
    const target = {}

    // 2. 开始核心代码
    for (let key in origin) {
        // console.log(key, origin[key])
        Object.defineProperty(target, key, {
            get () {
                return origin[key]
            },
            set (val) {
                origin[key] = val
                cb(target) // 这里到底是调用
            }
        })
    }

    // 2.5 因为当前函数页面一打开就回调用, 所以我这个位置也会在页面初次渲染的时候执行
    cb(target)

    // 3. 将数据劫持后的新对象返回出去
    return target
}
  • 02_vue花括号语法
  <div id="root">
        <p>使用name属性: {{name}}</p>
        <p>使用age属性: {{ age}}</p>
        <p>使用width属性: {{width }}</p>
        <p>使用height属性: {{     height }}</p>
    </div>

function createApp(options) {
    // 1. 安全判断

    // 1.1 options 的 el 属性必须传递
    if (options.el === undefined) {
        // return console.log('此时代码出现问题, 应该阻断程序运行, 并且通知开发者有问题')
        throw new Error("el 选项必须传递");
    }

    // 1.2 options 的 data 属性必须传递, 并且只能是 对象类型
    if (Object.prototype.toString.call(options.data) !== "[object Object]") {
        throw new Error("data 选项必须传递, 并且只能是对象类型");
    }

    // 1.3 el 属性的值, 必须能够获取到 DOM 节点
    const root = document.querySelector(options.el);
    if (root === null) {
        throw new Error(
            "el 选项必须为字符串, 并且需要能够获取到当前页面的根元素"
        );
    }

    // 2. 开始数据劫持 (核心代码)

    // 创建一个对象 存储我们劫持后的数据
    const _data = {};

    // 开始劫持
    for (let key in options.data) {
        Object.defineProperty(_data, key, {
            get() {
                return options.data[key];
            },
            set(val) {
                options.data[key] = val;

                // 每次修改数据后, 重新渲染页面
                bindHtml(root, _data, rootHtml);
            },
        });
    }

    // 存储一个最开始的标签结构
    const rootHtml = root.innerHTML;

    // 首次打开页面就回执行这个地方的代码, 调用渲染函数
    bindHtml(root, _data, rootHtml);

    // 返回劫持好的对象
    return _data;
}

/**
 *  三个形参的拼写不重要
 *
 *      第一个: 去哪个标签内修改
 *      第二个: 变量对应的数据是什么
 *      第三个: 原本默认的 html 结构, 防止初次选然后找不到 {{}}
 */
function bindHtml(root, _data, _str) {
    // 1. 创建正则, 用于捕获出字符串中所有的 {{XXX}}
    // const reg = /{{name}}/
    // const reg = /{{\w+}}/
    // const reg = /{{(\w+)}}/
    // const reg = /{{ *(\w+) *}}/
    const reg = /{{ *(\w+) *}}/g;
    

    // 2. 利用字符串的方法, 将所有符合规则的部分捕获到一个数组中
    const arr = _str.match(reg);

    // console.log(arr)
    // 3. 遍历数组, 拿到所有的字符串, 然后对字符串进行后续的操作
    arr.forEach((item) => {
        const key = /{{ *(\w+) *}}/g.exec(item)[1];
        
        _str = _str.replace(/{{ *(\w+) *}}/, _data[key])
    })

    root.innerHTML = _str
}

 const app = createApp({
            el: '#root',
            data: {
                name: '张三',
                age: 18,
                width: 10086,
                height: 10010
            },
        })

        const inp = document.querySelector('#inp')

        inp.oninput = function () {
            app.age = this.value
        }
  • 第二种方式
  • Object.defineProperties('劫持到那个对象', 属性1: {属性1的配置项},)
Object.defineProperties('劫持到那个对象', {
        属性1: {属性1的配置项},
        属性2: {属性2的配置项},
        属性3: {属性3的配置项},
    })

数据代理

  • 利用 ES6 新推的一个内置构造函数 proxy

        const obj = {
            name: 'QF001',
            age: 18
        }
        // console.log('源对象: ', obj)

        const res = new Proxy(obj, {
            get(target, property) {
                /**
                 * 形参的拼写无所谓
                 * 
                 *  第一个形参: 我们的代理对象
                 *  第二个形参: 我们的代理对象中的某一个属性
                */
                
                return target[property]
            },
            set(target, property, val) {
                target[property] = val
            },
        })

        // 如果我们使用了 数据代理, 那么在代理完毕后, 给源对象中添加一个新的数据, 那么也会被自动代理 (由 proxy 帮助我们完成)
        obj.qwe = 100

        console.log(res.name)

         res.age = 10086
        // res.name = 'QF002'

        console.log(res)
        // console.log(res.age)