柯理化

103 阅读6分钟

柯理化

形成一个闭包(不被释放的上下文),我们就可以“预先”存储一些东西,而这些东西可以供其下级上下文“后期”调取使用 ->柯理化函数思想「预先存储的思想」

面试题1

实现fn

let res = fn(1,2)(3);
console.log(res);//6

解法

const fn = function(...params){
  return  function proxy(...args){
    params = params.concat(args)
    return params.reduce((res, item)=>res+item)
  }
}

//数组

数组求和

  • 命令式

关注如何做HOW,可以管控过程

优点:灵活把握过程中的每一步

弊端:代码冗余,写起来相对麻烦

let arr = [1,2,3,5]
let sum = 0;
for(let i=0;i<arr.length;i++){
  sum += arr[i]
}
  • 函数式【推荐】

关注结果WHAT, 把处理的过程封装为一个函数,后期直接执行函数拿到想要的结果即可

优势:封装性强,减少冗余代码。提高开发效率

弊端:不能把控每一个细节的操作步骤,不灵活

let arr = [1,2,3,5]
let sum = 0;
arr.forEach(item=>{
	sum += item
})
  • eval法
let arr = [1,2,3,5];
let sum = 0;
sum = eval(arr.join('+'))

reduce

数组中常用的迭代方法:forEach、map、filter、find、findIndex、every、some、reduce、reduceRight...

reduce实现数组的迭代,可以完成结果的累计

  • reduce第二个参数 传递
let arr = [10,20,30,40]
// 第一轮 result 10 从数组第二项开始迭代 item=20 index=1 回调函数返回值:30
// let res = arr.reduce((result,item,index)=>{});
// 第二轮 result 30(获取上一轮回调函数执行的返回结果) item=30 index=2 回调函数返回值:60
// 第二轮 result 60(获取上一轮回调函数执行的返回结果) item=40 index=3 回调函数返回值:100
// res等于最后返回的值 100
// reduce 就是把上一轮迭代返回的值返回给下一轮迭代
let res = arr.reduce((result,item,index)=>{
  return result+item
});
  • reduce第二个参数传递
// 第一轮 result 0 reduce第二个参数传递的值就是给result的初始值,如果不传,会把数组的第一项作为初始值,接下来从数组第一项开始迭代 item=10 index=0
let res = arr.reduce((result,item,index)=>{
  return result+item
}, 0);

面试题2

实现curring

需要指定执行次数

let add = curring(count);
let res = add(1)(2)(3);
console.log(+res); //->6

add = curring(count);
res = add(1, 2, 3)(4);
console.log(+res); //->10

add = curring(count);
res = add(1)(2)(3)(4)(5);
console.log(+res); //->15

解法

const curring = function curring(count) {
    let params = [],
        n = 0;//记录n执行次数
    const add = (...args) => {
        params = params.concat(args);
        n++;
        if (n >= count) {
            // 求和
            return params.reduce((result, item) => result + item);
        }
        return add;
    };
    return add;
};

不定执行次数

实现curring

难点:执行次数不确定,可以无限套娃

let add = curring();
let res = add(1)(2)(3);
console.log(+res); //->6

add = curring();
res = add(1, 2, 3)(4);
console.log(+res); //->10

add = curring();
res = add(1)(2)(3)(4)(5);
console.log(+res); //->15

前提知识:

const fn = function fn(){};
alert(fn);

执行过程:

把fn变为字符串,再输出,隐式转换String(fn): 先执行Symbol.toPrimitive,若没有,就执行valueOf获取原始值,若没有就执行toString获取原始值

const fn = function fn(){};
console.log(fn);

console.log(fn);也是要把fn变为字符串再输出,控制台会在字符串前面加一个f,代表这就是函数字符串。

const fn = function fn(x, y){
	return x+y
};
fn[Symbol.toPrimitive] = function(hint){
	return '10'
}
alert(fn)//'10'
const fn = function fn(x, y){
	return x+y
};
fn[Symbol.toPrimitive] = function(hint){
	return '10'
}
console.log(fn)

结果:

从浏览器打印结果发现console.log(fn);不是'10',而是函数字符串 「新版谷歌浏览器修改了机制:log输出也会执行Symbol.toPrimitive等方法,但是不论方法最后返回啥,还是输出的函数字符串」

可以执行console.log(fn+'')就会输出'10'

解法

const curring = function curring() {
    let params = [];
    const add = (...args) => {
        // 每一次执行函数都把传递的值存储起来
        params = params.concat(args);
        // 每一次执行函数都返回add,这样就可以一直无限次执行下去了
        return add;
    };
    // 每一次输出add函数或者拿着个函数进行运算,都需要先把其转换为字符串/数字
    add[Symbol.toPrimitive] = () => {
        // 把每一次传递的值进行求和
        return params.reduce((result, item) => result + item);
    };
    return add;
};

柯理化应用

compose函数

在函数式编程当中有一个重要概念:函数组合,实际上就是把处理数据的函数像管道一样连接起来,然后让数据穿过管道得到最终的结果。

例如:

const add1 = (x) => x+1;
const mul3 = (x) => x*3;
const div2 = (x) => x/2;
div2(mul3(add1(add1(0))));//3

而这样的写法可读性明显比较差,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:

const operate = compose(div2, mul3, add1, add1)
operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
operate(2) //=>相当于div2(mul3(add1(add1(2))))

简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x)。

面试题(阿里)

请完成 compose函数的编写

const add1 = x => x + 1;
const mul3 = x => x * 3;
const div2 = x => x / 2;
const compose = function compose(...funcs) {};
console.log(compose(div2, mul3, add1, add1)(0));//3
console.log(compose()(0));//0
console.log(compose(add1)(0));//1
const compose = function compose(...funcs) {
    let len = funcs.length;
    if (len === 0) return x => x;
    if (len === 1) return funcs[0];//可以不要这行程序
    return function operate(x) {
        return funcs.reduceRight((result, item) => {
            return typeof item === "function" ? item(result) : result;
        }, x);
    };
};

redux的compose源码

npm install redux

//redux->src->compose.js
export default function compose(...funcs) {
  if (funcs.length === 0) {//不传函数,传啥返回啥
    return (arg) => arg
  }

  if (funcs.length === 1) {//只传一个函数,就直接执行,返回结果
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
  /*
  return funcs.reduce((a, b) => {
     return x =>{
         return a(b(x))
     }
   })
  */
}

源码解析

这个源码是先将compose(div2, mul3, add1, add1)(0)转换成div2(mul3(add1(add1(0)))),再执行,返回结果

// funcs:[div2, mul3,add1,add1]
//a:div2 b:mul3 -> x=>a(b(x))即x=>div2(mul3(x))
//a:x=>div2(mul3(x)) b:add1  -> x=>a(b(x))即x=>a(add1(x)) -> x=>div2(mul3(add1(x))) 
//a: x=>div2(mul3(add1(x))) b:add1 -> x=>a(b(x))即x=>a(add1(x)) -> x=>div2(mul3(add1(add1(x)))) 
return funcs.reduce((a, b) => {
   return x =>{
       return a(b(x))
        }
    }
)

而上面写的代码,是执行一个函数,存储返回值,再执行下一个函数,再存储值,再依次往下,直到传入的所有函数都执行完毕

 return function operate(x) {
        return funcs.reduceRight((result, item) => {
            return typeof item === "function" ? item(result) : result;
        }, x);
    };

由于源码的compose函数最终执行之前每个函数的上下文都不能释放,而且阅读性也比较差,性能上来看,自己实现的要更好些。

惰性思想—懒

就是一次能搞定,就不会搞两次

举个例子

获取元素的样式

window.getComputedStyle([element],[?伪类])

获取当前元素所有经过浏览器计算的样式对象「但凡这个元素经过浏览器渲染了,所有的样式都可以获取到{含:自己写的、浏览器默认的...}」

getComputedStyle(box).width 不兼容IE6~8,在低版本浏览器中,需要使用 box.currentStyle.width

//初步实现
const css = function css(elem, attr) {
    if ('getComputedStyle' in window) { // in 检测某个属性是不是对象的属性「不论私有还是公有」
        return getComputedStyle(elem)[attr];
    }
    return elem.currentStyle[attr];//兼容IE6~8
}
console.log(getCss(document.body, 'width'));
console.log(getCss(document.body, 'margin'));
console.log(getCss(document.body, 'padding'));

每次执行getCss都要进行校验,很明显不必要。可以利用JS的惰性思想进行改写

function getCss(element, attr) {
    // 第一次执行,根据兼容情况,重构getCss函数
    if (window.getComputedStyle) {
        getCss = function (element, attr) {
            return window.getComputedStyle(element)[attr];
        };
    } else {
        getCss = function (element, attr) {
            return element.currentStyle[attr];
        };
    }
    // 第一次把重构的函数执行一次,获取对应的结果
    return getCss(element, attr);
}