《javascript函数类型介绍》


2020-07-20 上次更新时间:8/4/2020, 8:24:44 PM 0 javascript

# 普通函数

var sum = function (num1, num2) {
    return num1 + num2
}
sum(10, 10) // 20
1
2
3
4

# 箭头函数

Es6新增

var sum = (num1, num2) => {
    return num1 + num2
}
sum(10, 10) // 20
1
2
3
4

# 递归函数

递归函数:直接或间接调用函数本身,也会被叫作自调用函数

// 经典阶乘函数
function fun(num){ 
    if (num <= 1){ 
        return 1; 
    } else { 
        return num * fun(num-1); 
    } 
}

fun(10)
1
2
3
4
5
6
7
8
9
10

# 回调函数

回调函数就是一个通过函数指针调用的函数。就是我们经常说的callback

function getdata(callback){
    setTimeout(function(){ // 模拟异步
        var info = {
        "id":1,
        "name":'张三'
        }
        callback(info) // 回调函数
    },1000)
}
1
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)
}
1
2
3
4
5
6
7
8
9

# 匿名函数

匿名函数有两种用法:

  • 赋值
  • 自我执行

单独运行一个匿名函数会报错

// 语法错误
function (num1, num2) {
    return num1 + num2
}
1
2
3
4

修改成自执行函数正常运行

(function (num1, num2) {
    return num1 + num2
})(1, 2)

// 3
1
2
3
4
5

作用

  1. 匿名函数可以实现闭包。
  2. 模拟块级作用域,减少全局变量。

应用场景

  1. 赋值给事件
windon.onload = function(){
	console.log('hello');
};
1
2
3
  1. 函数表达式
var show = function(){
	console.log('hello');
};
show()
1
2
3
4
  1. 对象方法
var obj={
    fn:function(){
        console.log('hi');
    }
};

console.log(obj.fn()); // hi
1
2
3
4
5
6
7
  1. 回调函数
setInterval(function(){
    console.log('每一秒被调用一次');
},1000);
1
2
3
  1. 返回值
function fn(){
    //返回匿名函数
    return function(){
        return 'hi';
    }
}
fn()(); // hi

var box=fn();
1
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');
}()
1
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
1
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);
    });
}
1
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);
1
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);
1
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
1
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
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

参考文章:

理解运用JS的闭包、高阶函数、柯里化

上次更新时间: 8/4/2020, 8:24:44 PM