Skip to content

Commit 5db527a

Browse files
authored
perf: optimize Iterator (#2692)
1 parent eb53b61 commit 5db527a

File tree

4 files changed

+97
-95
lines changed

4 files changed

+97
-95
lines changed

lib/fetch/formdata.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
const { isBlobLike, toUSVString, makeIterator } = require('./util')
44
const { kState } = require('./symbols')
5+
const { kEnumerableProperty } = require('../core/util')
56
const { File: UndiciFile, FileLike, isFileLike } = require('./file')
67
const { webidl } = require('./webidl')
7-
const { Blob, File: NativeFile } = require('node:buffer')
8+
const { File: NativeFile } = require('node:buffer')
89

910
/** @type {globalThis['File']} */
1011
const File = NativeFile ?? UndiciFile
@@ -158,29 +159,32 @@ class FormData {
158159
webidl.brandCheck(this, FormData)
159160

160161
return makeIterator(
161-
() => this[kState].map(pair => [pair.name, pair.value]),
162+
() => this[kState],
162163
'FormData',
163-
'key+value'
164+
'key+value',
165+
'name', 'value'
164166
)
165167
}
166168

167169
keys () {
168170
webidl.brandCheck(this, FormData)
169171

170172
return makeIterator(
171-
() => this[kState].map(pair => [pair.name, pair.value]),
173+
() => this[kState],
172174
'FormData',
173-
'key'
175+
'key',
176+
'name', 'value'
174177
)
175178
}
176179

177180
values () {
178181
webidl.brandCheck(this, FormData)
179182

180183
return makeIterator(
181-
() => this[kState].map(pair => [pair.name, pair.value]),
184+
() => this[kState],
182185
'FormData',
183-
'value'
186+
'value',
187+
'name', 'value'
184188
)
185189
}
186190

@@ -200,14 +204,25 @@ class FormData {
200204
}
201205

202206
for (const [key, value] of this) {
203-
callbackFn.apply(thisArg, [value, key, this])
207+
callbackFn.call(thisArg, value, key, this)
204208
}
205209
}
206210
}
207211

208212
FormData.prototype[Symbol.iterator] = FormData.prototype.entries
209213

210214
Object.defineProperties(FormData.prototype, {
215+
append: kEnumerableProperty,
216+
delete: kEnumerableProperty,
217+
get: kEnumerableProperty,
218+
getAll: kEnumerableProperty,
219+
has: kEnumerableProperty,
220+
set: kEnumerableProperty,
221+
entries: kEnumerableProperty,
222+
keys: kEnumerableProperty,
223+
values: kEnumerableProperty,
224+
forEach: kEnumerableProperty,
225+
[Symbol.iterator]: { enumerable: false },
211226
[Symbol.toStringTag]: {
212227
value: 'FormData',
213228
configurable: true
@@ -225,7 +240,7 @@ function makeEntry (name, value, filename) {
225240
// 1. Set name to the result of converting name into a scalar value string.
226241
// "To convert a string into a scalar value string, replace any surrogates
227242
// with U+FFFD."
228-
// see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end
243+
// see: https://nodejs.org/dist/latest-v20.x/docs/api/buffer.html#buftostringencoding-start-end
229244
name = Buffer.from(name).toString('utf8')
230245

231246
// 2. If value is a string, then set value to the result of converting

lib/fetch/headers.js

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -492,48 +492,33 @@ class Headers {
492492
keys () {
493493
webidl.brandCheck(this, Headers)
494494

495-
if (this[kGuard] === 'immutable') {
496-
const value = this[kHeadersSortedMap]
497-
return makeIterator(() => value, 'Headers',
498-
'key')
499-
}
500-
501495
return makeIterator(
502-
() => [...this[kHeadersSortedMap].values()],
496+
() => this[kHeadersSortedMap],
503497
'Headers',
504-
'key'
498+
'key',
499+
0, 1
505500
)
506501
}
507502

508503
values () {
509504
webidl.brandCheck(this, Headers)
510505

511-
if (this[kGuard] === 'immutable') {
512-
const value = this[kHeadersSortedMap]
513-
return makeIterator(() => value, 'Headers',
514-
'value')
515-
}
516-
517506
return makeIterator(
518-
() => [...this[kHeadersSortedMap].values()],
507+
() => this[kHeadersSortedMap],
519508
'Headers',
520-
'value'
509+
'value',
510+
0, 1
521511
)
522512
}
523513

524514
entries () {
525515
webidl.brandCheck(this, Headers)
526516

527-
if (this[kGuard] === 'immutable') {
528-
const value = this[kHeadersSortedMap]
529-
return makeIterator(() => value, 'Headers',
530-
'key+value')
531-
}
532-
533517
return makeIterator(
534-
() => [...this[kHeadersSortedMap].values()],
518+
() => this[kHeadersSortedMap],
535519
'Headers',
536-
'key+value'
520+
'key+value',
521+
0, 1
537522
)
538523
}
539524

@@ -553,7 +538,7 @@ class Headers {
553538
}
554539

555540
for (const [key, value] of this) {
556-
callbackFn.apply(thisArg, [value, key, this])
541+
callbackFn.call(thisArg, value, key, this)
557542
}
558543
}
559544

lib/fetch/util.js

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -739,19 +739,23 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo
739739

740740
/**
741741
* @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object
742-
* @param {() => unknown[]} iterator
742+
* @param {() => unknown} iterator
743743
* @param {string} name name of the instance
744744
* @param {'key'|'value'|'key+value'} kind
745+
* @param {string | number} [keyIndex]
746+
* @param {string | number} [valueIndex]
745747
*/
746-
function makeIterator (iterator, name, kind) {
748+
function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) {
747749
const object = {
748750
index: 0,
749751
kind,
750752
target: iterator
751753
}
754+
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
755+
const iteratorObject = Object.create(esIteratorPrototype)
752756

753-
const i = {
754-
next () {
757+
Object.defineProperty(iteratorObject, 'next', {
758+
value: function next () {
755759
// 1. Let interface be the interface for which the iterator prototype object exists.
756760

757761
// 2. Let thisValue be the this value.
@@ -763,7 +767,7 @@ function makeIterator (iterator, name, kind) {
763767

764768
// 5. If object is not a default iterator object for interface,
765769
// then throw a TypeError.
766-
if (Object.getPrototypeOf(this) !== i) {
770+
if (Object.getPrototypeOf(this) !== iteratorObject) {
767771
throw new TypeError(
768772
`'next' called on an object that does not implement interface ${name} Iterator.`
769773
)
@@ -783,68 +787,66 @@ function makeIterator (iterator, name, kind) {
783787
if (index >= len) {
784788
return { value: undefined, done: true }
785789
}
786-
787790
// 11. Let pair be the entry in values at index index.
788-
const pair = values[index]
789-
791+
const { [keyIndex]: key, [valueIndex]: value } = values[index]
790792
// 12. Set object’s index to index + 1.
791793
object.index = index + 1
792-
793794
// 13. Return the iterator result for pair and kind.
794-
return iteratorResult(pair, kind)
795+
// https://webidl.spec.whatwg.org/#iterator-result
796+
// 1. Let result be a value determined by the value of kind:
797+
let result
798+
switch (kind) {
799+
case 'key':
800+
// 1. Let idlKey be pair’s key.
801+
// 2. Let key be the result of converting idlKey to an
802+
// ECMAScript value.
803+
// 3. result is key.
804+
result = key
805+
break
806+
case 'value':
807+
// 1. Let idlValue be pair’s value.
808+
// 2. Let value be the result of converting idlValue to
809+
// an ECMAScript value.
810+
// 3. result is value.
811+
result = value
812+
break
813+
case 'key+value':
814+
// 1. Let idlKey be pair’s key.
815+
// 2. Let idlValue be pair’s value.
816+
// 3. Let key be the result of converting idlKey to an
817+
// ECMAScript value.
818+
// 4. Let value be the result of converting idlValue to
819+
// an ECMAScript value.
820+
// 5. Let array be ! ArrayCreate(2).
821+
// 6. Call ! CreateDataProperty(array, "0", key).
822+
// 7. Call ! CreateDataProperty(array, "1", value).
823+
// 8. result is array.
824+
result = [key, value]
825+
break
826+
}
827+
// 2. Return CreateIterResultObject(result, false).
828+
return {
829+
value: result,
830+
done: false
831+
}
795832
},
796-
// The class string of an iterator prototype object for a given interface is the
797-
// result of concatenating the identifier of the interface and the string " Iterator".
798-
[Symbol.toStringTag]: `${name} Iterator`
799-
}
800-
801-
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%.
802-
Object.setPrototypeOf(i, esIteratorPrototype)
803-
// esIteratorPrototype needs to be the prototype of i
804-
// which is the prototype of an empty object. Yes, it's confusing.
805-
return Object.setPrototypeOf({}, i)
806-
}
833+
writable: true,
834+
enumerable: true,
835+
configurable: true
836+
})
807837

808-
// https://webidl.spec.whatwg.org/#iterator-result
809-
function iteratorResult (pair, kind) {
810-
let result
811-
812-
// 1. Let result be a value determined by the value of kind:
813-
switch (kind) {
814-
case 'key': {
815-
// 1. Let idlKey be pair’s key.
816-
// 2. Let key be the result of converting idlKey to an
817-
// ECMAScript value.
818-
// 3. result is key.
819-
result = pair[0]
820-
break
821-
}
822-
case 'value': {
823-
// 1. Let idlValue be pair’s value.
824-
// 2. Let value be the result of converting idlValue to
825-
// an ECMAScript value.
826-
// 3. result is value.
827-
result = pair[1]
828-
break
829-
}
830-
case 'key+value': {
831-
// 1. Let idlKey be pair’s key.
832-
// 2. Let idlValue be pair’s value.
833-
// 3. Let key be the result of converting idlKey to an
834-
// ECMAScript value.
835-
// 4. Let value be the result of converting idlValue to
836-
// an ECMAScript value.
837-
// 5. Let array be ! ArrayCreate(2).
838-
// 6. Call ! CreateDataProperty(array, "0", key).
839-
// 7. Call ! CreateDataProperty(array, "1", value).
840-
// 8. result is array.
841-
result = pair
842-
break
843-
}
844-
}
838+
// The class string of an iterator prototype object for a given interface is the
839+
// result of concatenating the identifier of the interface and the string " Iterator".
840+
Object.defineProperty(iteratorObject, Symbol.toStringTag, {
841+
value: `${name} Iterator`,
842+
writable: false,
843+
enumerable: false,
844+
configurable: true
845+
})
845846

846-
// 2. Return CreateIterResultObject(result, false).
847-
return { value: result, done: false }
847+
// esIteratorPrototype needs to be the prototype of iteratorObject
848+
// which is the prototype of an empty object. Yes, it's confusing.
849+
return Object.create(iteratorObject)
848850
}
849851

850852
/**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
"jest": "^29.0.2",
122122
"jsdom": "^24.0.0",
123123
"jsfuzz": "^1.0.15",
124-
"mitata": "^0.1.6",
124+
"mitata": "^0.1.8",
125125
"mocha": "^10.0.0",
126126
"p-timeout": "^3.2.0",
127127
"pre-commit": "^1.2.2",

0 commit comments

Comments
 (0)