02函数-02-方法 和 高阶函数

1、方法

1.1 方法的定义

方法不就是函数?还是有点区别的,在一个对象中绑定函数,称为这个对象的方法
var xiaoming = { 
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age() 返回函数内容
xiaoming.age(); // 2017年调用是27,第二年调用就变成28了

1.2 this关键字

上面的函数中用到了一个特殊的关键字,this,在一个方法内部,它始终指向当前对象,也就是xiaoming这个变量,所以this.birth可以拿到xiaoming的birth属性。

然而这个this也可以说是JavaScript中设计的一个缺陷,为什么呢?结合以上,我们看下面的代码:
var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN

是不是觉得很奇怪,为什么会得到NaN?this在之前不是指向了xiaoming吗?

实际上,this的指向是随情况而定的,如果我们使用 xiaoming.age()的方式,this才是正确指向xiaoming(如果普通函数没有写在对象里,实际上this又会指向window对象,即全局对象),所以,要保证this的指向正确,必须用obj.xxx()的形式调用方法

诸如以下的方式也是不行的,this的指向只在age的方法中指向xiaoming,方法中嵌套是无效的,会重新指向全局对象window:
var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age();

1.3 缺陷修复

1.3.1 申请额外变量获取

想要修复如此问题,只能再申请一个变量来捕获this:
var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var that = this; // 在方法内部一开始就捕获this
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - that.birth; // 用that而不是this
        }
        return getAgeFromBirth();
    }
};

xiaoming.age();

1.3.2 apply和call方法

或者使用函数本身的 apply 方法,来控制this的指向
该方法有两个参数,第一个参数是需要绑定的this变量,第二个参数是数组Array,表示函数本身的参数:
function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空

你可以这样理解,就类似于Java中类的非静态方法,它是属于对象的,不能直接使用,所以apply这里要求你定义两个东西,一个是对象,一个是必要参数。

还有一个和apply()类似的方法是 call() ,不同的是,call() 的参数传入直接按顺序传入,不需要打包成Array:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

思维拓展:用apply动态改变函数的行为
假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。
最佳方案是用我们自己的函数替换掉默认的parseInt():
var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};

// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3

2、高阶函数

我们知道函数实际也是一个对象,也可以i用变量指向函数,既然如此,那么我们完全可以将一个指向函数的变量a,作为另外一个函数的接收参数,这就是高阶函数:

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;

2.1 map

map() --> 定义在JavaScript中的数组Array中,在map()中传入自己的函数,可以得到一个新的Array作为结果

function pow(x) {
    return x * x;
}

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

map作为高阶函数,实际上把运算规则抽象了,上例中,实际上你也可以分别遍历出元素,然后每次把元素进行函数运算后的结果push到一个新的Array里,最终把这个Array返回。

所以map更能直观地表述:把f(x)作用在Array的每一个元素并把结果生成一个新的Array

2.2 reduce

reduce() --> 把函数作用在Array的[x1, x2, x3...]上,这个参数函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算
e.g.
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

比如对一个Array求和,如果用reduce实现的话就是:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25

//实际上就是1和3作为参数,带入函数运算得到4
//作为结果的4再和下一个元素5带入函数继续运算得到结果9
//循环,即最终得到25

继续拓展一下思维,把[1, 3, 5, 7, 9]变换成整数13579:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x * 10 + y;
}); // 13579

2.3 filter

filter() --> 把传入的函数依次作用于Array的每个元素,然后根据返回值是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]

为了去掉空字符串,利用filter和JS中对于boolean的判断:

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

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巧妙去重:
var
    r,
    arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];

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

alert(r.toString());

2.4 sort

sort作为排序的算法,想必其使用频率不言而喻,但是有一个大坑,那就是Array的sort()方法,默认是把所有元素转换为String再排序,这就会导致普通的数字排序中,例如10会排列在2的前面,不是因为10比2小,而是因为'10'中的字符'1'比‘2'的ASCII码小。另外,要注意的是,sort()方法是直接对原Array直接做出修改,返回当前的Array,如果你希望只是得到修改后的Array而不改变源Array,记得另起变量保存以后再执行sort。

还好的是,sort()也是一个高阶函数,可以接收一个比较函数来实现自定义排序,类似Java中集合实现排序,元素要覆盖Comparable接口的CompareTo方法一样,所以在sort()中如果要实现按数字大小排序,可以这样写:
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
}); // [1, 2, 10, 20]

还有诸如对字符串进行忽略大小写的字母排序:
var arr = ['Google', 'apple', 'Microsoft'];
arr.sort(function (s1, s2) {
    x1 = s1.toUpperCase();
    x2 = s2.toUpperCase();
    if (x1 < x2) {
        return -1;
    }
    if (x1 > x2) {
        return 1;
    }
    return 0;
}); // ['apple', 'Google', 'Microsoft']


posted @ 2017-03-26 13:07  Dulk  阅读(466)  评论(0编辑  收藏  举报