-
Notifications
You must be signed in to change notification settings - Fork 539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: optimize Iterator #2692
perf: optimize Iterator #2692
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -2,9 +2,10 @@ | |||
|
||||
const { isBlobLike, toUSVString, makeIterator } = require('./util') | ||||
const { kState } = require('./symbols') | ||||
const { kEnumerableProperty } = require('../core/util') | ||||
const { File: UndiciFile, FileLike, isFileLike } = require('./file') | ||||
const { webidl } = require('./webidl') | ||||
const { Blob, File: NativeFile } = require('node:buffer') | ||||
const { File: NativeFile } = require('node:buffer') | ||||
|
||||
/** @type {globalThis['File']} */ | ||||
const File = NativeFile ?? UndiciFile | ||||
|
@@ -158,29 +159,32 @@ class FormData { | |||
webidl.brandCheck(this, FormData) | ||||
|
||||
return makeIterator( | ||||
() => this[kState].map(pair => [pair.name, pair.value]), | ||||
() => this[kState], | ||||
'FormData', | ||||
'key+value' | ||||
'key+value', | ||||
'name', 'value' | ||||
) | ||||
} | ||||
|
||||
keys () { | ||||
webidl.brandCheck(this, FormData) | ||||
|
||||
return makeIterator( | ||||
() => this[kState].map(pair => [pair.name, pair.value]), | ||||
() => this[kState], | ||||
'FormData', | ||||
'key' | ||||
'key', | ||||
'name', 'value' | ||||
) | ||||
} | ||||
|
||||
values () { | ||||
webidl.brandCheck(this, FormData) | ||||
|
||||
return makeIterator( | ||||
() => this[kState].map(pair => [pair.name, pair.value]), | ||||
() => this[kState], | ||||
'FormData', | ||||
'value' | ||||
'value', | ||||
'name', 'value' | ||||
) | ||||
} | ||||
|
||||
|
@@ -200,14 +204,25 @@ class FormData { | |||
} | ||||
|
||||
for (const [key, value] of this) { | ||||
callbackFn.apply(thisArg, [value, key, this]) | ||||
callbackFn.call(thisArg, value, key, this) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
} | ||||
} | ||||
} | ||||
|
||||
FormData.prototype[Symbol.iterator] = FormData.prototype.entries | ||||
|
||||
Object.defineProperties(FormData.prototype, { | ||||
append: kEnumerableProperty, | ||||
delete: kEnumerableProperty, | ||||
get: kEnumerableProperty, | ||||
getAll: kEnumerableProperty, | ||||
has: kEnumerableProperty, | ||||
set: kEnumerableProperty, | ||||
entries: kEnumerableProperty, | ||||
keys: kEnumerableProperty, | ||||
values: kEnumerableProperty, | ||||
forEach: kEnumerableProperty, | ||||
[Symbol.iterator]: { enumerable: false }, | ||||
KhafraDev marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
[Symbol.toStringTag]: { | ||||
value: 'FormData', | ||||
configurable: true | ||||
|
@@ -225,7 +240,7 @@ function makeEntry (name, value, filename) { | |||
// 1. Set name to the result of converting name into a scalar value string. | ||||
// "To convert a string into a scalar value string, replace any surrogates | ||||
// with U+FFFD." | ||||
// see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end | ||||
// see: https://nodejs.org/dist/latest-v20.x/docs/api/buffer.html#buftostringencoding-start-end | ||||
name = Buffer.from(name).toString('utf8') | ||||
|
||||
// 2. If value is a string, then set value to the result of converting | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -492,48 +492,33 @@ class Headers { | |
keys () { | ||
webidl.brandCheck(this, Headers) | ||
|
||
if (this[kGuard] === 'immutable') { | ||
const value = this[kHeadersSortedMap] | ||
return makeIterator(() => value, 'Headers', | ||
'key') | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's necessary to keep it, because it's a fast-path to avoid shallow cloning. |
||
|
||
return makeIterator( | ||
() => [...this[kHeadersSortedMap].values()], | ||
() => this[kHeadersSortedMap], | ||
'Headers', | ||
'key' | ||
'key', | ||
0, 1 | ||
) | ||
} | ||
|
||
values () { | ||
webidl.brandCheck(this, Headers) | ||
|
||
if (this[kGuard] === 'immutable') { | ||
const value = this[kHeadersSortedMap] | ||
return makeIterator(() => value, 'Headers', | ||
'value') | ||
} | ||
|
||
return makeIterator( | ||
() => [...this[kHeadersSortedMap].values()], | ||
() => this[kHeadersSortedMap], | ||
'Headers', | ||
'value' | ||
'value', | ||
0, 1 | ||
) | ||
} | ||
|
||
entries () { | ||
webidl.brandCheck(this, Headers) | ||
|
||
if (this[kGuard] === 'immutable') { | ||
const value = this[kHeadersSortedMap] | ||
return makeIterator(() => value, 'Headers', | ||
'key+value') | ||
} | ||
|
||
return makeIterator( | ||
() => [...this[kHeadersSortedMap].values()], | ||
() => this[kHeadersSortedMap], | ||
Comment on lines
517
to
+518
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need to do a shallow clone here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually there is. The headers might change while being iterated? |
||
'Headers', | ||
'key+value' | ||
'key+value', | ||
0, 1 | ||
) | ||
} | ||
|
||
|
@@ -553,7 +538,7 @@ class Headers { | |
} | ||
|
||
for (const [key, value] of this) { | ||
callbackFn.apply(thisArg, [value, key, this]) | ||
callbackFn.call(thisArg, value, key, this) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -739,19 +739,23 @@ const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbo | |
|
||
/** | ||
* @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object | ||
* @param {() => unknown[]} iterator | ||
* @param {() => unknown} iterator | ||
* @param {string} name name of the instance | ||
* @param {'key'|'value'|'key+value'} kind | ||
* @param {string | number} [keyIndex] | ||
* @param {string | number} [valueIndex] | ||
*/ | ||
function makeIterator (iterator, name, kind) { | ||
function makeIterator (iterator, name, kind, keyIndex = 0, valueIndex = 1) { | ||
const object = { | ||
index: 0, | ||
kind, | ||
target: iterator | ||
} | ||
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. | ||
const iteratorObject = Object.create(esIteratorPrototype) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
const i = { | ||
next () { | ||
Object.defineProperty(iteratorObject, 'next', { | ||
value: function next () { | ||
// 1. Let interface be the interface for which the iterator prototype object exists. | ||
|
||
// 2. Let thisValue be the this value. | ||
|
@@ -763,7 +767,7 @@ function makeIterator (iterator, name, kind) { | |
|
||
// 5. If object is not a default iterator object for interface, | ||
// then throw a TypeError. | ||
if (Object.getPrototypeOf(this) !== i) { | ||
if (Object.getPrototypeOf(this) !== iteratorObject) { | ||
throw new TypeError( | ||
`'next' called on an object that does not implement interface ${name} Iterator.` | ||
) | ||
|
@@ -783,68 +787,62 @@ function makeIterator (iterator, name, kind) { | |
if (index >= len) { | ||
return { value: undefined, done: true } | ||
} | ||
|
||
// 11. Let pair be the entry in values at index index. | ||
const pair = values[index] | ||
|
||
const { [keyIndex]: key, [valueIndex]: value } = values[index] | ||
// 12. Set object’s index to index + 1. | ||
object.index = index + 1 | ||
|
||
// 13. Return the iterator result for pair and kind. | ||
return iteratorResult(pair, kind) | ||
// https://webidl.spec.whatwg.org/#iterator-result | ||
// 1. Let result be a value determined by the value of kind: | ||
let result | ||
if (kind === 'key') { | ||
tsctx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 1. Let idlKey be pair’s key. | ||
// 2. Let key be the result of converting idlKey to an | ||
// ECMAScript value. | ||
// 3. result is key. | ||
result = key | ||
} else if (kind === 'value') { | ||
// 1. Let idlValue be pair’s value. | ||
// 2. Let value be the result of converting idlValue to | ||
// an ECMAScript value. | ||
// 3. result is value. | ||
result = value | ||
} else { | ||
// 1. Let idlKey be pair’s key. | ||
// 2. Let idlValue be pair’s value. | ||
// 3. Let key be the result of converting idlKey to an | ||
// ECMAScript value. | ||
// 4. Let value be the result of converting idlValue to | ||
// an ECMAScript value. | ||
// 5. Let array be ! ArrayCreate(2). | ||
// 6. Call ! CreateDataProperty(array, "0", key). | ||
// 7. Call ! CreateDataProperty(array, "1", value). | ||
// 8. result is array. | ||
result = [key, value] | ||
} | ||
// 2. Return CreateIterResultObject(result, false). | ||
return { | ||
value: result, | ||
done: false | ||
} | ||
}, | ||
// The class string of an iterator prototype object for a given interface is the | ||
// result of concatenating the identifier of the interface and the string " Iterator". | ||
[Symbol.toStringTag]: `${name} Iterator` | ||
} | ||
|
||
// The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. | ||
Object.setPrototypeOf(i, esIteratorPrototype) | ||
// esIteratorPrototype needs to be the prototype of i | ||
// which is the prototype of an empty object. Yes, it's confusing. | ||
return Object.setPrototypeOf({}, i) | ||
} | ||
writable: true, | ||
enumerable: true, | ||
configurable: true | ||
}) | ||
|
||
// https://webidl.spec.whatwg.org/#iterator-result | ||
function iteratorResult (pair, kind) { | ||
let result | ||
|
||
// 1. Let result be a value determined by the value of kind: | ||
switch (kind) { | ||
case 'key': { | ||
// 1. Let idlKey be pair’s key. | ||
// 2. Let key be the result of converting idlKey to an | ||
// ECMAScript value. | ||
// 3. result is key. | ||
result = pair[0] | ||
break | ||
} | ||
case 'value': { | ||
// 1. Let idlValue be pair’s value. | ||
// 2. Let value be the result of converting idlValue to | ||
// an ECMAScript value. | ||
// 3. result is value. | ||
result = pair[1] | ||
break | ||
} | ||
case 'key+value': { | ||
// 1. Let idlKey be pair’s key. | ||
// 2. Let idlValue be pair’s value. | ||
// 3. Let key be the result of converting idlKey to an | ||
// ECMAScript value. | ||
// 4. Let value be the result of converting idlValue to | ||
// an ECMAScript value. | ||
// 5. Let array be ! ArrayCreate(2). | ||
// 6. Call ! CreateDataProperty(array, "0", key). | ||
// 7. Call ! CreateDataProperty(array, "1", value). | ||
// 8. result is array. | ||
result = pair | ||
break | ||
} | ||
} | ||
// The class string of an iterator prototype object for a given interface is the | ||
// result of concatenating the identifier of the interface and the string " Iterator". | ||
Object.defineProperty(iteratorObject, Symbol.toStringTag, { | ||
value: `${name} Iterator`, | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}) | ||
Comment on lines
+840
to
+845
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// 2. Return CreateIterResultObject(result, false). | ||
return { value: result, done: false } | ||
// esIteratorPrototype needs to be the prototype of iteratorObject | ||
// which is the prototype of an empty object. Yes, it's confusing. | ||
return Object.create(iteratorObject) | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the reason why the performance of the loop is poor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think we need to create a copy...