【问题标题】:Reference to slice of an array引用数组的切片
【发布时间】:2012-03-20 10:16:53
【问题描述】:

如何获取对数组切片的引用?

var A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
A.mySlice = function(l, h){return this.slice(l,h)};

var B = A.mySlice(1,5); // ["b", "c", "d", "e"]

它适用于从 A 派生的直接切片。但是,如何为所有派生的切片获取它? (在这种情况下为 B)

B.mySlice = function(l, h){return this.slice(l,h)};

A[3] = 33;
A.mySlice(1,5) // ["b", "c", 33, "e"] => ok
B.mySlice(0,3) // ["b", "c", "d"] => I would want ["b", "c", 33]

【问题讨论】:

  • 我不认为这是可能的,但我想看看其他人的想法,

标签: javascript slice


【解决方案1】:

slice() 将元素复制到新数组中。

因此,在您的示例中,AB 是两个完全不同的数组。因此,更改一个元素中的元素不会影响另一个元素。

【讨论】:

    【解决方案2】:

    我不认为你可以用原生 JS 数组来做到这一点(好吧,反正不是直接的方式)。

    我认为最简洁的方法是返回并使用自定义对象来表示切片。也许是这样的:

    function ArraySlice(arr, lo, hi){
        this.arr = arr;
        this.lo = lo;
        this.hi = hi;
        this.length = hi - lo;
    };
    ArraySlice.prototype._contains = function(ix){
        return this.lo + ix < this.hi;
    };
    ArraySlice.prototype.get = function(ix){
        if (this._contains(ix)){
            return this.arr[this.lo + ix];
        }else{
            return; //undefined
        }
    };
    ArraySlice.prototype.set = function(ix, value){
        if (this._contains(ix)){
            return (this.arr[this.lo + ix] = value);
        }else{
            return; //undefined
        }
    };
    
    var a = [0,1,2,3,4,5];
    var b = new ArraySlice(a, 1, 3);
    
    a[2] = 17;
    console.log( b.get(1) );
    

    当然,这失去了方便的[] 语法,并且对象不再是数组,但是子类化 Array 很烦人且容易出错,我不相信有跨浏览器的方式来进行运算符重载。

    【讨论】:

    • @Truth:谢谢。拿这 128 颗糖果来解决你的麻烦。
    • 糖果?我要大师球! :D
    • 我不使用 [] 语法没有问题
    • 我怀疑您可以通过处理get 陷阱来使用处理[] 语法的代理。就此而言,如果您不介意污染 Array 原型,则无需单独的类即可执行此操作。
    【解决方案3】:

    这是我看到的唯一可能的解决方案:

    • 扩展 Array 类并将所有数组数据存储在单例类实例中
    • 首次创建新数组时创建唯一标识符 (UID)
    • 尝试从单例获取数据时使用该标识符
    • 在制作切片时使用相同的标识符

    然而,这可能会破坏您的大量代码。

    【讨论】:

      【解决方案4】:

      我很确定这在 Javascript 中是不可能的。这行var B = A.mySlice(1,5); 将 B 设置为包含 A 数字切片的数组对象。它没有引用 A。它只是一个数组对象。

      【讨论】:

        【解决方案5】:

        它不是很干净,因为您要处理函数对象,但您可以使用闭包包装调用以链接原始数组:

        var A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
        A.mySlice = function (l, h) { return function() { return this.slice(l, h); }; };
        
        var B = A.mySlice(1, 5); // B() will resolve to ["b", "c", "d", "e"]
        B.mySlice = function (l, h) { return function() { return this().slice(1, h) }; };
        
        A[3] = 33;
        A.mySlice(1, 5)() // ["b", "c", 33, "e"]
        B.mySlice(0, 3)() // ["b", "c", 33]
        

        【讨论】:

          【解决方案6】:

          我使用Proxy 过度设计了一个解决方案。这将或多或少像一个指向 int[] 中某个位置的指针 C++ 会。它通过代理原始数组、跟踪开始和结束索引以及使用引用原始数组中的相对索引的版本捕获 getset 来做到这一点。

          Array.prototype.slicePtr = function(startIdx, endIdx = this.length) {
            if (startIdx < 0) {
              startIdx = this.length - startIdx - 1;
            }
          
            if (endIdx < 0) {
              endIdx = this.length - endIdx;
            }
          
            if (startIdx > endIdx) {
              throw new Error('startIdx must not be less than endIdx');
            }
          
            endIdx = endIdx == null ? this.length : endIdx;
          
            const self = this;
          
            function contains(idx) {
              return idx > 0 && idx < endIdx;
            }
          
            const proxy = new Proxy(this, {
              get(target, prop, receiver) {
                // symbols can't be converted to numbers, so handle them first
                if (typeof prop === 'symbol') {
                  switch (prop) {
                    case Symbol.iterator:
                      return function*() {
                        for (let i = startIdx; i < endIdx; i++) {
                          yield self[i];
                        }
                      }
                  }
                }
          
                const idx = startIdx + Number(prop);
                if (!isNaN(idx)) {
                  if (!contains(idx)) {
                    return undefined;
                  }
          
                  return Reflect.get(target, idx, receiver);
                }
          
                switch (prop) {
                  case 'splice':
                    return function splice(start, delCount, ...items) {
                      endIdx += items.length - delCount;
                      return self.splice(start + startIdx, delCount, ...items);
                    }
          
                  case 'length':
                    return endIdx - startIdx;
          
                  case 'pop':
                    return function pop() {
                      return proxy.splice(proxy.length - 1, 1)[0];
                    }
          
                  case 'push':
                    return function push(...items) {
                      proxy.splice(proxy.length, 0, ...items);
                      return proxy[proxy.length - 1];
                    }
          
                  case 'shift':
                    return function shift() {
                      return proxy.splice(0, 1)[0];
                    }
          
                  case 'unshift':
                    return function unshift(...items) {
                      proxy.splice(startIdx, 0, ...items);
                      return proxy[proxy.length - 1];
                    }
                }
          
                return Reflect.get(target, prop, receiver);
              },
          
              set(target, prop, value, receiver) {
                if (typeof prop !== 'symbol') {
                  const idx = startIdx + Number(prop);
                  if (!isNaN(idx)) {
                    if (Number.isFinite(idx) && Number.isInteger(idx) && Number(prop) >= proxy.length) {
                      endIdx = idx + 1;
                    }
          
                    return Reflect.set(target, idx, value, receiver);
                  }
                }
          
                switch (prop) {
                  case 'length':
                    endIdx = startIdx + value;
                    if (endIdx > self.length) {
                      self.length = endIdx;
                    }
          
                    return value;
                }
          
                return Reflect.set(target, prop, value, receiver);
              },
            });
          
            return proxy;
          }
          
          /////////////
          //  TESTS  //
          /////////////
          
          let array = [];
          let slice = [];
          
          const fnRegex = /\(?\w*\)? => (?:\{\}|(.*)?);?$/;
          const resultsTbl = $('#results tbody');
          const makeRow = (fn) => {
            const row = $('<tr>')
              .append($('<td>').append($('<code>').text(fn.toString().replace(fnRegex, '$1'))))
              .append($('<td>').append($('<code>').text(JSON.stringify(fn(), null, 2))))
              .append($('<td>').append($('<code>').text(JSON.stringify(array, null, 2))))
              .append($('<td>').append($('<code>').text(JSON.stringify(slice, null, 2))));
            resultsTbl.append(row);
          };
          
          [
            () => array = [ 0, 1, 2, 3, 4, 5, 6, 7 ],
            () => slice = array.slicePtr(2, 5),
            () => slice[2],
            () => slice[0] = 1000,
            () => slice.length = 4,
            () => slice.push(20),
            () => slice.shift(),
            () => slice.indexOf(4),
            () => slice.filter(v => !(v % 2)),
            () => slice.map(v => v / 2),
            () => slice.splice(1, 2, 30, 40, 50),
            () => slice = slice.slicePtr(1, 4),
            () => slice[1] = 60,
            () => slice.fill(0),
            () => JSON.stringify(slice),
            () => slice.toString(),
            () => slice[7] = 'end',
          ].forEach(fn => makeRow(fn));
          th {
            text-align: left;
            border-bottom: 1px solid darkgray;
            padding: 4px 2px;
          }
          td {
            padding-left: 2px;
            white-space: nowrap;
          }
          td:not(:last-child) {
            padding-right: 3em;
          }
          tr:nth-child(2n) {
            background: #eee;
          }
          table {
            border: 1px solid black;
          }
          <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
          <table id="results">
            <thead><tr><th>Code</th><th>Result</th><th><code>array</code></th><th><code>[ ...slice ]</code></th></tr></thead>
            <tbody></tbody>
          </table>

          我唯一没有的(我认为)是console.log(slice);它总是打印arrayAs far as I know,无法覆盖 console.log()console.dir() 将为给定对象打印的内容。

          很可能我遗漏了一些我没有想到的东西,而且我从未测试过copyWithin(),因为我仍然无法完全理解它的实际作用。不过可能会奏效。 ?‍♀️

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-11-10
            • 2017-06-24
            • 1970-01-01
            • 1970-01-01
            • 2021-05-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多