有没有办法跟踪对象属性的值历史,比如记住和回忆它最近的任何值?

Is there a way to keep track of an object-property's value-history, like memorizing and recalling any of its recent values?

提问人:Scandidi 提问时间:11/16/2023 最后编辑:Peter SeligerScandidi 更新时间:11/19/2023 访问量:143

问:

假设我有一个根据当前值更改值的方法。我在这里就让它变得非常简单:

  changeValue(){
    if(!this.value){
      this.value = 'value1'
    }
    else if(this.value === 'value1'){
      this.value = 'value2'
    }
    else if(this.value === 'value2'){
      this.value = 'value3'
    }
  },

然后我还有另一种方法,如果当前值为 2 或 3,则更改为第四个值

  changeValues(){
    if(this.value === 'value2' || this.value === 'value3'){
      this.value = 'valueX'
    }
  },

现在,我想添加一个单击事件,将 valueX 的值更改回以前的值,但我无法在代码中指定它,因为以前的值可以是 value2 或 value3。

有没有办法查看变量“history”,检查以前的变量,然后将其设置回该变量?

  changeBack(){
    // if last value was value2 change back to value2
    // if last value was value3 then change back to value3
  }
javascript 数组 存储 getter-setter 键值存储

评论

4赞 jabaa 11/16/2023
不,没有内置功能,但您可以将前一个值存储在不同的变量中,或者对整个历史记录使用数组。prevValue
1赞 Rory McCrossan 11/16/2023
一定要为此使用一个数组 - 然后你可以只存储当前状态的索引并增加/减少它 - 假设进度是线性的,因为它从你的描述中看起来是。
0赞 Mark Schultheiss 11/16/2023
存储旧值的几种方法,但没有更多上下文,@RoryMcCrossan和 jabaa 所说的 - 当然,当您刷新页面时,这些方法就会消失
0赞 mousetail 11/16/2023
尽管如果您真的想在页面重新加载后保留值,则可以使用 localStorage 或 cookie 或类似内容
0赞 Estus Flask 11/16/2023
这与 vue 有什么关系?

答:

0赞 Hasan Raza 11/16/2023 #1
let valueHistory = [];
function changeValue(newValue) {
    valueHistory.push(this.value);
    this.value = newValue;
}
function changeBack() {
    if (valueHistory.length > 0) {
        this.value = valueHistory.pop();
    }
}
changeValue('hasanraza');
console.log(this.value);

评论

0赞 Hasan Raza 11/16/2023
修改了 changeValue 函数,以便在更新当前值之前将当前值推送到历史记录数组。changeBack 函数检查历史记录中是否有以前的值,并将其设置为当前值。
0赞 Eyad OP 11/16/2023 #2

你的问题被标记为 vue.js,所以我在 vue 中的答案

你可以使用 vue 观察者并创建一个列表,并按它们的索引或对象选择它们,其中包含所有更改的键值对。

data() {
  return {
    val: '',
    listOfValues: [],
  }
},
watch: {
  val(newVal, oldVal) {
    this.listOfValues.push(newVal)
    // or with if statement if you want to check
    if (...) {
    
    }
  }
},

Vue 观察者

2赞 Jaykant 11/16/2023 #3

您可以使用数组来跟踪值的历史记录。

例:

class YourClass {
  constructor() {
    this.value = null;
    this.valueHistory = [];
  }

  changeValue() {
    if (!this.value) {
      this.value = 'value1';
    } else if (this.value === 'value1') {
      this.value = 'value2';
    } else if (this.value === 'value2') {
      this.value = 'value3';
    }

    // Save the current value to history
    this.valueHistory.push(this.value);
  }

  changeValues() {
    if (this.value === 'value2' || this.value === 'value3') {
      this.value = 'valueX';
    }
  }

  changeBack() {
    // Pop the last value from history and set it as the current value
    if (this.valueHistory.length > 0) {
      this.value = this.valueHistory.pop();
    }
  }
}

valueHistory是一个跟踪值历史记录的数组。changeBack 方法从 history 数组中弹出最后一个值,并将其设置为当前值。

0赞 Peter Seliger 11/17/2023 #4

通用的、非侵入性的、因此可重用的方法需要基于 WeakMap。此类注册表的键完全是对象的引用,其中后者的键希望存储每个对象条目的值历史记录。因此,注册表的每个值都需要是一个 Map 实例,其中基于映射字符串的键是对象条目的属性名称,值是 Set 实例。后者用作对象特定条目(键值对)的所有最近唯一值的历史记录存储。

然后,对象的任何属性都可以通过 get/set 功能实现,该功能在要设置的值上会跟踪每个新的、唯一设置的值。

然后,代码必须附带两个辅助函数,例如 并且每个函数在传递的参数上,并且确实为特定于对象的 -indicate 属性返回传递参数之前或旁边的值。getPreviousOfValueHistorygetNextOfValueHistorytargetkeyvaluetargetkeyvalue

如果未显示在 / 指示属性的值历史记录中,则将返回符号值。valuetargetkeySymbol('no history value found')

如果匹配第一个或最后一个唯一历史记录值,则返回该值本身。value

示例代码...

const noHistoryValueFound = Symbol('no history value found');

const valueHistoryRegistry = new WeakMap;

const getPreviousOfValueHistory = (target, key, value) => {
  let result = noHistoryValueFound;

  const valueHistory =
    valueHistoryRegistry?.get(target)?.get(key);

  if (valueHistory) {
    const valueList = [...valueHistory];
    let valueIdx = valueList.indexOf(value);

    if (valueIdx >= 0) {

      result = (--valueIdx in valueList)
        ? valueList.at(valueIdx)
        : value;
    }
  }
  return result;
}
const getNextOfValueHistory = (target, key, value) => {
  let result = noHistoryValueFound;

  const valueHistory =
    valueHistoryRegistry?.get(target)?.get(key);

  if (valueHistory) {
    const valueList = [...valueHistory];
    let valueIdx = valueList.indexOf(value);

    if (valueIdx >= 0) {

      result = (++valueIdx in valueList)
        ? valueList.at(valueIdx)
        : value;
    }
  }
  return result;
}

const logUniqueValueHistory = (target, key, value) => {
  if (valueHistoryRegistry.has(target)) {

    const valueRegistry = valueHistoryRegistry.get(target);
    if (valueRegistry.has(key)) {

      const valueHistory = valueRegistry.get(key);
      if (!valueHistory.has(value)) {

        valueHistory
          .add(value);
      }
    } else {
      valueRegistry
        .set(key, new Set([value]));
    }
  } else {
    valueHistoryRegistry
      .set(target, new Map([[key, new Set([value])]]));
  }
}

const getSnapshotOfWritableOwnProperties = (value) => {
  const source = Object(value);

  return Object
    .entries(
      Object.getOwnPropertyDescriptors(source)
    )
    .concat(
      Object
        .getOwnPropertySymbols(source)
        .map(symbol => [symbol, Object.getOwnPropertyDescriptor(source, symbol)])
    )
    .filter(([_, descriptor]) =>
      !!descriptor.writable && Object.hasOwn(descriptor, 'value')
    )
    .reduce((snapshot, [key, { value }]) => {

      snapshot[key] = value;
      return snapshot;

    }, {});
}

function withContextuallyBoundUniqueValueHistory(key, value) {
  const { context, state } = this;

  Reflect.defineProperty(context, key, {
    get: () => Reflect.get(state, key),
    set: value => {
      logUniqueValueHistory(context, key, value);

      return Reflect.set(state, key, value);
    },
  });
}

function withOwnUniqueValueHistory() {
  const state = getSnapshotOfWritableOwnProperties(this);

  Object
    .entries(state)
    .concat(
      Object
        .getOwnPropertySymbols(state)
        .map(symbol => [symbol, Reflect.get(state, symbol)])
    )
    .forEach(([key, value]) => {

      withContextuallyBoundUniqueValueHistory
        .call({ context: this, state }, key, value);

      // - force 1st history-value entry
      //   for every instance property.
      this[key] = value;
    });
}

class KeyValueStorageWithUniqueValueHistory {

  constructor(initialState= {}) {
    Object
      .assign(this, Object(initialState));

    withOwnUniqueValueHistory.call(this);
  }
  previousValueOf(/* key, value? */...args) {
    const key = args.at(0);
    const value = ('1' in args) ? args.at(1) : this[key];

    return getPreviousOfValueHistory(this, key, value);
  }
  nextValueOf(/* key, value? */...args) {
    const key = args.at(0);
    const value = ('1' in args) ? args.at(1) : this[key];

    return getNextOfValueHistory(this, key, value);
  }
}
const fooBarSymbol = Symbol('fooBar');

const storage = new KeyValueStorageWithUniqueValueHistory({
  quickBrownFox: 'the',
  [fooBarSymbol]: 'foo',
});

console.log(`const storage = new KeyValueStorageWithUniqueValueHistory({
  quickBrownFox: 'the',
  [fooBarSymbol]: 'foo',
});`);
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'quick';");
storage.quickBrownFox = 'quick';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'brown';");
storage.quickBrownFox = 'brown';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'fox';");
storage.quickBrownFox = 'fox';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'jumps';");
storage.quickBrownFox = 'jumps';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);

console.log('----- ----- ----- ----- -----');

console.log("storage.quickBrownFox = 'brown';");
storage.quickBrownFox = 'brown';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log(
  "storage.previousValueOf('quickBrownFox') ...",
  storage.previousValueOf('quickBrownFox'),
);
console.log(
  "storage.nextValueOf('quickBrownFox') ...",
  storage.nextValueOf('quickBrownFox'),
);
console.log('\n');

console.log(
  "storage.previousValueOf('quickBrownFox', 'quick') ...",
  storage.previousValueOf('quickBrownFox', 'quick'),
);
console.log(
  "storage.previousValueOf('quickBrownFox', 'jumps') ...",
  storage.previousValueOf('quickBrownFox', 'jumps'),
);
console.log('\n');

console.log(
  "storage.nextValueOf('quickBrownFox', 'the') ...",
  storage.nextValueOf('quickBrownFox', 'the'),
);
console.log(
  "storage.nextValueOf('quickBrownFox', 'fox') ...",
  storage.nextValueOf('quickBrownFox', 'fox'),
);

console.log('----- ----- ----- ----- -----');

console.log('storage[fooBarSymbol] ...',storage[fooBarSymbol]);

console.log(
  "storage.previousValueOf(fooBarSymbol) ...",
  storage.previousValueOf(fooBarSymbol),
);
console.log(
  "storage.nextValueOf(fooBarSymbol) ...",
  storage.nextValueOf(fooBarSymbol),
);
console.log('\n');

console.log(
  "storage.nextValueOf(fooBarSymbol, 'bar') ...",
  String(storage.nextValueOf(fooBarSymbol, 'bar')),
);

console.log('----- ----- ----- ----- -----');

console.log("storage[fooBarSymbol] = 'bar';");
storage[fooBarSymbol] = 'bar';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log("storage[fooBarSymbol] = 'baz';");
storage[fooBarSymbol] = 'baz';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log("storage[fooBarSymbol] = 'biz';");
storage[fooBarSymbol] = 'biz';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);

console.log('----- ----- ----- ----- -----');

console.log("storage[fooBarSymbol] = 'bar';");
storage[fooBarSymbol] = 'bar';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log(
  "storage.previousValueOf(fooBarSymbol) ...",
  storage.previousValueOf(fooBarSymbol),
);
console.log(
  "storage.nextValueOf(fooBarSymbol) ...",
  storage.nextValueOf(fooBarSymbol),
);
console.log('\n');

console.log(
  "storage.previousValueOf(fooBarSymbol, 'biz') ...",
  storage.previousValueOf(fooBarSymbol, 'biz'),
);
console.log(
  "storage.nextValueOf(fooBarSymbol, 'baz') ...",
  storage.nextValueOf(fooBarSymbol, 'baz'),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

考虑到上述示例代码提供的所有内容,OP 现在可以轻松实现如下所示的通用方法......changeBack

setPreviousValueOf(/* key, value? */...args) {
  const key = args.at(0);
  const value = ('1' in args) ? args.at(1) : this[key];

  this[key] = getPreviousOfValueHistory(this, key, value);
}

...然后可以像......

this.setPreviousValueOf('valueX'/*,anyCurrentOrPreviousValueOfProperty_valueX*/);

同样,堆栈代码段的示例代码确实提供了其用法的实现和示例。getPreviousOfValueHistory

评论

0赞 Peter Seliger 11/20/2023
@Scandidi......OP 可能会查看上面提供的示例代码和答案,这些代码和答案在通用的基础上解决了 OP 当前和未来与任何对象属性的唯一值历史记录相关的任何问题。