├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── biblio.json ├── package-lock.json ├── package.json ├── polyfill.d.ts ├── polyfill.js ├── polyfill.test.js ├── polyfill.test262.mjs └── spec.html /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Spec publish 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Install dev deps 11 | run: npm install 12 | - name: Build the spec 13 | run: npm run build 14 | - name: Deploy 🚀 15 | uses: JamesIves/github-pages-deploy-action@releases/v3 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | BRANCH: gh-pages 19 | FOLDER: out 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Polyfill test 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | jobs: 7 | test262: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | - name: Install dev deps 14 | run: npm install 15 | - name: Run test262 tests 16 | run: npm run test262 17 | test: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install dev deps 22 | run: npm install 23 | - name: Test the polyfill 24 | run: npm run test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test262"] 2 | path = test262 3 | url = https://github.com/nicolo-ribaudo/test262.git 4 | branch = change-array-by-copy 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Note** 2 | > This proposal is ['finished'](https://github.com/tc39/proposals/blob/main/finished-proposals.md) and has been merged into the ECMAScript spec. [PR](https://github.com/tc39/ecma262/pull/2997) 3 | 4 | # Change Array by copy 5 | 6 | Provides additional methods on `Array.prototype` and `TypedArray.prototype` to enable changes on the array by returning a new copy of it with the change. 7 | 8 | ## Status 9 | 10 | This proposal is currently at [Stage 4]. 11 | 12 | - [Candidate spec text][spec] 13 | 14 | [Stage 4]: https://github.com/tc39/proposals/commit/ad4df8435f27f39eda26db3b940ae151980c8015#diff-af1d66eb7dbbf6f66e871d26bcad07076a557256a957f558ca21e60924e2b0b7 15 | [spec]: https://github.com/tc39/ecma262/pull/2997/files 16 | 17 | ## Champions 18 | 19 | - Robin Ricard (Bloomberg) 20 | - Ashley Claymore (Bloomberg) 21 | 22 | ## Reviewers 23 | 24 | - [Jordan Harband](https://github.com/ljharb) 25 | - [Justin Ridgewell](https://github.com/jridgewell) 26 | - [Sergey Rubanov](https://github.com/chicoxyzzy) 27 | 28 | ## Overview 29 | 30 | This proposal introduces the following function properties to `Array.prototype`: 31 | 32 | - `Array.prototype.toReversed() -> Array` 33 | - `Array.prototype.toSorted(compareFn) -> Array` 34 | - `Array.prototype.toSpliced(start, deleteCount, ...items) -> Array` 35 | - `Array.prototype.with(index, value) -> Array` 36 | 37 | All of those methods keep the target Array untouched and returns a copy of it with the change performed instead. 38 | 39 | `toReversed`, `toSorted`, and `with` will also be added to TypedArrays: 40 | 41 | - `TypedArray.prototype.toReversed() -> TypedArray` 42 | - `TypedArray.prototype.toSorted(compareFn) -> TypedArray` 43 | - `TypedArray.prototype.with(index, value) -> TypedArray` 44 | 45 | These methods will then be available on subclasses of `TypedArray`. i.e. the following: 46 | 47 | - `Int8Array` 48 | - `Uint8Array` 49 | - `Uint8ClampedArray` 50 | - `Int16Array` 51 | - `Uint16Array` 52 | - `Int32Array` 53 | - `Uint32Array` 54 | - `Float32Array` 55 | - `Float64Array` 56 | - `BigInt64Array` 57 | - `BigUint64Array` 58 | 59 | ### Example 60 | 61 | ```js 62 | const sequence = [1, 2, 3]; 63 | sequence.toReversed(); // => [3, 2, 1] 64 | sequence; // => [1, 2, 3] 65 | 66 | const outOfOrder = new Uint8Array([3, 1, 2]); 67 | outOfOrder.toSorted(); // => Uint8Array [1, 2, 3] 68 | outOfOrder; // => Uint8Array [3, 1, 2] 69 | 70 | const correctionNeeded = [1, 1, 3]; 71 | correctionNeeded.with(1, 2); // => [1, 2, 3] 72 | correctionNeeded; // => [1, 1, 3] 73 | ``` 74 | 75 | ## Motivation 76 | 77 | The [`Tuple.prototype`][tuple-proto] introduces these functions as a way to deal with the immutable aspect of the Tuples in [Record & Tuple][r-t]. While Arrays are not immutable by nature, this style of programming can be beneficial to users dealing with frozen arrays for instance. 78 | 79 | This proposal notably makes it easier to write code able to deal with Arrays and Tuples interchangeably. 80 | 81 | ## Relationship with [Record & Tuple][r-t] 82 | 83 | While this proposal is derived from [Record & Tuple][r-t], it should progress independently. 84 | 85 | If web compatibility prescribes it, property names defined in this proposal are going to be changed. Those changes should be reflected on [`Tuple.prototype`][tuple-proto]. 86 | 87 | [tuple-proto]: https://tc39.es/proposal-record-tuple/#sec-properties-of-the-tuple-prototype-object 88 | [r-t]: https://github.com/tc39/proposal-record-tuple 89 | 90 | ## Implementations 91 | 92 | - [Firefox/SpiderMonkey](https://bugzilla.mozilla.org/show_bug.cgi?id=1729563), shipping unflagged since [Firefox 115](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/115) 93 | - [Safari/JavaScriptCore](https://bugs.webkit.org/show_bug.cgi?id=234604), shipping unflagged since [Safari Tech Preview 146](https://developer.apple.com/safari/technology-preview/release-notes/#:~:text=bug%20tracker.-,Release%20146,-Note%3A%20Tab) 94 | - [Chrome/V8](https://bugs.chromium.org/p/v8/issues/detail?id=12764), shipping unflagged since Chrome 110 95 | 96 | - [Ladybird/LibJS](https://github.com/SerenityOS/serenity/issues/16353), shipping unflagged 97 | 98 | - [core-js](https://github.com/zloirock/core-js) 99 | - [change-array-by-copy](https://github.com/zloirock/core-js#change-array-by-copy) 100 | 101 | - [es-shims](https://github.com/es-shims): 102 | - [`array.prototype.tosorted`](https://www.npmjs.com/package/array.prototype.tosorted) 103 | - [`array.prototype.toreversed`](https://www.npmjs.com/package/array.prototype.toreversed) 104 | - [`array.prototype.tospliced`](https://www.npmjs.com/package/array.prototype.tospliced) 105 | - [`array.prototype.with`](https://www.npmjs.com/package/array.prototype.with) 106 | 107 | - [./polyfill.js](./polyfill.js) (minimalist reference implementation) 108 | -------------------------------------------------------------------------------- /biblio.json: -------------------------------------------------------------------------------- 1 | { 2 | "https://tc39.es/ecma262/": [] 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-change-array-by-copy", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "prebuild": "mkdirp out/", 7 | "test": "tape ./polyfill.test.js", 8 | "test262": "git submodule init && git submodule update --depth 1 && tape ./polyfill.test262.mjs", 9 | "build": "ecmarkup spec.html out/index.html", 10 | "prestart": "npm run build", 11 | "start": "serve out/" 12 | }, 13 | "dependencies": { 14 | "ecmarkup": "^9.7.1", 15 | "mkdirp": "^1.0.4", 16 | "serve": "^11.3.2", 17 | "tape": "^5.2.2" 18 | }, 19 | "devDependencies": { 20 | "eshost": "^8.2.0", 21 | "test262-stream": "^1.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /polyfill.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Array { 3 | with(index: number, value: T): T[]; 4 | toReversed(): T[]; 5 | toSorted(compareFn?: (a: T, b: T) => number): T[]; 6 | toSpliced(start: number, deleteCount?: number, ...values: T[]): T[]; 7 | [Symbol.unscopables]: { 8 | [N in keyof typeof Array.prototype as N extends "with" ? never : N]: true; 9 | }; 10 | } 11 | 12 | interface ReadonlyArray { 13 | with(index: number, value: T): T[]; 14 | toReversed(): T[]; 15 | toSorted(compareFn?: (a: T, b: T) => number): T[]; 16 | toSpliced(start: number, deleteCount?: number, ...values: T[]): T[]; 17 | } 18 | 19 | interface Int8Array { 20 | with(index: number, value: number): this; 21 | toReversed(): this; 22 | toSorted(compareFn?: (a: number, b: number) => number): this; 23 | } 24 | 25 | interface Uint8Array { 26 | with(index: number, value: number): this; 27 | toReversed(): this; 28 | toSorted(compareFn?: (a: number, b: number) => number): this; 29 | } 30 | 31 | interface Uint8ClampedArray { 32 | with(index: number, value: number): this; 33 | toReversed(): this; 34 | toSorted(compareFn?: (a: number, b: number) => number): this; 35 | } 36 | 37 | interface Int16Array { 38 | with(index: number, value: number): this; 39 | toReversed(): this; 40 | toSorted(compareFn?: (a: number, b: number) => number): this; 41 | } 42 | 43 | interface Uint16Array { 44 | with(index: number, value: number): this; 45 | toReversed(): this; 46 | toSorted(compareFn?: (a: number, b: number) => number): this; 47 | } 48 | 49 | interface Int32Array { 50 | with(index: number, value: number): this; 51 | toReversed(): this; 52 | toSorted(compareFn?: (a: number, b: number) => number): this; 53 | } 54 | 55 | interface Uint32Array { 56 | with(index: number, value: number): this; 57 | toReversed(): this; 58 | toSorted(compareFn?: (a: number, b: number) => number): this; 59 | } 60 | 61 | interface Float32Array { 62 | with(index: number, value: number): this; 63 | toReversed(): this; 64 | toSorted(compareFn?: (a: number, b: number) => number): this; 65 | } 66 | 67 | interface Float64Array { 68 | with(index: number, value: number): this; 69 | toReversed(): this; 70 | toSorted(compareFn?: (a: number, b: number) => number): this; 71 | } 72 | 73 | interface BigInt64Array { 74 | with(index: number, value: bigint): this; 75 | toReversed(): this; 76 | toSorted(compareFn?: (a: bigint, b: bigint) => number | bigint): this; 77 | } 78 | 79 | interface BigUint64Array { 80 | with(index: number, value: bigint): this; 81 | toReversed(): this; 82 | toSorted(compareFn?: (a: bigint, b: bigint) => number | bigint): this; 83 | } 84 | } 85 | export {}; 86 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /// 3 | /// 4 | 5 | ((arrayPrototype, typedArrayPrototype) => { 6 | "use strict"; 7 | 8 | const typedArrayLength = Function.call.bind( 9 | Object.getOwnPropertyDescriptor(typedArrayPrototype, "length").get 10 | ); 11 | 12 | function toIntegerOrInfinity(arg) { 13 | let n = Number(arg); 14 | if (Number.isNaN(n) || n === 0) { 15 | return 0; 16 | } 17 | if (n === Number.POSITIVE_INFINITY) { 18 | return Number.POSITIVE_INFINITY; 19 | } 20 | if (n === Number.NEGATIVE_INFINITY) { 21 | return Number.NEGATIVE_INFINITY; 22 | } 23 | let i = Math.floor(Math.abs(n)); 24 | if (n < 0) { 25 | i = -i; 26 | } 27 | return i; 28 | } 29 | 30 | function toObject(val) { 31 | if (val === null || val === undefined) { 32 | throw new TypeError(`${val} is not an object`); 33 | } 34 | return Object(val); 35 | } 36 | 37 | function lengthOfArrayLike(arr) { 38 | if (!(typeof arr === "object" && arr !== null)) { 39 | throw new TypeError(); 40 | } 41 | let len = toIntegerOrInfinity(arr["length"]); 42 | if (!Number.isFinite(len)) { 43 | len = 0; 44 | } 45 | return Math.max(0, Math.min(len, Number.MAX_SAFE_INTEGER)); 46 | } 47 | 48 | /** @typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|BigInt64Array|BigUint64Array} TypedArray */ 49 | 50 | /** 51 | * @param {unknown} v 52 | * @returns {TypedArray} 53 | */ 54 | function assertTypedArray(v) { 55 | typedArrayPrototype.keys.call(v); 56 | // @ts-expect-error 57 | return v; 58 | } 59 | 60 | /** 61 | * @param {TypedArray} arr 62 | * @returns {TypedArray[typeof Symbol.toStringTag]} 63 | */ 64 | function typedArrayNameInternalSlot(arr) { 65 | return Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag) 66 | .get.call(arr); 67 | } 68 | 69 | /** 70 | * @param {TypedArray} example 71 | * @param {number} length 72 | * @returns {TypedArray} 73 | */ 74 | function typedArrayCreate(example, length) { 75 | assertTypedArray(example); 76 | const arrayName = typedArrayNameInternalSlot(example); 77 | switch (arrayName) { 78 | case 'Int8Array': 79 | return new Int8Array(length); 80 | case 'Uint8Array': 81 | return new Uint8Array(length); 82 | case 'Uint8ClampedArray': 83 | return new Uint8ClampedArray(length); 84 | case 'Int16Array': 85 | return new Int16Array(length); 86 | case 'Uint16Array': 87 | return new Uint16Array(length); 88 | case 'Int32Array': 89 | return new Int32Array(length); 90 | case 'Uint32Array': 91 | return new Uint32Array(length); 92 | case 'Float32Array': 93 | return new Float32Array(length); 94 | case 'Float64Array': 95 | return new Float64Array(length); 96 | case 'BigInt64Array': 97 | return new BigInt64Array(length); 98 | case 'BigUint64Array': 99 | return new BigUint64Array(length); 100 | default: 101 | /** @type {never} */ 102 | const n = arrayName; 103 | throw new Error(`Unexpected TypedArray name ${n}`); 104 | } 105 | } 106 | 107 | /** 108 | * @param {TypedArray} example 109 | * @returns {boolean} 110 | */ 111 | function isBigIntArray(example) { 112 | assertTypedArray(example); 113 | const arrayName = typedArrayNameInternalSlot(example); 114 | switch (arrayName) { 115 | case 'BigInt64Array': 116 | case 'BigUint64Array': 117 | return true; 118 | } 119 | return false; 120 | } 121 | 122 | function transfer({ count, src, srcStart, srcStep = 1, target, targetStart, targetStep = srcStep }) { 123 | let from = srcStart; 124 | let to = targetStart; 125 | for (let i = 0; i < count; i++) { 126 | target[to] = src[from]; 127 | from += srcStep; 128 | to += targetStep; 129 | } 130 | } 131 | 132 | /** 133 | * @param {TypedArray} example 134 | * @param {unknown} value 135 | * @description convert `value` to bigint or number based on the the type of array 136 | * @returns {bigint | number} 137 | * @throws if one of the override methods throws. e.g. `@@toPrimitive`, `valueOf`, `toString` 138 | */ 139 | function typedArrayNumberConversion(example, value) { 140 | let asNumber; 141 | { 142 | if (isBigIntArray(example)) { 143 | asNumber = 0n; 144 | } else { 145 | asNumber = -0; // important to use `-0` and not `0` 146 | } 147 | // @ts-ignore : using `+=` to emulate ToBigInt or ToNumber 148 | asNumber += value; 149 | } 150 | return asNumber; 151 | } 152 | 153 | defineArrayMethods({ 154 | toReversed() { 155 | const o = toObject(this); 156 | const len = lengthOfArrayLike(o); 157 | const a = new Array(len); 158 | transfer({ src: o, srcStart: len - 1, srcStep: -1, target: a, targetStart: 0, targetStep: 1, count: len }); 159 | return a; 160 | }, 161 | }); 162 | 163 | defineTypedArrayMethods({ 164 | toReversed() { 165 | const o = assertTypedArray(this); 166 | const len = typedArrayLength(o); 167 | const a = typedArrayCreate(o, len); 168 | transfer({ src: o, srcStart: len - 1, srcStep: -1, target: a, targetStart: 0, targetStep: 1, count: len }); 169 | return a; 170 | }, 171 | }); 172 | 173 | defineArrayMethods({ 174 | toSorted(compareFn) { 175 | if (compareFn !== void 0 && typeof compareFn !== "function") { 176 | throw new TypeError(); 177 | } 178 | const o = toObject(this); 179 | const len = lengthOfArrayLike(o); 180 | const a = new Array(len);; 181 | transfer({ src: o, srcStart: 0, target: a, targetStart: 0, count: len }); 182 | arrayPrototype.sort.call(a, compareFn); 183 | return a; 184 | }, 185 | }); 186 | 187 | defineTypedArrayMethods({ 188 | toSorted(compareFn) { 189 | if (compareFn !== void 0 && typeof compareFn !== "function") { 190 | throw new TypeError(); 191 | } 192 | const o = assertTypedArray(this); 193 | const len = typedArrayLength(o); 194 | const a = typedArrayCreate(o, len); 195 | transfer({ src: o, srcStart: 0, target: a, targetStart: 0, count: len }); 196 | typedArrayPrototype.sort.call(a, compareFn); 197 | return a; 198 | }, 199 | }); 200 | 201 | function calculateSplice({ start, len, deleteCount, values, argsCount }) { 202 | const relativeStart = toIntegerOrInfinity(start); 203 | let actualStart; 204 | if (relativeStart === -Infinity) { 205 | actualStart = 0; 206 | } else if (relativeStart < 0) { 207 | actualStart = Math.max(len + relativeStart, 0); 208 | } else { 209 | actualStart = Math.min(relativeStart, len); 210 | } 211 | const insertCount = values.length; 212 | let actualDeleteCount; 213 | if (/* start is not present */ argsCount === 0) { 214 | actualDeleteCount = 0; 215 | } else if (/* deleteCount is not present */ argsCount === 1) { 216 | actualDeleteCount = len - actualStart; 217 | } else { 218 | const dc = toIntegerOrInfinity(deleteCount); 219 | actualDeleteCount = Math.max(0, Math.min(dc, len - actualStart)); 220 | } 221 | const newLen = len + insertCount - actualDeleteCount; 222 | return { actualStart, newLen, actualDeleteCount }; 223 | } 224 | 225 | function doSplice({ src, target, actualStart, actualDeleteCount, values, newLen }) { 226 | let i = 0; 227 | while (i < actualStart) { 228 | target[i] = src[i]; 229 | i++; 230 | } 231 | for (const E of values) { 232 | target[i] = E; 233 | i++; 234 | } 235 | let r = actualStart + actualDeleteCount; 236 | while (i < newLen) { 237 | let fromValue = src[r]; 238 | target[i] = fromValue; 239 | i++; 240 | r++; 241 | } 242 | } 243 | 244 | defineArrayMethods({ 245 | toSpliced(start, deleteCount, ...values) { 246 | const o = toObject(this); 247 | const len = lengthOfArrayLike(o); 248 | const { actualStart, actualDeleteCount, newLen } = calculateSplice({ start, deleteCount, len, values, argsCount: arguments.length }); 249 | if (newLen > Number.MAX_SAFE_INTEGER) { 250 | throw new TypeError(); 251 | } 252 | const a = new Array(newLen); 253 | doSplice({ src: o, target: a, actualStart, actualDeleteCount, values, newLen }); 254 | return a; 255 | } 256 | }); 257 | 258 | defineArrayMethods({ 259 | with(index, value) { 260 | const o = toObject(this); 261 | const len = lengthOfArrayLike(o); 262 | const relativeIndex = toIntegerOrInfinity(index); 263 | const actualIndex = relativeIndex < 0 ? len + relativeIndex : relativeIndex; 264 | if (actualIndex < 0 || actualIndex >= len) { 265 | throw new RangeError(); 266 | } 267 | const a = new Array(len); 268 | for (let k = 0; k < len; k++) { 269 | const v = k === actualIndex ? value : o[k]; 270 | a[k] = v; 271 | } 272 | return a; 273 | } 274 | }); 275 | 276 | defineTypedArrayMethods({ 277 | with(index, value) { 278 | const o = assertTypedArray(this); 279 | const len = typedArrayLength(o); 280 | const relativeIndex = toIntegerOrInfinity(index); 281 | const actualIndex = relativeIndex < 0 ? len + relativeIndex : relativeIndex; 282 | const asNumber = typedArrayNumberConversion(o, value); 283 | if (actualIndex < 0 || actualIndex >= len) { 284 | throw new RangeError(); 285 | } 286 | const a = typedArrayCreate(o, len); 287 | for (let k = 0; k < len; k++) { 288 | const v = k === actualIndex ? asNumber : o[k]; 289 | a[k] = v; 290 | } 291 | return a; 292 | } 293 | }); 294 | 295 | /** @type {(def: { [N in "with" | "toReversed" | "toSorted" | "toSpliced"]?: typeof Array.prototype[N] }) => void} */ 296 | function defineArrayMethods(def) { 297 | defineMethods(arrayPrototype, def).forEach(name => { 298 | if (name !== 'with') { // 'with' is already a keyword 299 | arrayPrototype[Symbol.unscopables][name] = true; 300 | } 301 | }); 302 | } 303 | 304 | /** @type {(def: { [N in "with" | "toReversed" | "toSorted"]?: (this: TypedArray, ...args: Parameters) => TypedArray }) => void} */ 305 | function defineTypedArrayMethods(def) { 306 | defineMethods(typedArrayPrototype, def); 307 | } 308 | 309 | function defineMethods(obj, def) { 310 | return Object.entries(def).map(([name, method]) => { 311 | Object.defineProperty(obj, name, { 312 | value: method, 313 | enumerable: false, 314 | configurable: true, 315 | writable: true, 316 | }); 317 | return name; 318 | }); 319 | } 320 | })(Array.prototype, Object.getPrototypeOf(Int8Array.prototype)); 321 | -------------------------------------------------------------------------------- /polyfill.test.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | require("./polyfill.js"); 3 | const tape = require("tape"); 4 | 5 | tape("Array.prototype.toReversed works with array-like values", (t) => { 6 | const orig = { 7 | 0: 3, 8 | 1: 2, 9 | 2: 1, 10 | length: 3 11 | }; 12 | const expected = [1, 2, 3]; 13 | 14 | const copy = Array.prototype.toReversed.call(orig); 15 | 16 | t.deepEqual(copy, expected); 17 | t.notEqual(orig, copy); 18 | t.notDeepEqual(orig, copy); 19 | t.end(); 20 | }); 21 | 22 | tape("Array.prototype.toReversed", (t) => { 23 | const orig = [3, 2, 1]; 24 | const expected = [1, 2, 3]; 25 | 26 | const copy = orig.toReversed(); 27 | 28 | t.deepEqual(copy, expected); 29 | t.notEqual(orig, copy); 30 | t.notDeepEqual(orig, copy); 31 | t.end(); 32 | }); 33 | 34 | tape("Array.prototype.toSorted", (t) => { 35 | const orig = [3, 1, 2]; 36 | const expected = [1, 2, 3]; 37 | 38 | const copy = orig.toSorted(); 39 | 40 | t.deepEqual(copy, expected); 41 | t.notEqual(orig, copy); 42 | t.notDeepEqual(orig, copy); 43 | t.end(); 44 | }); 45 | 46 | tape("Array.prototype.toSorted(compareFn)", (t) => { 47 | const orig = [3, 1, 2]; 48 | const expected = [3, 2, 1]; 49 | function compareFn(a, b) { 50 | return a > b ? -1 : 1; 51 | } 52 | 53 | const copy = orig.toSorted(compareFn); 54 | 55 | t.deepEqual(copy, expected); 56 | t.notEqual(orig, copy); 57 | t.notDeepEqual(orig, copy); 58 | t.end(); 59 | }); 60 | 61 | tape("Array.prototype.toSpliced", (t) => { 62 | const orig = [1, -1, 0, -1, 4]; 63 | const expected = [1, 2, 3, 4]; 64 | const idx = 1; 65 | const delNum = 3; 66 | const ins = [2, 3]; 67 | 68 | const copy = orig.toSpliced(idx, delNum, ...ins); 69 | 70 | t.deepEqual(copy, expected); 71 | t.notEqual(orig, copy); 72 | t.notDeepEqual(orig, copy); 73 | t.end(); 74 | }); 75 | 76 | tape("Array.prototype.with", (t) => { 77 | const orig = [1, 1, 3]; 78 | const expected = [1, 2, 3]; 79 | const idx = 1; 80 | const val = 2; 81 | 82 | const copy = orig.with(idx, val); 83 | 84 | t.deepEqual(copy, expected); 85 | t.notEqual(orig, copy); 86 | t.notDeepEqual(orig, copy); 87 | t.end(); 88 | }); 89 | 90 | tape(`Array.prototype.with negativeIndex`, (t) => { 91 | const orig = [1, 2, 2]; 92 | const expected = [1, 2, 3]; 93 | const idx = -1; 94 | const val = 3; 95 | 96 | const copy = orig.with(idx, val); 97 | 98 | t.deepEqual(copy, expected); 99 | t.notEqual(orig, copy); 100 | t.notDeepEqual(orig, copy); 101 | t.end(); 102 | }); 103 | 104 | tape(`Array.prototype.with out of bounds`, (t) => { 105 | const orig = [1, 2, 2]; 106 | const idx = 3; 107 | const val = 4; 108 | 109 | t.throws(() => { 110 | orig.with(idx, val); 111 | }, RangeError); 112 | 113 | t.end(); 114 | }); 115 | 116 | tape(`Array does not use Symbol.species for the new methods`, (t) => { 117 | class SubClass extends Array { } 118 | 119 | const orig = new SubClass([1, 2, 3]); 120 | 121 | function assertType(arr) { 122 | t.equal(arr instanceof SubClass, false); 123 | t.equal(arr instanceof Array, true); 124 | } 125 | 126 | assertType(orig.with(0, 0)); 127 | assertType(orig.toReversed()); 128 | assertType(orig.toSorted()); 129 | assertType(orig.toSpliced(0, 0)); 130 | 131 | t.end(); 132 | }); 133 | 134 | tape("Array.prototype[Symbol.unscopables]", (t) => { 135 | const methodNames = ['toReversed', 'toSorted', 'toSpliced']; // 'with' is omitted as it is a keyword 136 | t.plan(6); 137 | 138 | // ensure we are checking the correct methods names, otherwise test will always pass, regardless of Symbol.unscopables 139 | for (const method of methodNames) { 140 | t.equal(typeof Array.prototype[method], 'function'); 141 | } 142 | 143 | const marker = Symbol(); 144 | const toReversed = marker; 145 | const toSorted = marker; 146 | const toSpliced = marker; 147 | 148 | // @ts-expect-error: 'with' is unsupported 149 | with ([]) { 150 | for (const method of methodNames) { 151 | t.equal(eval(method), marker); 152 | } 153 | } 154 | t.end(); 155 | }); 156 | 157 | [ 158 | Int8Array, 159 | Uint8Array, 160 | Uint8ClampedArray, 161 | Int16Array, 162 | Uint16Array, 163 | Int32Array, 164 | Uint32Array, 165 | Float32Array, 166 | Float64Array 167 | ].forEach((TypedArray) => { 168 | tape(`${TypedArray.name}.prototype.toReversed`, (t) => { 169 | const orig = new TypedArray([3, 2, 1]); 170 | const expected = new TypedArray([1, 2, 3]); 171 | 172 | const copy = orig.toReversed(); 173 | 174 | t.deepEqual(copy, expected); 175 | t.notEqual(orig, copy); 176 | t.notDeepEqual(orig, copy); 177 | t.end(); 178 | }); 179 | 180 | tape(`${TypedArray.name}.prototype.toSorted`, (t) => { 181 | const orig = new TypedArray([3, 1, 2]); 182 | const expected = new TypedArray([1, 2, 3]); 183 | 184 | const copy = orig.toSorted(); 185 | 186 | t.deepEqual(copy, expected); 187 | t.notEqual(orig, copy); 188 | t.notDeepEqual(orig, copy); 189 | t.end(); 190 | }); 191 | 192 | tape(`${TypedArray.name}.prototype.toSorted(compareFn)`, (t) => { 193 | const orig = new TypedArray([3, 1, 2]); 194 | const expected = new TypedArray([3, 2, 1]); 195 | function compareFn(a, b) { 196 | return a > b ? -1 : 1; 197 | } 198 | 199 | const copy = orig.toSorted(compareFn); 200 | 201 | t.deepEqual(copy, expected); 202 | t.notEqual(orig, copy); 203 | t.notDeepEqual(orig, copy); 204 | t.end(); 205 | }); 206 | 207 | tape(`${TypedArray.name}.prototype.with`, (t) => { 208 | const orig = new TypedArray([1, 1, 3]); 209 | const expected = new TypedArray([1, 2, 3]); 210 | const idx = 1; 211 | const val = 2; 212 | 213 | const copy = orig.with(idx, val); 214 | 215 | t.deepEqual(copy, expected); 216 | t.notEqual(orig, copy); 217 | t.notDeepEqual(orig, copy); 218 | t.end(); 219 | }); 220 | 221 | tape(`${TypedArray.name}.prototype.with negativeIndex`, (t) => { 222 | const orig = new TypedArray([1, 2, 2]); 223 | const expected = new TypedArray([1, 2, 3]); 224 | const idx = -1; 225 | const val = 3; 226 | 227 | const copy = orig.with(idx, val); 228 | 229 | t.deepEqual(copy, expected); 230 | t.notEqual(orig, copy); 231 | t.notDeepEqual(orig, copy); 232 | t.end(); 233 | }); 234 | 235 | tape(`${TypedArray.name}.prototype.with out of bounds`, (t) => { 236 | const orig = new TypedArray([1, 2, 2]); 237 | const idx = 3; 238 | const val = 4; 239 | 240 | t.throws(() => { 241 | orig.with(idx, val); 242 | }, RangeError); 243 | 244 | t.end(); 245 | }); 246 | 247 | tape(`${TypedArray.name}.prototype.with executes 'user code' before starting copy`, (t) => { 248 | const orig = new TypedArray([1, 2, 3]); 249 | const idx = 1; 250 | const valueUserCodeWillInsert = 4; 251 | const userCodeReturnValue = 5; 252 | const expected = new TypedArray([valueUserCodeWillInsert, userCodeReturnValue, 3]); 253 | let userCodeExecuted = false; 254 | /** @type any */ 255 | const val = { 256 | valueOf() { 257 | userCodeExecuted = true; 258 | orig[0] = valueUserCodeWillInsert; 259 | return userCodeReturnValue; 260 | } 261 | }; 262 | 263 | const copy = orig.with(idx, val); 264 | t.assert(userCodeExecuted); 265 | t.deepEqual(copy, expected); 266 | 267 | t.end(); 268 | }); 269 | 270 | tape(`${TypedArray.name} does not use Symbol.species for the new methods`, (t) => { 271 | class SubClass extends TypedArray { } 272 | 273 | function assertType(arr) { 274 | t.equal(arr instanceof SubClass, false); 275 | t.equal(arr instanceof TypedArray, true); 276 | } 277 | 278 | /** @type {Uint8Array} */ 279 | // @ts-ignore 280 | const orig = new SubClass([1, 2, 3]); 281 | 282 | assertType(orig.with(0, 0)); 283 | assertType(orig.toReversed()); 284 | assertType(orig.toSorted()); 285 | 286 | t.end(); 287 | }); 288 | }); 289 | 290 | [ 291 | BigInt64Array, 292 | BigUint64Array 293 | ].forEach((BigIntArray) => { 294 | tape(`${BigIntArray.name}.prototype.toReversed`, (t) => { 295 | const orig = new BigIntArray([3n, 2n, 1n]); 296 | const expected = new BigIntArray([1n, 2n, 3n]); 297 | 298 | const copy = orig.toReversed(); 299 | 300 | t.deepEqual(copy, expected); 301 | t.notEqual(orig, copy); 302 | t.notDeepEqual(orig, copy); 303 | t.end(); 304 | }); 305 | 306 | tape(`${BigIntArray.name}.prototype.toSorted`, (t) => { 307 | const orig = new BigIntArray([3n, 1n, 2n]); 308 | const expected = new BigIntArray([1n, 2n, 3n]); 309 | 310 | const copy = orig.toSorted(); 311 | 312 | t.deepEqual(copy, expected); 313 | t.notEqual(orig, copy); 314 | t.notDeepEqual(orig, copy); 315 | t.end(); 316 | }); 317 | 318 | tape(`${BigIntArray.name}.prototype.toSorted(compareFn)`, (t) => { 319 | const orig = new BigIntArray([3n, 1n, 2n]); 320 | const expected = new BigIntArray([3n, 2n, 1n]); 321 | function compareFn(a, b) { 322 | return a > b ? -1 : 1; 323 | } 324 | 325 | const copy = orig.toSorted(compareFn); 326 | 327 | t.deepEqual(copy, expected); 328 | t.notEqual(orig, copy); 329 | t.notDeepEqual(orig, copy); 330 | t.end(); 331 | }); 332 | 333 | tape(`${BigIntArray.name}.prototype.with`, (t) => { 334 | const orig = new BigIntArray([1n, 1n, 3n]); 335 | const expected = new BigIntArray([1n, 2n, 3n]); 336 | const idx = 1; 337 | const val = 2n; 338 | 339 | const copy = orig.with(idx, val); 340 | 341 | t.deepEqual(copy, expected); 342 | t.notEqual(orig, copy); 343 | t.notDeepEqual(orig, copy); 344 | t.end(); 345 | }); 346 | 347 | tape(`${BigIntArray.name}.prototype.with non bigint throws`, (t) => { 348 | const orig = new BigIntArray([1n, 2n, 2n]); 349 | const idx = 3; 350 | const val = 4; 351 | 352 | t.throws(() => { 353 | // @ts-expect-error inserting number instead of bigint 354 | orig.with(idx, val); 355 | }, TypeError); 356 | 357 | t.end(); 358 | }); 359 | 360 | tape(`${BigIntArray.name}.prototype.with negativeIndex`, (t) => { 361 | const orig = new BigIntArray([1n, 2n, 2n]); 362 | const expected = new BigIntArray([1n, 2n, 3n]); 363 | const idx = -1; 364 | const val = 3n; 365 | 366 | const copy = orig.with(idx, val); 367 | 368 | t.deepEqual(copy, expected); 369 | t.notEqual(orig, copy); 370 | t.notDeepEqual(orig, copy); 371 | t.end(); 372 | }); 373 | 374 | tape(`${BigIntArray.name}.prototype.with out of bounds`, (t) => { 375 | const orig = new BigIntArray([1n, 2n, 2n]); 376 | const idx = 3; 377 | const val = 4n; 378 | 379 | t.throws(() => { 380 | orig.with(idx, val); 381 | }, RangeError); 382 | 383 | t.end(); 384 | }); 385 | 386 | tape(`${BigIntArray.name}.prototype.with executes 'user code' before starting copy`, (t) => { 387 | const orig = new BigIntArray([1n, 2n, 3n]); 388 | const idx = 1; 389 | const valueUserCodeWillInsert = 4n; 390 | const userCodeReturnValue = 5n; 391 | const expected = new BigIntArray([valueUserCodeWillInsert, userCodeReturnValue, 3n]); 392 | let userCodeExecuted = false; 393 | /** @type any */ 394 | const val = { 395 | valueOf() { 396 | userCodeExecuted = true; 397 | orig[0] = valueUserCodeWillInsert; 398 | return userCodeReturnValue; 399 | } 400 | }; 401 | 402 | const copy = orig.with(idx, val); 403 | t.assert(userCodeExecuted); 404 | t.deepEqual(copy, expected); 405 | 406 | t.end(); 407 | }); 408 | 409 | tape(`${BigIntArray.name} does not use Symbol.species for the new methods`, (t) => { 410 | class SubClass extends BigIntArray { } 411 | 412 | function assertType(arr) { 413 | t.equal(arr instanceof SubClass, false); 414 | t.equal(arr instanceof BigIntArray, true); 415 | } 416 | 417 | /** @type {BigInt64Array} */ 418 | // @ts-ignore 419 | const orig = new SubClass([1n, 2n, 3n]); 420 | 421 | assertType(orig.with(0, 0n)); 422 | assertType(orig.toReversed()); 423 | assertType(orig.toSorted()); 424 | 425 | t.end(); 426 | }); 427 | }); 428 | -------------------------------------------------------------------------------- /polyfill.test262.mjs: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import tape from "tape"; 3 | import Test262Stream from "test262-stream"; 4 | import NodeAgent from "eshost/lib/agents/node.js"; 5 | import { readFileSync } from "fs"; 6 | 7 | const POLYFILL = readFileSync( 8 | new URL("./polyfill.js", import.meta.url), 9 | "utf-8" 10 | ); 11 | 12 | const agent = new NodeAgent({ 13 | hostPath: process.argv0, 14 | transform(source) { 15 | return POLYFILL + ";\n" + source; 16 | }, 17 | }); 18 | 19 | const testsPaths = [ 20 | ["Array", "toReversed"], 21 | ["Array", "toSorted"], 22 | ["Array", "toSpliced"], 23 | ["Array", "with"], 24 | ["Array", "Symbol.unscopables/change-array-by-copy.js"], 25 | ["TypedArray", "toReversed"], 26 | ["TypedArray", "toSorted"], 27 | ["TypedArray", "with"], 28 | ].map( 29 | ([constructor, method]) => `test/built-ins/${constructor}/prototype/${method}` 30 | ); 31 | 32 | const tests = new Test262Stream( 33 | new URL("./test262", import.meta.url).pathname, 34 | { paths: testsPaths } 35 | ); 36 | 37 | for await (const test of tests) { 38 | tape(`[${test.file}] ${test.attrs.description}`, async (t) => { 39 | const result = await timeout(30_000, agent.evalScript(test)); 40 | if (result.error) { 41 | t.fail(Object.assign(new Error(), result.error)); 42 | } else { 43 | t.pass("PASSED"); 44 | } 45 | t.end(); 46 | }); 47 | } 48 | 49 | function timeout(time, promise) { 50 | return new Promise((resolve, reject) => { 51 | const timeout = setTimeout(() => reject(new Error("Timed out")), time); 52 | promise.finally(() => clearTimeout(timeout)); 53 | promise.then(resolve, reject); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 |

Indexed Collections

14 | 15 | 16 |

Array Objects

17 | 18 | 19 |

Properties of the Array Prototype Object

20 | 21 | 22 |

Array.prototype.sort ( _comparefn_ )

23 | 24 |

When the `sort` method is called, the following steps are taken:

25 | 26 | 1. [id="step-array-sort-comparefn"] If _comparefn_ is not *undefined* and IsCallable(_comparefn_) is *false*, throw a *TypeError* exception. 27 | 1. Let _obj_ be ? ToObject(*this* value). 28 | 1. [id="step-array-sort-len"] Let _len_ be ? LengthOfArrayLike(_obj_). 29 | 1. Let _SortCompare_ be a new Abstract Closure with parameters (_x_, _y_) that captures _comparefn_ and performs the following steps when called: 30 | 1. If _x_ and _y_ are both *undefined*, return *+0*𝔽. 31 | 1. If _x_ is *undefined*, return *1*𝔽. 32 | 1. If _y_ is *undefined*, return *-1*𝔽. 33 | 1. If _comparefn_ is not *undefined*, then 34 | 1. Let _v_ be ? ToNumber(? Call(_comparefn_, *undefined*, « _x_, _y_ »)). 35 | 1. If _v_ is *NaN*, return *+0*𝔽. 36 | 1. Return _v_. 37 | 1. [id="step-sortcompare-tostring-x"] Let _xString_ be ? ToString(_x_). 38 | 1. [id="step-sortcompare-tostring-y"] Let _yString_ be ? ToString(_y_). 39 | 1. Let _xSmaller_ be ! IsLessThan(_xString_, _yString_, *true*). 40 | 1. If _xSmaller_ is *true*, return *-1*𝔽. 41 | 1. Let _ySmaller_ be ! IsLessThan(_yString_, _xString_, *true*). 42 | 1. If _ySmaller_ is *true*, return *1*𝔽. 43 | 1. Return *+0*𝔽. 44 | 1. Return ? CompareArrayElements(_x_, _y_, _comparefn_). 45 | 1. Return ? SortIndexedProperties(_obj_, _len_, _SortCompare_). 46 | 1. [id="step-array-sortindexedproperties"] Let _sortedList_ be ? SortIndexedProperties(_obj_, _len_, _SortCompare_, *true*). 47 | 1. Let _itemCount_ be the number of elements in _sortedList_. 48 | 1. Let _j_ be 0. 49 | 1. Repeat, while _j_ < _itemCount_, 50 | 1. Perform ? Set(_obj_, ! ToString(𝔽(_j_)), _sortedList_[_j_], true). 51 | 1. Set _j_ to _j_ + 1. 52 | 1. NOTE: The call to SortIndexedProperties in step has the *skipHoles* parameter set to *true*. The remaining indexes are deleted to preserve the number of holes that were detected and excluded from the sort. 53 | 1. Repeat, while _j_ < _len_, 54 | 1. Perform ? DeletePropertyOrThrow(_obj_, ! ToString(𝔽(_j_))). 55 | 1. Set _j_ to _j_ + 1. 56 | 1. Return _obj_. 57 | 58 |
59 | 60 | 61 |

62 | CompareArrayElements( 63 | _x_: unknown, 64 | _y_: unknown, 65 | _comparefn_: a function object or *undefined* 66 | ): either a normal completion containing a Number or an abrupt completion 67 |

68 |
69 |
70 | 71 | 1. If _x_ and _y_ are both *undefined*, return *+0*𝔽. 72 | 1. If _x_ is *undefined*, return *1*𝔽. 73 | 1. If _y_ is *undefined*, return *-1*𝔽. 74 | 1. If _comparefn_ is not *undefined*, then 75 | 1. Let _v_ be ? ToNumber(? Call(_comparefn_, *undefined*, « _x_, _y_ »)). 76 | 1. If _v_ is *NaN*, return *+0*𝔽. 77 | 1. Return _v_. 78 | 1. [id="step-sortcompare-tostring-x"] Let _xString_ be ? ToString(_x_). 79 | 1. [id="step-sortcompare-tostring-y"] Let _yString_ be ? ToString(_y_). 80 | 1. Let _xSmaller_ be ! IsLessThan(_xString_, _yString_, *true*). 81 | 1. If _xSmaller_ is *true*, return *-1*𝔽. 82 | 1. Let _ySmaller_ be ! IsLessThan(_yString_, _xString_, *true*). 83 | 1. If _ySmaller_ is *true*, return *1*𝔽. 84 | 1. Return *+0*𝔽. 85 | 86 |
87 | 88 | 89 |

90 | SortIndexedProperties ( 91 | _obj_: an Object, 92 | _len_: a non-negative integer, 93 | _SortCompare_: an Abstract Closure with two parameters, 94 | _skipHoles_: a Boolean 95 | ): either a normal completion containing an Objecta List or an abrupt completion 96 |

97 |
98 |
99 | 100 | 1. Let _items_ be a new empty List. 101 | 1. Let _k_ be 0. 102 | 1. Repeat, while _k_ < _len_, 103 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 104 | 1. Let _kPresent_ be ? HasProperty(_obj_, _Pk_). 105 | 1. If _skipHoles_ is *true*, then 106 | 1. Let _kRead_ be ? HasProperty(_obj_, _Pk_). 107 | 1. Else, 108 | 1. Let _kRead_ be *true*. 109 | 1. If _kPresent__kRead_ is *true*, then 110 | 1. Let _kValue_ be ? Get(_obj_, _Pk_). 111 | 1. Append _kValue_ to _items_. 112 | 1. Set _k_ to _k_ + 1. 113 | 1. Let _itemCount_ be the number of elements in _items_. 114 | 1. [id="step-array-sort"] Sort _items_ using an implementation-defined sequence of calls to _SortCompare_. If any such call returns an abrupt completion, stop before performing any further calls to _SortCompare_ or steps in this algorithm and return that Completion Record. 115 | 1. Let _j_ be 0. 116 | 1. Repeat, while _j_ < _itemCount_, 117 | 1. Perform ? Set(_obj_, ! ToString(𝔽(_j_)), _items_[_j_], *true*). 118 | 1. Set _j_ to _j_ + 1. 119 | 1. Repeat, while _j_ < _len_, 120 | 1. Perform ? DeletePropertyOrThrow(_obj_, ! ToString(𝔽(_j_))). 121 | 1. Set _j_ to _j_ + 1. 122 | 1. Return _obj_. 123 | 1. Return _items_. 124 | 125 |
126 | 127 | 128 |

Array.prototype.toReversed ( )

129 | 130 |

When the *toReversed* method is called, the following steps are taken:

131 | 132 | 133 | 1. Let _O_ be ? ToObject(*this* value). 134 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 135 | 1. Let _A_ be ? ArrayCreate(𝔽(_len_)). 136 | 1. Let _k_ be 0. 137 | 1. Repeat, while _k_ < _len_, 138 | 1. Let _from_ be ! ToString(𝔽(_len_ - _k_ - 1)). 139 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 140 | 1. Let _fromValue_ be ? Get(_O_, _from_). 141 | 1. Perform ! CreateDataPropertyOrThrow(_A_, _Pk_, _fromValue_). 142 | 1. Set _k_ to _k_ + 1. 143 | 1. Return _A_. 144 | 145 |
146 | 147 | 148 |

Array.prototype.toSorted ( _comparefn_ )

149 | 150 |

When the *toSorted* method is called, the following steps are taken:

151 | 152 | 153 | 1. If _comparefn_ is not *undefined* and IsCallable(_comparefn_) is *false*, throw a *TypeError* exception. 154 | 1. Let _O_ be ? ToObject(*this* value). 155 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 156 | 1. Let _A_ be ? ArrayCreate(𝔽(_len_)). 157 | 1. Let _SortCompare_ be a new Abstract Closure with parameters (_x_, _y_) that captures _comparefn_ and performs the following steps when called: 158 | 1. Return ? CompareArrayElements(_x_, _y_, _comparefn_). 159 | 1. Let _sortedList_ be ? SortIndexedProperties(_obj_, _len_, _SortCompare_, *false*). 160 | 1. Let _j_ be 0. 161 | 1. Repeat, while _j_ < _len_, 162 | 1. Perform ! CreateDataPropertyOrThrow(_A_, ! ToString(𝔽(_j_)), _sortedList_[_j_]). 163 | 1. Set _j_ to _j_ + 1. 164 | 1. Return _A_. 165 | 166 |
167 | 168 | 169 |

Array.prototype.toSpliced ( _start_, _deleteCount_, ..._items_ )

170 | 171 |

When the *toSpliced* method is called, the following steps are taken:

172 | 173 | 174 | 1. Let _O_ be ? ToObject(*this* value). 175 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 176 | 1. Let _relativeStart_ be ? ToIntegerOrInfinity(_start_). 177 | 1. If _relativeStart_ is -∞, let _actualStart_ be 0. 178 | 1. Else if _relativeStart_ < 0, let _actualStart_ be max(_len_ + _relativeStart_, 0). 179 | 1. Else, let _actualStart_ be min(_relativeStart_, _len_). 180 | 1. Let _insertCount_ be the number of elements in _items_. 181 | 1. If _start_ is not present, then 182 | 1. Let _actualDeleteCount_ be 0. 183 | 1. Else if _deleteCount_ is not present, then 184 | 1. Let _actualDeleteCount_ be _len_ - _actualStart_. 185 | 1. Else, 186 | 1. Let _dc_ be ? ToIntegerOrInfinity(_deleteCount_). 187 | 1. Let _actualDeleteCount_ be the result of clamping _dc_ between 0 and _len_ - _actualStart_. 188 | 1. Let _newLen_ be _len_ + _insertCount_ - _actualDeleteCount_. 189 | 1. If _newLen_ > 253 - 1, throw a *TypeError* exception. 190 | 1. Let _A_ be ? ArrayCreate(𝔽(_newLen_)). 191 | 1. Let _i_ be 0. 192 | 1. Let _r_ be _actualStart_ + _actualDeleteCount_. 193 | 1. Repeat, while _i_ < _actualStart_, 194 | 1. Let _Pi_ be ! ToString(𝔽(_i_)). 195 | 1. Let _iValue_ be ? Get(_O_, _Pi_). 196 | 1. Perform ! CreateDataPropertyOrThrow(_A_, _Pi_, _iValue_). 197 | 1. Set _i_ to _i_ + 1. 198 | 1. For each element _E_ of _items_, do 199 | 1. Let _Pi_ be ! ToString(𝔽(_i_)). 200 | 1. Perform ! CreateDataPropertyOrThrow(_A_, _Pi_, _E_). 201 | 1. Set _i_ to _i_ + 1. 202 | 1. Repeat, while _i_ < _newLen_, 203 | 1. Let _Pi_ be ! ToString(𝔽(_i_)). 204 | 1. Let _from_ be ! ToString(𝔽(_r_)). 205 | 1. Let _fromValue_ be ? Get(_O_, _from_). 206 | 1. Perform ! CreateDataPropertyOrThrow(_A_, _Pi_, _fromValue_). 207 | 1. Set _i_ to _i_ + 1. 208 | 1. Set _r_ to _r_ + 1. 209 | 1. Return _A_. 210 | 211 |
212 | 213 | 214 |

Array.prototype.with ( _index_, _value_ )

215 | 216 |

When the *with* method is called, the following steps are taken:

217 | 218 | 219 | 1. Let _O_ be ? ToObject(*this* value). 220 | 1. Let _len_ be ? LengthOfArrayLike(_O_). 221 | 1. Let _relativeIndex_ be ? ToIntegerOrInfinity(_index_). 222 | 1. If _relativeIndex_ ≥ 0, let _actualIndex_ be _relativeIndex_. 223 | 1. Else, let _actualIndex_ be _len_ + _relativeIndex_. 224 | 1. If _actualIndex_ ≥ _len_ or _actualIndex_ < 0, throw a *RangeError* exception. 225 | 1. Let _A_ be ? ArrayCreate(𝔽(_len_)). 226 | 1. Let _k_ be 0. 227 | 1. Repeat, while _k_ < _len_, 228 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 229 | 1. If _k_ is _actualIndex_, let _fromValue_ be _value_. 230 | 1. Else, let _fromValue_ be ? Get(_O_, _Pk_). 231 | 1. Perform ! CreateDataPropertyOrThrow(_A_, _Pk_, _fromValue_). 232 | 1. Set _k_ to _k_ + 1. 233 | 1. Return _A_. 234 | 235 |
236 | 237 | 238 |

Array.prototype [ @@unscopables ]

239 |

The initial value of the @@unscopables data property is an object created by the following steps:

240 | 241 | 1. Let _unscopableList_ be ! OrdinaryObjectCreate(*null*). 242 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"at"*, *true*). 243 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"copyWithin"*, *true*). 244 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"entries"*, *true*). 245 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"fill"*, *true*). 246 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"find"*, *true*). 247 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"findIndex"*, *true*). 248 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"flat"*, *true*). 249 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"flatMap"*, *true*). 250 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"includes"*, *true*). 251 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"keys"*, *true*). 252 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"toReversed"*, *true*). 253 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"toSorted"*, *true*). 254 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"toSpliced"*, *true*). 255 | 1. Perform ! CreateDataPropertyOrThrow(_unscopableList_, *"values"*, *true*). 256 | 1. Return _unscopableList_. 257 | 258 |

This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.

259 | 260 |

The reason that *"with"* is not included in the _unscopableList_ is because it is already a reserved word.

261 |
262 |
263 |
264 |
265 | 266 | 267 |

TypedArray Objects

268 | 269 | 270 |

Abstract Operations for TypedArray Objects

271 | 272 | 273 |

274 | TypedArrayCreateSameType ( 275 | _exemplar_: a TypedArray, 276 | _argumentList_: unknown, 277 | ) 278 |

279 |
280 |
description
281 |
It is used to specify the creation of a new TypedArray using a constructor function that is derived from _exemplar_. Unlike TypedArraySpeciesCreate, which can construct custom TypedArray subclasses through the use of @@species, this operation always uses one of the built-in TypedArray constructors.
282 |
283 | 284 | 1. Assert: _exemplar_ is an Object that has [[TypedArrayName]] and [[ContentType]] internal slots. 285 | 1. Let _constructor_ be the intrinsic object listed in column one of for _exemplar_.[[TypedArrayName]]. 286 | 1. Let _result_ be ? TypedArrayCreate(_constructor_, _argumentList_). 287 | 1. Assert: _result_ has [[TypedArrayName]] and [[ContentType]] internal slots. 288 | 1. Assert: _result_.[[ContentType]] is _exemplar_.[[ContentType]]. 289 | 1. Return _result_. 290 | 291 |
292 |
293 | 294 | 295 |

The %TypedArray% Intrinsic Object

296 | 297 | 298 |

Properties of the %TypedArray% Prototype Object

299 | 300 | 301 |

%TypedArray%.prototype.sort ( _comparefn_ )

302 | 303 |

The following steps are performed:

304 | 305 | 1. If _comparefn_ is not *undefined* and IsCallable(_comparefn_) is *false*, throw a *TypeError* exception. 306 | 1. Let _obj_ be the *this* value. 307 | 1. Perform ? ValidateTypedArray(_obj_). 308 | 1. Let _len_ be _obj_.[[ArrayLength]]. 309 | 1. NOTE: The following closure performs a numeric comparison rather than the string comparison used in . 310 | 1. Let _SortCompare_ be a new Abstract Closure with parameters (_x_, _y_) that captures _comparefn_ and performs the following steps when called: 311 | 1. Assert: _x_ is a Number and _y_ is a Number, or _x_ is a BigInt and _y_ is a BigInt. 312 | 1. If _comparefn_ is not *undefined*, then 313 | 1. Let _v_ be ? ToNumber(? Call(_comparefn_, *undefined*, « _x_, _y_ »)). 314 | 1. If _v_ is *NaN*, return *+0*𝔽. 315 | 1. Return _v_. 316 | 1. If _x_ and _y_ are both *NaN*, return *+0*𝔽. 317 | 1. If _x_ is *NaN*, return *1*𝔽. 318 | 1. If _y_ is *NaN*, return *-1*𝔽. 319 | 1. If _x_ < _y_, return *-1*𝔽. 320 | 1. If _x_ > _y_, return *1*𝔽. 321 | 1. If _x_ is *-0*𝔽 and _y_ is *+0*𝔽, return *-1*𝔽. 322 | 1. If _x_ is *+0*𝔽 and _y_ is *-0*𝔽, return *1*𝔽. 323 | 1. Return *+0*𝔽. 324 | 1. Return ? CompareTypedArrayElements(_x_, _y_, _comparefn_). 325 | 1. Return ? SortIndexedProperties(_obj_, _len_, _SortCompare_). 326 | 1. Let _sortedList_ be ? SortIndexedProperties(_obj_, _len_, _SortCompare_, *false*). 327 | 1. Let _j_ be 0. 328 | 1. Repeat, while _j_ < _len_, 329 | 1. Perform ! Set(_obj_, ! ToString(𝔽(_j_)), _sortedList_[_j_], *true*). 330 | 1. Set _j_ to _j_ + 1. 331 | 1. Return _obj_. 332 | 333 |
334 | 335 | 336 |

337 | CompareTypedArrayElements( 338 | _x_: unknown, 339 | _y_: unknown, 340 | _comparefn_: a function object or *undefined*, 341 | ): either a normal completion containing a Number or an abrupt completion 342 |

343 |
344 |
345 | 346 | 1. Assert: _x_ is a Number and _y_ is a Number, or _x_ is a BigInt and _y_ is a BigInt. 347 | 1. If _comparefn_ is not *undefined*, then 348 | 1. Let _v_ be ? ToNumber(? Call(_comparefn_, *undefined*, « _x_, _y_ »)). 349 | 1. If _v_ is *NaN*, return *+0*𝔽. 350 | 1. Return _v_. 351 | 1. If _x_ and _y_ are both *NaN*, return *+0*𝔽. 352 | 1. If _x_ is *NaN*, return *1*𝔽. 353 | 1. If _y_ is *NaN*, return *-1*𝔽. 354 | 1. If _x_ < _y_, return *-1*𝔽. 355 | 1. If _x_ > _y_, return *1*𝔽. 356 | 1. If _x_ is *-0*𝔽 and _y_ is *+0*𝔽, return *-1*𝔽. 357 | 1. If _x_ is *+0*𝔽 and _y_ is *-0*𝔽, return *1*𝔽. 358 | 1. Return *+0*𝔽. 359 | 360 | 361 | This performs a numeric comparison rather than the string comparison used in . 362 | 363 |
364 | 365 | 366 |

%TypedArray%.prototype.toReversed ( )

367 | 368 |

When the *toReversed* method is called, the following steps are taken:

369 | 370 | 371 | 1. Let _O_ be the *this* value. 372 | 1. Perform ? ValidateTypedArray(_O_). 373 | 1. Let _length_ be _O_.[[ArrayLength]]. 374 | 1. Let _A_ be ? TypedArrayCreateSameType(_O_, « 𝔽(_length_) »). 375 | 1. Let _k_ be 0. 376 | 1. Repeat, while _k_ < _length_, 377 | 1. Let _from_ be ! ToString(𝔽(_length_ - _k_ - 1)). 378 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 379 | 1. Let _fromValue_ be ! Get(_O_, _from_). 380 | 1. Perform ! Set(_A_, _Pk_, _fromValue_, *true*). 381 | 1. Set _k_ to _k_ + 1. 382 | 1. Return _A_. 383 | 384 |
385 | 386 | 387 |

%TypedArray%.prototype.toSorted ( _comparefn_ )

388 | 389 |

When the *toSorted* method is called, the following steps are taken:

390 | 391 | 392 | 1. If _comparefn_ is not *undefined* and IsCallable(_comparefn_) is *false*, throw a *TypeError* exception. 393 | 1. Let _O_ be the *this* value. 394 | 1. Perform ? ValidateTypedArray(_O_). 395 | 1. Let _len_ be _O_.[[ArrayLength]]. 396 | 1. Let _A_ be ? TypedArrayCreateSameType(_O_, « 𝔽(_len_) »). 397 | 1. NOTE: The following closure performs a numeric comparison rather than the string comparison used in . 398 | 1. Let _SortCompare_ be a new Abstract Closure with parameters (_x_, _y_) that captures _comparefn_ and performs the following steps when called: 399 | 1. Return ? CompareTypedArrayElements(_x_, _y_, _comparefn_). 400 | 1. Let _sortedList_ be ? SortIndexedProperties(_obj_, _len_, _SortCompare_, *false*). 401 | 1. Let _j_ be 0. 402 | 1. Repeat, while _j_ < _len_, 403 | 1. Perform ! Set(_A_, ! ToString(𝔽(_j_)), _sortedList_[_j_], *true*). 404 | 1. Set _j_ to _j_ + 1. 405 | 1. Return _A_. 406 | 407 |
408 | 409 | 410 |

%TypedArray%.prototype.with ( _index_, _value_ )

411 | 412 |

When the *with* method is called, the following steps are taken:

413 | 414 | 415 | 1. Let _O_ be the *this* value. 416 | 1. Perform ? ValidateTypedArray(_O_). 417 | 1. Let _len_ be _O_.[[ArrayLength]]. 418 | 1. Let _relativeIndex_ be ? ToIntegerOrInfinity(_index_). 419 | 1. If _relativeIndex_ ≥ 0, let _actualIndex_ be _relativeIndex_. 420 | 1. Else, let _actualIndex_ be _len_ + _relativeIndex_. 421 | 1. If _O_.[[ContentType]] is ~BigInt~, set _value_ to ? ToBigInt(_value_). 422 | 1. Else, set _value_ to ? ToNumber(_value_). 423 | 1. If ! IsValidIntegerIndex(_O_, 𝔽(_actualIndex_)) is *false*, throw a *RangeError* exception. 424 | 1. Let _A_ be ? TypedArrayCreateSameType(_O_, « 𝔽(_len_) »). 425 | 1. Let _k_ be 0. 426 | 1. Repeat, while _k_ < _len_, 427 | 1. Let _Pk_ be ! ToString(𝔽(_k_)). 428 | 1. If _k_ is _actualIndex_, let _fromValue_ be _value_. 429 | 1. Else, let _fromValue_ be ! Get(_O_, _Pk_). 430 | 1. Perform ! Set(_A_, _Pk_, _fromValue_, *true*). 431 | 1. Set _k_ to _k_ + 1. 432 | 1. Return _A_. 433 | 434 |
435 |
436 |
437 |
438 |
439 | --------------------------------------------------------------------------------