• 【面筋烧烤手册】函数柯里化延伸的知识点


    1、递归

    一个函数调用同一个函数
    1. 自己调用自己
    2. 因为自己调用自己会出现无限死循环 所以还需要设置一个停止条件
    3. 递归永远表现的是树形结构 => 递归树
    4. 最先调用的函数 最后执行完毕 最后调用的函数 最先执行完毕

    // 阶乘
    // 5! = 5 * 4 * 3 * 2 * 1 => fn(4) * 5 => fn(n-1) * n
    // 4! =     4 * 3 * 2 * 1 => fn(3) * 4 => fn(n-1) * n
    // 3! =         3 * 2 * 1 => fn(2) * 3 => fn(n-1) * n
    // 2! =             2 * 1 => fn(1) * 2 => fn(n-1) * n
    // 1! =                 1 => 1
    function fn(n) {
      if (n === 1) return 1;
      return n * fn(n - 1);
    }
    console.log(fn(5));
    

    推导出递归公式就行

    2、作用域

    JavaScript有几个作用域

    • global scope 全局作用域
    • local scope 本地作用域&函数作用域
    • block scope 块级作用域(ES6)

    作用域常见需求1

    现在我们有一个需求 定义一个函数 依次输出1 2 3
    这个往往是初学者的想法 初学者往往会习惯性的定义大量的全局变量

      1. 如果又有另外一个变量叫i 会不会出现变量冲突
      2. 如果别的作用域想要修改我这个作用域的i 那他是不是能轻易的修改
    
    // 全局变量污染
    let i = 1;
    function show() {
      console.log(i++);
    }
    show();
    show();
    show();
    

    作用域常见需求2

    这里每次调用都重新创建了一个i

    function show() {
      let i = 1;
      console.log(i++);
    }
    show(); //1
    show(); //1
    show(); //1
    

    3、闭包

    因为我要求你的变量 尽量是局部变量
    因为全局变量会污染全局作用域 但是又要求i有持久化的功能

    局部变量 记忆变量

    • 闭包
    • 闭包概念: 形成一个[不被销毁]的[私有作用域]
    • 闭包作用: 保存变量[不被销毁] 保护变量[私有作用域]
    • 闭包形式: 函数嵌套函数,且返回一个堆内存
    function outer() {
      let a = 1;
      function inner() {
        console.log(a++);
      }
      inner();
      inner();
      inner();
    }
    outer();
    

    外部闭包

    function outer() {
      //定义一个局部变量
      let i = 1;
    
      //定义一个内部函数
      function inner() {
        console.log(i);
        i = i + 1;
      }
    
      //返回内部函数体/堆内存
      return inner;
    }
    
    //1. 开辟一个outer作用域
    //2. 将inner函数体赋值给 add
    let add = outer();
    add(); //1
    add(); //2
    add(); //3
    
    outer()();//1
    outer()();//1
    outer()();//1
    

    4、偏函数

    let show = (greetstr, username) => `${username}, ${greetstr}!`;
    
    let show1 = show.bind(null, "欢迎来到京北商城");
    
    console.log(show1("张三"));
    console.log(show1("李四"));
    console.log(show1("王五"));
    
    let outer = (greetstr) => (username) => `${username}, ${greetstr}`;
    //let outer = (greetstr) => {
    //  return (username) => {
    //    return `${username}, ${greetstr}`;
    //  };
    //};
    
    let jbshop = outer("欢迎来到京北商城"); //预定义函数(偏函数)
    
    console.log(jbshop("张三"));
    console.log(jbshop("李四"));
    console.log(jbshop("王五"));
    

    5、柯里化函数思想

    //时间节点1 收集了一些参数 时间节点2 收集了一些参数
    //args1 = [1, 2, 3]
    //args2 = ["a", "b", "c"]
    //concat
    let outer = (...args1) => {
      return (...args2) => {
        //...args1 = ...[1, 2, 3] => 1,2,3
        //...args2 = ...["a", "b", "c"] => "a", "b", "c"
        //[1,2,3,"a", "b", "c"]
        return [...args1, ...args2];
      };
    };
    
    let outer1 = 1;
    let outer2 = 2;
    let outer3 = 3;
    
    let inner1 = "a";
    let inner2 = "b";
    let inner3 = "c";
    
    //[1,2,3,a,b,c]
    console.log(outer(outer1, outer2, outer3)(inner1, inner2, inner3));
    

    6、add面试题(两个括号解决)

    //请编写一个add求和函数 要求实现以下效果
    // add(1)  //1
    // add(1, 2) //3
    // add(1, 2, 3)  //6
    // add(1,2)(3)  //6
    // add(1, 2)(3, 4)  //10
    // add(1, 2)(3, 4)(5, 6, 7)  //28
    // add(1, 2)(3)(4, 5)  //15
    
    // 一个小括号
    // function add(...arr) {
    //   let result = 0;
    //   for (let i = 0; i < arr.length; i++) {
    //     result += arr[i];
    //   }
    //   return result;
    // }
    
    // 两个小括号
    // 这里的返回值必定要是一个函数
    // add(1,2)(3)  //6
    // 刚好有且仅有两个小括号的情况
    function add(...arr) {
      return function (...arr2) {
        // console.log(arr, arr2);
        let allArr = [...arr, ...arr2]; //[1, 2, 3]
        let result = 0;
        for (let i = 0; i < allArr.length; i++) {
          result += allArr[i];
        }
        return result;
      };
    }
    //add(1, 2)(3)()()()()()()
    //add(1, 2)
    // console.log(add(1, 2));
    console.log(add(1, 2)(3));
    

    7、toString 和 valueOf

    • 如果涉及到内容输出 打印内容 console.log() alert() 或者涉及到 计算 + ++ -
    • 都会自动调用valueOf toString()
    • console.log() 就会调用valueOf和toString
    let show = () => {
    	return "我是一个函数";
    };
    //自动调用
    //toString
    //valueOf
    Function.prototype.toString = function () {
      console.log("toString被调用");
    };
    // Function.prototype.valueOf = function () {
    //   console.log("valueOf被调用");
    // };
    //一定不会被输出
    //他们俩有区别吗?
    console.log(show());
    

    区别

    1. valueOf应用于运算,toString应用于显示.

    2. 在进行对象转换成字符串时(例如:alert(test)),将优先调用 toString,如果没有 toString 方法了,就调用 valueOf方法,如果tostring 和 valueOf都没重写,就按照 Object的toString 方法输出.

    3. 在进行强转字符串类型时将优先调用toString方法,强转为数字时优先调用valueOf。

    4. 在有运算操作符的情况下,valueOf的优先级高于toString。

    8、add函数添加停止条件

    • 停止条件返回值也是一个函数
    // add(1, 2, 3)(4, 5)(6, 7);
    // console.log() 会自动触发toString方法
    // add(1, 2, 3)(4, 5)(6, 7) => add(1, 2, 3, 4, 5)(6, 7) => add(1, 2, 3, 4, 5, 6, 7)
    
    // add(1, 2, 3)(4, 5)(6, 7);
    // add(1, 2, 3)(4, 5) => inner(4, 5) => add(1, 2, 3, 4, 5)
    // add(1, 2, 3, 4, 5)(6, 7) => inner(6, 7) => add(1, 2, 3, 4, 5, 6, 7) => inner
    // 停止条件
    function add(...outerArgs) {
      inner.toString = function () {
        //处理参数
      };
      function inner(...innerArgs) {
        return add(...outerArgs, ...innerArgs);
      }
      return inner;
    }
    console.log(add(1, 2, 3)(4, 5)(6, 7));
    // console.log(add(1, 2, 3, 4, 5, 6, 7));
    

    9、add解决

    //1. 调用add([1, 2, 3]) 记住了[1, 2, 3]
    //7. 又执行了add([1, 2, 3, 4, 5])
    //12. 再次执行add([1, 2, 3, 4, 5, 6, 7])
    function add(...outerArgs) {
      //2. 为inner绑定一个本地方法叫toString()
      //8. 为inner绑定一个本地方法叫toString()
      //13. 在打印inner函数体的那一瞬间 toString方法会自动调用 在调用的瞬间 对所有的参数进行粘合
      inner.toString = function () {
        //求和
        let result = 0;
        for (let i = 0; i < outerArgs.length; i++) {
          result += outerArgs[i];
        }
        return result;
      };
      //3. 初始化一个inner函数 同时接收参数
      //9. 初始化一个inner函数 同时接收参数
      function inner(...innerArgs) {
        //6.返回add函数 add(...[1, 2, 3], ...[4, 5])
        //6.返回add函数 add(1, 2, 3, 4, 5)
        //11. 返回新的add函数 add(1, 2, 3, 4, 5, 6, 7)
        return add(...outerArgs, ...innerArgs);
      }
      return inner;
    }
    //4. 返回inner函数 形成一个闭包 inner(4, 5)(6, 7)
    //5. 执行inner([4, 5])
    //10. 又返回inner函数 形成一个闭包 inner(6, 7)
    //5. 执行inner([6, 7])
    //14. 输出inner这个函数的字符串表现形式
    console.log(add(1)); //1
    console.log(add(1, 2)); //3
    console.log(add(1, 2, 3)); //6
    console.log(add(1, 2)(3)); //6
    console.log(add(1, 2)(3, 4)); //10
    console.log(add(1, 2)(3, 4)(5, 6, 7)); //28
    console.log(add(1, 2)(3)(4, 5)); //15
    

    10、add简化版

    let add = (...outerArgs) => {
      //1. Accumulator acc 累加器
      //2. Current Value cur 当前值
      //3. Current index idx 当前下标
      //4. Source Array ary 源数组
      inner.toString = () => outerArgs.reduce((acc, cur) => acc + cur);
      // let result = 0;
      // for (let i = 0; i < outerArgs.length; i++) {
      //   result += outerArgs[i];
      // }
      // return result;
      let inner = (...innerArgs) => add(...outerArgs, ...innerArgs);
      return inner;
    };
    console.log(add(1)); //1
    console.log(add(1, 2)); //3
    console.log(add(1, 2, 3)); //6
    console.log(add(1, 2)(3)); //6
    console.log(add(1, 2)(3, 4)); //10
    console.log(add(1, 2)(3, 4)(5, 6, 7)); //28
    console.log(add(1, 2)(3)(4, 5)); //15
    

    11、reduce总结

    1. Accumulator acc 累加器
    2. Current Value cur 当前值
    3. Current index idx 当前下标
    4. Source Array ary 源数组

    手写reduce

    function reduce(arr, reduceCallback, initialValue){
    	//首先,检查传递的参数是否正确
    	if(!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function'){ return []; }
    	else{
    		//如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initial Value
    		let hasInitialValue = initialValue !== undefined;
    		let value = hasInitialValue ? initialValue : arr[0];
    	}
    	//如果有传递inisialValue,则索引从1开始,否则从0开始
    	for(let i = hasInitialValue ? 1 : 0,len = arr.length; i < len; i
    	++){
    		value = reduceCallback(value, arr[i], i, arr);
    	}
    	return value;
    }
    

    12、函数柯里化

    function curry(fn, args){
        //获取函数需要的参数长度
        let length = fn.length;
        args = args || [];
        return function(){
            let subArgs = args.slice(0);
            //拼接所有参数,不仅是curry的,还有fn的
            for(let i = 0; i < arguments.length; i++){
                subArgs.push(arguments[i]);
            }
            //判断参数的长度是否已经满足函数所需参数的长度
            if(subArgs.length >= length){
                //如果满足,执行函数
                return fn.apply(this, subArgs);
            }else{
                //如果不满足,递归返回柯里化的函数,等待参数的传入
                return curry.call(this, fn, subArgs);
            }
        };
    }
    
    //es6实现
    function curry(fn, ...args){
        return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
    }
    
  • 相关阅读:
    Eclipse中SVN插件的安装方式
    Javascript实现DIV滚动自动滚动到底部
    Android程序开发的环境配置
    Android程序开发基础之——页面布局
    TinyMCE使用手册
    PHP中使用mktime获取时间戳的一个黑色幽默
    VS11本地IIS调试时(URL不使用虚拟目录,直接用localhost)
    [VS扩展工具] Image Optimizer(图像优化压缩)
    [程序安装包制作] Advanced Installer 备忘
    安装VS2010 SP1时遇到WCF RIA Service 版本错误
  • 原文地址:https://www.cnblogs.com/SiriusZHT/p/14521327.html
Copyright © 2020-2023  润新知