by Thomas Barrasso
由Thomas Barrasso
第一個應在JavaScript數組的最后 (The first shall be last with JavaScript arrays)
So the last shall be
[0]
, and the first [length — 1].所以最后一個應該是
[0]
,第一個[length_1]。
– Adapted from Matthew 20:16
–根據馬太福音20:16改編
I’ll skip the Malthusian Catastrophe and get to it: arrays are one of the simplest and most important data structures. While terminal elements (first and last) are frequently accessed, Javascript provides no convenient property or method for doing so and using indices can be redundant and prone to side effects and off-by-one errors.
我將跳過馬爾薩斯災難,然后繼續進行下去:數組是最簡單也是最重要的數據結構之一。 雖然經常訪問終端元素(第一個和最后一個),但是Javascript沒有提供方便的屬性或方法,使用索引可能是多余的,并且容易產生副作用和一次性錯誤 。
A lesser-known, recent JavaScript TC39 Proposal offers solace in the form of two “new” properties: Array.lastItem
& Array.lastIndex
.
最近鮮為人知的JavaScript TC39提案以兩個“新”屬性的形式提供了安慰: Array.lastItem
和Array.lastIndex
。
Javascript數組 (Javascript Arrays)
In many programming languages including Javascript, arrays are zero-indexed. The terminal elements–first and last– are accessed via the [0]
and [length — 1]
indices, respectively. We owe this pleasure to a precedent set by C, where an index represents an offset from the head of an array. That makes zero the first index because it is the array head. Also Dijkstra proclaimed “zero as a most natural number.” So let it be written. So let it be done.
在包括Javascript在內的許多編程語言中,數組都是零索引的。 終端元素first和last分別通過[0]
和[length — 1]
length_1 [length — 1]
索引進行訪問。 我們將這種樂趣歸功于C設置的先例 ,其中索引表示距數組開頭的偏移量。 這使第一個索引為零,因為它是數組頭。 迪克斯特拉還宣稱“ 零是最自然的數字。 ”所以就這樣寫吧。 因此,讓它完成。
I suspect if you averaged access by index you would find that terminal elements are referenced most often. After all, arrays are commonly used to store a sorted collection and doing so places superlative elements (highest, lowest, oldest, newest, etc.) at the ends.
我懷疑如果按索引平均訪問,您會發現終端元素被最頻繁地引用。 畢竟,數組通常用于存儲排序的集合,并且這樣做會將最高級的元素(最高,最低,最舊,最新等)放置在末尾。
Unlike other scripting languages (say PHP or Elixir), Javascript does not provide convenient access to terminal array elements. Consider a trivial example of swapping the last elements in two arrays:
與其他腳本語言(例如PHP或Elixir )不同,Javascript無法提供對終端數組元素的便捷訪問。 考慮一下在兩個數組中交換最后一個元素的簡單例子:
let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];
let lastAnimal = animals[animals.length - 1];animals[animals.length - 1] = faces[faces.length - 1];faces[faces.length - 1] = lastAnimal;
The swapping logic requires 2 arrays referenced 8 times in 3 lines! In real-world code, this can quickly become very repetitive and difficult for a human to parse (though it is perfectly readable for a machine).
交換邏輯需要在3行中引用2次8的數組! 在現實世界的代碼中,這可能很快就會變得非常重復且難以解析(盡管對于機器而言,這是完全可讀的)。
What’s more, solely using indices, you cannot define an array and get the last element in the same expression. That might not seem important, but consider another example where the function, getLogins()
, makes an asynchronous API call and returns a sorted array. Assuming we want the most recent login event at the end of the array:
而且,僅使用索引就無法定義數組并獲得同一表達式中的最后一個元素。 這似乎并不重要,但請考慮另一個示例,其中的函數getLogins()
進行異步API調用并返回已排序的數組。 假設我們希望在數組末尾有最新的登錄事件:
let lastLogin = async () => { let logins = await getLogins(); return logins[logins.length - 1];};
Unless the length is fixed and known in advance, we have to assign the array to a local variable to access the last element. One common way to address this in languages like Python and Ruby is to use negative indices. Then [length - 1]
can be shortened to [-1]
, removing the need for local reference.
除非長度是固定的并且事先知道,否則我們必須將數組分配給局部變量以訪問最后一個元素。 在Python和Ruby等語言中解決此問題的一種常見方法是使用負索引。 然后,可以將[length - 1]
縮短為[-1]
,從而無需本地引用。
I find -1
only marginally more readable than length — 1
, and while it is possible to approximate negative array indices in Javascript with ES6 Proxy or Array.slice(-1)[0]
, both come with significant performance implications for what should otherwise constitute simple random access.
我發現-1
可讀性僅比length — 1
Array.slice(-1)[0]
,雖然可以使用ES6 Proxy或Array.slice(-1)[0]
來近似Javascript中的負數組索引 ,但兩者對于其他方面都具有重大的性能影響 。構成簡單的隨機訪問。
下劃線和羅達斯 (Underscore & Lodash)
One of the most well-known principles in software development is Don’t Repeat Yourself (DRY). Since accessing terminal elements is so common, why not write a helper function to do it? Fortunately, many libraries like Underscore and Lodash already provide utilities for _.first
& _.last
.
軟件開發中最著名的原則之一是“不要重復自己”(DRY)。 由于訪問終端元素非常普遍,為什么不編寫一個輔助函數來實現呢? 幸運的是,像許多圖書館下劃線和Lodash已經為公用事業_.first
& _.last
。
This offers a big improvement in the lastLogin()
example above:
這在上面的lastLogin()
示例中提供了很大的改進:
let lastLogin = async () => _.last(await getLogins());
But when it comes to the example of swapping last elements, the improvement is less significant:
但是,以交換最后一個元素為例,改進的意義并不大:
let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];
let lastAnimal = _.last(animals);animals[animals.length - 1] = _.last(faces);faces[faces.length - 1] = lastAnimal;
These utility functions removed 2 of the 8 references, only now we introduced an external dependency that, oddly enough, does not include a function for setting terminal elements.
這些實用程序功能刪除了8個引用中的2個,只是現在我們引入了一個外部依賴關系,奇怪的是,它不包含用于設置端子元素的功能。
Most likely such a function is deliberately excluded because its API would be confusing and hard to readable. Early versions of Lodash provided a method _.last(array, n)
where n was the number of items from the end but it was ultimately removed in favor of _.take(array, n)
.
很有可能故意排除了此類功能,因為其API會令人困惑且難以閱讀。 Lodash的早期版本提供了_.last(array, n)
方法_.last(array, n)
其中n是末尾的項目數,但最終由于_.take (array, n)
而被刪除。
Assuming nums
is an array of numbers, what would be the expected behavior of _.last(nums, n)
? It could return the last two elements like _.take
, or it could set the value of the last element equal to n.
假設nums
是一個數字數組,則_.last(nums, n)
的預期行為是什么? 它可以返回最后兩個元素,例如_.take
,也可以將最后一個元素的值設置為n 。
If we were to write a function for setting the last element in an array, there are only a few approaches to consider using pure functions, method chaining, or using prototype:
如果我們要編寫一個用于設置數組中最后一個元素的函數,則只有幾種方法可以考慮使用純函數,方法鏈接或原型:
let nums = ['d', 'e', 'v', 'e', 'l']; // set first = last
_.first(faces, _.last(faces)); // Lodash style
$(faces).first($(faces).last()); // jQuery style
faces.first(faces.last()); // prototype
I do not find any of these approaches to be much of an improvement. In fact, something important is lost here. Each performs an assignment, but none use the assignment operator (=
).This could be made more apparent with naming conventions like getLast
and setFirst
, but that quickly becomes overly verbose. Not to mention the fifth circle of hell is full of programmers forced to navigate “self-documenting” legacy code where the only way to access or modify data is through getters and setters.
我認為這些方法都沒有太大的改進。 實際上,這里丟失了一些重要的東西。 每個=
都執行一個賦值,但是沒有一個使用賦值運算符( =
),這可以通過諸如getLast
和setFirst
這樣的命名約定變得更加明顯,但是很快就會變得過于冗長。 更不用說地獄的第五個圈子,滿是程序員被迫瀏覽“自我記錄”的舊代碼,而訪問或修改數據的唯一方法是通過getter和setter。
Somehow, it looks like we are stuck with [0]
& [length — 1]
…
不知何故,似乎我們陷入了[0]
和[length — 1]
困境……
Or are we? ?
還是我們? ?
提案 (The Proposal)
As mentioned, an ECMAScript Technical Candidate (TC39) proposal attempts to address this problem by defining two new properties on the Array
object: lastItem
& lastIndex
. This proposal is already supported in core-js 3 and usable today in Babel 7 & TypeScript. Even if you are not using a transpiler, this proposal includes a polyfill.
如前所述,ECMAScript技術候選人(TC39)提案試圖通過在Array
對象上定義兩個新屬性來解決此問題: lastItem
和lastIndex
。 該建議已在core-js 3中 得到支持 ,并且今天可以在Babel 7和TypeScript中使用。 即使你不使用transpiler,這一建議包括填充工具 。
Personally, I do not find much value in lastIndex
and prefer Ruby’s shorter naming for first
and last
, although this was ruled out because of potential web compatibility issues. I am also surprised that this proposal does not suggest a firstItem
property for consistency and symmetry.
就個人而言,我沒有在lastIndex
找到太多價值,并且更喜歡Ruby的first
和last
較短的命名,盡管由于潛在的Web兼容性問題而將其排除在外。 我也感到驚訝的是,該提議并未建議使用firstItem
屬性來保持一致性和對稱性。
In the interim, I can offer a no-dependency, Ruby-esque approach in ES6:
在此期間,我可以在ES6中提供一種不依賴Ruby風格的方法:
第一和最后 (First & Last)
We now have two new Array properties–first
& last
–and a solution that:
我們現在有兩個新的磁盤陣列屬性- first
和last
-和一個解決方案:
? Uses the assignment operator
?使用賦值運算符
? Does not clone the array
?不克隆陣列
? Can define an array and get a terminal element in one expression
?可以定義一個數組并在一個表達式中獲取一個終端元素
? Is human-readable
?易于閱讀
? Provides one interface for getting & setting
?提供一個獲取和設置的界面
We can rewrite lastLogin()
again in a single line:
我們可以在一行中再次重寫lastLogin()
:
let lastLogin = async () => (await getLogins()).last;
But the real win comes when we swap the last elements in two arrays with half the number of references:
但是,當我們用兩個引用數的一半交換兩個數組中的最后一個元素時,真正的勝利就來了:
let faces = ["?", "?", "?", "?", "?"];let animals = ["?", "?", "?", "?", "?"];
let lastAnimal = animals.last;animals.last = faces.last;faces.last = lastAnimal;
Everything is perfect and we have solved one of CS’ most difficult problems. There are no evil covenants hiding in this approach…
一切都很完美,我們已經解決了CS最棘手的問題之一。 這種方法沒有隱藏邪惡的盟約……
原型偏執狂 (Prototype Paranoia)
Surely there is no one [programmer] on earth so righteous as to do good without ever sinning.– Adapted from Ecclesiastes 7:20
當然,在地球上,沒有人[程序員]如此正義,以至沒有做任何事都不會犯罪。–改編自傳道書7:20
Many consider extending a native Object’s prototype an anti-pattern and a crime punishable by 100 years of programming in Java. Prior to the introduction of the enumerable
property, extending Object.prototype
could change the behavior of for in
loops. It could also lead to conflict between various libraries, frameworks, and third-party dependencies.
許多人認為擴展本機Object的原型是一種反模式 ,是一種使用Java進行100年編程應受懲罰的罪行。 在引入enumerable
屬性之前, 擴展Object.prototype
可能會更改for in
循環的行為。 它還可能導致各種庫,框架和第三方依賴項之間的沖突。
Perhaps the most insidious issue is that, without compile-time tools, a simple spelling mistake could inadvertently create an associative array.
也許最陰險的問題是,如果沒有編譯時工具,一個簡單的拼寫錯誤可能會無意間創建一個關聯數組 。
let faces = ["?", "?", "?", "?", "?"];let ln = faces.length
faces.lst = "?"; // (5) ["?", "?", "?", "?", "?", lst: "?"]
faces.lst("?"); // Uncaught TypeError: faces.lst is not a function
faces[ln] = "?"; // (6) ["?", "?", "?", "?", "?", "?"]
This concern is not unique to our approach, it applies to all native Object prototypes (including arrays). Yet this offers safety in a different form. Arrays in Javascript are not fixed in length and consequently, there are no IndexOutOfBoundsExceptions
. Using Array.last
ensures we do not accidentally try to access [length]
and unintentionally enter undefined
territory.
這種擔憂并非我們的方法所獨有,它適用于所有本機Object原型(包括數組)。 但這以另一種形式提供了安全性。 Javascript中的數組長度不固定,因此沒有IndexOutOfBoundsExceptions
。 使用Array.last
確保我們不會意外嘗試訪問[length]
并無意間輸入undefined
區域。
No matter which approach you take, there are pitfalls. Once again, software proves to be an art of making tradeoffs.
無論您采用哪種方法,都有陷阱。 再次證明,軟件是權衡的藝術 。
Continuing with the extraneous biblical reference, assuming we do not believe extending Array.prototype
is an eternal sin, or we’re willing to take a bite of the forbidden fruit, we can use this concise and readable syntax today!
繼續使用無關緊要的圣經參考,假設我們不認為擴展Array.prototype
是永恒的罪過,或者我們愿意咬一口禁果,那么今天就可以使用這種簡潔易懂的語法!
最后的話 (Last Words)
Programs must be written for people to read, and only incidentally for machines to execute. – Harold Abelson
必須編寫程序供人們閱讀,并且只能偶然地使機器執行。 – 哈羅德·阿伯森 ( Harold Abelson)
In scripting languages like Javascript, I prefer code that is functional, concise, and readable. When it comes to accessing terminal array elements, I find the Array.last
property to be the most elegant. In a production front-end application, I might favor Lodash to minimize conflict and cross-browser concerns. But in Node back-end services where I control the environment, I prefer these custom properties.
在像Javascript這樣的腳本語言中,我更喜歡功能性,簡潔性和可讀性的代碼。 在訪問終端數組元素時,我發現Array.last
屬性是最優雅的。 在生產前端應用程序中,我可能會喜歡Lodash以最大程度地減少沖突和跨瀏覽器的問題。 但是在我控制環境的Node后端服務中,我更喜歡這些自定義屬性。
I am certainly not the first, nor will I be the last, to appreciate the value (or caution about the implications) of properties like Array.lastItem
, which is hopefully coming soon to a version of ECMAScript near you.
當然,我不是第一個 ,也不是最后一個欣賞Array.lastItem
之類的屬性的價值(或謹慎暗示)的Array.lastItem
,希望該屬性很快會出現在您附近的ECMAScript版本中。
Follow me on LinkedIn · GitHub · Medium
在領英 上關注我· GitHub · 中
翻譯自: https://www.freecodecamp.org/news/the-first-shall-be-last-with-javascript-arrays-11172fe9c1e0/