date1117柯里化函数

94 阅读3分钟

一、柯里化函数

概念:将一个接受多个参数的函数(假如有三个函数 funA funB funC),更改为需要调用多次 每次只传入一个参数的函数,(funA里传入一个形参a funB里传入一个形参b funC里传入一个形参c 利用return返回函数)。

假设我们要计算的是a、b、c的和。

语法形式:

方法一:是直接在函数传入全部参数后,进行打印所有值之和

function funA(a){
     return function funB(b){
           return function funC(c){
                 console.log(a + b + c);
           }
     }
}

方法二:利用闭包的方法将内部的和在外部调用打印

function funA(a){
   return function funB(b){
         return function funC(c){
               return a+b+c;
         }
   }
}
let res = funA(10)(10)(10);
console.log(res);

利用了闭包 延长了外部函数的参数使用时间。

return 的可以是一个匿名函数也可以是一个普通函数 实质上没有什么区别 一般都是写匿名函数 优化代码 。

实际项目中会结合模块化开发的语法形式 执行柯里化函数。

         /*
             求和
        */
         // 普通函数求和
         function fun1(a, b) {
             console.log(a + b);
         }
         // 如果有相同的参数 会多次调用 代码会比较冗余
         fun1(10, 20);
         fun1(10, 30);
         fun1(10, 40);



        // 利用柯里化解决相同的参数多次调用的问题
        function fun2(a) {
            return function(b){
                return a + b;
            }
        }
        /*
            第一种调用方法:分两次两用
        */
         // 先将全局函数赋值a为10
         let res1 = fun2(10);
         // 返回的是里面的函数体
         console.log(res1);
         // 再次调用全局函数 返回局部函数的值之和
         res1(20);
         res1(30);
         res1(40);
        /*
            第二种调用方法:直接一次性调用
        */
       console.log(fun2(20)(30));
       console.log(fun2(20)(46));


        /*
            利用柯里化求正则
        */
        // 普通函数求正则
        function fun3(reg , str){
             console.log(reg.test(str));
        }
        fun3(/^\w{5,10}$/ , 'abc');
        fun3(/^\w{5,10}$/ , 'abcdef');
        fun3(/^\d{1,5}$/ , 'abcdef'); 
        fun3(/^\d{1,5}$/ , '123'); 



        // 利用柯里化函数求正则
        function fun4(reg){
             return function(str){
                 return reg.test(str);
             }
        }
        console.log(fun4(/^\w{5,10}$/)('123'));
        console.log(fun4(/^\w{5,10}$/)('123456'));
        console.log(fun4(/^\d{7,10}$/)('123456'));

二、封装柯里化函数

外层函数负责收集参数

内层函数负责 在 参数收集完毕的时候 执行功能 www.baidu.com:8080/index.html www.baidu.com:8080/a.html

协议: https http

域名: www.baidu.com www.taobao.com 127.0.0.1

端口号: 0~65535 80 443 7777

地址: index.html a.html /a/b/c.html

 function fn(a, b, c, d) {
            return a + '://' + b + ':' + c + d
        }
        // let res = fn('http', '127.0.0.1', '80', '/a.html')
        // console.log(res)

        // let res1 = fn('http', '127.0.0.1', '443', '/b.html')
        // console.log(res1)

        // let res2 = fn('http', '127.0.0.1', '8080', '/c.html')
        // console.log(res2)


        // 外层函数 负责接接收参数
        function currying(callback, ...arg) {
            // ...arg 将后续所有实参, 以数组的形式存放在 arg 形参内, 如果没有传递的话, 是一个空数组
            // console.log(callback)    // 功能函数, 首次调用必传
            // console.log(arg)         // 基本参数, 首次调用可传可不传
            // console.log(callback.length)    // 函数名.length 能获取到函数的形参数量

            // 内层函数负责 当前是否接受够参数了
            return function (...iArg) {
                iArg = [...arg, ...iArg]    // 开始执行共能前, 先将两次函数调用接受的参数合并为一个数组, 后续用于计算传递的参数是否足够

                if (iArg.length === callback.length) {  // 如果传递的参数数量, 刚好是功能函数需要的数量, 代表此时参数足够, 直接执行函数即可
                    return callback(...iArg)    // 执行功能函数, 此时会得到一个拼接好的字符串, 我们将这个字符串返回出去, 就能得到功能函数的执行结果
                } else {                        // else 分支执行时表明此时函数参数仍未接受足够, 此时需要继续接受参数, 根据函数功能, 我们应该调用 currying并将之前接收的功能函数与之前传递所有的参数全部传递进去
                    return currying(callback, ...iArg)  // callback => 功能函数     ...iArg => 之前传递进来的所有的参数
                    // 这里是返回了一个 currying的调用结果, 所以相当于是返回了内层函数, 并且外层函数是接受了对应的功能函数与实际参数
                }

            }
        }

        // 无注释
        function currying(callback, ...arg) {
            return function (...iArg) {
                iArg = [...arg, ...iArg]

                if (iArg.length === callback.length) {
                    return callback(...iArg)
                } else {
                    return currying(callback, ...iArg)
                }
            }
        }

        let res = currying(fn, 'https') // 此处传递了功能函数与一个基本参数, 按照函数规则, 后续起码应该再传递够三个参数, 才能正常执行功能
        let res1 = res('127.0.0.1', '80')   // 此时传递了两个参数, 加上首次调用的一个参数, 现在接收到了 3个字符串, 所以应该在传递一个参数, 才能够正常执行
        let str1 = res1('/a.html')          // 此时传递了 一个 参数, 加上之前的三个字符串参数, 所以现在正好满足 4个参数, 所以现在就可能正常执行功能函数
        console.log(str1)   // https://127.0.0.1:80/a.html

        let res2 = currying(fn) // 此处传递了 功能函数, 没有传递基本参数, 按照函数规则, 后续起码应该传递四个参数, 才能正常执行功能
        let str2 = res2('https', 'www.baidu.com', '8080', '/a.html')    // 此时传递了四个参数, 因为首次没有传递字符串, 这里就是有4个参数, 所以正常执行函数
        console.log(str2)   // https://www.baidu.com:8080/a.html

        let res3 = currying(fn, 'https', 'www.baidu.com', '8080', '/a.html')    // 此处传递了 功能函数, 与 四个后续的参数, 根据函数规则, 后续不需要传递参数即可
        let str3 = res3()   // 因为之前传递的参数已足够, 所以此处可以不调用
        console.log(str3)   // https://www.baidu.com:8080/a.html



        // let str2 = res('127.0.0.1', '443', '/a.html')
        // let str3 = res('www.taobao.com', '8080', '/c.html')


        // let res2 = currying(fn, 'https', '127.0.0.1')
        // let str4 = res2('8080', '/q.html')


        // let res3 = currying(fn)
        // let str5 = res3('http', '127.0.0.1', '8080', '/c.html')

三、函数的防抖和节流

防抖和节流

            防抖:在一定时间内 快速触发同一事件
                    每次重新触发 都取消前一次事件 以后一次事件为主
                    防抖使用的是清除定时器来实现


            节流:
                在一定时间内 快速触发同一件事件
                在规定时间内 只能触发一次 下一次必须等到规定时间结束以后才能执行
                使用的是开关变量flag和自执行函数来执行的
                
// 普通操作
        // 每一次输入都会很快的触发一件事件发生
        // const oIpt = document.querySelector('.ipt');
        // oIpt.oninput = function(){
        //     console.log(`输入的参数是${oIpt.value}`);
        // }




        // 节流操作
        // 使用的是开关变量或者自执行函数实现
        const oIpt = document.querySelector('.ipt');
        // // 定义开关变量 来控制函数防抖的产生
        // // 一开始输入事件的时候  允许输出  开启开关变量
        // let flag = true;
        // oIpt.oninput = function (e) {
        //     // console.log(this);   // input
        //     // 如果开关变量为false 就返回不执行函数
        //     if (flag == false) return;
        //     // 再重新将开关变量赋值为false
        //     // 也就是当点击输入框的时候输入第一次过后 就不可以再输出了 尽管输入很多次
        //     flag = false;
        //     console.log(`输入的参数是${e.target.value}`);
        //     // 设置定时器 控制输入框输出 当输入框输出第一次过后
        //     // 中途在输入的时候 倒计时3s过后再将开关变量赋值为true 在进行打印
        //     // 有效防止节流
        //     setTimeout(() => {
        //         flag = true;
        //     }, 3000)
        // }

        // // 使用自执行函数
        // const oIpt = document.querySelector('.ipt');
        // oIpt.oninput = (function (flag) {
        //     return  function(e){
        //         // 如果开关变量为false 就返回不执行函数
        //         if (flag == false) return;
        //         // 再重新将开关变量赋值为false
        //         // 也就是当点击输入框的时候输入第一次过后 就不可以再输出了 尽管输入很多次
        //         flag = false;
        //         console.log(`输入的参数是${e.target.value}`);
        //         // 设置定时器 控制输入框输出 当输入框输出第一次过后
        //         // 中途在输入的时候 倒计时3s过后再将开关变量赋值为true 在进行打印
        //         // 有效防止节流
        //         setTimeout(() => {
        //             flag = true;
        //         }, 3000)
        //     }
        // })(true);




        // 自执行函数
        // 一定要写分号 两个或多个自执行函数过后
        fun1();
        function fun1() {
            console.log(888);
        }

        (function () {
            console.log(999);
        })();

        (function () {
            console.log(123523);
        })();



        // 防抖操作
        // 使用的是自执行函数
        oIpt.oninput = (function (timer) {
            return function (e) {
                // 先清除定时器
                clearInterval(timer);
                timer = setTimeout(function () {
                    console.log(`${e.target.value}内容`);
                }, 3000)
            }
        })(0)
    </script>

四、数据劫持

1、数据劫持1

 const box = document.querySelector('.box')
        // 数据劫持
        // const obj = {
        //     name: 'QF666',
        //     age: 18
        // }
        // box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`

        // obj.age = 99
        // box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`


        // obj.name = 'QF999'
        // box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`
        // console.log(obj)

        /**
         *  数据驱动视图
         * 
         * 1. 数据劫持
         *      将原始数据 劫持出一份一摸一样, 听起来有点像 浅拷贝
         * 
         *          劫持出来的数据, 默认是不可以修改的
         * 
         *  语法: Object.defineProperty(那个对象, '对象的key', {配置项})
         *      配置项:
         *          1. value    访问这个值 之后, 得到结果
         *          2. writable     决定当前这个属性能否被修改, 默认是 false
         *          3. enumerable   决定当前这个属性能否被枚举, 决定当前这个属性能否被遍历到
         *          4. getter   是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数
         *                  + getter 不能和 value    writable   一起使用
         *          5. setter   是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数
        */

        const obj = {}
        obj.name = 'QF666'
        console.log(obj)

        // let str = 'age'
        // Object.defineProperty(obj, str, {配置项})
        Object.defineProperty(obj, 'age', {
            // value: 'QF999',
            // writable: true,
            enumerable: true,
            get() {
                // console.log('你当前访问了 这个 age 属性, 触发了 get 函数')
                return 'qwer'
            },
            set(val) {
                console.log('你当前想要修改这个 age 属性, 修改的值 是: ', val)
            }
        })
        // console.log(obj)
        // for (let k in obj) {
        //     console.log(k)
        // }
        console.log(obj.age)

        obj.age = 100


        // obj.age = 999
        // console.log(obj.age)

    </script>

2、数据劫持2

<body>
    <div class="box"></div>
    <script>
        const oBox = document.querySelector('.box');

        /*
            劫持一部分obj中的数据到res中去
        */

        // 定义数据劫持
        const obj = {
            name:'qqqq',
            age:18,
        }
        console.log('原始对象:' , obj);
        const res = {};

        Object.defineProperty(res , 'name' , {
            get(){
                return obj.name;
            },
            set(val){
                obj.name = val;
                oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;
            }
        })


        Object.defineProperty(res , 'age' , {
            get(){
                return obj.age;
            },
            set(val){
                // 将年龄重新赋值 将val只赋值给obj中的age
                obj.age = val;
                oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;
            }
        })


        console.log('数据劫持后的:' , res);
        console.log(res.name); // qqqq
        // 如果只劫持一个
        // console.log(res.age);  // undefined
        // 如果劫持两个
        console.log(res.age);     // 18

        // 修改其中的值
        res.name = 'wwwww';
        res.age = 222;
        console.log(res);
        console.log(res.name);
        console.log(res.age);

        // 将数据写入到页面中去
        oBox.innerHTML = `劫持后的数据为:${res.age} , ${res.name}`;

        // 写入到页面过后 再进行修改值
        // res里面的数据修改了  但是页面的数据没有修改  所以要在循环的时候  也就是set里面进行重新赋值写入
        res.name = 'hack';
        res.age = '100';
        console.log(res);

    </script>

3、封装数据劫持

<body>
    <input type="text" class="ipt">
    <div class="box"></div>
    <script>
        /*
            数据劫持
                自己将劫持的数据封装成函数 这个封装的函数在框架中是已经封装好了的
        */
        const oIpt = document.querySelector('.ipt');
        const oBox = document.querySelector('.box');

        const obj = {
            name: 'rose',
            age: 18,
        }
        console.log(obj);
        // 这里回调函数是调用fn函数
        function hiJack(origin, callback) {
            const res = {};
            for (let key in origin) {
                Object.defineProperty(res, key, {
                    get() {
                        // 返回原始对象中的每一个值
                        return origin[key];
                    },
                    set(val) {
                        // 设置里面的属性 将val值赋值给原始数组
                        origin[key] = val;
                        callback(res);
                    },
                })
            }

            callback(res);
            return res;
        }

        // 将写入页面的代码单独封装成一个 以便后面每次都要修改
        function fn(res) {
            oBox.innerHTML = `劫持后的数据为:${res.name} , ${res.age}`;
        }

        // 打印出定义的res
        const app = hiJack(obj , fn);

        oIpt.oninput = function(e){
            app.age = e.target.value;
        }

    </script>