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

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);
}
原文地址:https://www.cnblogs.com/SiriusZHT/p/14521327.html