本文深入解析JavaScript中通过call、apply和bind方法借用对象技术的原理。从基础的数组slice借用,到字面量简化写法,再到Function.prototype.call.bind的高级技巧,详细阐述如何在不继承的情况下复用函数逻辑,并探讨其在自定义对象及函数式编程中的应用场景。
前言
前言
借助call()、apply()以及bind()这三个核心方法,开发者能够轻松实现其他对象方法的借用,从而避免复杂的继承关系。
在JavaScript中借用方法
在JavaScript中借用方法
在JavaScript开发实践中,有时需要复用其他对象定义的函数或方法,而无需局限于对象本身或其原型链。利用call()、apply()和bind()方法,可以便捷地借用其他对象的方法,无需建立继承关系。这是资深JavaScript开发者常用的一种技巧。
原型方法
原型方法
在JavaScript中,除了string、number和boolean等不可变的原始数据类型外,几乎所有数据形式都是对象。Array类型提供了如slice、join、push和pop等用于遍历和转换有序数列的实用方法,这些方法定义在其原型上。
一个常见的应用场景是,当对象与数组同属列表类型的数据结构时,对象可以从数组中“借用”方法。其中最常用的借用方法是 Array.prototype.slice。
function myFunc() {
// error, arguments is an array like object, not a real array
arguments.sort();
// "borrow" the Array method slice from its prototype, which takes an array like object (key:value)
// and returns a real array
var args = Array.prototype.slice.call(arguments);
// args is now a real Array, so can use the sort() method from Array
args.sort();
}
myFunc('bananas', 'cherries', 'apples');
方法借用的可行性源于call和apply方法允许在不同上下文中执行函数,这也是复用已有功能而无需继承其他对象的有效途径。实际上,数组原型中定义了许多常用方法,例如join和filter同样适用:
// takes a string "abc" and produces "a|b|c
Array.prototype.join.call('abc', '|');
// takes a string and removes all non vowels
Array.prototype.filter.call('abcdefghijk', function(val) {
return ['a', 'e', 'i', 'o', 'u'].indexOf(val) !== -1;
}).join('');
由此可见,不仅对象可以借用数组的方法,字符串类型也可以。然而,由于这些泛型方法定义在原型上,每次借用时都必须显式使用 String.prototype 或 Array.prototype。这种写法较为繁琐,容易引发代码冗余。更高效的替代方案是使用字面量来实现相同效果。
使用字面量借用方法
使用字面量借用方法
字面量是遵循JavaScript语法规则的结构,MDN对其解释如下:
在JavaScript中,字面量用于表示固定值,而非变量,即在脚本中直接给出的值。 利用字面量可以简化原型方法的调用:
[].slice.call(arguments);
[].join.call('abc', '|');
''.toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
虽然上述写法减少了部分冗长,但仍需直接在[]和""上操作以借用方法,代码观感依然不够优雅。可以通过变量保存对字面量及方法的引用,使代码更加简洁:
var slice = [].slice;
slice.call(arguments);
var join = [].join;
join.call('abc', '|');
var toUpperCase = ''.toUpperCase;
toUpperCase.call(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
获得借用方法的引用后,即可轻松使用call()进行调用,从而实现代码复用。遵循减少冗余的原则,我们进一步探讨是否可以在借用方法时,避免每次调用都显式书写call()或apply():
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);
var join = Function.prototype.call.bind(Array.prototype.join);
join('abc', '|');
var toUpperCase = Function.prototype.call.bind(String.prototype.toUpperCase);
toUpperCase(['lowercase', 'words', 'in', 'a', 'sentence']).split(',');
如前所述,现在可以使用 Function.prototype.call.bind 来静态绑定从不同原型“借来的”方法。然而,var slice = Function.prototype.call.bind(Array.prototype.slice) 这一语句的实际运作机制是怎样的呢?
理解 Function.prototype.call.bind
理解 Function.prototype.call.bind
Function.prototype.call.bind 乍看之下较为复杂,但深入理解其运作机制将带来极大的益处。
Function.prototype.call 是一种引用,用于“调用”函数并设置其“this”值以供函数内部使用。
需要注意的是,“bind”方法返回一个新函数,该新函数的“this”值被固定。因此,.bind(Array.prototype.slice) 返回的新函数的“this”始终指向Array.prototype.slice函数。
综上所述,新函数会调用“call”函数,且其“this”上下文被限定为“slice”函数。当调用slice()时,实际指向的是之前限定的方法。
自定义对象的方法
自定义对象的方法
继承机制虽然强大,但开发者通常仅在需要复用对象或模块间的通用功能时才使用它。并非所有代码复用场景都适合使用继承,因为在多数情况下,简单的借用方法更为灵活且复杂度高。
此前我们仅讨论了借用原生方法,实际上借用任何方法都是可行的。例如,以下代码展示了如何计算积分游戏的玩家分数:
var scoreCalculator = {
getSum: function(results) {
var score = 0;
for (var i = 0, len = results.length; i < len; i++) {
score = score + results[i];
}
return score;
},
getScore: function() {
return scoreCalculator.getSum(this.results) / this.handicap;
}
};
var player1 = {
results: [69, 50, 76],
handicap: 8
};
var player2 = {
results: [23, 4, 58],
handicap: 5
};
var score = Function.prototype.call.bind(scoreCalculator.getScore);
// Score: 24.375
console.log('Score: ' + score(player1));
// Score: 17
console.log('Score: ' + score(player2));
尽管上述示例略显生硬,但可以看出,与原生方法类似,用户自定义的方法也可以被轻松借用。
总结
总结
Call、bind和apply能够改变函数的调用方式,常在借用函数时使用。大多数开发者熟悉借用原生方法,但较少涉及借用自定义方法。
近年来JavaScript函数式编程发展迅速,如何利用Function.prototype.call.bind更简便地借用方法?预计此类话题将愈发常见。
以上就是JavaScript中借用方法的总结,希望对大家理解JavaScript中的方法借用有所帮助。