JS

JS深入浅出之关于函数的二三事(二)

Posted by weite122 on 2018-02-15

call / apply

  • fn.call(asThis, p1,p2) 是函数的正常调用方式
    当你不确定参数的个数时,就使用 apply
    fn.apply(asThis, params)

bind

  • call 和 apply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由你指定。

  • 举个例子,一个简单的点击事件,当element被点击后调用onClick方法。onclick函数被浏览器调用时他的this就是触发事件的元素,也就是代码中的div1,但onClick方法的this必须指向view,所以我们通常用提前声明_this或that来保存this,防止this不明确。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 下面都是伪代码
var view = {
element: $('#div1')
bindEvents: function() {
var _this = this
this.element.onclick = function() {
_this.onClick.call(_this)
}
},
onClick: function() {
this.element.addClass('active')
}
}
  • 通过bind我们可以解决以上问题,直接用bind返回一个新的函数,新函数call原来函数,this和参数可以自己指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 下面都是伪代码
var view = {
element: $('#div1')
bindEvents: function() {

/* 这里是伪代码,解释bind内部工作是返回一个新的函数,这个新函数会 call 原来的函数,call 的参数由你指定
this.onClick.bind = function(a, b, c) {
var oldFn = this //也就是外面的 this.onClick
return function() {
oldFn.call(a, b, c)
}
} */
this.element.onClick = this.onClick.bind(this)
},
onClick: function() {
this.element.addClass('active')
}
}

柯里化 / 高阶函数

柯里化:返回函数的函数

将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//柯里化之前
function sum(x,y){
return x+y
}
//柯里化之后
function addOne(y){
return sum(1, y)
}
//柯里化之前
function Handlebar(template, data){
return template.replace('{{name}}', data.name)
}
//柯里化之后
function Handlebar(template){
return function(data){
return template.replace('{{name}}', data.name)
}
}

高阶函数:

  • 在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
    • 接受一个或多个函数作为输入:forEach sort map filter reduce
    • 输出一个函数:lodash.curry
    • 不过它也可以同时满足两个条件:Function.prototype.bind

作用:可以将多个函数组合成一个新函数,功能更强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 对一个数组进行偶数求和,单数排序

// 第一种是一般写法
array = [1,2,3,4,5,6,7,8]

var sum = 0
for(var i = 0;i < array.length;i++) {
if(array[i] % 2 === 0) {
sum += array[i]
}
return sum
}

// 第二种利用数组API
array.filter.call(array, function(n){return n%2===0}).reduce(function(prev, next){return prev+next}, 0) //20

// 第三种利用高阶函数方法,以后函数用fn.call()方式,不用fn(),好处是知道this的值是什么
array.reduce.call(array.filter(function(n){return n%2===0}),function(prev, next){return prev+next}, 0)

回调

  • 名词形式:被当做参数的函数就是回调
  • 动词形式:调用这个回调

构造函数

  • 返回对象的函数就是构造函数
  • 一般首字母大写

箭头函数

  • 使用箭头函数的好处就是箭头函数的参数没有this,它无法指定this的值,你可以根据词法作用域知道this的值。箭头函数参数就是arguments,你传入一个参数,它会自己return结果,写法非常直观。这样我们不需要关系this有没有改变,因为箭头函数不会改变this的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 我想连续都打印出我的名字
setTimeout(function(a) {
console.log(this)
setTimeout(function(a){
console.log(this)
}, 1000)
}.bind({name:'tutu'}), 1000)

//{name:'tutu'}
//window

// setTimeout会改变this的值,所以我们需要bind,通过返回一个新的函数,新函数的内部调用旧函数,函数的第一个参数this我们可以指定
setTimeout(function(a) {
console.log(this)
setTimeout(function(a){
console.log(this)
}.bind({name:'tutu'}), 1000)
}.bind({name:'tutu'}), 1000)

// 但是上面写法看过去不是很好,通过使用箭头函数,它并不会改变this的值,所以我们的this不需要特意去指定
setTimeout(function(a) {
console.log(this)
setTimeout((a) => {console.log(this)}, 1000)
}.bind({name:'tutu'}), 1000)

用柯里化实现下面函数功能,这个函数能够将接受多个参数的函数,变成多个接受一个参数的函数

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
function curry(???){
???
return ???
}


var abc = function(a, b, c) {
return [a, b, c];
};

var curried = curry(abc);

curried(1)(2)(3);
// => [1, 2, 3]

curried(1, 2)(3);
// => [1, 2, 3]

curried(1, 2, 3);
// => [1, 2, 3]


function curry(func , fixedParams){
if ( !Array.isArray(fixedParams) ) { fixedParams = [ ] }
return function(){
let newParams = Array.prototype.slice.call(arguments); // 新传的所有参数
if ( (fixedParams.length+newParams.length) < func.length ) {
return curry(func , fixedParams.concat(newParams));
}else{
return func.apply(undefined, fixedParams.concat(newParams));
}
}
}