文章已同步至掘金:https://juejin.cn/post/6844903921173921799
欢迎访问😃,有任何问题都可留言评论哦~
高阶函数是指至少满足下列条件之一的函数:
- 函数可以作为参数被传递
- 函数可以作为返回值输出
其实我们在平常撸代码的时候,会不知不觉使用到高阶函数
比如:数组中常用的方法:map
、reduce
、filter
、sort
等等,好多都是高阶函数,(想了解数组中常用的方法,请参考我的文章《JS 数组中常用方法总结》)
这里就不再说这些方法了,没什么意思!!!
作为参数传递
先给一个简单的吧!!!
function func1(theFunc){
theFunc();
}
function func2(){
alert("ok");
}
func1(func2);
就是这样的,把 func2 当做 func1 的参数传递
其实说起这个,最先想到的就是回调函数
提起回调,最经典的就是 Ajax 的异步请求
给出一段代码,看一哈:
// callback为待传入的回调函数
var getUserInfo = function(userId, callback) {
$.ajax("http://xxx.com/getUserInfo?" + userId, function(data) {
if (typeof callback === "function") {
callback(data);
}
});
}
getUserInfo(666666, function(data) {
alert (data.userName);
});
作为返回值输出
给出一个简单的例子看一下就好了
function a(){
alert('aa')
return function(){
alert ('bb')
}
}
newFun=a();
newFun();//aa bb
实现AOP
其实上面的都没什么好说的,下面的才是重点
通常,在JS中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中,具体的实现技术有很多,下面说几个方法,还是比较有难度的,
好好琢磨一下
before函数
before函数意思就是在某个函数执行前执行某个函数(什么某个某个,听不懂)
例子:
在 f1函数 执行前,执行 f2函数
const f1 = ()=>{
console.log("正在执行任务...");
}
Function.prototype.before = function(beforeFn){
return()=>{
beforeFn()
this()
}
}
const f2 = f1.before(()=>{
console.log("开始...");
})
f2()
还可以传参
//...args就是接收传递过来的所有参数
const f1 = (...args)=>{
console.log("正在执行任务",args);
}
Function.prototype.before = function(beforeFn){
return (...args)=>{ //...args 叫rest参数
beforeFn()
this(...args)
}
}
const f2 = f1.before(()=>{
console.log("开始...");
})
f2(1,2,3)
after函数
不知道怎么描述,看例子:
调用一个函数3次后,触发另一个函数
const after = (times,fn)=>{
return ()=>{
if(--times === 0){
fn()
}
}
}
const f = after(3,()=>{
console.log("调用三次后执行...");
})
f()
f()
f()
假如只调用了两次,那么是不会执行 f函数 的
all函数
以读取文件为例,在当前目录下新建两个文件name.txt
、age.txt
在name.txt
里写入内容:xiaoming
在age.txt
里写入内容:20
all函数,顾名思义,就是把所有文件读取成功,那么才可以,如果有一个读取不成功,那么就不行
下面给出两种实现方法,上代码:
//第一种
const fs = require("fs")
let content = []
let index = 0
fs.readFile("name.txt","utf8",(err,data)=>{
content[0]=data
index++
out()
})
fs.readFile("age.txt","utf8",(err,data)=>{
content[1]=data
index++
out()
})
function out(){
if(index == 2){
console.log(content); //[ 'xiaoming' , '20' ]
}
}
//第二种
const fs = require("fs")
let content = {}
const after = (times,fn)=>{
return ()=>{
if(--times === 0){
fn()
}
}
}
let newAfter = after(2,()=>{
console.log(content); //{ name: 'xiaoming', age: '20' }
})
fs.readFile("name.txt","utf8",(err,data)=>{
content['name'] = data
newAfter()
})
fs.readFile("age.txt","utf8",(err,data)=>{
content['age'] = data
newAfter()
})
包括函数
在执行某个函数之前执行若干个函数,在执行某个函数之后执行若干个函数,事务函数
例子:
let f1 = function(){
console.log("正在执行任务...")
}
let wrappers = [
{
// warpper
init(){
console.log("hello 1")
},
close(){
console.log("bye 1")
}
},
{
// warpper
init(){
console.log("hello 2")
},
close(){
console.log("bye 2")
}
}
]
// 定义一个函数,把上面两者结合起来
const work = (core,wrappers)=>{
// core是核心函数
wrappers.forEach(wrap=>{
wrap.init()
})
core()
wrappers.forEach(wrap=>{
wrap.close()
})
}
work(f1,wrappers)
这个就到此为止了
柯里化函数(currying)
函数柯里化,又称部分求值,
一个currying的函数首先会接收一些参数,接收了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数
刚才传入的参数在函数形成的闭包中被保存起来。
转换成代码大致就这样:
f(1,2,3) --> f(1)(2)(3)()
举一个例子:
实现两个数相加
//普通函数写法
function add(x,y){
return x+y;
}
add(1,2) //3
//实现柯里化
let add = function(x){
return function(y){
return x + y;
}
};
add(1)(2); //3
通用的柯里化函数
function curry(fn) {
let slice = Array.prototype.slice, // 将slice缓存起来
args = slice.call(arguments, 1); // 这里将arguments转成数组并保存
return function() {
// 将新旧的参数拼接起来
let newArgs = args.concat(slice.call(arguments));
return fn.apply(null, newArgs); // 返回执行的fn并传递最新的参数
}
}
ES6版的柯里化函数
function curry(fn) {
const g = (...allArgs) => allArgs.length >= fn.length ?
fn(...allArgs) :
(...args) => g(...allArgs, ...args)
return g;
}
// 测试用例
const foo = curry((a, b, c, d) => {
console.log(a, b, c, d);
});
foo(1)(2)(3)(4); // 1 2 3 4
const f = foo(1)(2)(3);
f(5); // 1 2 3 5
两者实现的思路不同,可以尝试着分析一下
反柯里化函数(uncurrying)
与柯里化相反
- 柯里化是为了缩小适用范围,创建一个针对性更强的函数;
- 反柯里化则是扩大适用范围,创建一个应用范围更广的函数。
对应的代码转换就变成这样。
fn(1)(2)(3)() --> fn(1,2,3)
所以说,一个对象也未必只能使用它自身的方法,就比如call
和apply
都可以完成这个需求,因为用call
和apply
可以把任意对象当作this
传入某个方法,这样一来,方法中用到this
的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性。
而uncurrying
的目的是将泛化this
的过程提取出来,将fn.call
或者fn.apply
抽象成通用的函数。
实现反柯里化
Function.prototype.uncurrying = function() {
let self = this;
return function() {
return Function.prototype.call.apply(self, arguments);
}
};
// 将Array.prototype.push进行uncurrying,此时push函数的作用就跟Array.prototype.push一样了,且不仅仅局限于只能操作array对象。
let push = Array.prototype.push.uncurrying();
let obj = {
"length": 1,
"0": 1
};
push(obj, 2);
console.log(obj); // 输出:{0: 1, 1: 2, length: 2}
分时函数
当一次的用户操作会严重地影响页面性能,如在短时间内往页面中大量添加DOM节点显然也会让浏览器吃不消,结果往往就是浏览器的卡顿甚至假死。
这个问题可以用timeChunk
函数解决
timeChunk
函数可以让创建节点的工作分批进行,比如把1秒钟创建1000个节点,改为每隔200毫秒创建8个节点。
timeChunk
函数接受3个参数:
- 第1个参数是创建节点时需要用到的数据
- 第2个参数是封装了创建节点逻辑的函数
- 第3个参数表示每一批创建的节点数量
let timeChunk = function(ary, fn, count) {
let t;
let start = function() {
for ( let i = 0; i < Math.min( count || 1, ary.length ); i++ ){
let obj = ary.shift();
fn( obj );
}
};
return function() {
t = setInterval(function() {
if (ary.length === 0) { // 如果全部节点都已经被创建好
return clearInterval(t);
}
start();
}, 200); // 分批执行的时间间隔,也可以用参数的形式传入
};
};
函数分流
当一个函数被频繁调用时,就可能会造成很大的性能问题
这个时候可以考虑函数节流,降低函数被调用的频率。
throttle
函数的原理是,将即将被执行的函数用setTimeout
延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来调用该函数的请求。
throttle
函数接受2个参数:
- 第一个参数为需要被延迟执行的函数
- 第二个参数为延迟执行的时间
let throttle = function(fn, interval) {
let __self = fn, // 保存需要被延迟执行的函数引用
timer, // 定时器
firstTime = true; // 是否是第一次调用
return function() {
let args = arguments,
__me = this;
if (firstTime) { // 如果是第一次调用,不需延迟执行
__self.apply(__me, args);
return firstTime = false;
}
if (timer) { // 如果定时器还在,说明前一次延迟执行还没有完成
return false;
}
timer = setTimeout(function() { // 延迟一段时间执行
clearTimeout(timer);
timer = null;
__self.apply(__me, args);
}, interval || 500 );
};
};
window.onresize = throttle(function() {
console.log(1);
}, 500 );
惰性加载函数
个人认为就是不同浏览器之间有相同的事情
在平常开发中,因为浏览器之间的实现差异,一些嗅探工作总是不可避免
比如我们需要一个在各个浏览器中能够通用的事件绑定函数addEvent
常见写法:
方法一:
let addEvent = function(elem, type, handler) {
if (window.addEventListener) {
return elem.addEventListener(type, handler, false);
}
if (window.attachEvent) {
return elem.attachEvent('on' + type, handler);
}
};
缺点:当它每次被调用的时候都会执行里面的if条件分支,虽然执行这些if分支的开销不算大,但也许有一些方法可以让程序避免这些重复的执行过程。
方法二:
let addEvent = (function() {
if (window.addEventListener) {
return function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
}
if (window.attachEvent) {
return function(elem, type, handler) {
elem.attachEvent('on' + type, handler);
}
}
})();
缺点:也许我们从头到尾都没有使用过addEvent函数,这样看来,一开始的浏览器嗅探就是完全多余的操作,而且这也会稍稍延长页面ready的时间。
方法三:
let addEvent = function(elem, type, handler) {
if (window.addEventListener) {
addEvent = function(elem, type, handler) {
elem.addEventListener(type, handler, false);
}
} else if (window.attachEvent) {
addEvent = function(elem, type, handler) {
elem.attachEvent('on' + type, handler);
}
}
addEvent(elem, type, handler);
};
此时addEvent
依然被声明为一个普通函数,在函数里依然有一些分支判断。
但是在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的addEvent
函数,
在下一次进入addEvent
函数的时候,addEvent
函数里不再存在条件分支语句。
评论区