生成器和迭代器

一、迭代器

1.什么是迭代器

迭代器(iterator),是使用戶可在容器對象(container,例如鏈表或數(shù)組)上遍訪的對象,使用該接口無需關(guān)心對象的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)?!镜鞅旧硎且粋€(gè)對象】

  • 其行為像數(shù)據(jù)庫中的光標(biāo),迭代器最早出現(xiàn)在1974年設(shè)計(jì)的CLU編程語言中。
  • 在各種編程語言的實(shí)現(xiàn)中,迭代器的實(shí)現(xiàn)方式各不相同,但是基本都有迭代器,比如Java、Python等。

從迭代器的定義我們可以看出來,迭代器是幫助我們對某個(gè)數(shù)據(jù)結(jié)構(gòu)進(jìn)行遍歷的對象。

在JavaScript中,迭代器也是一個(gè)具體的對象,這個(gè)對象需要符合迭代器協(xié)議(iterator protocol)

  • 迭代器協(xié)議定義了產(chǎn)生一系列值(無論是有限還是無限個(gè))的標(biāo)準(zhǔn)方式;
  • 那么在js中這個(gè)標(biāo)準(zhǔn)就是一個(gè)特定的next方法。

next方法有如下的要求:

  • 一個(gè)無參數(shù)或一個(gè)參數(shù)的函數(shù),返回一個(gè)應(yīng)當(dāng)擁有以下兩個(gè)屬性的對象:
    • done(boolean)
      • 如果迭代器可以產(chǎn)生序列中的下一個(gè)值,則為false。(這等價(jià)于沒有指定done這個(gè)屬性。)
      • 如果迭代器已將序列迭代完畢,則為true。這種情況下,value是可選的,如果它依然存在,即為迭代結(jié)束之后的默認(rèn)返回值。
    • value
      • 迭代器返回的任何javascript值。done為true時(shí)可省略。
const names=["abc","cba","nba"];


// * 創(chuàng)建一個(gè)迭代器對象來訪問數(shù)組
let index=0;
const namesIterator={
  next:function(){
    // return {done:false,"abc"};
    // return {done:false,"cba"};
    // return {done:false,"nba"};
    // return {done:true,undefined};
    if(index<names.length){
      return {value:names[index++],done:false};
    }else{
      return {value:undefined,done:true};
    }
  }
}
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

1.1 生成迭代器的函數(shù)

// * 其實(shí)數(shù)組本身包含迭代器,只是我們想要實(shí)現(xiàn)這個(gè)思想
const names=["wjy","hyz","tqy"];
const nums=[1,2,3,4,5]

// * 這些都有限迭代器
function createArrayIterator(arr){
  let index=0;
  let iterator={
    index:0,
    next(){
      if(index<arr.length){
        return {done:false,value:arr[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
  return iterator;
}

let namesIterator=createArrayIterator(names);
let numsIterator=createArrayIterator(nums);
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

console.log("-----分割線------------");
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());

console.log("----------無限迭代器--------");

// * 創(chuàng)建一個(gè)無限的迭代器
function createNumberIterator()
{
  let index=0;
  return {
    next:function(){
      return {done:false,value:index++}
    }
  }
}

const numberIterator=createNumberIterator();
console.log(numberIterator.next());

2.可迭代對象

但是上面的代碼整體來說有點(diǎn)奇怪的:

  • 我們獲取一個(gè)數(shù)組的時(shí)候,我們需要自己去創(chuàng)建一個(gè)index變量,再創(chuàng)建一個(gè)所謂的迭代器對象
  • 事實(shí)上我們可以對上面的代碼進(jìn)行進(jìn)一步的封裝,讓其變成一個(gè)可迭代對象

什么是可迭代對象呢?

  • 它和迭代器是不同的概念
  • 當(dāng)一個(gè)對象實(shí)現(xiàn)了iterable protocol協(xié)議時(shí),它就是一個(gè)可迭代對象
  • 這個(gè)對象的要求是必須實(shí)現(xiàn)@@iterator方法,在代碼中我們使用Symbol.iterator訪問該屬性。

那我們要問一個(gè)問題,我們轉(zhuǎn)成這樣的一個(gè)東西有什么好處呢?

  • 當(dāng)一個(gè)對象變成一個(gè)可迭代對象的時(shí)候,進(jìn)行某些迭代操作,比如for……of操作,其實(shí)就會(huì)調(diào)用它的@@iterator方法。

每次調(diào)用都是生成一個(gè)新的迭代器

// *可迭代對象是符合iterable protocol協(xié)議,它是一個(gè)對象,
// * 這個(gè)對象實(shí)現(xiàn)了@@iterator,在代碼中我們可以使用Symbol.iterator去訪問該屬性,這個(gè)屬性本身是一個(gè)函數(shù),返回一個(gè)迭代器

const iterableObj={
 names:["wjy","hyz","tqy"],
 [Symbol.iterator]:function(){
   let index=0;
   return {
    next:()=>{
      if(index<this.names.length){
        return {done:false,value:this.names[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
 }
}
// * iterableObj就是一個(gè)可迭代的對象

// * 生成了一個(gè)迭代器,每次調(diào)用都是產(chǎn)生了一個(gè)新的迭代器對象
let iterator=iterableObj[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

console.log("-------------分割線-------");

let iterator2=iterableObj[Symbol.iterator]();

console.log(iterator2.next());
console.log(iterator2.next());
console.log(iterator2.next());

2.1 for...of只遍歷可迭代的對象

for...of只遍歷可迭代的對象,例如下面的變量obj就是一個(gè)普通的變量,不是一個(gè)可迭代對象,所以會(huì)報(bào)錯(cuò)。

// * for……of可以遍歷的東西必須是一個(gè)可迭代的對象
const obj={
  name:"wjy",
  age:18
}

// * 普通的對象是不支持for……of的,因?yàn)樗皇且粋€(gè)可迭代對象
for(const item of obj){
  console.log("item:",item);
}

使用for...of遍歷iterableObj對象,它會(huì)調(diào)用@@iterator方法,那它什么時(shí)候會(huì)停止呢?

當(dāng)done為true時(shí),就會(huì)停止。

每次取的是value的值。

const iterableObj={
 names:["wjy","hyz","tqy"],
 [Symbol.iterator]:function(){
   let index=0;
   return {
    next:()=>{
      if(index<this.names.length){
        return {done:false,value:this.names[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
 }
}

for(const item of iterableObj){
  console.log("item:",item);
}

3.原生迭代器對象

事實(shí)上我們平時(shí)創(chuàng)建的很多原生對象已經(jīng)實(shí)現(xiàn)了可迭代協(xié)議,會(huì)生成一個(gè)迭代器對象的:

  • String、Array、Map、Set、arguments對象、NodeList集合
    • 它們的實(shí)例對象就是一個(gè)可迭代對象
    • 函數(shù)的arguments也是一個(gè)可迭代對象
//  * Array、String、Map、Set、NodeList的實(shí)例對象就是一個(gè)可迭代對象
const names=["wjy","hyz","tqy"];

// const iterator=names[Symbol.iterator]();
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());

for(const item of names){
  console.log(item);
}

console.log("-----------分割線------------");

// * Set/Map
const set=new Set();
set.add("java");
set.add("python");
set.add("javascript");

console.log(set[Symbol.iterator]);
for(const item of set){
  console.log(item);
}
console.log("----------函數(shù)的arguements----------");

// * 函數(shù)的arguments也是一個(gè)可迭代的對象
function foo(x,y,z){
  console.log(arguments[Symbol.iterator]);
  for(const item of arguments){
    console.log("參數(shù):",item);
  }
}

foo(10,20,30,50)

4.可迭代對象的應(yīng)用

那么這些東西可以被用在哪里呢?

  • javascript中語法: for……of、展開語法、yield、解構(gòu)賦值
  • 創(chuàng)建一些對象:new Map([iterable])、new WeakMap([iterable])、new Set([Iierable])、new Set([iterable])、new WeakSet([iterable])
  • 一些方法的調(diào)用:Promise.all([iterable])、Promise.race(iterable)、Array.from([iterable])

這些方法本質(zhì)上是調(diào)用迭代器的next方法

const iterableObj={
  names:["wjy","hyz","tqy"],
  [Symbol.iterator]:function(){
    let index=0;
    return {
     next:()=>{
       if(index<this.names.length){
         return {done:false,value:this.names[index++]}
       }else{
         return {done:true,value:undefined};
       }
     }
   }
  }
 }

//  * 展開運(yùn)算符
 const names=["wjy","hyz","tqy"];
 const newNames=[...names,...iterableObj];
 console.log(newNames);


 const obj={
   name:"ysc",
   age:20
 }
//  * ES9 新增的一個(gè)特性:用的不是一個(gè)迭代器
 const newObj={...obj};
 console.log(newObj);

 /**
  * * 解構(gòu)
  */

 const[name1,name2]=names; // * 使用的是iterator.next()方法取出值
 const {name,age}=obj;//* 不一樣,使用的ES9新增的特性。

// * 創(chuàng)建一些其他對象時(shí)
const set=new Set(iterableObj);
const set2=new Set(names);
console.log(set);
console.log(set2);

/**
 * * 數(shù)組 Array.from([iterable])
 */
const arr1=Array.from(set);
console.log("arr1:",arr1);

/**
 * * Promise
 */
// * 如果可迭代對象中的value是一個(gè)普通的值,而不是一個(gè)Promise,其實(shí)實(shí)際執(zhí)行的是Promise.resolve(value)
Promise.all(iterableObj).then(res=>{
  console.log(res);
})
  • forEach是數(shù)組的方法

5. 自定義類的迭代

在前面我們可以看到Array、Set、Map、等類創(chuàng)建出來的對象都是可迭代對象

  • 在面向?qū)ο箝_發(fā)中,我們可以通過class類定義一個(gè)自己的類,這個(gè)類可以創(chuàng)建很多對象
  • 如果我們希望自己的類創(chuàng)建出來的對象默認(rèn)都是可迭代的,那么在設(shè)計(jì)類的時(shí)候我們就可以添加上@@iterator方法

案例:創(chuàng)建一個(gè)calssroom方法

  • 教室中有自己的位置、名稱、當(dāng)前教室的學(xué)生
  • 這個(gè)教室可以進(jìn)來新學(xué)生(push)
  • 創(chuàng)建的教室對象是可迭代對象


// * 創(chuàng)建一個(gè)classroom類,這個(gè)類創(chuàng)建出來的對象都是可迭代對象
class Classroom{
  constructor(address,name,students){
    this.address=address;
    this.name=name;
    this.students=students;
  }
  entry(newStudent){
    this.students.push(newStudent)
  }
  [Symbol.iterator](){
    let index=0;
    return {
      next:()=>{
        if(index<this.students.length){
          return {done:false,value:this.students[index++]};
        }else{
          return {done:true,value:undefined};
        }
       
      },
      return:()=>{
        // * 這個(gè)可以監(jiān)聽迭代器的終止
        console.log("迭代器提前終止了");
        return {done:true,value:undefined}
      }
    }
  }
}

const classroom=new Classroom("厚德樓A棟","311",["james","kobe","curry","why"]);
classroom.entry("lilei")
for(const item of classroom){
  console.log(item);
  if(item=="james") break;
}

5.1 迭代器的中斷

迭代器在某些情況下會(huì)在沒有完全迭代的情況下中斷:

  • 比如遍歷的過程中通過break、continue、return、throw中斷了循環(huán)操作
  • 比如在解構(gòu)的時(shí)候,沒有解構(gòu)所有的值

那么這個(gè)時(shí)候我們想要監(jiān)聽中斷的話,可以添加eturn方法

image-20220516203348619.png

二、生成器

  • 生成器是ES6中新增的一種函數(shù)控制、使用的方案,它可以讓我們更加靈活的控制函數(shù)什么時(shí)候繼續(xù)執(zhí)行、暫停執(zhí)行等。

  • 平時(shí)我們會(huì)編寫很多的函數(shù),這些函數(shù)終止的條件通常是返回值或者發(fā)生了異常。

  • 生成器函數(shù)也是一個(gè)函數(shù),但是和普通的函數(shù)有一些區(qū)別:

    • 首先,生成器函數(shù)需要在function的后面添加一個(gè)符號 : *
    • 其次,生成器函數(shù)可以通過yield關(guān)鍵字來控制函數(shù)的執(zhí)行流程。
    • 最后,生成器函數(shù)的返回值是一個(gè)Generator(生成器)
      • 生成器事實(shí)上是一種特殊的迭代器
      • MDN:Instead ,they return a special type of iterator,called a Generator
/**
 * * 生成器函數(shù)是一個(gè)函數(shù),但和普通函數(shù)有一些區(qū)別
 * * 1.需要在function加 *
 * * 2. 可以使用yield關(guān)鍵字控制函數(shù)的執(zhí)行流程
 * * 3. 生成器函數(shù)返回的其實(shí)是一個(gè)生成器(Generator)
 */
function* foo(){
  console.log("函數(shù)開始執(zhí)行");
  const value1=100;
  console.log(value1);
  yield
  const value2=200;
  console.log(value2);
  yield
  const value3=300;
  console.log(value3);
  yield
  console.log("函數(shù)執(zhí)行結(jié)束");
}

let g=foo();//* 執(zhí)行生成器函數(shù)時(shí),其實(shí)里面的一行代碼都不會(huì)執(zhí)行,所以不會(huì)有任何的打印和輸出,但會(huì)返回一個(gè)生成器
console.log(g);
// * 執(zhí)行第一段代碼
g.next()
console.log("----------分割線--------");
// * 執(zhí)行第二段代碼
g.next()
console.log("----------分割線--------");

// * 執(zhí)行第三段代碼
g.next()
console.log("----------分割線--------");

// * 執(zhí)行第四段代碼
g.next()

1.生成器函數(shù)的執(zhí)行

  • 生成器函數(shù)的執(zhí)行體不會(huì)執(zhí)行,但是會(huì)返回一個(gè)生成器對象。
    • 那么如何執(zhí)行讓它執(zhí)行函數(shù)中的東西呢?調(diào)用next即可。
    • 我們之前學(xué)習(xí)迭代器時(shí),知道迭代器的next是會(huì)有返回值的。
    • 但是我們很多時(shí)候不希望next返回的是一個(gè)undefined,這個(gè)時(shí)候我們可以通過yield來返回結(jié)果。
/**
 * * 當(dāng)遇到y(tǒng)ield的時(shí)候,是暫停函數(shù)的執(zhí)行
 * * 當(dāng)遇到return的時(shí)候,是結(jié)束函數(shù)的執(zhí)行,將done變?yōu)閠rue,如果后面有代碼都不會(huì)執(zhí)行。
 */

/**
 * * yield后面可以添加一個(gè)表達(dá)式,后面作為本次next的value的值
 */
function* foo(){
  console.log("函數(shù)開始執(zhí)行");
  const value1=100;
  console.log(value1);
  // return value1;//* return是一個(gè)特殊的yield,它就會(huì)將done變?yōu)閠rue,后面的代碼都不會(huì)執(zhí)行了。
  // console.log("繼續(xù)執(zhí)行代碼");
  yield value1;
  const value2=200;
  console.log(value2);
  yield value2;
  const value3=300;
  console.log(value3);
  yield value3
  console.log("函數(shù)執(zhí)行結(jié)束");
  return "123"
}


/**
 * * 生成器是一個(gè)特殊的迭代器
 *  * next()函數(shù)會(huì)返回一個(gè)對象 {value:"",done:false/true}
 */
const generator=foo()
console.log("返回值1:",generator.next());
console.log("返回值2:",generator.next());
console.log("返回值3:",generator.next());
console.log("返回值4:",generator.next());

yield和return的區(qū)別:

  • yield會(huì)暫停函數(shù)的執(zhí)行
  • return是終止函數(shù)的執(zhí)行,并將done設(shè)置為true,后面的代碼都不會(huì)執(zhí)行。

2. next方法傳遞參數(shù)

函數(shù)既然可以暫停分段執(zhí)行,那么函數(shù)應(yīng)該是可以傳遞參數(shù)的,我們是否可以給每個(gè)分段來傳遞參數(shù)呢?

  • 答案是可以的。

  • next方法可以傳遞參數(shù),它傳遞的值會(huì)作為上一個(gè)yield的返回值。

  • 注意:也就是說 我們是為本次的函數(shù)代碼塊執(zhí)行提供了一個(gè)值。

function* foo(){
  console.log("函數(shù)開始執(zhí)行");
  const value1=100;
  console.log(value1);
  const n=yield value1;
  
  const value2=200*n;
  console.log(value2);
  const count=yield value2;

  const value3=300*count;
  console.log(value3);
  yield value3;

  console.log("函數(shù)執(zhí)行結(jié)束");
  return "123"
}

const generator=foo();

// * 生成器的next可以傳遞參數(shù)
// * next()傳入的參數(shù)會(huì)作為上一個(gè)yield的返回值
console.log(generator.next());

// * 第二段代碼的執(zhí)行是第二次next的執(zhí)行
console.log(generator.next(10));

// * 第三段代碼的執(zhí)行是第三次next的執(zhí)行
console.log(generator.next(20));

3. return方法終止執(zhí)行

還可以調(diào)用return方法:它會(huì)終止函數(shù)的執(zhí)行,將傳遞的參數(shù)作為value的值,done設(shè)置為true

之后調(diào)用next不會(huì)繼承生成值了,只會(huì)生成 {value:undefined,done:true}

function* foo(){
  console.log("函數(shù)開始執(zhí)行");
  const value1=100;
  console.log("第一段代碼:",value1);
  const n=yield value1;
  
  const value2=200*n;
  console.log("第二段代碼:",value2);
  const count=yield value2;

  const value3=300*count;
  console.log("第三段代碼:",value3);
  yield value3;

  console.log("函數(shù)執(zhí)行結(jié)束");
  return "123"
}

const generator=foo();
console.log(generator.next());

// * 第二段代碼執(zhí)行:使用了return:終止函數(shù)執(zhí)行
// * 那么就意味著相當(dāng)于在第一段代碼的后面添加了 return 參數(shù),執(zhí)行了return操作會(huì)將參數(shù)作為value的值,done設(shè)置為true
// * 后面的代碼都不會(huì)執(zhí)行了。
console.log(generator.return(10));
image-20220516220727626.png

4.trow方法拋出異常

function* foo(){
  console.log("代碼開始執(zhí)行~");
   const  value1=100;
  try{
    yield value1;
  }catch(err){
    console.log("捕獲到異常情況:",err);
    yield "abc"
  }

   const value2=200;
   yield value2; 

  console.log("代碼執(zhí)行結(jié)束~");
}
const generator=foo()

const result=generator.next();
// * 第二段代碼不會(huì)執(zhí)行 
if(result.value!==200){
  console.log(generator.throw("error message"));
}
// * 如果你將異常捕獲,代碼可以繼續(xù)執(zhí)行,如果沒有捕獲,則是不可以的。

5. 生成器替代迭代器

因?yàn)樯a(chǎn)器是一種特殊的迭代器,那么在某些情況下我們可以使用生成器替代迭代器。

  • 事實(shí)上我們還可以使用yield*來生產(chǎn)一個(gè)可迭代對象
    • 這個(gè)時(shí)候相當(dāng)于yield的語法糖,只不過會(huì)依次迭代這個(gè)可迭代對象,每次迭代其中的一個(gè)值。
function* createArrayIterator(arr){
  
  // * 第一種寫法
  // yield "wjy";
  // yield "hyz";
  // yield "tqy";

  // * 第二種寫法
  // for(const item of arr){
  //   yield item;
  // }

  // * 第三種寫法 yield*,后面跟一個(gè)可迭代的對象
  yield* arr;


}

const names=["wjy","hyz","tqy"];
const namesIterator=createArrayIterator(names);
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

/**
 * * yield * 
 */

創(chuàng)建一個(gè)函數(shù),這個(gè)函數(shù)可以遍歷一個(gè)范圍內(nèi)的數(shù)字

// * 2. 創(chuàng)建一個(gè)函數(shù),這個(gè)函數(shù)可以迭代一個(gè)范圍內(nèi)的數(shù)字
function* createRangeIterator(start,end){
  let index=start;
  while(index<end){
    yield index++;
  }
  // let index=start;
  // return {
  //   next:function(){
  //     if(start<end){
  //       return {done:false,value:start++};
  //     }else{
  //       return {done:true,value:undefined}
  //     }
  //   }
  // }
}
let  rangeIterator=createRangeIterator(2,10);
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());

Classroom的Symbol.iterator方法改寫,里面使用生成器來完成迭代。

// * classroom案例

class Classroom{
  constructor(address,name,students){
    this.address=address;
    this.name=name;
    this.students=students;
  }
  entry(newStudent){
    this.students.push(newStudent)
  }
 foo=()=>{
   console.log("foo function");
 }
//  [Symbol.iterator]=function*(){
//   yield* this.students;
//  }
  *[Symbol.iterator](){
  yield* this.students;
  }
} 

const classroom=new Classroom("厚德樓","311",["hyz","wjy"])
// const classroomIterator=classroom[Symbol.iterator]();
// console.log(classroomIterator.next());

for(const item of classroom){
  console.log(item);
}

6.異步處理方案

function requestData(url){
  // 模擬網(wǎng)絡(luò)請求
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(url);
    },3000);
  })
}

需求:

// * 多次調(diào)用
// * 1.url:why->res:why
// * 2.url:res+"aaa"->res:whyaaa
// * 3.url: res+"bbb"=>res:whyaaabbb

6.1 回調(diào)地獄

// * 回調(diào)函數(shù)嵌套回調(diào)函數(shù),回調(diào)地獄,代碼可讀性差
// * 第一種方案:多次回調(diào)
requestData("why").then(res=>{
  requestData(res+"aaa").then(res=>{
    requestData(res+"bbb").then(res=>{
      console.log("最終:",res);
    })
  })
})

6.2 Promise.then的返回值來實(shí)現(xiàn)

  • 可讀性差
requestData("why").then(res=>{
  return requestData(res+"aaa");
}).then(res=>{
  return requestData(res+"bbb");
}).then(res=>{
  console.log("最終:",res);
})

6.3 Promise和generator的實(shí)現(xiàn)

function* getData(){ //* 封裝成一個(gè)生成器函數(shù)
  const res1=yield requestData("why");
  const res2=yield requestData(res1+"aaa");
  const res3=yield requestData(res2+"bbb");
  console.log(res3);
}
// * 手動(dòng)執(zhí)行生成器函數(shù)
const generator=getData();//* 生成器函數(shù)返回一個(gè)生成器
generator.next().value.then(res=>{
  generator.next(res).value.then(res=>{
    generator.next(res).value.then(res=>{
      console.log(res);
    })
  })
})

改寫了一個(gè)自動(dòng)執(zhí)行生成器函數(shù):

function exeGenerator(genFn){
  const generator=genFn();
  function exec(res){
    const result=generator.next(res);
    if(result.done) return result.value;
    result.value.then(res=>{
      // console.log(res);
      exec(res);
    })
  }
  exec();
}
exeGenerator(getData)

或使用第三方庫:co

// * 第三方包c(diǎn)o自動(dòng)執(zhí)行
const co=require("co");
co(getData)

6.4 async和await

// * 第四種方案: async/await:本質(zhì)上是generator和Promise的語法糖
async function getData(){ //* 封裝成一個(gè)生成器函數(shù)
  const res1=await requestData("why");
  const res2=await requestData(res1+"bbb");
  const res3=await requestData(res2+"ccc");
  console.log(res3);
}
getData()

三、總結(jié)

生成器和迭代器.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容