第一个应在JavaScript数组的最后

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.lastItemArray.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:

与其他脚本语言(例如PHPElixir )不同,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.

除非长度是固定的并且事先知道,否则我们必须将数组分配给局部变量以访问最后一个元素。 在PythonRuby等语言中解决此问题的一种常见方法是使用负索引。 然后,可以将[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 ProxyArray.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.

我认为这些方法都没有太大的改进。 实际上,这里丢失了一些重要的东西。 每个=都执行一个赋值,但是没有一个使用赋值运算符( = ),这可以通过诸如getLastsetFirst这样的命名约定变得更加明显,但是很快就会变得过于冗长。 更不用说地狱第五个圈子,满是程序员被迫浏览“自我记录”的旧代码,而访问或修改数据的唯一方法是通过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对象上定义两个新属性来解决此问题: lastItemlastIndex 。 该建议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:

我们现在有两个新的磁盘阵列属性- firstlast -和一个解决方案:

✓ 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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值