在JavaScript中借用方法
在JavaScript中,有時候需要在一個不同的對象上重用一個函數,而不是在定義它的對象或者原型中。通過使用call(),applay()和bind(),我們可以很方便地從不同的對象借用方法,而不需要繼承它們 – 這是一個在專業JavaScript開發者的工具箱中很有用的工具。
這篇文章假設你已經充分了解了call(),apply() 和 bind() 以及它們的不同點。
在JavaScript中,你接觸的幾乎所有東西都是一個對象,除了string,number 和 booleans這樣不可變的原始值。一個數組是一種對象類型,適合用於有序數據列表的遍歷和修改,在它的原型中有很多有用的方法,例如slice,join,push 和 pop。
我們看到對象最常見的使用情況就是從一個數組中借用方法,因為它們都是列表類型的數據結構。最常被借用的方法是 Array.prototype.slice
。
function myFunc() { // 錯誤, arguments是一個類數組對象, 不是一個真實的數組 arguments.sort(); // 借用 Array 原型中的方法 slice, 它接受一個類數組對象 (key:value) // 並返回一個真實的數組 var args = Array.prototype.slice.call(arguments); // args 現在是一個真正的數組, 所以可以使用Array的sort()方法 args.sort(); } myFunc('bananas', 'cherries', 'apples');
借用方法是可行的,因為call和apply允許我們在一個不同的上下文中調用方法,是一個很好的方式來重用已經存在的函數,而無需讓一個對象擴展自另一個對象。數組實際上在它的原型中定義了很多方法,而且一般是可重用的,下面再舉兩個例子是join和filter:
// 接收一個字符串 "abc" 並輸出 "a|b|c
Array.prototype.join.call('abc', '|');
// 接收一個字符串並移除所有的非元音字母
Array.prototype.filter.call('abcdefghijk', function(val) { return ['a', 'e', 'i', 'o', 'u'].indexOf(val) !== -1; }).join('');
正如你所看到的,不僅僅對象可以得益於從數組中借用方法,字符串也可以。然而,因為方法一般被定義在了原型上,所以每次我們借用方法都要寫String.prototype
或者 Array.prototype
,這是很多余並且很煩人的事。一種有效的方法的是使用Literals(字面量)。
Literal是一種語法語言結構, 遵循 JavaScript 的規則,MDN 上這樣解釋:
你可以使用literals來表示JavaScript中的值。這些都是固定的值,不是變量,你可以再腳本中按照字面寫出來。
Literals允許我們以縮略的形式訪問原型方法:
[].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
是一個引用,用來調用一個函數並且把它的“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來借用方法將變得越來越普遍。
鏈接:https://www.zcfy.cc/article/borrowing-methods-in-javascript-by-david-shariff-794.html