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了
11
1
var xiaoming = {
2
name: '小明',
3
birth: 1990,
4
age: function () {
5
var y = new Date().getFullYear();
6
return y - this.birth;
7
}
8
};
9
10
xiaoming.age; // function xiaoming.age() 返回函数内容
11
xiaoming.age(); // 2017年调用是27,第二年调用就变成28了
1.2 this关键字
上面的函数中用到了一个特殊的关键字,this,在一个方法内部,它始终指向当前对象,也就是xiaoming这个变量,所以this.birth可以拿到xiaoming的birth属性。
然而这个this也可以说是JavaScript中设计的一个缺陷,为什么呢?结合以上,我们看下面的代码:
var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
2
1
var fn = xiaoming.age; // 先拿到xiaoming的age函数
2
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();
13
1
var xiaoming = {
2
name: '小明',
3
birth: 1990,
4
age: function () {
5
function getAgeFromBirth() {
6
var y = new Date().getFullYear();
7
return y - this.birth;
8
}
9
return getAgeFromBirth();
10
}
11
};
12
13
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();
14
1
var xiaoming = {
2
name: '小明',
3
birth: 1990,
4
age: function () {
5
var that = this; // 在方法内部一开始就捕获this
6
function getAgeFromBirth() {
7
var y = new Date().getFullYear();
8
return y - that.birth; // 用that而不是this
9
}
10
return getAgeFromBirth();
11
}
12
};
13
14
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, 参数为空
13
1
function getAge() {
2
var y = new Date().getFullYear();
3
return y - this.birth;
4
}
5
6
var xiaoming = {
7
name: '小明',
8
birth: 1990,
9
age: getAge
10
};
11
12
xiaoming.age(); // 25
13
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
2
1
Math.max.apply(null, [3, 5, 4]); // 5
2
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
13
1
var count = 0;
2
var oldParseInt = parseInt; // 保存原函数
3
4
window.parseInt = function () {
5
count += 1;
6
return oldParseInt.apply(null, arguments); // 调用原函数
7
};
8
9
// 测试:
10
parseInt('10');
11
parseInt('20');
12
parseInt('30');
13
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;
10
1
function add(x, y, f) {
2
return f(x) + f(y);
3
}
4
5
当我们调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs:
6
x = -5;
7
y = 6;
8
f = Math.abs;
9
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
10
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]
6
1
function pow(x) {
2
return x * x;
3
}
4
5
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
6
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)
2
1
e.g.
2
[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
8
1
var arr = [1, 3, 5, 7, 9];
2
arr.reduce(function (x, y) {
3
return x + y;
4
}); // 25
5
6
//实际上就是1和3作为参数,带入函数运算得到4
7
//作为结果的4再和下一个元素5带入函数继续运算得到结果9
8
//循环,即最终得到25
继续拓展一下思维,把[1, 3, 5, 7, 9]变换成整数13579:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579
4
1
var arr = [1, 3, 5, 7, 9];
2
arr.reduce(function (x, y) {
3
return x * 10 + y;
4
}); // 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]
6
1
//比如去掉一个Array中的偶数
2
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
3
var r = arr.filter(function (x) {
4
return x % 2 !== 0;
5
});
6
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']
5
1
var arr = ['A', '', 'B', null, undefined, 'C', ' '];
2
var r = arr.filter(function (s) {
3
return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
4
});
5
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;
});
7
1
var arr = ['A', 'B', 'C'];
2
var r = arr.filter(function (element, index, self) {
3
console.log(element); // 依次打印'A', 'B', 'C'
4
console.log(index); // 依次打印0, 1, 2
5
console.log(self); // self就是变量arr
6
return true;
7
});
拓展思维:利用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());
x
1
var
2
r,
3
arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
4
5
r = arr.filter(function (element, index, self) {
6
return self.indexOf(element) === index;
7
});
8
9
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]
10
1
var arr = [10, 20, 1, 2];
2
arr.sort(function (x, y) {
3
if (x < y) {
4
return -1;
5
}
6
if (x > y) {
7
return 1;
8
}
9
return 0;
10
}); // [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']
1
var arr = ['Google', 'apple', 'Microsoft'];
2
arr.sort(function (s1, s2) {
3
x1 = s1.toUpperCase();
4
x2 = s2.toUpperCase();
5
if (x1 < x2) {
6
return -1;
7
}
8
if (x1 > x2) {
9
return 1;
10
}
11
return 0;
12
}); // ['apple', 'Google', 'Microsoft']