英文:
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] === "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)); // 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 == '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())
<!-- end snippet -->
答案3
得分: 2
以下是您要翻译的内容:
"要缩短它的一种方法是使用装饰器,但目前它是一个实验性功能。JS装饰器提案目前处于第3阶段TC9/proposal-decorators。我无法在NodeJS上运行它或设置它使其工作,没有在互联网上找到足够的材料,但我使用了 babeljs/repl 与stage-3预设来获得与浏览器兼容的JS代码并进行了测试。
装饰器函数的形式(更多信息请参阅提案页面):
type ClassMethodDecorator = (value: Function, context: {
kind: "method";
name: string | symbol;
access: { get(): unknown };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}) => Function | void;
带缓存的类示例:
function cache(value, { kind, name, addInitializer }) {
// 如果不返回任何内容,就不会进行修改
if (kind !== "method") return;
// 这用于将函数绑定到类实例
// 在提案页面上阅读更多关于initializer的信息
addInitializer(function () {
// 在类实例上初始化缓存
if (this.cache === undefined) this.cache = {};
if (this.cache[name] === undefined) this.cache = {};
// 将函数绑定到类实例
this[name] = this[name].bind(this);
})
// 如果不使用addInitializer,"this"将是全局对象
// 因为返回函数中的"this"是全局对象的cache函数
return function(...args) {
const argsKey = args.join("-");
if (this.cache[argsKey] === undefined) {
console.log("缓存输出");
this.cache[argsKey] = value(...args);
}
return this.cache[argsKey];
}
}
class exampleClass {
@cache add(x,y) {
return x + y;
}
}
const c = new exampleClass();
console.log("第一次调用", c.add(5, 5));
// -> "缓存输出"
// -> "第一次调用 10"
console.log("第二次调用", c.add(5, 5));
// -> "第二次调用 10"
英文:
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: "method";
name: string | symbol;
access: { get(): unknown };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}) => Function | void;
Example of a class with cache:
function cache(value, { kind, name, addInitializer }) {
// If nothing is returned, nothing is modified
if (kind !== "method") 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);
})
// "this" is a global object if you do not use addInitializer
// because the "this" in the returned function is the "this"
// of the cache function which is global object
return function(...args) {
const argsKey = args.join("-");
if (this.cache[argsKey] === undefined) {
console.log("Caching the output");
this.cache[argsKey] = value(...args);
}
return this.cache[argsKey];
}
}
class exampleClass {
@cache add(x,y) {
return x + y;
}
}
const c = new exampleClass();
console.log("First call", c.add(5, 5));
// -> "Caching the output"
// -> "First call 10"
console.log("Second call", c.add(5, 5));
// -> "Second call 10"
答案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] === 'function' && method.indexOf('_get') === 0) {
const theValue = this[method]();
Object.defineProperty (
this,
method,
{ value: () => theValue, writable: false}
)
}
}
}
_getSomeValue = () => {
return /* do work here*/;
}
_getSomeValue2 = () => {
return /* do work here*/;
}
_getSomeValue3 = () => {
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 <= n; i++) {
if (n % i == 0) arr.push(i);
}
return arr;
},
fact(n){
console.log(`executing factorial(${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));
**Update:**
[test][1]
[1]: https://perf.link/#eyJpZCI6Inl4NzBseWc2OHBlIiwidGl0bGUiOiJGaW5kaW5nIG51bWJlcnMgaW4gYW4gYXJyYXkgb2YgMTAwMCIsImJlZm9yZSI6IiIsInRlc3RzIjpbeyJuYW1lIjoiVGVzdCBDYXNlIiwiY29kZSI6ImNvbnN0IG15RnVuYyA9IHtcbiAgZGl2KG4pe1xuICAgICAgY29uc3QgYXJyID0gW107XG4gICAgICBmb3IgKGxldCBpID0gMjsgaSA8PSBuOyBpKyspIHtcbiAgICAgICAgaWYgKG4gJSBpID09IDApIGFyci5wdXNoKGkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGFycjtcbiAgfSxcbiAgZmFjdChuKXtcbiAgICAgIGxldCByZXMgPSAxO1xuICAgICAgd2hpbGUgKG4gPiAxKSByZXMgKj0gbi0tO1xuICAgICAgcmV0dXJuIHJlcztcbiAgfVxufVxuXG5Qcm9taXNlLnJlc29sdmUoW1xuICAgICAgbXlGdW5jLmRpdigxNSksXG4gICAgICBteUZ1bmMuZGl2KDQ4KSxcbiAgICAgIG15RnVuYy5mYWN0KDEwKSxcbiAgICAgIG15RnVuYy5kaXYoMTUpLFxuICAgICAgbXlGdW5jLmZhY3QoMTApXG5dKTsiLCJydW5zIjpbMjAwMCwzMDAwMCwxMzAwMDAsOTUwMDAsMTYwMDAwLDk3MDAwLDEyNTAwMCwxNDMwMDAsMjcwMDAsMTIzMDAwLDY3MDAwLDIwMDAsMzAwMCwzMzAwMCwyNTYwMDAsNDAwMCw1ODAwMCwxMDEwMDAsODAwMCw0OTAwMCwzMzAwMCw2OTAwMCwzMDAwLDIwNjAwMCwyMzQwMDAsMTM4MDAwLDEwMDAsMTY4MDAwLDEzNTAwMCwyMzEwMDAsMzAwMDAsOTQwMDAsMzQwMDAsMTIwMDAsMjI5MDAwLDEyNTAwMCwzMDAwMCw4NzAwMCwxMjUwMDAsMzAwMDAsMTQ0MDAwLDEzMjAwMCwyNTAwMCwyNjIwMDAsMjcwMDAwLDEyNjAwMCw2MjAwMCwzODAwMCw3NDAwMCwxNzgwMDAsMTk2MDAwLDQ1MDAwLDIxNTAwMCwxMzQwMDAsMTEwMDAsMzAxMDAwLDM5MDAwLDc3MDAwLDEwMDAsMTAzMDAwLDM0MjAwMCwxMDQwMDAsNTAwMDAsMTA2MDAwLDMxMTAwMCw4NzAwMCw5MTAwMCwxMjcwMDAsNTQwMDAsMTAyMDAwLDEyMDAwMCwxNzUwMDAsNDIwMDAsMjU0MDAwLDEwMDAsMTU3MDAwLDMwMDAwLDMzMDAwLDg3MDAwLDMwMDAwLDEwMDAsMzQzMDAwLDExMzAwMCwxODMwMDAsMzgwMDAsNjUwMDAsNjAwMCwzMTgwMDAsNDIwMDAsOTgwMDAsMzAwMDAsMTM0MDAwLDc0MDAwLDEzOTAwMCwxMTAwMCwxMzUwMDAsMTE2MDAwLDE2MDAwLDU0MDAwLDEzNDAwMF0sIm9wcyI6MTAzMTMwfSx7Im5hbWUiOiJUZXN0IENhc2UiLCJjb2RlIjoiZnVuY3Rpb24gZW5hYmxlQ2FjaGUoZnVuYykge1xuICAgIGNvbnN0IGNhY2hlID0ge307XG4gICAgcmV0dXJuIGZ1bmN0aW9uKC4uLmFyZ3MpIHtcbiAgICAgICAgcmV0dXJuIChjYWNoZVtKU09OLnN0cmluZ2lmeShbdGhpcywgLi4uYXJnc10pXSA%2FPz0ge1xuICAgICAgICAgICAgdmFsdWU6IGZ1bmMuYXBwbHkodGhpcywgYXJncylcbiAgICAgICAgfSkudmFsdWU7XG4gICAgfTtcbn1cblxuZnVuY3Rpb24gZW5hYmxlQ2FjaGVPbk1ldGhvZHMoY2xzKSB7XG4gICAgY29uc3Qgb2JqID0gY2xzLnByb3RvdHlwZTtcbiAgICBmb3IgKGNvbnN0IG1ldGhvZCBvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhvYmopKSB7XG4gICAgICAgIGlmICh0eXBlb2Ygb2JqW21ldGhvZF0gPT09IFwiZnVuY3Rpb25cIiAmJiBtZXRob2QgIT09IFwiY29uc3RydWN0b3JcIikgeyAvLyBtZXRob2Qgb2YgY2xzXG4gICAgICAgICAgICBvYmpbbWV0aG9kXSA9IGVuYWJsZUNhY2hlKG9ialttZXRob2RdKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuY2xhc3MgbXlDbGFzcyB7XG4gICAgZGl2aXNvcnMobikge1xuICAgICAgICBjb25zdCBhcnIgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDI7IGkgPD0gbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAobiAlIGkgPT0gMCkgYXJyLnB1c2goaSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGFycjtcbiAgICB9XG4gICAgZmFjdG9yaWFsKG4pIHtcbiAgICAgICAgbGV0IHJlcyA9IDE7XG4gICAgICAgIHdoaWxlIChuID4gMSkgcmVzICo9IG4tLTtcbiAgICAgICAgcmV0dXJuIHJlcztcbiAgICB9XG59XG5cbmxldCBvYmogPSBuZXcgbXlDbGFzcztcbmVuYWJsZUNhY2hlT25NZXRob2RzKG15Q2xhc3MpO1xuUHJvbWlzZS5yZXNvbHZlKFtcbiAgICBvYmouZGl2aXNvcnMoMTUpLFxuICAgIG9iai5kaXZpc29ycyg0OCksXG4gICAgb2JqLmZhY3RvcmlhbCgxMCksXG4gICAgb2JqLmRpdmlzb3JzKDE1KSxcbiAgICBvYmouZmFjdG9yaWFsKDEwKVxuXSk7IiwicnVucyI6WzEwMDAsMTMwMDAsNjAwMCwxMjAwMCw0MjAwMCwyNzAwMCwxNzAwMCw0MDAwLDI1MDAwLDMxMDAwLDE2MDAwLDM4MDAwLDEwMDAsNDEwMDAsMzgwMDAsMTkwMDAsNDEwMDAsODAwMCw1MDAwLDEwMDAsNTAwMCwyMjAwMCwxMDAwLDQ0MDAwLDM3MDAwLDI4MDAwLDM4MDAwLDM1MDAwLDUwMDAwLDU1MDAwLDM4MDAwLDcwMDAsMjQwMDAsMTgwMDAsNTEwMDAsMjAwMCwzODAwMCwxMDAwLDIxMDAwLDc0MDAwLDQwMDAwLDM2MDAwLDYwMDAsMjYwMDAsNTEwMDAsMjkwMDAsMTMwMDAsMzAwMCwxNjAwMCwzMTAwMCw0NzAwMCw5MDAwLDQ0MDAwLDUzMDAwLDQ4MDAwLDM5MDAwLDM4MDAwLDcwMDAsMjAwMCwxMDAwLDY0MDAwLDUwMDAsMzUwMDAsNDAwMDAsNDcwMDAsMjAwMCw0MTAwMCwyMzAwMCwyMDAwLDIwMDAsMzAwMCwzODAwMCwzNzAwMCw3NTAwMCwxNDAwMCwzNzAwMCwzMDAwLDY0MDAwLDM1MDAwLDYwMDAwLDE4MDAwLDYxMDAwLDIwMDAsMzgwMDAsNDEwMDAsNDEwMDAsNjAwMDAsNTUwMDAsNDgwMDAsMjAwMDAsNjUwMDAsMjcwMDAsMjQwMDAsMjEwMDAsMzgwMDAsMTgwMDAsMTAwMCwzNzAwMCwxMjAwMCw4MDAwXSwib3BzIjoyNzc2MH1dLCJ1cGRhdGVkIjoiMjAyMy0wNi0xMFQwOTowMDo0MC44OTlaIn0%3D
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论