2025-09-05 14:59:21 +08:00

116 lines
2.8 KiB
JavaScript

import path from '../../path.js';
import isArray from '../../is-array.js';
import isObject from '../../is-object.js';
import {MUTABLE_ARRAY_METHODS} from '../methods/array.js';
import {MUTABLE_SET_METHODS} from '../methods/set.js';
import {MUTABLE_MAP_METHODS} from '../methods/map.js';
import {IMMUTABLE_OBJECT_METHODS} from '../methods/object.js';
export default class CloneObject {
constructor(value, path, argumentsList, hasOnValidate) {
this._path = path;
this._isChanged = false;
this._clonedCache = new Set();
this._hasOnValidate = hasOnValidate;
this._changes = hasOnValidate ? [] : null;
this.clone = path === undefined ? value : this._shallowClone(value);
}
static isHandledMethod(name) {
return IMMUTABLE_OBJECT_METHODS.has(name);
}
_shallowClone(value) {
let clone = value;
if (isObject(value)) {
clone = {...value};
} else if (isArray(value) || ArrayBuffer.isView(value)) {
clone = [...value];
} else if (value instanceof Date) {
clone = new Date(value);
} else if (value instanceof Set) {
clone = new Set([...value].map(item => this._shallowClone(item)));
} else if (value instanceof Map) {
clone = new Map();
for (const [key, item] of value.entries()) {
clone.set(key, this._shallowClone(item));
}
}
this._clonedCache.add(clone);
return clone;
}
preferredThisArg(isHandledMethod, name, thisArgument, thisProxyTarget) {
if (isHandledMethod) {
if (isArray(thisProxyTarget)) {
this._onIsChanged = MUTABLE_ARRAY_METHODS[name];
} else if (thisProxyTarget instanceof Set) {
this._onIsChanged = MUTABLE_SET_METHODS[name];
} else if (thisProxyTarget instanceof Map) {
this._onIsChanged = MUTABLE_MAP_METHODS[name];
}
return thisProxyTarget;
}
return thisArgument;
}
update(fullPath, property, value) {
const changePath = path.after(fullPath, this._path);
if (property !== 'length') {
let object = this.clone;
path.walk(changePath, key => {
if (object?.[key]) {
if (!this._clonedCache.has(object[key])) {
object[key] = this._shallowClone(object[key]);
}
object = object[key];
}
});
if (this._hasOnValidate) {
this._changes.push({
path: changePath,
property,
previous: value,
});
}
if (object?.[property]) {
object[property] = value;
}
}
this._isChanged = true;
}
undo(object) {
let change;
for (let index = this._changes.length - 1; index !== -1; index--) {
change = this._changes[index];
path.get(object, change.path)[change.property] = change.previous;
}
}
isChanged(value) {
return this._onIsChanged === undefined
? this._isChanged
: this._onIsChanged(this.clone, value);
}
isPathApplicable(changePath) {
return path.isRootPath(this._path) || path.isSubPath(changePath, this._path);
}
}