《javascript函数类型介绍》
# 普通函数
var sum = function (num1, num2) {
return num1 + num2
}
sum(10, 10) // 20
2
3
4
# 箭头函数
Es6新增
var sum = (num1, num2) => {
return num1 + num2
}
sum(10, 10) // 20
2
3
4
# 递归函数
递归函数:直接或间接调用函数本身,也会被叫作自调用函数
// 经典阶乘函数
function fun(num){
if (num <= 1){
return 1;
} else {
return num * fun(num-1);
}
}
fun(10)
2
3
4
5
6
7
8
9
10
# 回调函数
回调函数就是一个通过函数指针调用的函数。就是我们经常说的callback
function getdata(callback){
setTimeout(function(){ // 模拟异步
var info = {
"id":1,
"name":'张三'
}
callback(info) // 回调函数
},1000)
}
2
3
4
5
6
7
8
9
# 嵌套函数
顾名思义,就是函数内嵌套函数
function isSumLess(arrA, arrB){
function sum(arr){
var s=0;
for(var i=0; i<arr.length; i++)
s += arr[i]
return s
}
return sum(arrA) < sum(arrB)
}
2
3
4
5
6
7
8
9
# 匿名函数
匿名函数有两种用法:
- 赋值
- 自我执行
单独运行一个匿名函数会报错
// 语法错误
function (num1, num2) {
return num1 + num2
}
2
3
4
修改成自执行函数正常运行
(function (num1, num2) {
return num1 + num2
})(1, 2)
// 3
2
3
4
5
作用
- 匿名函数可以实现闭包。
- 模拟块级作用域,减少全局变量。
应用场景
- 赋值给事件
windon.onload = function(){
console.log('hello');
};
2
3
- 函数表达式
var show = function(){
console.log('hello');
};
show()
2
3
4
- 对象方法
var obj={
fn:function(){
console.log('hi');
}
};
console.log(obj.fn()); // hi
2
3
4
5
6
7
- 回调函数
setInterval(function(){
console.log('每一秒被调用一次');
},1000);
2
3
- 返回值
function fn(){
//返回匿名函数
return function(){
return 'hi';
}
}
fn()(); // hi
var box=fn();
2
3
4
5
6
7
8
9
# 自执行函数
自执行函数也是匿名函数
//1.使用 !开头,结构清晰,不容易混乱,推荐使用;
!function(){
document.write('ni hao');
}()
//2.无法表明函数与之后的()的整体性,不推荐使用。
(function(){
document.write('wo hao');
})();
//3.能够将匿名函数与调用的()为一个整体,官方推荐使用;
(function(){
document.write('hello');
}());
//4.放在中括号内执行
[function(){
document.write('world');
}()];
//5.使用 + 运算符
+function(){
document.write('ni hao');
}()
//6.使用 - 运算符
-function(){
document.write('ni hao');
}()
//7.使用波浪符 ~
~function(){
document.write('ni hao');
}()
//8.使用 void
void function(){
document.write('ni hao');
}()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
for (var i = 0; i < 5; ++i) {
(function(i) { // 形成块级作用域
func[i] = function() {
console.log(i);
}
})(i);
}
func[3](); // 3
// 或
for (var i = 0; i < 5; ++i) {
(function() {
var n = i;
func[i] = function() {
console.log(n);
}
})();
}
func[3](); // 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 高阶函数
高阶函数(high-order function 简称:HOF),咋一听起来那么高级,满足了以下两点就可以称作高阶函数了
- 函数可以作为参数被传递
- 函数可以作为返回值输出
在维基中的定义是
- 接受一个或多个函数作为输入
- 输出一个函数
可以将高阶函数理解为函数之上的函数,它很常用,比如常见的
var getData = function(url, callback) {
$.get(url, function(data){
callback(data);
});
}
2
3
4
5
# 防抖函数
防抖函数(Debounce)也是高阶函数。
防抖,指的是无论某个动作被连续触发多少次,直到这个连续动作停止后,才会被当作一次来执行。比如页面滚动。
// 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
function debounce(fn, delay) {
delay = delay || 200;
var timer = null;
return function() {
var arg = arguments;
// 每次操作时,清除上次的定时器
clearTimeout(timer);
timer = null;
// 定义新的定时器,一段时间后进行操作
timer = setTimeout(function() {
fn.apply(this, arg);
}, delay);
}
};
var count = 0;
window.onscroll = debounce(function(e) {
console.log(e.type, ++count); // scroll
}, 500);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 节流函数
防抖函数(Throttle)也是高阶函数。
节流,指的是无论某个动作被连续触发多少次,在定义的一段时间之内,它仅能够触发一次。比如resize和scroll时间频繁触发的操作,如果都接受了处理,可能会影响性能,需要进行节流控制。
// 函数节流,频繁操作中间隔 delay 的时间才处理一次
function throttle(fn, delay) {
delay = delay || 200;
var timer = null;
// 每次滚动初始的标识
var timestamp = 0;
return function() {
var arg = arguments;
var now = Date.now();
// 设置开始时间
if (timestamp === 0) {
timestamp = now;
}
clearTimeout(timer);
timer = null;
// 已经到了delay的一段时间,进行处理
if (now - timestamp >= delay) {
fn.apply(this, arg);
timestamp = now;
}
// 添加定时器,确保最后一次的操作也能处理
else {
timer = setTimeout(function() {
fn.apply(this, arg);
// 恢复标识
timestamp = 0;
}, delay);
}
}
};
var count = 0;
window.onscroll = throttle(function(e) {
console.log(e.type, ++count); // scroll
}, 500);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 柯里化
柯里化(Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。
比较经典的例子是
实现累加 add(1)(2)(3)(4)
function add() {
var args = [].slice.call(arguments);
// 用以存储更新参数数组
function adder() {
var arg = [].slice.call(arguments);
args = args.concat(arg);
// 每次调用,都返回自身,取值时可以通过内部的toString取到值
return adder;
}
// 指定 toString的值,用以隐示取值计算
adder.toString = function() {
return args.reduce(function(v1, v2) {
return v1 + v2;
});
};
return adder;
}
console.log(add(1, 2), add(1, 2)(3), add(1)(2)(3)(4)); // 3 6 10
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
上面这段代码,就能够实现了这个“柯里化”
需要注意的两个点是
- arguments并不是真正的数组,所以不能使用数组的原生方法(如 slice)
- 在取值时,会进行隐示的求值,即先通过内部的toString()进行取值,再通过 valueOf()进行取值,valueOf优先级更高,我们可以进行覆盖初始的方法 当然,并不是所有类型的toString和toValue都一样,Number、String、Date、Function 各种类型是不完全相同的
将柯里化抽取成一个公用函数
// 柯里化
function curry(fn) {
var args = [].slice.call(arguments, 1);
function inner() {
var arg = [].slice.call(arguments);
args = args.concat(arg);
return inner;
}
inner.toString = function() {
return fn.apply(this, args);
};
return inner;
}
function add() {
return [].slice.call(arguments).reduce(function(v1, v2) {
return v1 + v2;
});
}
function mul() {
return [].slice.call(arguments).reduce(function(v1, v2) {
return v1 * v2;
});
}
var curryAdd = curry(add);
console.log(curryAdd(1)(2)(3)(4)(5)); // 15
var curryMul = curry(mul, 1);
console.log(curryMul(2, 3)(4)(5)); // 120
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 反柯里化
反柯里化,将柯里化过后的函数反转回来,由原先的接受单个参数的几个调用转变为接受多个参数的单个调用。
一种简单的实现方法是:将多个参数一次性传给柯里化的函数,因为我们的柯里化函数本身就支持多个参数的传入处理,反柯里化调用时,仅使用“一次调用”即可。
结合上方的柯里化代码,反柯里化代码如下:
// 反柯里化
function uncurry(fn) {
var args = [].slice.call(arguments, 1);
return function() {
var arg = [].slice.call(arguments);
args = args.concat(arg);
return fn.apply(this, args);
}
}
var uncurryAdd = uncurry(curryAdd);
console.log(uncurryAdd(1, 2, 3, 4)); // 10
var uncurryMul = uncurry(curryMul, 2);
console.log(uncurryMul(3, 4)); // 24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
参考文章: