在不使用数组的情况下在 JavaScript 中实现类似数组的行为 [英] Implement Array-like behavior in JavaScript without using Array

查看:37
本文介绍了在不使用数组的情况下在 JavaScript 中实现类似数组的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法在 JavaScript 中创建类数组对象,而不使用内置数组?我特别关注这样的行为:

Is there any way to create an array-like object in JavaScript, without using the built-in array? I'm specifically concerned with behavior like this:

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 的方式来复制这种行为,或者语言不够灵活?

The particular behavior I'm looking at here is that sup.length changes without any methods being called. I understand from this question that the [] operator is overloaded in the case of arrays, and this accounts for this behavior. Is there a pure-javascript way to duplicate this behavior, or is the language not flexible enough for that?

根据 Mozilla 文档,正则表达式返回的值也可以这个索引的时髦东西.这可以用普通的 javascript 实现吗?

According to the Mozilla docs, values returned by regex also do funky things with this index. Is this possible with plain javascript?

推荐答案

现在我们有了 ECMAScript 2015 (ECMA-262 6th Edition; ES6),我们有了 代理对象,它们允许我们在语言本身中实现 ​​Array 行为,大致如下:

Now we have ECMAScript 2015 (ECMA-262 6th Edition; ES6), we have proxy objects, and they allow us to implement the Array behaviour in the language itself, something along the lines of:

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 属性不变式成立),但它给出了如何实现它的粗略概述.它也不会模仿 Array 上的 [[Call]] 和 [[Construct]] 的行为,这是在 ES6 之前你不能做的另一件事——不可能有分歧ES 代码中两者之间的行为,尽管这些都不难.

I can't guarantee this is actually totally correct, and it doesn't handle the case where you alter length to be smaller than its previous value (the behaviour there is a bit complex to get right; roughly it deletes properties so that the length property invariant holds), but it gives a rough outline of how you can implement it. It also doesn't mimic behaviour of [[Call]] and [[Construct]] on Array, which is another thing you couldn't do prior to ES6—it wasn't possible to have divergent behaviour between the two within ES code, though none of that is hard.

这实现了 length 属性,与规范定义它的工作方式相同:它拦截对对象属性的赋值,如果是,则更改 length 属性一个数组索引".

This implements the length property in the same way the spec defines it as working: it intercepts assignments to properties on the object, and alters the length property if it is an "array index".

与 ES5 和 getter 可以做的不同,这允许我们在恒定时间内获取 length(显然,这仍然取决于 VM 中的底层属性访问是恒定时间),并且只有当 newLen - oldLen 属性被删除(并且在大多数 VM 中删除速度很慢!)时,它提供非恒定时间性能的唯一情况是未实现的情况.

Unlike what one can do with ES5 and getters, this allows one to get length in constant time (obviously, this still depends on the underlying property access in the VM being constant time), and the only case in which it provides non-constant time performance is the not implemented case when newLen - oldLen properties are deleted (and deletion is slow in most VMs!).

这篇关于在不使用数组的情况下在 JavaScript 中实现类似数组的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆