一次性搞懂 CommonJS, AMD, CMD , ES Module 等模塊化規(guī)范

1 常見的模塊化規(guī)范

  • CommonJs (Node.js)
  • AMD (RequireJS)
  • CMD (SeaJS)
  • ES Module(ES6 模塊)

2 模塊化的優(yōu)點(diǎn)

  • 在模塊化開發(fā)中,通常一個文件就是一個模塊。該文件有自己的作用域,只向外暴露特定的變量和函數(shù),并且可以按需加載。
  • 依賴自動加載,按需加載。
  • 提高代碼復(fù)用率,方便進(jìn)行代碼的管理,使得代碼管理更加清晰、規(guī)范。
  • 減少了命名沖突,消除全局變量。
  • 目前流行的 js 模塊化規(guī)范有 CommonJS、AMD、CMD 以及 ES6 的模塊系統(tǒng)。

3 CommonJS(Node.js)同步加載模塊

CommonJS 是服務(wù)器模塊的規(guī)范,Node.js采用了這個規(guī)范。

根據(jù) CommonJS 規(guī)范,一個單獨(dú)的文件就是一個模塊,每個模塊都是一個單獨(dú)的作用域,每個文件中定義的變量(還包括函數(shù)和類),都是私有的,對其他文件是不可見的。

CommonJS 規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。

CommonJS 中,加載模塊要使用 require 方法。該方法讀取一個文件并執(zhí)行,最后返回文件內(nèi)部的 exports 對象。

math.js

var x = 5;
var addX = function(value) {
  return value + x;
};

module.exports.x = x;
module.exports.addX = addX;

// 也可以改寫為如下
module.exports = {
  x: x,
  addX: addX,
};

main.js

let math = require('./math.js');
console.log('math.x',math.x);
console.log('math.addX', math.addX(4));

Node.js 主要用于服務(wù)器編程,加載的模塊文件一般都已經(jīng)存在于本地硬盤中,加載起來較快,不用考慮異步加載的方式,故 CommonJS 的同步加載模塊規(guī)范是比較適用的。

但若在瀏覽器環(huán)境中,要從服務(wù)器加載模塊,這時就必須采用異步加載模式,故有了 AMD,CMD 等解決方案。

4 AMD(RequireJS)異步加載模塊

  • AMD = Asynchronous Module Definition,即 異步模塊定義。
  • AMD 規(guī)范加載模塊是異步的,并允許函數(shù)回調(diào)。不必等到所有模塊都加載完成,后續(xù)操作也可正常執(zhí)行。
  • AMD 中,使用 require 獲取依賴模塊,使用 exports 導(dǎo)出 API。
// 規(guī)范 API
define(id?, dependencies?, factory);
define.amd = {};

// 定義無依賴的模塊
define({
    add: function(x,y){
        return x + y;
    }
});

// 定義有依賴的模塊
define(["alpha"], function(alpha){
    return {
        verb: function(){
            return alpha.verb() + 1;
        }
    }
});

異步加載和回調(diào)

require([module], callback)callback 為模塊加載完成后的回調(diào)函數(shù)。

// 加載 math模塊,完成之后執(zhí)行回調(diào)函數(shù)
require(['math'], function(math) {
  math.add(2, 3);
});

RequireJS

RequireJS 是一個 前端模塊化管理 的工具庫,遵循 AMD 規(guī)范,RequireJS 是對 AMD 規(guī)范的闡述。

RequireJS 的基本思想為,通過一個函數(shù)將所需或所依賴的模塊全部裝載進(jìn)來,然后返回一個新的函數(shù)(模塊)。后續(xù)所有關(guān)于新模塊的業(yè)務(wù)代碼,都在這個函數(shù)內(nèi)部操作。

RequireJS 要求每個模塊均放在獨(dú)立的文件之中,并使用 define 定義模塊,使用 require 方法調(diào)用模塊。

根據(jù)是否有依賴其他模塊的情況,可分為 獨(dú)立模塊非獨(dú)立模塊

獨(dú)立模塊(不依賴其他模塊,直接定義)

define({
    method1: function(){},
    method2: function(){}
});

// 等價于
define(function() {
    return {
        method1: function(){},
        method2: function(){}
    }
});

非獨(dú)立模塊(依賴其他模塊)

define([ 'module1', 'module2' ], function(m1, m2) {
    ...
});

// 等價于
define(function(require) {
    var m1 = require('module1');
    var m2 = require('module2');
    ...
});

調(diào)用模塊(使用 require 方法)

require(['foo', 'bar'], function(foo, bar) {
    foo.func();
    bar.func();
});

5 CMD(SeaJS)異步模塊

CMD 的定義

  • CMD = Common Module Definition,即 通用模塊定義CMDSeaJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
  • CMD 規(guī)范和 AMD 規(guī)范類似,都主要運(yùn)行于瀏覽器端,寫法上看起來也很類似。主要區(qū)別,在于 模塊初始化時機(jī)。

AMD 與 CMD 的異同

  • AMD 中,只要模塊作為依賴時就會加載并進(jìn)行初始化。
  • CMD 中,模塊作為依賴且被引用時才會初始化,否則只會加載。
  • CMD 推崇依賴就近,可以把依賴寫進(jìn)你的代碼中的任意一行。AMD 推崇依賴前置。
  • AMDAPI 默認(rèn)是一個當(dāng)多個用,CMD 嚴(yán)格的區(qū)分推崇職責(zé)單一。例如:AMDrequire 分全局的和局部的。CMD里面沒有全局的 require,提供 seajs.use() 來實(shí)現(xiàn)模塊系統(tǒng)的加載啟動。CMD 里每個 API 都簡單純粹。
// AMD
define(['./a','./b'], function (a, b) {

    // 依賴一開始就寫好
    a.test();
    b.test();
});

// CMD
define(function (require, exports, module) {

    // 依賴可以就近書寫
    var a = require('./a');
    a.test();

    ...
    // 軟依賴
    if (status) {
        var b = require('./b');
        b.test();
    }
});

Sea.js

使用 Sea.js,在書寫文件時需要遵守 CMD(Common Module Definition)模塊定義規(guī)范,一個文件就是一個模塊。

用法
  • 通過 exports 暴露接口。這意味著不需要命名空間了,更不需要全局變量。這是一種徹底的命名沖突解決方案。
  • 通過 require 引入依賴。這可以讓依賴內(nèi)置,開發(fā)者只需關(guān)心當(dāng)前模塊的依賴,其他事情 Sea.js 都會自動處理好。對模塊開發(fā)者來說,這是一種很好的 關(guān)注度分離,能讓程序員更多地享受編碼的樂趣。
  • 通過 define 定義模塊。
示例

例如,對于下述 util.js 代碼

var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};

org.CoolSite.Utils.each = function (arr) {
  // 實(shí)現(xiàn)代碼
};

org.CoolSite.Utils.log = function (str) {
  // 實(shí)現(xiàn)代碼
};

可以采用 SeaJS 重寫為


define(function(require, exports) {
  exports.each = function (arr) {
    // 實(shí)現(xiàn)代碼
  };

  exports.log = function (str) {
    // 實(shí)現(xiàn)代碼
  };
});

通過 exports 就可以向外提供接口。通過 require('./util.js') 就可以拿到 util.js 中通過 exports 暴露的接口。這里的 require 可以認(rèn)為是 Sea.js 給 JavaScript 語言增加的一個語法關(guān)鍵字,通過 require 可以獲取其他模塊提供的接口。

define(function(require, exports) {
  var util = require('./util.js');
  exports.init = function() {
    // 實(shí)現(xiàn)代碼
  };
});

SeaJS 與 RequireJS 區(qū)別

二者區(qū)別主要表現(xiàn)在 模塊初始化時機(jī)

  • AMD(RequireJS)中只要模塊作為依賴時,就會加載并初始化。即盡早地執(zhí)行(依賴)模塊。相當(dāng)于所有的 require 都被提前了,而且模塊的執(zhí)行的順序也不一定就是 require 的書寫順序。

  • CMD(SeaJS)中,模塊作為依賴且被引用時才會初始化,否則只會加載。即只會在模塊真正需要使用的時候才初始化。模塊加載的順序是嚴(yán)格按照 require 書寫的順序來的。

規(guī)范 生態(tài) 書寫 易于實(shí)現(xiàn) NodeJS 模塊相似性
AMD ★★★★★ ★★★ ★★★★★ ★★★
CMD ★★ ★★★★★ ★★★ ★★★★★

從規(guī)范上來說,AMD 更加簡單且嚴(yán)謹(jǐn),適用性更廣,而在 RequireJS 強(qiáng)力的推動下,在國外幾乎成了事實(shí)上的異步模塊標(biāo)準(zhǔn),各大類庫也相繼支持 AMD 規(guī)范。

但從 SeaJS 與 CMD 來說,也做了很多不錯東西:1. 相對自然的依賴聲明風(fēng)格 2. 小而美的內(nèi)部實(shí)現(xiàn) 3. 貼心的外圍功能設(shè)計(jì) 4. 更好的中文社區(qū)支持。

6 UMD

  • UMD = Universal Module Definition,即 通用模塊定義,它是AMDCommonJS的糅合。
  • AMD 模塊以 瀏覽器第一的原則 發(fā)展,選擇異步加載。CommonJS 模塊以 服務(wù)器第一原則 發(fā)展,選擇同步加載。由此,迫使人們又想出另一個更通用的模式 UMD(Universal Module Definition),實(shí)現(xiàn)跨平臺的解決方案。
  • UMD 先判斷支持 Node.js 的模塊(exports)是否存在,存在則使用 Node.js 模塊模式。再判斷支持 AMDdefine )是否存在,存在則使用 AMD 方式加載模塊。
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.utilName = factory());
}(this, function () {
    //module ...
});

7 ES Module(ES6 模塊)

CommonJS 和 ES6 模塊 的區(qū)別

  • CommonJS 模塊是 運(yùn)行時加載,ES6 模塊是 編譯時輸出接口。
  • ES6 模塊 輸出的是值的引用,輸出接口動態(tài)綁定,而 CommonJS 輸出的是值的拷貝。
CommonJS 輸出值的拷貝

CommonJS 模塊輸出的是值的拷貝,導(dǎo)出值改變不會導(dǎo)致導(dǎo)入值改變。

lib.js

var counter = 3;
var obj = {
    name: 'David'
};

function changeValue() {
    counter++;
    obj.name = 'Peter';
};

module.exports = {
    counter: counter,
    obj: obj,
    changeValue: changeValue,
};

main.js

var mod = require('./lib');

console.log(mod.counter);  // 3
console.log(mod.obj.name);  //  'David'
mod.changeValue();

console.log(mod.counter);  // 3
console.log(mod.obj.name);  //  'Peter'
// But
console.log(require('./lib').counter);  // 3
console.log(require('./lib').obj.name);  //  'Peter'
  • counter 是基本類型值,模塊內(nèi)部值的變化 不影響 輸出的值變化。
  • obj 是引用類型值,模塊內(nèi)部值的變化 會影響 輸出的值變化。
  • 上述兩點(diǎn)區(qū)別,可類比基本類型和引用類型的賦值操作。

也可以借助取值函數(shù)(getter),將 counter 轉(zhuǎn)為引用類型值,效果如下。

在類的內(nèi)部,可以使用 getset 關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。 —— class | 阮一峰

lib.js

var counter = 3;
function incCounter() {
  counter++;
}

module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
};

main.js

var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 4
ES6 輸出值的引用

ES6 模塊是動態(tài)關(guān)聯(lián)模塊中的值,輸出的是值的引用。原始值變了,import 加載的值也會跟著變。

ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析時,遇到模塊加載命令 import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。ES6 模塊中,原始值變了,import 加載的值也會跟著變。因此,ES6 模塊是動態(tài)引用,并且不會緩存值。 —— ES6 Module 的加載實(shí)現(xiàn) | 阮一峰

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
CommonJS 運(yùn)行時加載 ES6靜態(tài)編譯

CommonJS 模塊是運(yùn)行時加載,ES6 模塊是編譯時輸出接口。

這是因?yàn)椋?strong>CommonJS 加載的是一個對象(即 module.exports 屬性),該對象只有在腳本運(yùn)行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。

ES6 模塊是編譯時輸出接口,因此有如下2個特點(diǎn)

  • import 命令會被 JS 引擎靜態(tài)分析,優(yōu)先于模塊內(nèi)的其他內(nèi)容執(zhí)行
  • export 命令會有變量聲明提升的效果
import 優(yōu)先執(zhí)行

在文件中的任何位置引入 import 模塊都會被提前到文件頂部

// a.js
console.log('a.js')
import { foo } from './b';

// b.js
export let foo = 1;
console.log('b.js 先執(zhí)行');

// 執(zhí)行結(jié)果:
// b.js 先執(zhí)行
// a.js

雖然 a 模塊中 import 引入晚于 console.log('a'),但是它被 JS 引擎通過靜態(tài)分析,提到模塊執(zhí)行的最前面,優(yōu)于模塊中的其他部分的執(zhí)行。

export 命令變量提升效果

由于 importexport 是靜態(tài)執(zhí)行,所以 importexport 具有變量提升效果。即 importexport 命令在模塊中的位置并不影響程序的輸出。

// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
  console.log('bar2');
}
export function bar3() {
  console.log('bar3');
}

// b.js
export let foo = 1;
import * as a from './a';
console.log(a);

// 執(zhí)行結(jié)果:
// { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
// a.js

a 模塊引用了 b 模塊,b 模塊也引用了 a 模塊,export 聲明的變量也是優(yōu)于模塊其它內(nèi)容的執(zhí)行的。但具體對變量賦值需要等到執(zhí)行到相應(yīng)代碼的時候。

ES6 模塊和 CommonJS 相同點(diǎn)

模塊不會重復(fù)執(zhí)行

重復(fù)引入某個相同的模塊時,模塊只會執(zhí)行一次。

循環(huán)依賴

CommonJS 模塊循環(huán)依賴

CommonJS 模塊的重要特性是加載時執(zhí)行,即腳本代碼在 require 的時候,就會全部執(zhí)行。一旦出現(xiàn)某個模塊被“循環(huán)加載”,就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會輸出。

Demo 1
//a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done =', b.done);
exports.done = true;
console.log('a.js 執(zhí)行完畢');

上面代碼之中,a.js 腳本先輸出一個 done 變量,然后加載另一個腳本文件 b.js。注意,此時 a.js 代碼就停在這里,等待 b.js 執(zhí)行完畢,再往下執(zhí)行。

再看 b.js 的代碼。

//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done =', a.done);
exports.done = true;
console.log('b.js 執(zhí)行完畢');

上面代碼之中,b.js 執(zhí)行到第二行,就會去加載 a.js,這時,就發(fā)生了“循環(huán)加載”。系統(tǒng)會在 a.js 模塊對應(yīng)對象的 exports 屬性取值。可是因?yàn)?a.js 還沒有執(zhí)行完,從 exports 屬性只能取回已經(jīng)執(zhí)行的部分,而不是最后的值。

a.js 已經(jīng)執(zhí)行的部分,只有一行。

exports.done = false;

因此,對于 b.js來說,它從 a.js 只輸入一個變量 done,值為 false。

然后,b.js 接著往下執(zhí)行,等到全部執(zhí)行完畢,再把執(zhí)行權(quán)交還給 a.js。于是,a.js 接著往下執(zhí)行,直到執(zhí)行完畢。我們寫一個腳本 main.js,驗(yàn)證這個過程。

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=, b.done=', a.done, b.done);

執(zhí)行 main.js,運(yùn)行結(jié)果如下。

$ node main.js

在 b.js 之中,a.done = false
b.js 執(zhí)行完畢
在 a.js 之中,b.done = true
a.js 執(zhí)行完畢
在 main.js 之中, a.done = true, b.done = true

上面的代碼證明了2點(diǎn)

  • b.js 之中,a.js 沒有執(zhí)行完畢,只執(zhí)行了第一行。
  • main.js 執(zhí)行到第二行時,不會再次執(zhí)行 b.js,而是輸出緩存的 b.js 的執(zhí)行結(jié)果,即它的第四行。
exports.done = true;

總之,CommonJS 輸入的是被輸出值的拷貝,不是引用。

另外,由于 CommonJS 模塊遇到循環(huán)加載時,返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值,而不是代碼全部執(zhí)行后的值,兩者可能會有差異。所以,輸入變量的時候,必須非常小心。

var a = require('a'); // 安全的寫法 導(dǎo)入整體,保證 module 已經(jīng)執(zhí)行完成
var foo = require('a').foo; // 危險(xiǎn)的寫法

exports.good = function (arg) {
  return a.foo('good', arg); // 使用的是 a.foo 的最新值
};

exports.bad = function (arg) {
  return foo('bad', arg); // 使用的是一個部分加載時的值
}; 

上面代碼中,如果發(fā)生循環(huán)加載,require('a').foo 的值很可能后面會被改寫,改用 require('a') 會更保險(xiǎn)一點(diǎn)。

Demo 2
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');

// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');

// node a.js
// 執(zhí)行結(jié)果:
// a starting
// b starting
// in b, a.done = false
// b done
// in a, b.done = true
// a done

從上面的執(zhí)行過程中,可以看到,在 CommonJS 規(guī)范中,當(dāng)遇到 require() 語句時,會執(zhí)行 require 模塊中的代碼,并緩存執(zhí)行的結(jié)果,當(dāng)下次再次加載時不會重復(fù)執(zhí)行,而是直接取緩存的結(jié)果。正因?yàn)榇?,出現(xiàn)循環(huán)依賴時才不會出現(xiàn)無限循環(huán)調(diào)用的情況。

ES6 模塊循環(huán)依賴

跟 CommonJS 模塊一樣,ES6 不會再去執(zhí)行重復(fù)加載的模塊,又由于 ES6 動態(tài)輸出綁定的特性,能保證 ES6 在任何時候都能獲取其它模塊當(dāng)前的最新值。

ES6 動態(tài) import()

ES6 模塊在編譯時就會靜態(tài)分析,優(yōu)先于模塊內(nèi)的其他內(nèi)容執(zhí)行,所以導(dǎo)致了我們無法寫出像下面這樣的代碼

if(some condition) {
  import a from './a';
}else {
  import b from './b';
}

// or 

import a from (str + 'b');

因?yàn)榫幾g時靜態(tài)分析,導(dǎo)致了我們在引用的時候,無法使用 條件語句 或者 拼接字符串模塊,因?yàn)檫@些都是需要在運(yùn)行時才能確定的結(jié)果在 ES6 模塊是不被允許的,所以 動態(tài)引入import() 應(yīng)運(yùn)而生。

import() 允許你在運(yùn)行時動態(tài)地引入 ES6 模塊,想到這,你可能也想起了 require.ensure 這個語法,但是它們的用途卻截然不同的。

require.ensure 的出現(xiàn)是 webpack 的產(chǎn)物,它是因?yàn)闉g覽器需要一種異步的機(jī)制可以用來異步加載模塊,從而減少初始的加載文件的體積,所以如果在服務(wù)端的話, require.ensure 就無用武之地了,因?yàn)榉?wù)端不存在異步加載模塊的情況,模塊同步進(jìn)行加載就可以滿足使用場景了。 CommonJS 模塊可以在運(yùn)行時確認(rèn)模塊加載。

import() 則不同,它主要是為了解決 ES6 模塊無法在運(yùn)行時確定模塊的引用關(guān)系,所以需要引入 import()。

先來看下它的用法

  • 動態(tài)的 import() 提供一個基于 PromiseAPI
  • 動態(tài)的 import() 可以在腳本的任何地方使用 import() 接受字符串文字,可以根據(jù)需要構(gòu)造說明符
// a.js
const str = './b';
const flag = true;
if(flag) {
  import('./b').then(({foo}) => {
    console.log(foo);
  })
}
import(str).then(({foo}) => {
  console.log(foo);
})

// b.js
export const foo = 'foo';

// babel-node a.js
// 執(zhí)行結(jié)果
// foo
// foo

當(dāng)然,如果在瀏覽器端的 import() 的用途就會變得更廣泛,比如 按需異步加載模塊,那么就和 require.ensure 功能類似了。

因?yàn)槭腔?Promise 的,所以如果你想要同時加載多個模塊的話,可以是 Promise.all 進(jìn)行并行異步加載。

Promise.all([
  import('./a.js'),
  import('./b.js'),
  import('./c.js'),
]).then(([a, {default: b}, {c}]) => {
    console.log('a.js is loaded dynamically');
    console.log('b.js is loaded dynamically');
    console.log('c.js is loaded dynamically');
});

還有 Promise.race 方法,它檢查哪個 Promise 被首先 resolvedreject。我們可以使用 import() 來檢查哪個 CDN 速度更快:

const CDNs = [
  {
    name: 'jQuery.com',
    url: 'https://code.jquery.com/jquery-3.1.1.min.js'
  },
  {
    name: 'googleapis.com',
    url: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'
  }
];

console.log(`------`);
console.log(`jQuery is: ${window.jQuery}`);

Promise.race([
  import(CDNs[0].url).then(()=>console.log(CDNs[0].name, 'loaded')),
  import(CDNs[1].url).then(()=>console.log(CDNs[1].name, 'loaded'))
]).then(()=> {
  console.log(`jQuery version: ${window.jQuery.fn.jquery}`);
});

當(dāng)然,如果你覺得這樣寫還不夠優(yōu)雅,也可以結(jié)合 async/await 語法糖來使用。

async function main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [module1, module2, module3] =
    await Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ]);
}

動態(tài) import() 為我們提供了以異步方式使用 ES 模塊的額外功能。

根據(jù)我們的需求動態(tài)或有條件地加載它們,這使我們能夠更快,更好地創(chuàng)建更多優(yōu)勢應(yīng)用程序。

8 webpack中加載3種模塊 | 語法

Webpack 允許使用不同的模塊類型,但是底層必須使用同一種實(shí)現(xiàn)。所有的模塊可以直接在盒外運(yùn)行。

  • ES6 模塊
import MyModule from './MyModule.js';
  • CommonJS(require)
var MyModule = require('./MyModule.js');
  • AMD
define(['./MyModule.js'], function (MyModule) {});

9 參考資料

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

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