有没有办法在JavaScript中创建类似数组的对象,而不使用内置数组?我特别关注这样的行为:
var sup = new Array(5); //sup.length here is 0 sup[0] = 'z3ero'; //sup.length here is 1 sup[1] = 'o3ne'; //sup.length here is 2 sup[4] = 'f3our'; //sup.length here is 5
我在这里看到的特殊行为是sup.length更改而没有调用任何方法.我从这个问题中了解到,在数组的情况下,[]运算符会被重载,这就解释了这种行为.有没有一种纯粹的JavaScript方式来复制这种行为,或者语言是不是足够灵活?
根据Mozilla文档,正则表达式返回的值也可以用这个索引做一些时髦的事情.用普通的javascript可以实现吗?
[]运算符是访问对象属性的本机方式.为了更改其行为,它无法覆盖语言.
如果您想要的是在[]运算符上返回计算值,则不能在JavaScript中执行此操作,因为该语言不支持计算属性的概念.唯一的解决方案是使用与[]运算符相同的方法.
MyClass.prototype.getItem = function(index) { return { name: 'Item' + index, value: 2 * index }; }
如果您想要的是与类中的本机Array具有相同的行为,则始终可以直接在您的类上使用本机Array方法.在内部,您的类将像本机数组一样存储数据但会保持其类状态.jQuery这样做可以使jQuery类在保留其方法的同时具有数组行为.
MyClass.prototype.addItem = function(item) { // Will add "item" in "this" as if it was a native array // it will then be accessible using the [] operator Array.prototype.push.call(this, item); }
是的,您可以在JavaScript中轻松地将数组子类化为arraylike对象:
var ArrayLike = function() {}; ArrayLike.prototype = []; ArrayLike.prototype.shuffle = // ... and so on ...
然后,您可以实例化类似对象的新数组:
var cards = new Arraylike; cards.push('ace of spades', 'two of spades', 'three of spades', ... cards.shuffle();
不幸的是,这在MSIE中不起作用.它没有跟踪length
财产.这相当于整个事情的缩小.
关于Dean Edwards 如何对JavaScript数组对象进行子类化的更详细问题.后来证明他的解决方法并不安全,因为一些弹出窗口拦截器会阻止它.
更新:值得一提的是Juriy"kangax"Zaytsev 关于此主题的绝对史诗文章.它几乎涵盖了这个问题的每个方面.
现在我们有ECMAScript 2015(ECMA-262第6版; ES6),我们有代理对象,它们允许我们Array
在语言本身中实现行为,类似于:
function FakeArray() { const target = {}; Object.defineProperties(target, { "length": { value: 0, writable: true }, [Symbol.iterator]: { // http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype-@@iterator value: () => { let index = 0; return { next: () => ({ done: index === target.length, value: target[index++] }) }; } } }); const isArrayIndex = function(p) { /* an array index is a property such that ToString(ToUint32(p)) === p and ToUint(p) !== 2^32 - 1 */ const uint = p >>> 0; const s = uint + ""; return p === s && uint !== 0xffffffff; }; const p = new Proxy(target, { set: function(target, property, value, receiver) { // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-exotic-objects-defineownproperty-p-desc if (property === "length") { // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-arraysetlength const newLen = value >>> 0; const numberLen = +value; if (newLen !== numberLen) { throw RangeError(); } const oldLen = target.length; if (newLen >= oldLen) { target.length = newLen; return true; } else { // this case gets more complex, so it's left as an exercise to the reader return false; // should be changed when implemented! } } else if (isArrayIndex(property)) { const oldLenDesc = Object.getOwnPropertyDescriptor(target, "length"); const oldLen = oldLenDesc.value; const index = property >>> 0; if (index > oldLen && oldLenDesc.writable === false) { return false; } target[property] = value; if (index > oldLen) { target.length = index + 1; } return true; } else { target.property = value; return true; } } }); return p; }
我不能保证这实际上是完全正确的,并且它不处理你将长度改为小于其先前值的情况(行为有点复杂才能正确;大致它删除属性以便length
属性不变量),但它概述了如何实现它.它也不模仿[[Call]]和[[Construct]]的行为Array
,这是ES6之前你不能做的另一件事 - 在ES代码中两者之间不可能有不同的行为,虽然这些都不难.
这length
以规范将其定义为工作方式实现属性:它拦截对象上属性的赋值,length
如果属性是"数组索引",则更改属性.
与ES5和getter不同的是,这允许一个人进入length
恒定时间(显然,这仍然取决于VM中的基础属性访问是恒定时间),并且它是唯一能够提供非恒定时间性能的情况是newLen - oldLen
删除属性时未实现的情况(大多数VM中的删除速度很慢!).