2.5 方法上下文
在JavaScript中,执行函数的方式是在其引用的结尾处追加花括号,下面我们对highPass()函数做略微修改。
function highPass(number, cutoff) {
cutoff = cutoff || this.cutoff;
return (number >= cutoff);
}
var filter1 = {
highPass: highPass,
cutoff: 5
},
filter2 = {
// No highPass here!
cutoff: 3
};
highPass()的入参包含一个必选参数Number与一个可选参数cutoff。如果未传入cutoff,函数会认为自己是作为方法在对象上(filter)调用,会使用对象(filter)中的属性cutoff而非入参cutoff。
普通函数的调用:
test('Invoking a function.', function () {
var result = highPass(6, 5);
equal(result, true,
'6 > 5 should be true.');
});
警告: 一般来说,函数中的this始终是指向全局对象,除非你将函数作为对象的方法执行(使用点语法或者方括号)。所以在做函数调用时,最好先确认其this指向是否正确,以防像属性赋值这样的操作污染全局对象。
方法调用将函数与对象关联起来,像object.methodName()(点语法)、object['method Name']()(方括号语法)都属于方法调用。
test('Invoking a method.', function () {
var result1 = filter1.highPass(3),
result2 = highPass.call(filter2, 3),
result3 = filter1.highPass(6);
equal(result1, false,
'3 >= filter1.cutoff should be false.');
equal(result2, true,
'3 >= filter2.cutoff should be true.');
equal(result3, true,
'6 >= filter1.cutoff should be true.');
});
当你通过点语法调用方法时,在方法中使用this,可以访问到对象的属性。在上例中,将入参与filter对象上的cutoff属性作比较,随后返回false,因为3明显是小于this.cutoff值。请牢记,this值的指向取决于方法在哪一个对象上执行。
在第二个例子中,call()方法(继承自Function.prototype)将highPass()方法代理到了filter2对象上,由于filter2对象中cutoff值是3而不是5,测试依然通过。
确切来说,call()方法存在于每一个函数中,理论上可以使用call()方法让函数在任意对象上进行方法调用,换句话说,它将函数中的this指向了你所指定的对象。方法签名如下:
someMethod.call(context, argument1, argument2, ...);
其中context是你希望this所指向的对象,如果想传入一组数组作为入参,可以使用apply():
someMethod.apply(context, someArray);
Function.prototype.bind()
诚然,call()与apply()方法非常实用,不过使用它们时,你需要格外小心,因为它们所绑定的this上下文指向是临时的,每次调用都需要准确无误地传入,而且时刻得确保this能够在当前函数作用域中访问到。不过每次调用都这么做略显麻烦,特别是在事件监听器中。
bind()方法可以解决这个问题,它用来将函数的this指向与目标对象绑定。bind()方法是JavaScript语言规范中的一门新特性,最开始在Prototype等JavaScript类库中出现,随后在ECMAScript5规范中被标准化,但是老版本的浏览器对它的兼容度不高,你可以考虑自己实现或者采用第三方类库。
bind()方法的使用场景之一,将事件监听器与对象绑定:
var lightbulb = {
toggle: function toggle() {
this.isOn = !this.isOn;
return this.isOn;
},
isOn: false
},
toggle = lightbulb.toggle,
lightswitch = document.getElementById('lightswitch');
lightswitch = document.getElementById('lightswitch');
lightswitch.addEventListener('click',
lightbulb.toggle, false);
上述代码示例很好理解,事件监听器通过addEventListener方法绑定至lightswitch的DOM元素上。那么问题来了,事件监听器中的this指向并不是lightbulb对象,而是点击触发时的DOM元素,所以lightbulb的开关逻辑不会被执行。
打开界面上的开关按钮,lightbulb.isOn的值仍为false。下面来用bind()方法修复这个问题,仅需对toggle的赋值方式做略微修改。
toggle = lightbulb.toggle.bind(lightbulb);
OK,现在lightbulb的开关可以响应来自用户的操作了。