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