寻找在JS类中处理缓存属性的更短方式

huangapple go评论65阅读模式
英文:

Looking for shorter way to handle cache properties in a JS class

问题

我有一个类,其中有十几种方法,每个方法都返回一个值。每个方法的返回值只需要计算一次,即使方法被多次调用。因此,我正在使用这种方法来避免反复执行相同的工作:

class myClass {
    myFunc = () => {
        if (typeof this._myCacheVar === "undefined") {
            this._myCacheVar = /* 进行获取值的操作 */;
        }
        return this._myCacheVar;
    }
}

然而,在数十种方法中,这种方法变得冗长而重复。我知道类可以为属性使用getter和setter,但那更加冗长。

有没有一种简化这段代码的好方法?

**附注:**其中一些值可能为假值,因此使用 typeof 检查而不仅仅是检查 if (this._myCacheVar)

英文:

I have class with a good dozen or so methods which return one value per method. Each method's return value only needs to be calculated once, even if the method is called many times. So, I am using this approach to avoid doing the same work over and over:

class myClass {
    myFunc = () => {
        if (typeof this._myCacheVar === "undefined") {
            this._myCacheVar = /* do stuff to get the value */;
        }
        return this._myCacheVar;
    }
}

However, over dozens of methods, this approach gets really long-winded and repetitious. I know that classes can have getters and setters for properties, but that's even more long-winded.

Is there a good way to shorten this code?

PS: Some of these values can be falsy, hence the typeof check instead of just checking if (this._myCacheVar).

答案1

得分: 4

如果你担心为每个类的方法重复编写代码,只需创建一个执行该工作的函数。

你可以通过迭代类的方法(位于原型上)在每个方法上应用这样的装饰器。

其次,你可以通过将缓存条目缩短为一个普通对象,该对象具有包含返回值的 value 属性来缩短支持缓存的代码。使用这样的对象包装器,测试和设置代码可以利用 ??= 运算符:

function enableCache(func) {
    const cache = {};
    return function(...args) {
        return (cache[JSON.stringify([this, ...args])] ??= {
            value: func.apply(this, args)
        }).value;
    };
}

function enableCacheOnMethods(cls) {
    const obj = cls.prototype;
    for (const method of Object.getOwnPropertyNames(obj)) {
        if (typeof obj[method] === "function" && method !== "constructor") { // method of cls
            obj[method] = enableCache(obj[method]);
        }
    }
}

// Demo
class myClass {
    divisors(n) { // Example method (without caching)
        console.log(`executing divisors(${n})`);
        const arr = [];
        for (let i = 2; i <= n; i++) {
            if (n % i == 0) arr.push(i);
        }
        return arr;
    }
    factorial(n) { // Example method (without caching)
        console.log(`executing factorial(${n})`);
        let res = 1;
        while (n > 1) res *= n--;
        return res;
    }
}

enableCacheOnMethods(myClass);

let obj = new myClass;
console.log(...obj.divisors(15));
console.log(...obj.divisors(48));
console.log(obj.factorial(10));
console.log(...obj.divisors(15)); // 使用缓存
console.log(obj.factorial(10)); // 使用缓存

以上是你提供的代码的翻译部分。

英文:

If you are worried about repeating the code for every method of your class, just make one function that does the job.

You could apply such a decorator on each method of your class by iterating its methods (which are on the prototype).

Secondly, you can shorten the cache supporting code by making the cached entry a plain object that has a value property that contains the return value. With such object wrapper the test-and-set code can make use of the ??= operator:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

function enableCache(func) {
const cache = {};
return function(...args) {
return (cache[JSON.stringify([this, ...args])] ??= {
value: func.apply(this, args)
}).value;
};
}
function enableCacheOnMethods(cls) {
const obj = cls.prototype;
for (const method of Object.getOwnPropertyNames(obj)) {
if (typeof obj[method] === &quot;function&quot; &amp;&amp; method !== &quot;constructor&quot;) { // method of cls
obj[method] = enableCache(obj[method]);
}
}
}
// Demo
class myClass {
divisors(n) { // Example method (without caching)
console.log(`executing divisors(${n})`);
const arr = [];
for (let i = 2; i &lt;= n; i++) {
if (n % i == 0) arr.push(i);
}
return arr;
}
factorial(n) { // Example method (without caching)
console.log(`executing factorial(${n})`);
let res = 1;
while (n &gt; 1) res *= n--;
return res;
}
}
enableCacheOnMethods(myClass);
let obj = new myClass;
console.log(...obj.divisors(15));
console.log(...obj.divisors(48));
console.log(obj.factorial(10));
console.log(...obj.divisors(15)); // Uses the cache
console.log(obj.factorial(10)); // Uses the cache

<!-- end snippet -->

答案2

得分: 2

你可以创建一个代理对象,而不是修改每个方法。

基于这个答案:https://stackoverflow.com/a/69860903/5089567

class MyClass {
  constructor() {
    return new Proxy(this, {
      get(target, prop) {
        const origMethod = target[prop];
        if (typeof origMethod == 'function') {
          const key = '_' + prop;
          return function (...args) {
            if (key in target) {
              return target[key];
            }
            target[key] = origMethod.apply(target, args);
            return target[key];
          };
        }
      },
    });
  }

  myFunc = () => {
    return Math.random();
  };
}

const instance = new MyClass;

console.log(instance.myFunc());
console.log(instance.myFunc());

希望这对你有帮助。

英文:

You can create a proxy instead of changing every method

Based on this answer: https://stackoverflow.com/a/69860903/5089567

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

class MyClass {
constructor() {
return new Proxy(this, {
get(target, prop) {
const origMethod = target[prop];
if (typeof origMethod == &#39;function&#39;) {
const key = &#39;_&#39; + prop
return function(...args) {
if (key in target) {
return target[key]
}
target[key] = origMethod.apply(target, args)
return target[key]
}
}
}
})
}
myFunc = () =&gt; {
return Math.random()
}
}
const instance = new MyClass
console.log(instance.myFunc())
console.log(instance.myFunc())

<!-- end snippet -->

答案3

得分: 2

以下是您要翻译的内容:

"要缩短它的一种方法是使用装饰器,但目前它是一个实验性功能。JS装饰器提案目前处于第3阶段TC9/proposal-decorators。我无法在NodeJS上运行它或设置它使其工作,没有在互联网上找到足够的材料,但我使用了 babeljs/repl 与stage-3预设来获得与浏览器兼容的JS代码并进行了测试。

装饰器函数的形式(更多信息请参阅提案页面):

type ClassMethodDecorator = (value: Function, context: {
  kind: &quot;method&quot;;
  name: string | symbol;
  access: { get(): unknown };
  static: boolean;
  private: boolean;
  addInitializer(initializer: () =&gt; void): void;
}) =&gt; Function | void;

带缓存的类示例:

function cache(value, { kind, name, addInitializer }) {
    // 如果不返回任何内容,就不会进行修改
  	if (kind !== &quot;method&quot;) return;
    
  	// 这用于将函数绑定到类实例
    // 在提案页面上阅读更多关于initializer的信息
  	addInitializer(function () {
      	// 在类实例上初始化缓存
    	if (this.cache === undefined) this.cache = {};
      	if (this.cache[name] === undefined) this.cache = {};
      	// 将函数绑定到类实例
      	this[name] = this[name].bind(this);
    })

  	// 如果不使用addInitializer,&quot;this&quot;将是全局对象
  	// 因为返回函数中的&quot;this&quot;是全局对象的cache函数
    return function(...args) {
      const argsKey = args.join(&quot;-&quot;);
      if (this.cache[argsKey] === undefined) {
        	console.log(&quot;缓存输出&quot;);
            this.cache[argsKey] = value(...args);
      }
      return this.cache[argsKey];
    }  
}

class exampleClass {
  	@cache add(x,y) {
    	return x + y;
    }
}

const c = new exampleClass();
console.log(&quot;第一次调用&quot;, c.add(5, 5));
// -&gt; &quot;缓存输出&quot;
// -&gt; &quot;第一次调用 10&quot;

console.log(&quot;第二次调用&quot;, c.add(5, 5));
// -&gt; &quot;第二次调用 10&quot;
英文:

One way to shorten it is to use decorators but it is an experimental feature for now. The JS decorator proposal is in stage 3 right now TC9/proposal-decorators. I was not able to run it with NodeJS or set it up to get it working, didn't find enough materials on the internet, however I used babeljs/repl with stage-3 preset to get browser-compatible JS code and tested it.

A decorator function has the form (more on the proposal page):

type ClassMethodDecorator = (value: Function, context: {
  kind: &quot;method&quot;;
  name: string | symbol;
  access: { get(): unknown };
  static: boolean;
  private: boolean;
  addInitializer(initializer: () =&gt; void): void;
}) =&gt; Function | void;

Example of a class with cache:

function cache(value, { kind, name, addInitializer }) {
    // If nothing is returned, nothing is modified
  	if (kind !== &quot;method&quot;) return;
    
  	// This is used to bind the function to the class instance
    // Read more about the initializer on the proposal page
  	addInitializer(function () {
      	// Initializing the cache on the class instance
    	if (this.cache === undefined) this.cache = {};
      	if (this.cache[name] === undefined) this.cache = {};
      	// Binding the function to class instance
      	this[name] = this[name].bind(this);
    })

  	// &quot;this&quot; is a global object if you do not use addInitializer
  	// because the &quot;this&quot; in the returned function is the &quot;this&quot;
  	// of the cache function which is global object
    return function(...args) {
      const argsKey = args.join(&quot;-&quot;);
      if (this.cache[argsKey] === undefined) {
        	console.log(&quot;Caching the output&quot;);
            this.cache[argsKey] = value(...args);
      }
      return this.cache[argsKey];
    }  
}

class exampleClass {
  	@cache add(x,y) {
    	return x + y;
    }
}

const c = new exampleClass();
console.log(&quot;First call&quot;, c.add(5, 5));
// -&gt; &quot;Caching the output&quot;
// -&gt; &quot;First call 10&quot;

console.log(&quot;Second call&quot;, c.add(5, 5));
// -&gt; &quot;Second call 10&quot;

答案4

得分: 0

受@trincot的启发,我想出了这个方法,似乎可以正常工作。

欢迎提出改进的建议。(另外,TS不太喜欢this[method]。)

class myClass {
    constructor() {
        for (const method of Object.getOwnPropertyNames(this)) {
            if (typeof this[method] === 'function' && method.indexOf('_get') === 0) {
                const theValue = this[method]();
                Object.defineProperty (
                    this,
                    method,
                    { value: () => theValue, writable: false}
                )
            }
        }
    }

    _getSomeValue = () => {
        return /* 在此处执行工作 */;
    }

    _getSomeValue2 = () => {
        return /* 在此处执行工作 */;
    }

    _getSomeValue3 = () => {
        return /* 在此处执行工作 */;
    }
}
英文:

Inspired by @trincot, I came up with this, which seems to work okay.

Open to suggestions on ways to improve it. (Also, TS doesn't like this[method] very much.)

class myClass {
constructor() {
for (const method of Object.getOwnPropertyNames(this)) {
if (typeof this[method] === &#39;function&#39; &amp;&amp; method.indexOf(&#39;_get&#39;) === 0) {
const theValue = this[method]();
Object.defineProperty (
this,
method,
{ value: () =&gt; theValue, writable: false}
)
}
}
}
_getSomeValue = () =&gt; {
return /* do work here*/;
}
_getSomeValue2 = () =&gt; {
return /* do work here*/;
}
_getSomeValue3 = () =&gt; {
return /* do work here*/;
}
}

答案5

得分: 0

@trincot启发似乎运行速度稍快

    const myFunc = {
    	div(n){
      	console.log(`执行除数(${n})`);
      	const arr = [];
        for (let i = 2; i <= n; i++) {
          if (n % i == 0) arr.push(i);
        }
        return arr;
      },
      fact(n){
      	console.log(`执行阶乘(${n})`);
            let res = 1;
            while (n > 1) res *= n--;
            return res;
      }
    }
    
    console.log(...myFunc.div(15));
    console.log(...myFunc.div(48));
    console.log(myFunc.fact(10));
    console.log(...myFunc.div(15));
    console.log(myFunc.fact(10));

**更新:**
[测试][1]


  [1]: https://perf.link/#eyJpZCI6Inl4NzBseWc2OHBlIiwidGl0bGUiOiJGaW5kaW5nIG51bWJlcnMgaW4gYW4gYXJyYXkgb2YgMTAwMCIsImJlZm9yZSI6IiIsInRlc3RzIjpbeyJuYW1lIjoiVGVzdCBDYXNlIiwiY29kZSI6ImNvbnN0IG15RnVuYyA9IHtcbiAgZGl2KG4pe1xuICAgICAgY29uc3QgYXJyID0gW107XG4gICAgICBmb3IgKGxldCBpID0gMjsgaSA8PSBuOyBpKyspIHtcbiAgICAgICAgaWYgKG4gJSBpID09IDApIGFyci5wdXNoKGkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGFycjtcbiAgfSxcbiAgZmFjdChuKXtcbiAgICAgIGxldCByZXMgPSAxO1xuICAgICAgd2hpbGUgKG4gPiAxKSByZXMgKj0gbi0tO1xuICAgICAgcmV0dXJuIHJlcztcbiAgfVxufVxuXG5Qcm9taXNlLnJlc29sdmUoW1xuICAgICAgbXlGdW5jLmRpdigxNSksXG4gICAgICBteUZ1bmMuZGl2KDQ4KSxcbiAgICAgIG15RnVuYy5mYWN0KDEwKSxcbiAgICAgIG15RnVuYy5kaXYoMTUpLFxuICAgICAgbXlGdW5jLmZhY3QoMTApXG5dKTsiLCJydW5zIjpbMjAwMCwzMDAwMCwxMzAwMDAsOTUwMDAsMTYwMDAwLDk3MDAwLDEyNTAwMCwxNDMwMDAsMjcwMDAsMTIzMDAwLDY3MDAwLDIwMDAsMzAwMCwzMzAwMCwyNTYwMDAsNDAwMCw1ODAwMCwxMDEwMDAsODAwMCw0OTAwMCwzMzAwMCw2OTAwMCwzMDAwLDIwNjAwMCwyMzQwMDAsMTM4MDAwLDEwMDAsMTY4MDAwLDEzNTAwMCwyMzEwMDAsMzAwMDAsOTQwMDAsMzQwMDAsMTIwMDAsMjI5MDAwLDEyNTAwMCwzMDAwMCw4NzAwMCwxMjUwMDAsMzAwMDAsMTQ0MDAwLDEzMjAwMCwyNTAwMCwyNjIwMDAsMjcwMDAwLDEyNjAwMCw2MjAwMCwzODAwMCw3NDAwMCwxNzgwMDAsMTk2MDAwLDQ1MDAwLDIxNTAwMCwxMzQwMDAsMTEwMDAsMzAxMDAwLDM5MDAwLDc3MDAwLDEwMDAsMTAzMDAwLDM0MjAwMCwxMDQwMDAsNTAwMDAsMTA2MDAwLDMxMTAwMCw4NzAwMCw5MTAwMCwxMjcwMDAsNTQwMDAsMTAyMDAwLDEyMDAwMCwxNzUwMDAsNDIwMDAsMjU0MDAwLDEwMDAsMTU3MDAwLDMwMDAwLDMzMDAwLDg3MDAwLDMwMDAwLDEwMDAsMzQzMDAwLDExMzAwMCwxODMwMDAsMzgwMDAsNjUwMDAsNjAwMCwzMTgwMDAsNDIwMDAsOTgwMDAsMzAwMDAsMTM0MDAwLDc0MDAwLDEzOTAwMCwxMTAwMCwxMzUwMDAsMTE2MDAwLDE2MDAwLDU0MDAwLDEzNDAwMF0sIm9wcyI6MTAzMTMwfSx7Im5hbWUiOiJUZXN0IENhc2UiLCJjb2RlIjoiZnVuY3Rpb24gZW5hYmxlQ2FjaGUoZnVuYykge1xuICAgIGNvbnN0IGNhY2hlID0ge307XG4gICAgcmV0dXJuIGZ1bmN0aW9uKC4uLmFyZ3MpIHtcbiAgICAgICAgcmV0dXJuIChjYWNoZVtKU09OLnN0cmluZ2lmeShbdGhpcywgLi4uYXJnc10pXSA%2FPz0ge1xuICAgICAgICAgICAgdmFsdWU6IGZ1bmMuYXBwbHkodGhpcywgYXJncylcbiAgICAgICAgfSkudmFsdWU7XG4gICAgfTtcbn1cblxuZnVuY3Rpb24gZW5hYmxlQ2FjaGVPbk1ldGhvZHMoY2xzKSB7XG4gICAgY29uc3Qgb2JqID0gY2xzLnByb3RvdHlwZTt

<details>
<summary>英文:</summary>

Inspired by @trincot, seems to work slightly faster

    const myFunc = {
    	div(n){
      	console.log(`executing divisors(${n})`);
      	const arr = [];
        for (let i = 2; i &lt;= n; i++) {
          if (n % i == 0) arr.push(i);
        }
        return arr;
      },
      fact(n){
      	console.log(`executing factorial(${n})`);
            let res = 1;
            while (n &gt; 1) res *= n--;
            return res;
      }
    }
    
    console.log(...myFunc.div(15));
    console.log(...myFunc.div(48));
    console.log(myFunc.fact(10));
    console.log(...myFunc.div(15));
    console.log(myFunc.fact(10));

**Update:**
[test][1]


  [1]: https://perf.link/#eyJpZCI6Inl4NzBseWc2OHBlIiwidGl0bGUiOiJGaW5kaW5nIG51bWJlcnMgaW4gYW4gYXJyYXkgb2YgMTAwMCIsImJlZm9yZSI6IiIsInRlc3RzIjpbeyJuYW1lIjoiVGVzdCBDYXNlIiwiY29kZSI6ImNvbnN0IG15RnVuYyA9IHtcbiAgZGl2KG4pe1xuICAgICAgY29uc3QgYXJyID0gW107XG4gICAgICBmb3IgKGxldCBpID0gMjsgaSA8PSBuOyBpKyspIHtcbiAgICAgICAgaWYgKG4gJSBpID09IDApIGFyci5wdXNoKGkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGFycjtcbiAgfSxcbiAgZmFjdChuKXtcbiAgICAgIGxldCByZXMgPSAxO1xuICAgICAgd2hpbGUgKG4gPiAxKSByZXMgKj0gbi0tO1xuICAgICAgcmV0dXJuIHJlcztcbiAgfVxufVxuXG5Qcm9taXNlLnJlc29sdmUoW1xuICAgICAgbXlGdW5jLmRpdigxNSksXG4gICAgICBteUZ1bmMuZGl2KDQ4KSxcbiAgICAgIG15RnVuYy5mYWN0KDEwKSxcbiAgICAgIG15RnVuYy5kaXYoMTUpLFxuICAgICAgbXlGdW5jLmZhY3QoMTApXG5dKTsiLCJydW5zIjpbMjAwMCwzMDAwMCwxMzAwMDAsOTUwMDAsMTYwMDAwLDk3MDAwLDEyNTAwMCwxNDMwMDAsMjcwMDAsMTIzMDAwLDY3MDAwLDIwMDAsMzAwMCwzMzAwMCwyNTYwMDAsNDAwMCw1ODAwMCwxMDEwMDAsODAwMCw0OTAwMCwzMzAwMCw2OTAwMCwzMDAwLDIwNjAwMCwyMzQwMDAsMTM4MDAwLDEwMDAsMTY4MDAwLDEzNTAwMCwyMzEwMDAsMzAwMDAsOTQwMDAsMzQwMDAsMTIwMDAsMjI5MDAwLDEyNTAwMCwzMDAwMCw4NzAwMCwxMjUwMDAsMzAwMDAsMTQ0MDAwLDEzMjAwMCwyNTAwMCwyNjIwMDAsMjcwMDAwLDEyNjAwMCw2MjAwMCwzODAwMCw3NDAwMCwxNzgwMDAsMTk2MDAwLDQ1MDAwLDIxNTAwMCwxMzQwMDAsMTEwMDAsMzAxMDAwLDM5MDAwLDc3MDAwLDEwMDAsMTAzMDAwLDM0MjAwMCwxMDQwMDAsNTAwMDAsMTA2MDAwLDMxMTAwMCw4NzAwMCw5MTAwMCwxMjcwMDAsNTQwMDAsMTAyMDAwLDEyMDAwMCwxNzUwMDAsNDIwMDAsMjU0MDAwLDEwMDAsMTU3MDAwLDMwMDAwLDMzMDAwLDg3MDAwLDMwMDAwLDEwMDAsMzQzMDAwLDExMzAwMCwxODMwMDAsMzgwMDAsNjUwMDAsNjAwMCwzMTgwMDAsNDIwMDAsOTgwMDAsMzAwMDAsMTM0MDAwLDc0MDAwLDEzOTAwMCwxMTAwMCwxMzUwMDAsMTE2MDAwLDE2MDAwLDU0MDAwLDEzNDAwMF0sIm9wcyI6MTAzMTMwfSx7Im5hbWUiOiJUZXN0IENhc2UiLCJjb2RlIjoiZnVuY3Rpb24gZW5hYmxlQ2FjaGUoZnVuYykge1xuICAgIGNvbnN0IGNhY2hlID0ge307XG4gICAgcmV0dXJuIGZ1bmN0aW9uKC4uLmFyZ3MpIHtcbiAgICAgICAgcmV0dXJuIChjYWNoZVtKU09OLnN0cmluZ2lmeShbdGhpcywgLi4uYXJnc10pXSA%2FPz0ge1xuICAgICAgICAgICAgdmFsdWU6IGZ1bmMuYXBwbHkodGhpcywgYXJncylcbiAgICAgICAgfSkudmFsdWU7XG4gICAgfTtcbn1cblxuZnVuY3Rpb24gZW5hYmxlQ2FjaGVPbk1ldGhvZHMoY2xzKSB7XG4gICAgY29uc3Qgb2JqID0gY2xzLnByb3RvdHlwZTtcbiAgICBmb3IgKGNvbnN0IG1ldGhvZCBvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhvYmopKSB7XG4gICAgICAgIGlmICh0eXBlb2Ygb2JqW21ldGhvZF0gPT09IFwiZnVuY3Rpb25cIiAmJiBtZXRob2QgIT09IFwiY29uc3RydWN0b3JcIikgeyAvLyBtZXRob2Qgb2YgY2xzXG4gICAgICAgICAgICBvYmpbbWV0aG9kXSA9IGVuYWJsZUNhY2hlKG9ialttZXRob2RdKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuY2xhc3MgbXlDbGFzcyB7XG4gICAgZGl2aXNvcnMobikge1xuICAgICAgICBjb25zdCBhcnIgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDI7IGkgPD0gbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAobiAlIGkgPT0gMCkgYXJyLnB1c2goaSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGFycjtcbiAgICB9XG4gICAgZmFjdG9yaWFsKG4pIHtcbiAgICAgICAgbGV0IHJlcyA9IDE7XG4gICAgICAgIHdoaWxlIChuID4gMSkgcmVzICo9IG4tLTtcbiAgICAgICAgcmV0dXJuIHJlcztcbiAgICB9XG59XG5cbmxldCBvYmogPSBuZXcgbXlDbGFzcztcbmVuYWJsZUNhY2hlT25NZXRob2RzKG15Q2xhc3MpO1xuUHJvbWlzZS5yZXNvbHZlKFtcbiAgICBvYmouZGl2aXNvcnMoMTUpLFxuICAgIG9iai5kaXZpc29ycyg0OCksXG4gICAgb2JqLmZhY3RvcmlhbCgxMCksXG4gICAgb2JqLmRpdmlzb3JzKDE1KSxcbiAgICBvYmouZmFjdG9yaWFsKDEwKVxuXSk7IiwicnVucyI6WzEwMDAsMTMwMDAsNjAwMCwxMjAwMCw0MjAwMCwyNzAwMCwxNzAwMCw0MDAwLDI1MDAwLDMxMDAwLDE2MDAwLDM4MDAwLDEwMDAsNDEwMDAsMzgwMDAsMTkwMDAsNDEwMDAsODAwMCw1MDAwLDEwMDAsNTAwMCwyMjAwMCwxMDAwLDQ0MDAwLDM3MDAwLDI4MDAwLDM4MDAwLDM1MDAwLDUwMDAwLDU1MDAwLDM4MDAwLDcwMDAsMjQwMDAsMTgwMDAsNTEwMDAsMjAwMCwzODAwMCwxMDAwLDIxMDAwLDc0MDAwLDQwMDAwLDM2MDAwLDYwMDAsMjYwMDAsNTEwMDAsMjkwMDAsMTMwMDAsMzAwMCwxNjAwMCwzMTAwMCw0NzAwMCw5MDAwLDQ0MDAwLDUzMDAwLDQ4MDAwLDM5MDAwLDM4MDAwLDcwMDAsMjAwMCwxMDAwLDY0MDAwLDUwMDAsMzUwMDAsNDAwMDAsNDcwMDAsMjAwMCw0MTAwMCwyMzAwMCwyMDAwLDIwMDAsMzAwMCwzODAwMCwzNzAwMCw3NTAwMCwxNDAwMCwzNzAwMCwzMDAwLDY0MDAwLDM1MDAwLDYwMDAwLDE4MDAwLDYxMDAwLDIwMDAsMzgwMDAsNDEwMDAsNDEwMDAsNjAwMDAsNTUwMDAsNDgwMDAsMjAwMDAsNjUwMDAsMjcwMDAsMjQwMDAsMjEwMDAsMzgwMDAsMTgwMDAsMTAwMCwzNzAwMCwxMjAwMCw4MDAwXSwib3BzIjoyNzc2MH1dLCJ1cGRhdGVkIjoiMjAyMy0wNi0xMFQwOTowMDo0MC44OTlaIn0%3D

</details>



huangapple
  • 本文由 发表于 2023年6月8日 03:01:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76426339.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定