深入浅出原生js之高阶函数

高阶函数

什么是高阶函数?

JS的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数.

一个最简单的高阶函数:

function add(x,y,f){
    return f(x)+f(y);
}

当我们调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs,根据函数定义,我们可以推导计算过程为:

x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;

编写高阶函数的意义:
就是让函数的参数能够接收别的函数.

map/reduce

map

map()方法:原数组中的每个元素调用一个指定方法后,返回返回值组成的新数组.

举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map实现如下:

由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:

function pow(x){ //定义一个平方函数 return x*x; } var arr=[1,2,3,4,5,6,7,8,9]; var result = arr.map(pow); //map()传入的是函数对象本身 console.log(result); //结果:[1,4,9,16,25,36,49,64,81];

注意:map()传入的参数是pow,即函数对象本身.

map()作为高阶函数,事实上它把运算规则抽象了.

reduce

reduce()方法:为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素),返回一个具体的结果.

reduce语法:

arr.reduce(callback,[initialValue])
  • callback(执行数组中每个值得函数,包含四个参数)
  • previousValue(第一项的值或者上一次叠加的结果值,或者是提供的初始值(initialValue))
  • index(当前元素在数组中的索引)
  • array(数组本身)
  • initialValue(作为第一次调用callback的第一个参数,可以控制返回值的格式)

filter

filter是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素.

和map()类似,Array的filter()也接收一个函数.和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素.

例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:

var arr=[1,2,4,5,6,9,10,15];
var r= arr.filter(function (x){
    return x%2!==0;
});
r;//[1,5,9,15]

把一个Array中的空字符串删掉,可以这么写:

var arr = ['A','','B',NULL,undefined,'C',' '];
var r = arr.filter(function (s){
    return s&& s.trim();//注意:IE9以下版本没有trim()方法
});
r;//['A','B','C']

用filter()这个高阶函数,关键在于正确实现一个’筛选’函数.

回调函数

filter()接收的回调函数,其实可以有多个参数,通常我们仅使用第一个参数,表示Array的某个元素.回调函数还可以接收另外两个参数,表示元素的位置和数组本身:

var arr=['A','B','C'];
var r=arr.filter(function (element,index,self){
    console.log(element);//依次打印'A','B','C'
    console.log(index);//依次打印0,1,2
    console.log(self);self就是变量arr
    return true;
});

利用filter,可以巧妙地去除Array的重复元素:

'use strict';
var r,
arr=['apple','strawberry','banana','pear', 'apple', 'orange', 'orange', 'strawberry']

r=arr.filter(function(element,index,self){
    return self.indexOf(element)==index;
});
console.log(r.toString());

去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素与indexOf返回的位置不相等,因此被filter滤掉了.

排序算法

sort()方法原理

js中的sort方法不同于java中的soft方法,默认的按照升序排列数组项-最小的排在最前面,最大的排在最后面.
但在js中,sort方法会调用每个数组项的toString()转型方法,然后比较得到的字符串,以确定如何排序,即使数组中每一项都是数组,sort()方法比较的也是字符串.
如:

var values=[0.1.5.10.15];
values.sort();
alert(values);//0.1.10.15.5

默认情况下,对字符串排序,是按照ASCLL的大小比较的.

sort()方法会直接对Array进行修改,它返回的结果仍是当前Array:

Array

对于数组,除了map().reduce.filter().sort()这些方法可以传入一个函数外,Array对象还提供了很多非常实用的高阶函数.

every

every()方法可以判断数组的所有元素是否满足测试条件.

例如,给定一个包含若干字符串的数组,判断所有的字符串是否满足指定的测试条件:

'use strict';
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
    return s.length > 0;
})); // true, 因为每个元素都满足s.length>0

console.log(arr.every(function (s) {
    return s.toLowerCase() === s;
})); // false, 因为不是每个元素都全部是小写

find

find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined:

'use strict'
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
    return s.toLowerCase() === s;
})); // 'pear', 因为pear全部是小写

console.log(arr.find(function (s) {
    return s.toUpperCase() === s;
})); // undefined, 因为没有全部是大写的元素

findIndex

findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1:

'use strict'
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.findIndex(function (s) {
    return s.toLowerCase() === s;
})); // 1, 因为'pear'的索引是1

console.log(arr.findIndex(function (s) {
    return s.toUpperCase() === s;
})); // -1

forEach

forEach()和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组.forEach()常用于遍历数组,因此,传入的函数不需要返回值:

'use strict';
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印每个元素

闭包

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回.

例子:实现一个对Array的求和.通常情况下,求和的函数是这样定义的:

function sum(arr) {
return arr.reduce(function (x, y) {
    return x + y;
});
}

sum([1, 2, 3, 4, 5]); // 15

但是,如果不需要立刻求和,而是在后面代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!

function lazy_sum(arr) {
var sum = function () {
    return arr.reduce(function (x, y) {
        return x + y;
    });
}
return sum;
}

当我们调用lazy_sum时,返回的并不是求和结果,而是求和函数:

var f=lazy_sum([1,2,3,4,5]);//function sum()

调用函数f时,才真正计算求和结果:

f();//15

在这例子中,我们在函数lazy_sum中又定义了函数sum.并且.内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为"闭包(Closure)"的程序结构拥有极大的威力.

需要注意的是:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1()和f2()的调用结果互不影响.

什么是闭包

什么是闭包

代码段1

function a(){
var n = 0;
function inc() {
    n++;
    console.log(n);
}
inc();  
inc(); 
}
a(); //控制台输出1,再输出2

代码段2

function a(){
var n = 0;
this.inc = function () {
    n++; 
    console.log(n);
};
}
var c = new a();
c.inc();    //控制台输出1
c.inc();    //控制台输出2

闭包:有权访问另一个函数作用域内变量的函数都是闭包.

注意,函数名只是一个标识(指向函数的指针),而()才是执行函数.

创建闭包的方式,就是在一个函数内部创建另一个内部(私有)函数.

总结

闭包就是一个函数引用另一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量.这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样.

箭头函数

ES6标准新增了一种新的函数:Arrow Function(箭头函数).

为什么叫Arrow Function?因为他的定义用的就是一个箭头:

x=>x*x
这个箭头函数相当于:
function(x){
    return x*x;
}

箭头函数相当于匿名函数,并且简化了函数定义.箭头函数有两种格式,一种像上面的,只包含一个表达式,连{…}和return都省略掉了.还有一种可以包含多条语句,这时候就不能省略{…}和return:

x=>{
    if(x>0){
        return x*x;
    }
    else{
        return -x*x;
    }
}

如果参数不是一个,就需要用括号()括起来:

//两个参数:
(x,y)=>x*x +y*y

//无参数:
()=>3.14

//可变参数:
(x,y,...rest)=>{
    var i,sum=x+y;
    for(i=0;i<rest.length;i++){
        sum+=rest[i];
    }
    return sum;
}

如果要返回一个对象,就要注意,如果是单表达式,这样写会报错:

//SyntaxError:
x=>{foo:x}

因为和函数体的{…}有语法冲突,所以要改为:

//ok:
x=>({foo:x})

this

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显得区别:箭头函数内部的this是词法作用域,由上下文确定.

由于JS函数对this绑定的错误处理,下面的例子无法得到预期结果:

var obj = {
birth: 1990,
getAge: function () {
    var b = this.birth; // 1990
    var fn = function () {
        return new Date().getFullYear() - this.birth; // this指向window或undefined
    };
    return fn();
}
};

现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj;

var obj = {
birth: 1990,
getAge: function () {
    var b = this.birth; // 1990
    var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
    return fn();
}
};
obj.getAge(); // 25

如果使用箭头函数,以前的那种hack写法:

var that = this;

就不再需要了.
由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:

var obj={
    birth:1990,
    getAge:function(year){
        var b=this.birth;//1990
        var fn=(y)=>y-this.birth;//this.birth;//this.birth仍是1990
        return fn.call({birth:2000},year);
    }
};
obj.getAge(2015);//25

generator

generator(生成器)是ES6标准引入的新数据类型.一个generator看上去像一个函数,但可以返回多次.

一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果:

function foo(x){
    return x+x;
}
var r = foo(1);//调用foo函数

函数在执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码.

generator跟函数很像,定义如下:

function * foo(x){
    yield x+1;
    yield x+2;
    return x+3;
}

generator 和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次.

举例:以著名的斐波那契数列为例,它由0,1开头:

0 1 1 2 3 5 8 13 21 34 ...

要编写一个产生斐波那契数列的函数,可以这样写:

function fib(max) {
var
    t,
    a = 0,
    b = 1,
    arr = [0, 1];
while (arr.length < max) {
    [a, b] = [b, a + b];
    arr.push(b);
}
return arr;
}

// 测试:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

函数只能返回一次,所以必须返回一个Array.但是,如果换成generator,就可以一次返回一个数,不断返回多次.用generator改写如下:

function * fib(max){
    var t,
        a= 0,
        b= 1,
        n= 0;
    while (n<max){
        yield a;
        [a,b]=[b,a+b];
        n++;
    }
    return;
}

直接调用试试:

fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

直接调用一个generator和调用函数不一样,fib(5)仅仅是创建了一个generator对象,还没有去执行它.

调用generator对象有两个办法,一是不断的调用generator对象的next()方法;

var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value:x,done:true/false},然后"暂停".返回的value就是yield的返回值,done表示这个generator是否已经执行结束了.如果donetrue,则value就是return的返回值.

当执行到donetrue时,这个generator对象就已经全部执行完毕了,不要再继续调用==next()==了.

第二个方法是直接用for…of循环迭代generator对象,这种方式不需要我们自己判断done:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值