函数柯里化 / 函数的防抖与节流 / 自执行函数 / 数据劫持 / 数据代理

136 阅读4分钟

js.webp

函数的柯里化

将一个 接收多个参数的函数 更改为需要调用多次的函数 ,每次只传入一个参数,外层函数负责收集参数,内层函数负责 在参数收集完毕时 执行功能

  • 通过一个函数计算 a、b的和
function fun(a,b){
    return a+b;  // 返回值 => a+b
}
console.log(fun(10,20));  // 30

我们可以看到,想要计算 a、b 的和,我们必须要传入两个参数,而通过函数柯里化后我们只需要传入一个参数即可实现 计算 a、b 的和

  • 函数柯里化 => 计算a 、 b的和
function fun(a){
    return function(b){
        console.log(a+b); // 打印 a+b
    }
}
let res = fun(10); // 调用外部函数  参数传入a  => a=10
let res1 = res(20); // 调用内部函数  参数传入b   =>  b=20
/*
    当调用外部函数时  参数 10 传入 a , a=10, 当调用内部函数时 参数 20 传入 b, b=20,
    利用了 闭包, 延长了 外部函数的参数使用时间   此时 a=10  
*/
  • 例如: 咱们通过正则表达式,制定一项密码规则,利用函数柯里化来判断传入的密码是否正确
        function fun(reg) {
            return function (str) {
                return reg.test(str); // 检验传入参数
            }
        }
        let test1 = fun(/^\d{4,8}$/);   // 将规则传入参数

        let boo1 = test1('123456'); 
        console.log(boo1);      // true

        let boo2 = test1('qwe123456'); 
        console.log(boo2);      // false
  • 封装柯里化函数 每次输入不同的实参,实现一个网址的输出 => 例: www.baidu.com/
// https://www.baidu.com/
        function fun(a,b,c,d){
            return a + '://' + b + '.' + c + '.' + d 
        }
        // 外层函数 负责接收参数
        function currying(callback, ...arg){  //callback 功能函数 首次调用必传    ...arg 将存入参数以数组形式存储 如果首次调用没有传入参数 那么 ...arg 为空数组
            // const len = callback.length;  // 传入参数的个数
            // console.log(...arg); 
            // 内层函数 负责参数执行完毕后 执行功能    
            return function(...iArg){    
                iArg = [...arg,...iArg];  // 执行功能前 先将两次函数调用传入的形参从数组中拿出来 存储在 iArg 中
                // console.log(iArg.length);
                if(iArg.length === callback.length){   // iArg.length === len => 4  fun()形参长度
                    return callback(...iArg); // 如果传入实参 个数 满足形参个数 以字符串的形式返回 
                }else{
                    return currying(callback,...iArg); // 如果传入实参不满足 形参个数  将继续传入  并将之前传入的实参拼接 返回
                }
            }
        }

        let res = currying(fun,'https');   // 首次调用函数currying  此时参数只有一个  不满足形参个数 将继续调用 加上之前传入的实参   直到 满足形参个数      fun  =>  为功能函数实参    
        let res1 = res('www','baidu');  // 此时不满足形参个数  将继续调用  加上之前传入的实参 直到满足形参个数
        let res2 = res1('com');  // 加上之前传入参数 满足形参个数  
        console.log(res2);  // 打印功能函数的返回值  得到传入参数的字符串拼接

函数的防抖与节流

防抖: 在一定时间内, 快速触发同一事件,每次重新触发, 都覆盖掉前一次事件, 以最后一次事件触发为主

节流: 在一定时间内, 快速触发同一事件,在规定时间内, 只能触发一次, 下一次必须等到 规定时间结束以后才能执行

  • 常见的节流

    在输入框输入时,输入的值不会在开始时输入事件不会打印或者返回结果,达到规定时间时才会触发事件,返回结果

1.1.jpg

// 输入框:<input type="text">
// 节流
        const inp = document.querySelector('input'); // 获取 input 输入框
        let flag = true;  // 定义开关变量
        inp.oninput = function(e){ 
            if(!flag) return;  // 输入开始后未到达时间  不再触发事件 
            flag = false; // 开始后 触发事件失效
            console.log(e.target.value);  // 打印输出 输入到输入框的值   e.target.value =>  输进输入框的值
            setInterval(function(){  // 计时器
               flag = true;  // 满足时间   开关打开  触发事件
            },300);       // 触发时间   输入开始300毫秒后触发  
        }

引入 => 自执行函数

见面知意, 自执行函数,就是不调用也可以执行的函数, 多个自执行函数连在一起时, 需要用 ' ; ' 隔开,则就会报错,不能正常执行.(分隔号 ' ; ' 加在函数前后都可以)

  • 自执行函数的写法

自执行函数, 第一个小括号内写 函数体,第二个小括号内写 实参(会传递给第一个小括号内部的函数)

// 自执行函数的写法 : ()()  
        (function () {
            console.log(999);    // 999
        })();

        (function (num){    // num 为形参  
            console.log(num)
        })(666);   // 666   实参 写在第二个括号里面

  • 函数的节流 (自执行函数)
// 输入框:<input type="text">
        inp.oninput = (function (flag) {  // flag 为形参   =>  开关变量
            return function (e) {
                if(!flag) return;  // 
                flag = false;  // 输入开始  开关关闭  不触发事件 
                console.log('e.target.value');  
                setTimeout(() => {
                    flag = true;   //  规定时间到达   开关打开  触发事件
                }, 300);
            }
        })(true);    // true 实参    =>   传入  flag
  • 防抖 => 输入框一直输入 以最后一次输入的为准 前面输入的会被后面的覆盖

image.png

//     输入框:<input type="text">

        // 防抖 =>  输入框一直输入  以最后一次输入的为准  前面输入的会被后面的覆盖  
            inp.oninput = (function(time){
                return function(e){
                    clearTimeout(time);  // 清除0   => 当倒计时器为0时  清除到计时器  开始输入时
                    time = setTimeout(function(){
                        console.log(e.target.value); // 拿到键盘输入的值
                    },300);
                }
            })(0); //time === 0

数据劫持

  • 将原数据 劫持一份一模一样的数据出来 存储到一个空对象中,劫持出来的数据默认默认是不能修改的
  • 语法: Object.defineProperty(那个对象, '对象的key', {配置项})
  • 配置项
    1. value 访问这个值 之后, 得到结果
    2. writable 决定当前这个属性能否被修改, 默认是 false
    3. enumerable 决定当前这个属性能否被枚举, 决定当前这个属性能
    4. getter 是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数 ( getter 不能和 value writable 一起使用 )
    5. setter 是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数
        const obj = {
            name:'QF100',
            age:18
        };
        console.log(obj);
        Object.defineProperty(obj,'age',{
            // value:'QF100',
            // writable:true,  //  value writable 不能与 get 一起用
            enumerable:true,
            get(){
                return 'qwer';   // 访问时返回的值
            },
            set(val){
                console.log(`修改后:${val}`);  // val 被修改的值
            }
        })
        console.log(obj.name);    // QF100
        obj.age = 40;   // 修改 age 
        console.log(obj.age);  // 40
            obj.name = 'QF111';  
            console.log(obj.name);  //  QF111

image.png

  • 由上述代码看来 我们在修改name时 并没有返回值 ,说明并不能实现多个值的修改,如果我们将劫持语法写为多个呢? 是否可以修改多个值?

  • 定义多个数据劫持虽然可以修改 截取到全部数据 但代码量过多 , 如果我们有一百个这样的数据要修改,要写这样的一百个?

// <div id="box"></div>

        const box = document.querySelector('#box');
        const  obj = {
            name:'qf100',
            age:18
        }
        console.log('原对象:',obj);
        const res = {};  // 将劫持到的数据 存储到res 中
        Object.defineProperty(res,'name',{
            get(){
                return obj.name ; // 访问 读取时返回 name
            },
            set(val){
                obj.name = val;  // 修改名字
                box.innerHTML = `名字:${res.name}; 年龄:${res.age}`
            }
        })
        Object.defineProperty(res,'age',{
            get(){
                return obj.age;  // 访问 读取时返回 age
            },
            set(val){
                obj.age = val;  // 修改年龄
                box.innerHTML = `名字:${res.name}; 年龄:${res.age}` // 将修改的数据渲染页面
            }
        })
        res.name = 'QF111';  // 修改name
        res.age = 99;  // 修改age

image.png

  • 数据劫持 优化1
语法: Object.defineProperties(到那个对象, {
        属性1: 配置项,
        属性2: 配置项
    })
// <div id="box"></div>
        const obj = {
            name: 'QF100',
            age: 18
        }
        const res = {};  // 定义空对象  用于存储 劫持到的数据
        // 基础版
        Object.defineProperties(res, {
            name: {  // name 项
                get() {
                    return obj.name;
                },
                set(val) {
                    obj.name = val;
                }
            },
            age:{  // age 项
                get(){
                    return obj.age;
                },
                set(val){
                    obj.age = val;
                }
            }
        })
        console.log(res);  // 获取劫持到的数据
        console.log(res.age);  // 获取劫持到的age
        res.name = 'QF1234'; // 修改 name 
        console.log(res.name);  // 获取修改后的name

image.png

  • 数据劫持 优化2

通过自己劫持自己,将自己的数据全部劫持存储在自身内部

        const obj = {
            name: 'QF001',
            age: 18
        }
        // 2. 升级版(plus)
        for (let k in obj) {   // 循环遍历对象
            Object.defineProperties(obj, {
                ['_' + k]: {    // 我在我这个对象内把所有属性复制一份, 放在自己这个对象内部
                    value: obj[k],
                    writable: true 
                },
                [k]: {
                    get () {
                        return obj['_' + k];   // 读取时返回劫持的数据
                    },
                    set(val) {
                        obj['_' + k] = val;   // 修改劫持后的数据

                        document.querySelector('#root').innerHTML = `name: ${obj.name}`
                    }
                }
            })
        }



        obj.newName = 'QF999';  // 添加数据
        console.log(obj);
        obj.abc = 'www';   // 增加一个key 时 可以加上  但加上的key  没有被劫持
        console.log(obj);
        document.querySelector('#root').innerHTML = `name: ${obj.name}`

image.png

  • 数据代理

    上述数据劫持中 当新增 obj 一个key 时 发现新增的key 并没有被劫持,劫持的数据仍是之前数据,所以,在此我们可以使用数据代理优化数据劫持

    语法: new Proxy(参数一: 代理那个对象)

        const obj = {
            name:'QF111',
            age:18
        }
        
        const res = new Proxy(obj,{
            get(target,key){
                return target[key];  // target 是当前代理对象,在当前代码中 也就时obj   而key 就是obj 中的全部数据 
            },
            set(target,key,val){
                target[key] = val;
                console.log('修改成功');
                return true;  // 代表修改成功返回true
            }
        })

        console.log(res);  
        obj.abc = 999;  // 当增加obj的key值时 新增key会被劫持
        console.log(obj);  // 打印对象  查看新增key
        console.log(res);  // 打印对象 查看新增key 是否被劫持
        res.def = 666;  // 新增到已经完成劫持的对象中
        console.log(res);  

image.png

  • 封装数据劫持
//     <input type="text">
//     <div class="box"></div>

        const box = document.querySelector('.box');
        const obj = {
            name:'QF100',
            age:18
        }
        function observer(origin, callback){
            const target = {};   // target 空对象 用于存储劫持后的数据
            for(let key in origin){  // 通过对象遍历方法 拿到 origin 中的每一项  origin  =>   形参 obj传入对象
                Object.defineProperty(target,key,{
                    get(){
                        return origin[key];  // 返回每一项
                    },
                    set(val){
                        origin[key] = val;  // 修改每一项
                        callback(target);  // 调用渲染函数   target
                    }
                })
            }
            callback(target); 
            return target;  //返回劫持后的数据
        }

        function fun(res){
            box.innerHTML = `名字:${res.name}; 年龄:${res.age}`
        }
        const app = observer(obj,fun);
        document.querySelector('input').oninput = function(e){
            app.age = e.target.value;  
        }

image.png