柯里化函数:
编程思想,函数执行,产生一个闭包,(不被释放的上下文)把一些信息"预先存储起来",目的是提供下级上下文中调使用,这样的预先存储思想,就叫做柯理化函数编程思想
作用域 vs上下文:
- 都是函数执行,在栈内存中所分配出来的空间
- 创建函数的时候,在那个上下文中创建的,那么其作用域就是谁(作用域不是函数自己执行产生的这个空间,而是创建函数所在的这个空间)
- 而上下文是函数自己执行产生的==>函数执行产生的私有上下文,它的上级上下文是它的作用域
应用场景
const fn = function fn(...params) {
// params[1,2]
return function proxy(...args) {
// args[3]
return params.concat(args).reduce((x, item) => x + item)
}
}
let res = fn(1, 2)(3)//fn(1,2)-->proxy(3)
console.log(res);
了解:新版浏览器无法实现
add一直持续执行,执行几次是不确定的,我们最后需要把每一次执行的值累加求和
在浏览器没有生级之前console.log(函数)会把函数转换为字符串输出
函数(Symbol.toPrimitive)-->函数.valueof()--->函数.tostring,但凡其中有一项返回了对象的值则控制台以返回输出为主,但是升级之后,转换为字符串的操作还会触发,但是控制台最后呈现的还是函数
const curring = function curring() {
let params = [];
const add = (...args) => {
params = params.concat(args);
return add;
};
add[Symbol.toPrimitive] = () => params.reduce((result, item) => result + item);
return add;
};
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
函数式编程vs命令式编程
函数式编程:WHAT 把具体执行的步骤封装到一个函数中,后期需要处理的时候,只要把函数执行即可,我们不在关注执行的步骤,只关注后期处理结果
- 优点:低耦合好内聚,快捷化开发,方便维护
- 缺点:不能灵活掌握程序处理步骤,无法在某一个步骤做些特殊处理
命令式编程:HOW 更关注处理步骤,需要自己去实现每一步的操作
- 优点:灵活想怎么处理就怎么处理
- 缺点:代码冗余度高,开发效率慢
真实项目中推荐使用函数式编程
栗子
// forEach就是函数式编程,函数内部实现了对数组迭代的封装,每一次迭代都把回调函数执行,并且把当前迭代这一项及索引传递过来
let arr = [10, 20, 30]
arr.forEach((item, index) => {
console.log(item, index);
});
// 命令式编程
for (let i = 0; i < arr.length; i++) {
console.log(arr[i], i);
}
//封装forEach
Array.prototype.forEach = function (callback) {
//this--->要操作的数组
let self = this;
for (let i = 0; i < self.length; i++) {
callback(self[i], i)
}
}
面试题:foreach和for循环的区别
forEach是数组内置的方法,内部对数组迭代进行了封装,直接使用就可以 函数式编程,不支持以任何形式中断它的迭代;forEach内部依次迭代每一项,每一次迭代把传递的回调函数执行,把迭代的内容及其索引传递给回调函数..直到这个数字迭代完毕
for循环是命令式编程,需要我们自己手动实现数组迭代,可以中断,可以设置间隔几个迭代
在项目开发的时候一般应用的是forEach,这样可以提高我的开发效率,减少代码冗余,但是遇到一些灵活的代码需求,则自己基于for循环操作
组合函数
/*
在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
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) {
//funcs:数组,一次存储要执行的函数,而且从右到左的顺序执行
return function operate(x) {
let len = funcs.length
// x 函数执行的初始值
if (len === 0) return x;//如果不传初始值是啥返回啥
if (len === 1) return funs(0)(x)
return funcs.reduceRight((x, item) => {
return item(x)
}, x)
}
};
const operate = compose(div2, mul3, add1, add1)
console.log(operate(0));//=> 3 相当于div2(mul3(add1(add1(0))))
console.log(operate(2));//=>6
惰性函数:懒 执行一次可以搞定,决对不会执行第二次
获取元素样式:
- getComputedStyle() 方法用于获取指定元素的 CSS 样式。
- currentStyle ie678
- 获取的样式是元素在浏览器中最终渲染效果的样式。
封装兼容获取样式方法
瑕疵:浏览器没换,页面没关,第一次处理需要判断兼容性是必须的,但是第二次即以后执行,如果还是要判断兼容性是没必要的
var getCss = function (elem, attr) {
if (window.getComputedStyle) {
getCss = function (elem, attr) {
return window.getComputedStyle(elem)[attr];
}
} else {
getCss = function (elem, attr) {
return elem.currentStyle[attr]
};
}
return getCss(elem, attr)
}
console.log(getCss(document.body, "margin"));
console.log(getCss(document.body, "padding"));
,函数的防抖和节流
防抖
防抖:防止"老年帕金森",在用户频繁点击某项操作的时候,我们只识别一次{自定义频繁规则,自定义触发边界}
const submit = document.getElementById('submit')
//模拟服务器异步获取数据的操作
const query = callback => {
setTimeout(() => {
callback({
code: 0,
msg: "success"
})
}, 1000);
}
submit.onclick = function () {
console.log('click,start');
query(value => {
console.log(value);
})
}
简单版本的防抖处理
点击按钮执行回调函数获取数据,在上一次请求没有完成之前,在次点击按钮不在执行callback获取数据 设置标识
const submit = document.getElementById('submit')
let runing = false;//设置标识判断
//点击按钮执行回调函数获取数据,在上一次请求没有完成之前,在次点击按钮不在执行callback获取数据
submit.onclick = function () {
if (runing) return;//如果true 就不在执行
runing = true;//点击之后把标识改为true
submit.disabled = true;//禁用按钮
query(value => {
console.log(value);
runing = false;//当获取数据之后,改为false
submit.disabled = false//恢复按钮禁用
})
}
通用防抖方法的封装处理
思路:
- 假设:300ms内多次触发事件,算做频繁操作:
- 第一次点击;等待300ms 设置定时器(执行具体要做的事情)
- 第二次点击:...
- 每一次点击都设置一个定时器,等待300ms,300ms之后执行要做的事情
- 如果定时器还没有到时间,要做的事情还没有干(300m以内)在次触发这个事件,首先把之前设置的定时器清除掉,在重新设置一个新的
扫盲:如何清除定时器
- timer是个数字 记录第几个定时器 例如1 clearTimeout(1/2 timer)清除第几个定时器
- 问题:没有任何的方法直接让我们检测这个定时器是否被清除掉,
- 所以我们每次设置定时器的时候,一般会把timer设置为null,设置定时器timer是个数字 代表定时器存在,清除定时器我们把timer也设置为null了,那以后我们只要判断timer的值,只要这个值不是null,说明这个定时器没有被清除掉,如果为null是被清除掉的
timer = setTimeout(() => {
func();
}, wait);
代码
// submit.onclick =operate 疯狂点击,会触发operate函数,我们要做的事情就是保证func(真实要干的事情,只执行一次)
const clearTimer = function clearTimer(timer) {
if (timer !== null) clearTimeout(timer)
return null
}
//不传:debounce(函数)-->debounce(函数,300,false)
// debounce(函数, 300)-->debounce(函数,300,false)
// debounce(函数,true)-->debounce(函数,300,false)
const debounce = function debounce(func, wait, immediate) {
//func要做的事情 wait时间 事件绑定一定是函数
if (typeof func !== "function") throw new TypeError('func is not a function!')
if (typeof wait === "boolean") { immediate = wait; console.log(wait); };
if (typeof wait !== "number") { wait = 300; console.log(wait); }
if (typeof immediate !== "boolean") immediate = false;
let timer = null;
return function operate(...params) {
let now = !timer && immediate;
//清除之前设置的定时器,this-->submit
timer = clearTimer(timer)
timer = setTimeout(() => {
console.log(timer);
// 清除最后一次设置的定时器
timer = clearTimer(timer)
// 设置在结束边界触发
if (!immediate) func.apply(this, params)
}, wait);
// 设置开始边界触发
if (now) {
console.log('开始边界', now); return func.apply(this, params)
}
}
}
//模拟服务器异步获取数据的操作
const query = callback => {
setTimeout(() => {
callback({
code: 0,
msg: "success"
})
}, 1000);
}
const submit = document.getElementById('submit')
submit.onclick = debounce(function (ev) {
console.log("click start", ev);
console.log(this);
query(value => {
console.log(value);
})
})
节流
window.onscroll//监听浏览器滚动条事件(默认触发频率 按照浏览器最快反应时间)(谷歌5-7ms触发,这样的触发频率太快了,非常消耗性能)
节流:'降频',在用户频繁点击某项操作的时候,我们降低默认的触发频率
思路window.οnscrοll=throttle 滚动过程中,operate函数会间隔 5ms触发一次,而我们编写throttle方法的目的,是operate执行的过程中,控制要真正执行的func,间隔300ms触发一次
- 第一次滚动,记录当前触发的时间,@A
- 间隔5ms第二次触发的时候,拿当前触发时间@A,和300做对比,发现还有295ms才应该去做func这件事,设置一个定时器
- 第三次及其以后触发,如果发现目前已经有一个等待执行的定时器,就啥也不干即可...,
- 直到上一次设置的定时器已经执行(两次间隔的时间超过300了),定时器已经被清掉了,此时我们在次设置一个定时器,还是让其等待300ms之后执行
- 特殊情况 发现两次时间差超过300,此时立即触发
代码
const clearTimer = function clearTimer(timer) {
if (timer !== null) clearTimeout(timer)
return null
}
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError('func is not a function!')
if (typeof wait === "boolean") { immediate = wait };
let timer = null,
previous = 0;//记录上一次触发的时间
return function operate(...params) {
let now = +new Date(),//当前时间
remaining = wait - (now - previous)//两次触发的间隔时间跟300做对比
if (remaining <= 0) {
//说明两次操作的间隔时间大于300的,此时立即触发执行
timer = clearTimer(timer)
previous = +new Date;//把触发时间记录下来
func.apply(this, params)
}
//间隔时间差不足300,如果还没有设置过定时器,则直接设置定时等着执行,如果已经设置过定时器啥事都不用干
if (!timer) {
timer = setTimeout(() => {
//清除定时器,保证每次定时器执行完为null
timer = clearTimer(timer)
previous = +new Date;//把触发时间记录下来
func.apply(this, params)
}, remaining)
}
}
}
window.onscroll = throttle(function (ev) {
console.log("scrolling");
}, 300)