高阶函数
什么是高阶函数?
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是否已经执行结束了.如果done为true,则value就是return的返回值.
当执行到done为true时,这个generator对象就已经全部执行完毕了,不要再继续调用==next()==了.
第二个方法是直接用for…of循环迭代generator对象,这种方式不需要我们自己判断done: