在一个对象中绑定函数,称为这个对象的方法。
在 JavaScript 中,对象的定义是这样的:
var xiaoming = {
name: '小明',
birth: 1990
};
我们给 xiaoming 绑定一个函数,就可以做更多的事情。比如,写个 age()
方法,返回 xiaoming 的年龄:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 26
绑定到对象上的函数称为 方法,和普通函数也没啥区别,但是它在内部使用了一个 this
关键字,这个东东是什么?
在一个方法内部,this 是一个特殊变量,它始终指向当前对象,也就是 xiaoming 这个变量。 所以,this.birth
可以拿到 xiaoming
的 birth
属性。
让我们拆开写:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26, 正常结果
getAge(); // NaN
如果以对象的方法形式调用,比如 xiaoming.age()
,该函数的 this
指向被调用的对象,也就是 xiaoming
,这是符合我们预期的。
如果单独调用函数,比如 getAge()
,此时,该函数的 this
指向全局对象,也就是 window
,所以单独调用函数 getAge()
返回了 NaN。
要保证 this
指向正确,必须用 obj.xxx()
的形式调用!
由于这是一个巨大的设计错误,要想纠正可没那么简单。ECMA 决定,在 strict 模式下让函数的 this
指向 undefined
,因此,在 strict 模式下,你会得到一个错误:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
var fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined
但这只是让错误及时暴露出来,并没有解决 this 应该指向的正确位置。
再看另一个例子,我们把上面的方法重构了一下:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
结果又报错了!原因是 this
指针只在 age
方法的函数内指向 xiaoming
,在函数内部定义的函数,this
又指向 undefined
了!(在非 strict 模式下,它重新指向全局对象 window
!)
修复的办法也不是没有,我们用一个 that
变量首先捕获 this
:
'use strict';
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(); // 26
apply
虽然在一个独立的函数调用中,根据是否是 strict 模式,this
指向 undefined
或 window
,不过,我们还是可以控制 this
的指向的!
要指定函数的 this
指向哪个对象,可以用函数本身的 apply()
方法,它接收两个参数,第一个参数就是需要绑定的 this
变量,第二个参数是 Array
,表示函数本身的参数。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26
getAge.apply(xiaoming, []); // 26, this指向xiaoming, 参数为空
使用参数的情况见下例:
function getAge(y) {
// var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
getAge.apply(xiaoming, [2016]); // 26,传入参数2016
另一个与 apply()
类似的方法是 call()
,唯一区别是:
apply()
把参数打包成 Array 再传入;call()
把参数按顺序传入。
getAge.call(xiaoming, 2016); // 26
传入多个参数见下例:
function getAge(i, j, k) {
console.log(i + '年,小明' + (i - this.birth) + '岁。');
console.log(j + '年,小明' + (j - this.birth) + '岁。');
console.log(k + '年,小明' + (k - this.birth) + '岁。');
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
getAge.apply(xiaoming,[2014,2015,2016])
2014年,小明24岁。
2015年,小明25岁。
2016年,小明26岁。
getAge.call(xiaoming,2014,2015,2016)
2014年,小明24岁。
2015年,小明25岁。
2016年,小明26岁。
装饰器
利用 apply()
,我们还可以动态改变函数的行为。
JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次 parseInt()
,我们手动在 paeselnt()
函数内部拯救一个 count
变量,来统计被调用是次数:
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1; // 统计调用次数
return oldParseInt.apply(null, arguments); // 调用原函数
};
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3