Vue version
3.5.x / main (verified at a2b40db)
Link to minimal reproduction
Reproducible directly against the repo (see below) — looseEqual is not exported publicly, so the SFC Playground can only show the v-model symptom, not the root cause.
Steps to reproduce
looseEqual() in packages/shared/src/looseEqual.ts reports unrelated Map and Set instances as equal:
import { looseEqual } from '@vue/shared'
looseEqual(new Map([['a', 1]]), new Map([['b', 2]])) // true, expected false
looseEqual(new Set([1]), new Set([2])) // true, expected false
looseEqual(new Map([['a', 1]]), new Map()) // true, expected false
This surfaces to users through v-model on <select>. The following selects the wrong option:
const mapA = new Map([['id', 1]])
const mapB = new Map([['id', 2]])
const selected = ref(mapB)
<select v-model="selected">
<option :value="mapA">A</option>
<option :value="mapB">B</option>
</select>
el.selectedIndex is 0 (option A) when it should be 1, because looseEqual(mapA, mapB) returns true and the loop in setSelected stops at the first option.
What is expected?
Map/Set values with different contents compare as not equal, so v-model selects the option that actually matches.
What is actually happening?
Map and Set fall through to the generic object branch. Object.keys() on either is always [], so the key-count check passes and the for...in loop body never executes — entries are never compared. Execution then reaches the final line:
return String(a) === String(b)
Every Map stringifies to '[object Map]' and every Set to '[object Set]', so any two of the same type are reported equal.
Note this differs from Date and Symbol, which are given explicit branches above, and from arrays and plain objects, which compare correctly:
looseEqual([1], [2]) // false, correct
looseEqual({ a: 1 }, { a: 2 }) // false, correct
packages/shared/__tests__/looseEqual.spec.ts has 13 cases but none for Map/Set, which is why this hasn't been caught.
Possible fix
Add explicit branches before the generic object check, mirroring the existing Date/Symbol pattern. isMap and isSet already exist in packages/shared/src/general.ts. Set needs content comparison rather than reference lookup to stay consistent with looseEqual's semantics (e.g. new Set([{ a: 1 }]) vs new Set([{ a: 1 }])).
Worth confirming the intended semantics first: should Map comparison be key-order-insensitive, and should Set compare loosely (looseEqual per element) or strictly? Happy to open a PR with tests once the direction is confirmed.
System Info
Vue: 3.5.x / main @ a2b40db
Any additional comments?
Unrelated to #15298, which touches only vModel.ts.
Vue version
3.5.x /
main(verified ata2b40db)Link to minimal reproduction
Reproducible directly against the repo (see below) —
looseEqualis not exported publicly, so the SFC Playground can only show thev-modelsymptom, not the root cause.Steps to reproduce
looseEqual()inpackages/shared/src/looseEqual.tsreports unrelatedMapandSetinstances as equal:This surfaces to users through
v-modelon<select>. The following selects the wrong option:el.selectedIndexis0(option A) when it should be1, becauselooseEqual(mapA, mapB)returnstrueand the loop insetSelectedstops at the first option.What is expected?
Map/Setvalues with different contents compare as not equal, sov-modelselects the option that actually matches.What is actually happening?
MapandSetfall through to the generic object branch.Object.keys()on either is always[], so the key-count check passes and thefor...inloop body never executes — entries are never compared. Execution then reaches the final line:Every
Mapstringifies to'[object Map]'and everySetto'[object Set]', so any two of the same type are reported equal.Note this differs from
DateandSymbol, which are given explicit branches above, and from arrays and plain objects, which compare correctly:packages/shared/__tests__/looseEqual.spec.tshas 13 cases but none forMap/Set, which is why this hasn't been caught.Possible fix
Add explicit branches before the generic object check, mirroring the existing
Date/Symbolpattern.isMapandisSetalready exist inpackages/shared/src/general.ts.Setneeds content comparison rather than reference lookup to stay consistent withlooseEqual's semantics (e.g.new Set([{ a: 1 }])vsnew Set([{ a: 1 }])).Worth confirming the intended semantics first: should
Mapcomparison be key-order-insensitive, and shouldSetcompare loosely (looseEqualper element) or strictly? Happy to open a PR with tests once the direction is confirmed.System Info
Any additional comments?
Unrelated to #15298, which touches only
vModel.ts.