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 greater 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>