【面筋烧烤手册】函数柯里化延伸的知识点
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());
区别
-
valueOf应用于运算,toString应用于显示.
-
在进行对象转换成字符串时(例如:alert(test)),将优先调用 toString,如果没有 toString 方法了,就调用 valueOf方法,如果tostring 和 valueOf都没重写,就按照 Object的toString 方法输出.
-
在进行强转字符串类型时将优先调用toString方法,强转为数字时优先调用valueOf。
-
在有运算操作符的情况下,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总结
- Accumulator acc 累加器
- Current Value cur 当前值
- Current index idx 当前下标
- 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);
}