Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 839|回复: 3

JavaScript代码模块的组织

[复制链接]

695

主题

1139

帖子

4064

积分

认证用户组

Rank: 5Rank: 5

积分
4064
发表于 2017-5-15 19:34:19 | 显示全部楼层 |阅读模式
本帖最后由 java 于 2018-1-9 11:59 编辑

通常有如下几种方法:
  • 通过书写在不同文件中,使用script标签进行加载
  • CommonJS进行加载(NodeJS就使用这种方式)
  • AMD进行加载(require.js使用这种方式)
  • ES6模块




模块的规范
1.CommonJS
node.js的模块系统,就是参照CommonJS规范实现的。
在CommonJS中,有一个全局性方法require(),用于加载模块, 是同步加载(synchronous)的。
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

CommonJS模块的特点如下:
a.所有代码都运行在模块作用域,不会污染全局作用域。
b.模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
c.模块加载的顺序,按照其在代码中出现的顺序。

模块的加载机制
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

2.AMD
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);

3.ES6的Module
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新.
如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。也就是说,import语句是 Singleton 模式。
import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)。 不能通过变量值决定是否加载某模块。
require是运行时加载模块,import命令无法取代require的动态加载功能。
引入import()函数,完成动态加载。import()返回一个 Promise 对象。
import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

回复

使用道具 举报

695

主题

1139

帖子

4064

积分

认证用户组

Rank: 5Rank: 5

积分
4064
 楼主| 发表于 2017-10-23 16:08:50 | 显示全部楼层
本帖最后由 java 于 2017-10-23 16:40 编辑

http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
http://www.ruanyifeng.com/blog/2 ... ule_definition.html
http://www.ruanyifeng.com/blog/2012/11/require_js.htmlhttp://www.cnblogs.com/fullhouse/archive/2011/07/15/2107416.html


一、原始写法
  1.   function m1(){
  2.     //...
  3.   }
  4.   function m2(){
  5.     //...
  6.   }
复制代码
二、对象写法
  1.   var module1 = new Object({
  2.     _count : 0,
  3.     m1 : function (){
  4.       //...
  5.     },
  6.     m2 : function (){
  7.       //...
  8.     }
  9.   });
复制代码
内部状态可以被外部改写


三、立即执行函数写法
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
  1.   var module1 = (function(){
  2.     var _count = 0;
  3.     var m1 = function(){
  4.       //...
  5.     };
  6.     var m2 = function(){
  7.       //...
  8.     };
  9.     return {
  10.       m1 : m1,
  11.       m2 : m2
  12.     };
  13.   })();
复制代码

四、放大模式
  1.   var module1 = (function (mod){
  2.     mod.m3 = function () {
  3.       //...
  4.     };
  5.     return mod;
  6.   })(module1);
复制代码
上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。

五、宽放大模式(Loose augmentation)
  1. var module1 = ( function (mod){
  2.     //...
  3.     return mod;
  4.   })(window.module1 || {});
复制代码
与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象。


六、输入全局变量
  1.   var module1 = (function ($, YAHOO) {
  2.     //...
  3.   })(jQuery, YAHOO);
复制代码

七、模块的规范
目前,通行的Javascript模块规范共有两种:CommonJSAMD


http://www.commonjs.org/
The Asynchronous Module Definition (AMD)


八、CommonJS
http://javascript.ruanyifeng.com/nodejs/module.html  CommonJS规范


2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。
node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。


CommonJS是一种规范,NodeJS是这种规范的实现。
CommonJS有很多实现,其中不乏很多大名鼎鼎的项目,比如 说:Apache的CouchDBnode.js等。但这些项目大 部分只实现了CommonJS的部分规范。具体的项目和实现部分参见官方网站的说明:http://commonjs.org/impl/
千万别小看作为后台应用的JavaScript,Palm的WebOS的开发计划中就曾提到CommonJS和其扩展的JS API,另外几乎所有的平台(包括智能手机平台和计算机平台都开始流程)都开始引入Web技术:比如说S60中的WRT,IPhone中的 WebWidget,Android平台上的WebWidget,还有Firefox直接用XML和CSS作界面用Javascript控制逻辑,甚至 Google的ChromeOS直接就是一个浏览器操作系统。  我做个技术上的赌注:动态语言和静态语言混合编程将会很流行(比如Android和PalmWebOS同时引入两种SDK,QT直至 QTScript)。


九、浏览器环境



浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。


十、AMD
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
  1. require([module], callback);
复制代码
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
AMD比较适合浏览器环境。
目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。


十一、AMD模块的写法


模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
  1.  // math.js
  2.   define(function (){
  3.     var add = function (x,y){
  4.       return x+y;
  5.     };
  6.     return {
  7.       add: add
  8.     };
  9.   });
复制代码
加载方法:

  1.   // main.js
  2.   require(['math'], function (math){
  3.     alert(math.add(1,1));
  4.   });
复制代码
如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
  1. define(['myLib'], function(myLib){
  2.     function foo(){
  3.       myLib.doSomething();
  4.     }
  5.     return {
  6.       foo : foo
  7.     };
  8.   });
复制代码




回复 支持 反对

使用道具 举报

695

主题

1139

帖子

4064

积分

认证用户组

Rank: 5Rank: 5

积分
4064
 楼主| 发表于 2017-10-23 17:23:29 | 显示全部楼层
本帖最后由 java 于 2017-10-26 14:52 编辑

CommonJS规范
1.概述
CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

  1. var x = 5;
  2. var addX = function (value) {
  3.   return value + x;
  4. };
  5. module.exports.x = x;
  6. module.exports.addX = addX;
复制代码
上面代码通过module.exports输出变量x和函数addX

require方法用于加载模块。
  1. var example = require('./example.js');

  2. console.log(example.x); // 5
  3. console.log(example.addX(1)); // 6
复制代码

CommonJS模块的特点如下。
  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。


2.module对象
每个模块内部,都有一个module对象,代表当前模块。它有以下属性。
  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

exports变量
  1. var exports = module.exports;
复制代码

3.AMD规范与CommonJS规范的兼容性


CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。
  1. define(['package/lib'], function(lib){
  2.   function foo(){
  3.     lib.log('hello world!');
  4.   }

  5.   return {
  6.     foo: foo
  7.   };
  8. });
复制代码
4.require命令
Node使用CommonJS模块规范,内置的require命令用于加载模块文件。
require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
如果模块输出的是一个函数,那就不能定义在exports对象上面,而要定义在module.exports变量上面。
  1. module.exports = function () {
  2.   console.log("hello world")
  3. }

  4. require('./example2.js')()
复制代码
上面代码中,require命令调用自身,等于是执行module.exports,因此会输出 hello world。

加载规则
require命令用于加载文件,后缀名默认为.js。
根据参数的不同格式,require命令去不同路径寻找模块文件。
(1)如果参数字符串以“/”开头,则表示加载的是一个位于绝对路径的模块文件。比如,require('/home/marco/foo.js')将加载/home/marco/foo.js。
(2)如果参数字符串以“./”开头,则表示加载的是一个位于相对路径(跟当前执行脚本的位置相比)的模块文件。比如,require('./circle')将加载当前脚本同一目录的circle.js。
(3)如果参数字符串不以“./“或”/“开头,则表示加载的是一个默认提供的核心模块(位于Node的系统安装目录中),或者一个位于各级node_modules目录的已安装模块(全局安装或局部安装)。
举例来说,脚本/home/user/projects/foo.js执行了require('bar.js')命令,Node会依次搜索以下文件。
  • /usr/local/lib/node/bar.js
  • /home/user/projects/node_modules/bar.js
  • /home/user/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js
这样设计的目的是,使得不同的模块可以将所依赖的模块本地化。
(4)如果参数字符串不以“./“或”/“开头,而且是一个路径,比如require('example-module/path/to/file'),则将先找到example-module的位置,然后再以它为参数,找到后续路径。
(5)如果指定的模块文件没有发现,Node会尝试为文件名添加.js、.json、.node后,再去搜索。.js件会以文本格式的JavaScript脚本文件解析,.json文件会以JSON格式的文本文件解析,.node文件会以编译后的二进制文件解析。
(6)如果想得到require命令加载的确切文件名,使用require.resolve()方法。

目录的加载规则
通常,我们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让require方法可以通过这个入口文件,加载整个目录。
在目录中放置一个package.json文件,并且将入口文件写入main字段。
require发现参数字符串指向一个目录以后,会自动查看该目录的package.json文件,然后加载main字段指定的入口文件。如果package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件。

模块的缓存
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。


5.模块的加载机制
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个例子。
  1. // lib.js
  2. var counter = 3;
  3. function incCounter() {
  4.   counter++;
  5. }
  6. module.exports = {
  7.   counter: counter,
  8.   incCounter: incCounter,
  9. };
复制代码
  1. // main.js
  2. var counter = require('./lib').counter;
  3. var incCounter = require('./lib').incCounter;

  4. console.log(counter);  // 3
  5. incCounter();
  6. console.log(counter); // 3
复制代码
counter输出以后,lib.js模块内部的变化就影响不到counter了。

require的内部处理流程


require命令是CommonJS规范之中,用来加载其他模块的命令。它其实不是一个全局命令,而是指向当前模块的module.require命令,而后者又调用Node的内部命令Module._load
  1. Module._load = function(request, parent, isMain) {
  2.   // 1. 检查 Module._cache,是否缓存之中有指定模块
  3.   // 2. 如果缓存之中没有,就创建一个新的Module实例
  4.   // 3. 将它保存到缓存
  5.   // 4. 使用 module.load() 加载指定的模块文件,
  6.   //    读取文件内容之后,使用 module.compile() 执行文件代码
  7.   // 5. 如果加载/解析过程报错,就从缓存删除该模块
  8.   // 6. 返回该模块的 module.exports
  9. };
复制代码
上面的第4步,采用module.compile()执行指定模块的脚本,逻辑如下。
  1. Module.prototype._compile = function(content, filename) {
  2.   // 1. 生成一个require函数,指向module.require
  3.   // 2. 加载其他辅助方法到require
  4.   // 3. 将文件内容放到一个函数之中,该函数可调用 require
  5.   // 4. 执行该函数
  6. };
复制代码
上面的第1步和第2步,require函数及其辅助方法主要如下。
  • require(): 加载外部模块
  • require.resolve():将模块名解析到一个绝对路径
  • require.main:指向主模块
  • require.cache:指向所有缓存的模块
  • require.extensions:根据文件的后缀名,调用不同的执行函数
一旦require函数准备完毕,整个所要加载的脚本内容,就被放到一个新的函数之中,这样可以避免污染全局环境。该函数的参数包括require、module、exports,以及其他一些参数。
  1. (function (exports, require, module, __filename, __dirname) {
  2.   // YOUR CODE INJECTED HERE!
  3. });
复制代码
Module._compile方法是同步执行的,所以Module._load要等它执行完成,才会向用户返回module.exports的值。


回复 支持 反对

使用道具 举报

695

主题

1139

帖子

4064

积分

认证用户组

Rank: 5Rank: 5

积分
4064
 楼主| 发表于 2017-10-26 15:01:04 | 显示全部楼层
本帖最后由 java 于 2017-10-26 15:38 编辑

ES6的Module
http://es6.ruanyifeng.com/#docs/module
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
  1. // CommonJS模块
  2. let { stat, exists, readFile } = require('fs');

  3. // 等同于
  4. let _fs = require('fs');
  5. let stat = _fs.stat;
  6. let exists = _fs.exists;
  7. let readfile = _fs.readfile;
复制代码
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
  1. // ES6模块
  2. import { stat, exists, readFile } from 'fs';
复制代码
上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。


严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
严格模式是 ES5 引入的.
注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
export 命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
  1. // profile.js
  2. export var firstName = 'Michael';
  3. export var lastName = 'Jackson';
  4. export var year = 1958;
复制代码
  1. // profile.js
  2. var firstName = 'Michael';
  3. var lastName = 'Jackson';
  4. var year = 1958;

  5. export {firstName, lastName, year};
复制代码
优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。


输出函数或类(class)
  1. export function multiply(x, y) {
  2.   return x * y;
  3. };
复制代码
使用as关键字重命名。
  1. function v1() { ... }
  2. function v2() { ... }

  3. export {
  4.   v1 as streamV1,
  5.   v2 as streamV2,
  6.   v2 as streamLatestVersion
  7. };
复制代码
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
  1. export var foo = 'bar';
  2. setTimeout(() => foo = 'baz', 500);
复制代码
上面代码输出变量foo,值为bar,500毫秒之后变成baz。
这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷。

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
  1. // main.js
  2. import {firstName, lastName, year} from './profile';

  3. function setName(element) {
  4.   element.textContent = firstName + ' ' + lastName;
  5. }
复制代码
1.
as关键字,将输入的变量重命名。
  1. import { lastName as surname } from './profile';
复制代码
2.
from指定模块文件的位置
可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
  1. import {myMethod} from 'util';
复制代码
util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。 ??


3.import命令具有提升效果
会提升到整个模块的头部,首先执行。
  1. foo();

  2. import { foo } from 'my_module';
复制代码
4.
import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
  1. // 报错
  2. import { 'f' + 'oo' } from 'my_module';

  3. // 报错
  4. let module = 'my_module';
  5. import { foo } from module;

  6. // 报错
  7. if (x === 1) {
  8.   import { foo } from 'module1';
  9. } else {
  10.   import { foo } from 'module2';
  11. }
复制代码
5.
import语句会执行所加载的模块
  1. import 'lodash';
复制代码
上面代码仅仅执行lodash模块,但是不输入任何值。
如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。
  1. import { foo } from 'my_module';
  2. import { bar } from 'my_module';

  3. // 等同于
  4. import { foo, bar } from 'my_module';
复制代码
上面代码中,虽然foobar在两个语句中加载,但是它们对应的是同一个my_module实例。也就是说,import语句是 Singleton 模式。


通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。
  1. require('core-js/modules/es6.symbol');
  2. require('core-js/modules/es6.promise');
  3. import React from 'React';
复制代码

模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
  1. // circle.js

  2. export function area(radius) {
  3.   return Math.PI * radius * radius;
  4. }

  5. export function circumference(radius) {
  6.   return 2 * Math.PI * radius;
  7. }
复制代码
整体加载写法:

  1. import * as circle from './circle';

  2. console.log('圆面积:' + circle.area(4));
  3. console.log('圆周长:' + circle.circumference(14));
复制代码

import()
简介
前面介绍过,import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行(叫做”连接“更合适)。所以,下面的代码会报错。
  1. // 报错
  2. if (x === 2) {
  3.   import MyModual from './myModual';
  4. }
复制代码
上面代码中,引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报句法错误,而不是执行时错误。也就是说,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。

  1. const path = './' + fileName;
  2. const myModual = require(path);
复制代码
上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道。import语句做不到这一点。

因此,有一个提案,建议引入import()函数,完成动态加载。
  1. import(specifier)
复制代码
上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。

import()返回一个 Promise 对象。下面是一个例子。
  1. const main = document.querySelector('main');

  2. import(`./section-modules/${someVariable}.js`)
  3.   .then(module => {
  4.     module.loadPageInto(main);
  5.   })
  6.   .catch(err => {
  7.     main.textContent = err.message;
  8.   });
复制代码
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,也会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。

import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|firemail ( 粤ICP备15085507号-1 )

GMT+8, 2020-6-7 17:32 , Processed in 1.219431 second(s), 19 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表