基本缘由
多年来,程序员一直要求能够像使用Python一样对JS数组进行“负索引”。也就是说,要求具有写入能力,arr[-1]而不是arr[arr.length-1],其中负数从最后一个元素开始倒数。
不幸的是,JS的语言设计使其无法实现。该[]语法不是针对于数组和字符串; 它适用于所有对象。像一样arr[1],通过索引引用值实际上只是使用键“ 1”引用对象的属性,这是任何对象都可以拥有的。因此,arr[-1]在今天的代码中已经“起作用”了,但是它返回对象的“ -1”属性的值,而不是返回从末尾算起的索引。
已经有很多尝试来解决此问题。最新的建议是受限制的建议,以使通过属性访问数组的最后一个元素(github.com/tc39/propos…
相反,该建议采用一种更通用的方法,并建议向.item()Array,String和TypedArray 添加一个方法,该方法采用整数值并使用如上所述的负数语义返回该索引处的项目。
这不仅以一种简单的方式解决了长期存在的请求,而且还碰巧解决了各种DOM API的单独问题,如下所述。
现有方法
当前,要从可索引对象的末尾访问值,通常的做法是write arr[arr.length - N],其中N是末尾的第N个项目(从1开始)。这需要命名两次可索引项,另外为加上7个字符.length,并且对匿名值不利。除非您首先将其存储在temp变量中,否则不能使用此技术获取函数返回值的最后一项。
避免这些缺点中的一些但自身具有一些性能缺点的另一种方法是arr.slice(-N)[0]。这样可以避免重复名称,因此也适合匿名值。但是,拼写有点奇怪,尤其是结尾[0](因为.slice()返回Array)。此外,还会创建一个临时数组,其中包含源中所有从所需项目到末尾的所有内容,直到检索到第一个项目后立即将其丢弃。
但是请注意,以下事实.slice()(以及相关的方法,例如.splice())已经具有负索引的概念,并可以根据需要完全解析它们。
DOM缘由
WebIDL规范的最新添加是ObservableArray<>(感谢@domenic!),它是Array上的代理,它允许Web API向页面作者展示与Array完全相同的内容,但仍允许浏览器拦截get / set / delete /索引属性等,执行类型检查和其他要求,就像今天使用命名属性一样。
我们计划开始对大多数希望公开列表的API使用此功能,但我们也希望在可能的情况下也升级较旧的 API来使用该功能。许多较旧的API使用定制的接口严重且不完整地复制Array接口的事实,这一直是Web作者沮丧的根源。
(例如,document.querySelectorAll()返回的不是返回数组,而是返回一个支持索引属性和的NodeList,.length因此可以用基本方式将其视为Array,但是只能选择Array原型方法的一小部分。.map()缺少流行的方法,例如要求作者编写类似的代码[...document.querySelectorAll("a")].map(foo)。)
这种升级几乎可以就地完成,只需将各种定制接口与ObservableArray交换,避免破坏任何未明确测试值类型的东西。有一个例外:它们都有一个.item()方法,该方法返回传递的索引处的值。
(这是对很古老(1990年代)的信念的遗留下来,即Java是可在网络上使用的合理语言,因此API以“最低公分母”样式设计,可用于JS和Java。除非您实际上是一个Java数组,否则当时无法使用索引属性,因此.item()方法是一种折衷方案,在两种语言中的作用相同。)
很可能有代码依赖于.item()这些接口上的使用,我们不想在那里冒险。
我们可以通过将ObservableArray子类化并添加子类来解决此问题.item()。但是,这将意味着这些值不是Array类型。在社区中寻找数组的各种类型检查方法将失败。
或者我们可以直接添加.item()到ObservableArray本身,因为它是Array的代理包装。但是,这将令人困惑和奇怪,从而使Array甚至在原型上也没有这种方法。
相反,对我们而言,理想的解决方案是将.item()Array原型本身以及完整性/一致性添加到其他支持相同索引相关属性通用集合的可索引类型(如).slice()。
因此,名称.item()是该提案的必要条件;将其更改为其他内容仍然可以帮助作者,但是不能满足DOM的需求。
可转换接口
假设采用此提议,则以下旧接口应该可以升级到ObservableArray:
- 节点列表
- 可能将DOMTokenList作为子类
- CSSRuleList
- 样式表清单
- 可能是CSSStyleDeclaration和MediaList,作为子类
可能的问题
与内置的任何其他内容一样,与此有关的一个明显迫在眉睫的问题是,该名称可能.item()已经被具有不兼容定义的框架添加到这些类的原型中,并使用一种避免破坏的脆弱模式添加了内置名称,因此取决于框架定义的代码将在给定新的内置定义后中断。
Polyfill
function item(n) {
n = Math.trunc(n) || 0;
if (n < 0) n += this.length;
if (n < 0 || n >= this.length) return undefined;
return this[n];
}
for (let C of[Array, String, Uint8Array]) {
Object.defineProperty(C.prototype, "item", {
value: item,
writable: true,
enumerable: false,
configurable: true
});
}
let arr = [1,2,3]
arr.item(-1);