原型和閉包學(xué)習(xí)總結(jié)

利用了一個下午的時間,將原型和閉包這塊的知識去了解了一下,做了些筆記和總結(jié),感興趣的童鞋可以移步王福朋的博客,寫的很清楚明了~

首先咱們還是先看看javascript中一個常用的運算符——typeof。typeof應(yīng)該算是咱們的老朋友,還有誰沒用過它?

typeof函數(shù)輸出的一共有幾種類型,在此列出:


            console.log(typeof x);    // undefined
            console.log(typeof 10);   // number
            console.log(typeof 'abc'); // string
            console.log(typeof true);  // boolean

            console.log(typeof function () {});  //function

            console.log(typeof [1, 'a', true]);  //object
            console.log(typeof { a: 10, b: 20 });  //object
            console.log(typeof null);  //object
            console.log(typeof new Number(10));  //object
        }
        show();

以上代碼列出了typeof輸出的集中類型標識,其中上面的四種(undefined, number, string, boolean)屬于簡單的值類型,不是對象。剩下的幾種情況——函數(shù)、數(shù)組、對象、null、new Number(10)都是對象。他們都是引用類型。
判斷一個變量是不是對象非常簡單。值類型的類型判斷用typeof,引用類型的類型判斷用instanceof。

var fn = function () { };
console.log(fn instanceof Object);  // true

對象——若干屬性的集合。

java或者C#中的對象都是new一個class出來的,而且里面有字段、屬性、方法,規(guī)定的非常嚴格。但是javascript就比較隨意了——數(shù)組是對象,函數(shù)是對象,對象還是對象。對象里面的一切都是屬性,只有屬性,沒有方法。那么這樣方法如何表示呢?——方法也是一種屬性。因為它的屬性表示為鍵值對的形式。

而且,更加好玩的事,javascript中的對象可以任意的擴展屬性,沒有class的約束。這個大家應(yīng)該都知道,就不再強調(diào)了。
先說個最常見的例子:


小例子

以上代碼中,obj是一個自定義的對象,其中a、b、c就是它的屬性,而且在c的屬性值還是一個對象,它又有name、year兩個屬性。

一切(引用類型)都是對象,對象是屬性的集合

函數(shù)是一種對象,但是函數(shù)卻不像數(shù)組一樣——你可以說數(shù)組是對象的一種,因為數(shù)組就像是對象的一個子集一樣。但是函數(shù)與對象之間,卻不僅僅是一種包含和被包含的關(guān)系,函數(shù)和對象之間的關(guān)系比較復(fù)雜,甚至有一點雞生蛋蛋生雞的邏輯,咱們這一節(jié)就縷一縷。
還是先看一個小例子吧。

function Fn() {
            this.name = '王福朋';
            this.year = 1988;
        }
        var fn1 = new Fn();

上面的這個例子很簡單,它能說明:對象可以通過函數(shù)來創(chuàng)建。對!也只能說明這一點。

但是我要說——對象都是通過函數(shù)創(chuàng)建的——有些人可能反駁:不對!因為:

var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];

但是不好意思,這個——真的——是一種——“快捷方式”,在編程語言中,一般叫做“語法糖”。

做“語法糖”做的最好的可謂是微軟大哥,它把他們家C#那小子弄的不男不女從的,本想圖個人見人愛,誰承想還得到處跟人解釋——其實它是個男孩!

話歸正傳——其實以上代碼的本質(zhì)是:

        //var obj = { a: 10, b: 20 };
        //var arr = [5, 'x', true];

        var obj = new Object();
        obj.a = 10;
        obj.b = 20;

        var arr = new Array();
        arr[0] = 5;
        arr[1] = 'x';
        arr[2] = true;

而其中的 Object 和 Array 都是函數(shù):

console.log(typeof (Object));  // function
console.log(typeof (Array));  // function

所以,可以很負責任的說

對象都是通過函數(shù)來創(chuàng)建的

現(xiàn)在是不是糊涂了—— 對象是函數(shù)創(chuàng)建的,而函數(shù)卻又是一種對象——天哪!函數(shù)和對象到底是什么關(guān)系???

別著急!揭開這個謎底,還得先去了解一下另一位老朋友——prototype原型。

之前說道,函數(shù)也是一種對象。他也是屬性的集合,你也可以對函數(shù)進行自定義屬性。

不用等咱們?nèi)ピ囼?,javascript自己就先做了表率,人家就默認的給函數(shù)一個屬性——prototype。對,每個函數(shù)都有一個屬性叫做prototype。

這個prototype的屬性值是一個對象(屬性的集合,再次強調(diào)!),默認的只有一個叫做constructor的屬性,指向這個函數(shù)本身。



如上圖,SuperType是是一個函數(shù),右側(cè)的方框就是它的原型。

原型既然作為對象,屬性的集合,不可能就只弄個constructor來玩玩,肯定可以自定義的增加許多屬性。例如這位Object大哥,人家的prototype里面,就有好幾個其他屬性。



當然,你也可以在自己自定義的方法的prototype中新增自己的屬性

function Fn() { }
        Fn.prototype.name = '王福朋';
        Fn.prototype.getYear = function () {
            return 1988;
        };

看到?jīng)]有,這樣就變成了



如果用咱們自己的代碼來演示,就是這樣

function Fn() { }
        Fn.prototype.name = '王福朋';
        Fn.prototype.getYear = function () {
            return 1988;
        };

        var fn = new Fn();
        console.log(fn.name);
        console.log(fn.getYear());

即,F(xiàn)n是一個函數(shù),fn對象是從Fn函數(shù)new出來的,這樣fn對象就可以調(diào)用Fn.prototype中的屬性。

因為每個對象都有一個隱藏的屬性——“proto”,這個屬性引用了創(chuàng)建這個對象的函數(shù)的prototype。即:fn.proto === Fn.prototype

這里的"proto"成為“隱式原型”。

上面已經(jīng)提到,每個函數(shù)function都有一個prototype,即原型。這里再加一句話

每個對象都有一個proto,可成為隱式原型。

這個proto是一個隱藏的屬性,javascript不希望開發(fā)者用到這個屬性值,有的低版本瀏覽器甚至不支持這個屬性值。所以你在Visual Studio 2012這樣很高級很智能的編輯器中,都不會有proto的智能提示,但是你不用管它,直接寫出來就是了。

var obj = {};
console.log(obj.__proto__);

obj這個對象本質(zhì)上是被Object函數(shù)創(chuàng)建的,因此obj.proto=== Object.prototype。我們可以用一個圖來表示。

即,每個對象都有一個proto屬性,指向創(chuàng)建該對象的函數(shù)的prototype。

那么上圖中的“Object prototype”也是一個對象,它的proto指向哪里?
好問題!
在說明“Object prototype”之前,先說一下自定義函數(shù)的prototype。自定義函數(shù)的prototype本質(zhì)上就是和 var obj = {} 是一樣的,都是被Object創(chuàng)建,所以它的proto指向的就是Object.prototype。
但是

Object.prototype確實一個特例——它的proto指向的是null。

切記切記!


還有——函數(shù)也是一種對象,函數(shù)也有proto嗎?

又一個好問題!——當然有。

函數(shù)也不是從石頭縫里蹦出來的,函數(shù)也是被創(chuàng)建出來的。誰創(chuàng)建了函數(shù)呢?——Function——注意這個大寫的“F”。
且看如下代碼。

function fn(x,y){
  return x+y;
};
console.log(fn(10,20));
var fn1 = new Function("x","y","return x+y;");
console.log(fn1(5,6));

以上代碼中,第一種方式是比較傳統(tǒng)的函數(shù)創(chuàng)建方式,第二種是用new Functoin創(chuàng)建。
首先根本不推薦用第二種方式。

這里只是向大家演示,函數(shù)是被Function創(chuàng)建的。

好了,根據(jù)上面說的一句話——對象的proto指向的是創(chuàng)建它的函數(shù)的prototype,就會出現(xiàn):Object.proto === Function.prototype。用一個圖來表示。


上圖中,很明顯的標出了:自定義函數(shù)Foo.proto指向Function.prototype,Object.proto指向Function.prototype,唉,怎么還有一個……Function.proto指向Function.prototype?這不成了循環(huán)引用了?

對!是一個環(huán)形結(jié)構(gòu)。

其實稍微想一下就明白了。Function也是一個函數(shù),函數(shù)是一種對象,也有proto屬性。既然是函數(shù),那么它一定是被Function創(chuàng)建。所以——Function是被自身創(chuàng)建的。所以它的proto指向了自身的Prototype。
最后一個問題:Function.prototype指向的對象,它的proto是不是也指向Object.prototype?

答案是肯定的。因為Function.prototype指向的對象也是一個普通的被Object創(chuàng)建的對象,所以也遵循基本的規(guī)則。



對于值類型,你可以通過typeof判斷,string/number/boolean都很清楚,但是typeof在判斷到引用類型的時候,返回值只有object/function,你不知道它到底是一個object對象,還是數(shù)組,還是new Number等等。

這個時候就需要用到instanceof。例如:

function Foo(){}
var f1 = new Foo();
console.log(f1 instanceof Foo);//true
console.log(f1 instanceof Object);//true

上圖中,f1這個對象是被Foo創(chuàng)建,但是“f1 instanceof Object”為什么是true呢?

至于為什么過會兒再說,先把instanceof判斷的規(guī)則告訴大家。根據(jù)以上代碼看下圖:


Instanceof運算符的第一個變量是一個對象,暫時稱為A;第二個變量一般是一個函數(shù),暫時稱為B。

Instanceof的判斷隊則是:

沿著A的proto這條線來找,同時沿著B的prototype這條線來找,如果兩條線能找到同一個引用,即同一個對象,那么就返回true。如果找到終點還未重合,則返回false。

按照以上規(guī)則,大家看看“ f1 instanceof Object ”這句代碼是不是true? 根據(jù)上圖很容易就能看出來,就是true。
通過上以規(guī)則,你可以解釋很多比較怪異的現(xiàn)象,例如:

console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
console.log(Function instanceof Function);//true

這些看似很混亂的東西,答案卻都是true,這是為何?
上一節(jié)咱們貼了好多的圖片,其實那些圖片是可以聯(lián)合成一個整體的,即:



看這個圖片,千萬不要嫌煩,必須一條線一條線挨著分析。如果上一節(jié)你看的比較仔細,再結(jié)合剛才咱們介紹的instanceof的概念,相信能看懂這個圖片的內(nèi)容。

看看這個圖片,你也就知道為何上面三個看似混亂的語句返回的是true了。

問題又出來了。Instanceof這樣設(shè)計,到底有什么用?到底instanceof想表達什么呢?

重點就這樣被這位老朋友給引出來了——繼承——原型鏈。

即,instanceof表示的就是一種繼承關(guān)系,或者原型鏈的結(jié)構(gòu)。

javascript中的繼承是通過原型鏈來體現(xiàn)的。先看幾句代碼

function Foo(){}
var f1 = new Foo();
f1.a = 10;
Foo.prototype.a = 100;
Foo.prototype.b = 200;
console.log(f1.a);//10
console.log(f2.b);//200

以上代碼中,f1是Foo函數(shù)new出來的對象,f1.a是f1對象的基本屬性,f1.b是怎么來的呢?——從Foo.prototype得來,因為f1.proto指向的是Foo.prototype

訪問一個對象的屬性時,先在基本屬性中查找,如果沒有,再沿著proto這條鏈向上找,這就是原型鏈。


上圖中,訪問f1.b時,f1的基本屬性中沒有b,于是沿著proto找到了Foo.prototype.b。

那么我們在實際應(yīng)用中如何區(qū)分一個屬性到底是基本的還是從原型中找到的呢?大家可能都知道答案了——hasOwnProperty,特別是在for…in…循環(huán)中,一定要注意。


等等,不對! f1的這個hasOwnProperty方法是從哪里來的? f1本身沒有,F(xiàn)oo.prototype中也沒有,哪兒來的?

好問題。

它是從Object.prototype中來的,請看圖:


對象的原型鏈是沿著proto這條線走的,因此在查找f1.hasOwnProperty屬性時,就會順著原型鏈一直查找到Object.prototype。

由于所有的對象的原型鏈都會找到Object.prototype,因此所有的對象都會有Object.prototype的方法。這就是所謂的“繼承”。

當然這只是一個例子,你可以自定義函數(shù)和對象來實現(xiàn)自己的繼承。
說一個函數(shù)的例子吧。

我們都知道每個函數(shù)都有call,apply方法,都有l(wèi)ength,arguments,caller等屬性。為什么每個函數(shù)都有?這肯定是“繼承”的。函數(shù)由Function函數(shù)創(chuàng)建,因此繼承的Function.prototype中的方法。不信可以請微軟的Visual Studio老師給我們驗證一下:



看到了吧,有call、length等這些屬性。

那怎么還有hasOwnProperty呢?——那是Function.prototype繼承自O(shè)bject.prototype的方法。

我們總結(jié)一下,在“準備工作”中完成了哪些工作:

變量、函數(shù)表達式——變量聲明,默認賦值為undefined;
this——賦值;
函數(shù)聲明——賦值;
這三種數(shù)據(jù)的準備情況我們稱之為“執(zhí)行上下文”或者“執(zhí)行上下文環(huán)境”。

其實,javascript在執(zhí)行一個代碼段之前,都會進行這些“準備工作”來生成執(zhí)行上下文。這個“代碼段”其實分三種情況——全局代碼,函數(shù)體,eval代碼。
這里解釋一下為什么代碼段分為這三種。

所謂“代碼段”就是一段文本形式的代碼。

首先,全局代碼是一種,這個應(yīng)該沒有非議,本來就是手寫文本到<script>標簽里面的。

其次,eval代碼接收的也是一段文本形式的代碼。

最后,函數(shù)體是代碼段是因為函數(shù)在創(chuàng)建時,本質(zhì)上是 new Function(…) 得來的,其中需要傳入一個文本形式的參數(shù)作為函數(shù)體。

這樣解釋應(yīng)該能理解了。

最后,eval不常用,也不推薦大家用。

全局代碼的上下文環(huán)境數(shù)據(jù)內(nèi)容為:

普通變量(包括函數(shù)表達式),如: var a = 10; 聲明(默認賦值為undefined)
函數(shù)聲明,如: function fn() { } 賦值
this 賦值
參數(shù) 賦值
arguments 賦值
自由變量的取值作用域 賦值

給執(zhí)行上下文環(huán)境下一個通俗的定義

在執(zhí)行代碼之前,把將要用到的所有的變量都事先拿出來,有的直接賦值了,有的先用undefined占個空。

this的取值,分四種情況。我們來挨個看一下。

在此再強調(diào)一遍一個非常重要的知識點

在函數(shù)中this到底取何值,是在函數(shù)真正被調(diào)用執(zhí)行的時候確定的,函數(shù)定義的時候確定不了。

因為this的取值是執(zhí)行上下文環(huán)境的一部分,每次調(diào)用函數(shù),都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境。
情況1:構(gòu)造函數(shù)

所謂構(gòu)造函數(shù)就是用來new對象的函數(shù)。其實嚴格來說,所有的函數(shù)都可以new一個對象,但是有些函數(shù)的定義是為了new一個對象,而有些函數(shù)則不是。另外注意,構(gòu)造函數(shù)的函數(shù)名第一個字母大寫(規(guī)則約定)。例如:Object、Array、Function等。

以上代碼中,如果函數(shù)作為構(gòu)造函數(shù)用,那么其中的this就代表它即將new出來的對象。

注意,以上僅限new Foo()的情況,即Foo函數(shù)作為構(gòu)造函數(shù)的情況。如果直接調(diào)用Foo函數(shù),而不是new Foo(),情況就大不一樣了。

這種情況下this是window,我們后文中會說到。

情況2:函數(shù)作為對象的一個屬性

如果函數(shù)作為對象的一個屬性時,并且作為對象的一個屬性被調(diào)用時,函數(shù)中的this指向該對象。

以上代碼中,fn不僅作為一個對象的一個屬性,而且的確是作為對象的一個屬性被調(diào)用。結(jié)果this就是obj對象。

注意,如果fn函數(shù)不作為obj的一個屬性被調(diào)用,會是什么結(jié)果呢?

如上代碼,如果fn函數(shù)被賦值到了另一個變量中,并沒有作為obj的一個屬性被調(diào)用,那么this的值就是window,this.x為undefined。

情況3:函數(shù)用call或者apply調(diào)用

當一個函數(shù)被call和apply調(diào)用時,this的值就取傳入的對象的值。至于call和apply如何使用,不會的朋友可以去查查其他資料,本系列教程不做講解。

情況4:全局 & 調(diào)用普通函數(shù)

在全局環(huán)境下,this永遠是window,這個應(yīng)該沒有非議。

普通函數(shù)在調(diào)用時,其中的this也都是window。

以上代碼很好理解。

不過下面的情況你需要注意一下:

函數(shù)f雖然是在obj.fn內(nèi)部定義的,但是它仍然是一個普通的函數(shù),this仍然指向window。

情況五:構(gòu)造函數(shù)中的prototype
在構(gòu)造函數(shù)的prototype中,this代表著什么。

如上代碼,在Fn.prototype.getName函數(shù)中,this指向的是f1對象。因此可以通過this.name獲取f1.name的值。

其實,不僅僅是構(gòu)造函數(shù)的prototype,即便是在整個原型鏈中,this代表的也都是當前對象的值。

完了。

看到了吧,this有關(guān)的知識點還是挺多的,不僅多而且非常重要。

最后,既然提到了this,有必要把一個非常經(jīng)典的案例介紹給大家,又是jQuery源碼的。

以上代碼是從jQuery中摘除來的部分代碼。jQuery.extend和jQuery.fn.extend都指向了同一個函數(shù),但是當執(zhí)行時,函數(shù)中的this是不一樣的。

執(zhí)行jQuery.extend(…)時,this指向jQuery;執(zhí)行jQuery.fn.extend(…)時,this指向jQuery.fn。

這樣就巧妙的將一段代碼同時共享給兩個功能使用,更加符合設(shè)計原則。

我們在聲明變量時,全局代碼要在代碼前端聲明,函數(shù)中要在函數(shù)體一開始就聲明好。除了這兩個地方,其他地方都不要出現(xiàn)變量聲明。而且建議用“單var”形式。

先解釋一下什么是“自由變量”。

在A作用域中使用的變量x,卻沒有在A作用域中聲明(即在其他作用域中聲明的),對于A作用域來說,x就是一個自由變量。如下圖

如上程序中,在調(diào)用fn()函數(shù)時,函數(shù)體中第6行。取b的值就直接可以在fn作用域中取,因為b就是在這里定義的。而取x的值時,就需要到另一個作用域中取。到哪個作用域中取呢?

有人說過要到父作用域中取,其實有時候這種解釋會產(chǎn)生歧義。例如:

所以,不要在用以上說法了。相比而言,用這句話描述會更加貼切——要到創(chuàng)建這個函數(shù)的那個作用域中取值——是“創(chuàng)建”,而不是“調(diào)用”,切記切記——其實這就是所謂的“靜態(tài)作用域”。

對于本文第一段代碼,在fn函數(shù)中,取自由變量x的值時,要到哪個作用域中取?——要到創(chuàng)建fn函數(shù)的那個作用域中取——無論fn函數(shù)將在哪里調(diào)用。
上面描述的只是跨一步作用域去尋找。

如果跨了一步,還沒找到呢?——接著跨!——一直跨到全局作用域為止。要是在全局作用域中都沒有找到,那就是真的沒有了。

這個一步一步“跨”的路線,我們稱之為——作用域鏈。

我們拿文字總結(jié)一下取自由變量時的這個“作用域鏈”過程:(假設(shè)a是自由量)

第一步,現(xiàn)在當前作用域查找a,如果有則獲取并結(jié)束。如果沒有則繼續(xù);

第二步,如果當前作用域是全局作用域,則證明a未定義,結(jié)束;否則繼續(xù);

第三步,(不是全局作用域,那就是函數(shù)作用域)將創(chuàng)建該函數(shù)的作用域作為當前作用域;

第四步,跳轉(zhuǎn)到第一步。

以上代碼中:第13行,fn()返回的是bar函數(shù),賦值給x。執(zhí)行x(),即執(zhí)行bar函數(shù)代碼。取b的值時,直接在fn作用域取出。取a的值時,試圖在fn作用域取,但是取不到,只能轉(zhuǎn)向創(chuàng)建fn的那個作用域中去查找,結(jié)果找到了。

閉包
前面提到的上下文環(huán)境和作用域的知識,除了了解這些知識之外,還是理解閉包的基礎(chǔ)。

至于“閉包”這個詞的概念的文字描述,確實不好解釋,我看過很多遍,但是現(xiàn)在還是記不住。

但是你只需要知道應(yīng)用的兩種情況即可——函數(shù)作為返回值,函數(shù)作為參數(shù)傳遞。

第一,函數(shù)作為返回值

如上代碼,bar函數(shù)作為返回值,賦值給f1變量。執(zhí)行f1(15)時,用到了fn作用域下的max變量的值。至于如何跨作用域取值,可以參考上一節(jié)。

第二,函數(shù)作為參數(shù)被傳遞

如上代碼中,fn函數(shù)作為一個參數(shù)被傳遞進入另一個函數(shù),賦值給f參數(shù)。執(zhí)行f(15)時,max變量的取值是10,而不是100。

上一節(jié)講到自由變量跨作用域取值時,曾經(jīng)強調(diào)過:要去創(chuàng)建這個函數(shù)的作用域取值,而不是“父作用域”。理解了這一點,以上兩端代碼中,自由變量如何取值應(yīng)該比較簡單。(不明白的朋友一定要去上一節(jié)看看,這個很重要?。?/p>

另外,講到閉包,除了結(jié)合著作用域之外,還需要結(jié)合著執(zhí)行上下文棧來說一下。

在前面講執(zhí)行上下文棧時(http://www.cnblogs.com/wangfupeng1988/p/3989357.html),我們提到當一個函數(shù)被調(diào)用完成之后,其執(zhí)行上下文環(huán)境將被銷毀,其中的變量也會被同時銷毀。

但是在當時那篇文章中留了一個問號——有些情況下,函數(shù)調(diào)用完成之后,其執(zhí)行上下文環(huán)境不會接著被銷毀。這就是需要理解閉包的核心內(nèi)容。

咱們可以拿本文的第一段代碼(稍作修改)來分析一下。

第一步,代碼執(zhí)行前生成全局上下文環(huán)境,并在執(zhí)行時對其中的變量進行賦值。此時全局上下文環(huán)境是活動狀態(tài)。

第二步,執(zhí)行第17行代碼時,調(diào)用fn(),產(chǎn)生fn()執(zhí)行上下文環(huán)境,壓棧,并設(shè)置為活動狀態(tài)。

第三步,執(zhí)行完第17行,fn()調(diào)用完成。按理說應(yīng)該銷毀掉fn()的執(zhí)行上下文環(huán)境,但是這里不能這么做。注意,重點來了:因為執(zhí)行fn()時,返回的是一個函數(shù)。函數(shù)的特別之處在于可以創(chuàng)建一個獨立的作用域。而正巧合的是,返回的這個函數(shù)體中,還有一個自由變量max要引用fn作用域下的fn()上下文環(huán)境中的max。因此,這個max不能被銷毀,銷毀了之后bar函數(shù)中的max就找不到值了。

因此,這里的fn()上下文環(huán)境不能被銷毀,還依然存在與執(zhí)行上下文棧中。

——即,執(zhí)行到第18行時,全局上下文環(huán)境將變?yōu)榛顒訝顟B(tài),但是fn()上下文環(huán)境依然會在執(zhí)行上下文棧中。另外,執(zhí)行完第18行,全局上下文環(huán)境中的max被賦值為100。如下圖:

第四步,執(zhí)行到第20行,執(zhí)行f1(15),即執(zhí)行bar(15),創(chuàng)建bar(15)上下文環(huán)境,并將其設(shè)置為活動狀態(tài)。

執(zhí)行bar(15)時,max是自由變量,需要向創(chuàng)建bar函數(shù)的作用域中查找,找到了max的值為10。這個過程在作用域鏈一節(jié)已經(jīng)講過。

這里的重點就在于,創(chuàng)建bar函數(shù)是在執(zhí)行fn()時創(chuàng)建的。fn()早就執(zhí)行結(jié)束了,但是fn()執(zhí)行上下文環(huán)境還存在與棧中,因此bar(15)時,max可以查找到。如果fn()上下文環(huán)境銷毀了,那么max就找不到了。

使用閉包會增加內(nèi)容開銷,現(xiàn)在很明顯了吧!

第五步,執(zhí)行完20行就是上下文環(huán)境的銷毀過程,這里就不再贅述了。

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

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

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