JS模块化 介绍(CommonJS、AMD-RequireJS、CMD-SeaJS、ES6_Babel_Browserify)


本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
学习资料来源于:尚硅谷


说在最前面:需要注意的是,代码中涉及到了ES6,出现了一些新特性,也许放在编译器里会报错。(因为我之前用Eclipse,发现把代码写在其中报了错,在idea上就没问题了,因为idea上是可以设置使用版本的,目前没发现Eclipse如何改版本)


JS模块化介绍

当项目功能越来越多,代码量也会越来越多,后期的维护难度也会增大,此时如何对项目的代码进行一个管理,方便开发人员开发就是一个避不开的问题。虽然现在Vue-cli帮我们做好了相关的工作,在Vue中也有各种各样的办法,帮助我们实现代码的分层次的管理,但是这本身也是Vue、NodeJS帮我们封装好的,而在这其中的内涵和发展,就是一个需要去学习和了解的内容。

首先总结一下,JS模块化就是复杂的程序依据一定的规则(规范)封装成几个块(文件),再把它们组合在一起。块的内部数据是私有的,只是向外部暴露一些方法,从而让其与外部其它模块通信。这种模块化也是Vue目前的实现方式,比如:将axios封装好、将Vuex封装好、utils工具封装好等等,并将它们放在各自的文件夹中分别管理,最后组合在一起,通过暴露的模块进行通信。

在总结当前的模块化方式之前,也用一点时间简述一下JS模块化的进化史。


模块化的进化史


1、全局function模式:

在这里插入图片描述
这种模式也是我们最开始学习的方式,从现在来看,这种方式很明显的会污染全局作用域。从平常的开发中也可以发现,可能会出现很多同名的方法,所以此时通过以下方式进行使用,就很容易出现命名冲突,但已初具模块化的雏形。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>01_全局function模式</title>
</head>
<body>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript">
  foo()
  bar()
</script>
</body>
</html>

module1.js

/**
 * 全局函数模式: 将不同的功能封装成不同的全局函数
 * 问题: Global被污染了, 很容易引起命名冲突
 */
function foo() {
    console.log('foo()')
}
function bar() {
    console.log('bar()')
}

module2.js

function foo() {  //与另一个模块中的函数冲突了
  console.log('foo()2')
}

2、namespace模式

在这里插入图片描述
由于第一种方式会污染全局作用域,也容易出现命名冲突,因此对第一种方式就有了改进后的第二种方式:namespace模式。这种方式就是对原来的方法进行了一次简单的封装,这样就减少了全局作用域上的变量数目,也解决了命名冲突问题,各自只需要使用各自内部的方法即可。但是这种方式最大的问题就是不安全,我们可以直接修改对象内部的数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>02_namespace模式</title>
</head>
<body>
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript">
  myModule.foo()
  myModule.data = 'other data' //能直接修改模块内部的数据
  myModule.foo()
</script>
</body>
</html>

module1.js

/**
 * namespace模式: 简单对象封装
 * 作用: 减少了全局变量
 * 问题: 不安全(数据不是私有的, 外部可以直接修改)
 */
let myModule = {
  data: 'hello',
  foo: function(){
    console.log(this.data);
  }
}

3、IIFE模式:匿名函数自调用

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03_IIFE模式</title>
</head>
<body>
	<script type="text/javascript" src="module3.js"></script>
	<script type="text/javascript">
		myModule.foo()
		console.log(myModule.data) //undefined 不能访问模块内部数据
		myModule.data = 'xxxx' //不是修改的模块内部的data
		myModule.foo() //没有改变
	</script>
</body>
</html>
/**
 * IIFE模式: 匿名函数自调用(闭包)
 * IIFE : immediately-invoked function expression(立即调用函数表达式)
 * 作用: 数据是私有的, 外部只能通过暴露的方法操作
 * 问题: 如果当前这个模块依赖另一个模块怎么办?
 */
(function (window) {
  //数据
  let data = 'hello'

  //操作数据的函数
  function foo() { //用于暴露有函数
    console.log('foo()',data)
  }

  //暴露行为
  window.myModule = {foo}

})(window)

针对第二种方式不安全的问题,对其改进就出现了第三种方式:匿名闭包。这种方式利用了匿名函数自调用的闭包安全性,通过上面图中的代码可以看到,在这种方式下,数据是私有的。如果外部想使用,就只能调用return暴露出来的方法进行操作,我们也无法对其内部的方法进行修改。但是这种方式也会有一个缺陷:通常多个模块间可能会互相调用,现在无法修改,所以就有了第四种方式。


4、IIFE模式增强

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>04_IIFE模式增强</title>
</head>
<body>
<!--引入的js必须有一定顺序-->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module4.js"></script>
<script type="text/javascript">
  myModule.foo()
</script>
</body>
</html>

module4.js

/**
 * IIFE模式增强 : 引入依赖
 * 这就是现代模块实现的基石
 */
(function (window, $) {
  //数据
  let data = 'hello'

  //操作数据的函数
  function foo() { //用于暴露有函数
    console.log('foo()' ,data)
    $('body').css('background', 'red')
  }

  //暴露行为
  window.myModule = {foo}

})(window, jQuery)

改进方式很简单,就是如果需要什么其他的依赖,就在匿名函数自调用时传入即可。如上图中,传入一个jQuery,就可以在该匿名函数中使用jQuery语法了。这种方式就是现代模块化实现的基石。


模块化的好处

从整个模块化的发展进程中,不难看出模块化的好处:
① 避免了命名冲突,减少了命名空间的污染;
② 更好的实现了功能的分离,只需按需加载即可;
③ 更高程度的体现复用性,多个模块可以同时引用;
④ 高可维护性,需要改动时只需要改变各功能模块。

但是,从目前来看,以上方案最大的问题就是在script标签需要引入多个js文件,这样会导致请求过多、依赖多时还是会比较模糊,难以区分,难以维护:
在这里插入图片描述
由于直接用script标签引入,在模块较多的时候还是会比较麻烦,所以就有了多种形式的模块化,也提出了很多的规范。


CommonJS ※

(在这里建议先学习Node.JS,相关语法在Node.JS中也有所提及)

CommonJS的出现就是为了弥补当时JavaScript没有模块化标准的缺陷,虽然这个名词已经很少涉及,但是它为JavaScript指定了一个美好的愿景,希望让JS在任何地方运行,而NodeJS就借助它实现了这个愿景。

说明:

  • 每个文件都可当作一个模块
  • 在服务器端:模块的加载是运行时同步加载的(不会出现什么问题)
  • 在浏览器端:模块需要提前编译打包处理(用户等待的时间可能会很长。需要提前编译是因为浏览器不知道require语法)
  • CommonJS-Node就是用来服务器端实现
    CommonJS-Browserify就是用来浏览器端实现。它也称为CommonJS的浏览器端的打包工具。(但其实现在也不能这么说,因为ES6中也用到Browserify了)

首先,在Node中,一个js文件就是一个模块,每一个js文件中的js代码都独立运行在一个函数中,而不是全局作用域,所以一个模块中的变量和函数在其他模块中就无法访问。这和IIFE模式很像。而Node实现的方式是:

/*
	模块化
		- 在Node中,一个js文件就是一个模块
		- 在Node中,每一个js文件中的js代码都是独立运行在一个函数中
			而不是全局作用域,所以一个模块的中的变量和函数在其他模块中无法访问
 */

console.log("我是一个模块,我是02.module.js");

/*外部不可见*/
var a = 10 ; 
 
/*
我们可以通过 exports 来向外部暴露变量和方法
	只需要将需要暴露给外部的变量或方法设置为exports的属性即可
 */
//向外部暴露属性或方法
exports.x = "我是02.module.js中的x";
exports.y = "我是y";
exports.fn = function () {};

使用的时候,我们不再需要使用script标签导入,而是通过require()来导入外部的模块,这就是require函数的由来。在使用require()时,我们只需要以一个文件的路径作为参数,node将会自动根据该路径来引入外部模块。

//引入其他的模块
/*
	在node中,通过require()函数来引入外部的模块
		require()可以传递一个文件的路径作为参数,node将会自动根据该路径来引入外部模块
		这里路径,如果使用相对路径,必须以.或..开头

	使用require()引入模块以后,该函数会返回一个对象,这个对象代表的是引入的模块
 */
var md = require("./02.module");
console.log(md);

在这里插入图片描述
补充说明:require()中的标识除了填写路径外,也可以直接写核心模块(npm中下载的模块)的名字来引入。比如:

var fs = require("fs");
var express = require("express");

根据在Node中的使用,CommonJS对模块的定义就已可以总结出来:模块引用(require)、模块定义(exports)、模块标识(let fs = require(“fs”))。

除此以外,在node中还有一个不可忽视的用法。在项目中经常会使用到module.exports,比如项目中的vue.config.js等文件都是这样使用的。

为什么能这么用?module又是怎么出来的?这是因为node在执行模块中的代码时,它会首先用如下代码包裹模块中的所有代码:

function (exports, require, module, __filename, __dirname) {

}

传递进的这5个实参意义如下:
exports:该对象用来将变量或函数暴露到外部
require:函数,用来引入外部的模块
module:module代表的是当前模块本身,那么exports就是module的属性。所以我们可以使用 exports 导出,也可以使用module.exports导出
__filename:当前模块的完整路径。如:D:\Projects\01.node\04.module.js
__dirname:当前模块所在文件夹的完整路径。如:D:\Projects\01.node

而使用 exports 导出、module.exports导出,它们之间的用法是有区别的:

helloModule.js

module.exports.name = "孙悟空";
module.exports.age = 18;
module.exports.sayName = function () {
	console.log("我是孙悟空");
};

/* 无法使用这种方式,会报错。但module.exports就不会
exports  = {
	name:"猪八戒",
	age:28,
	sayName:function () {
		console.log("我是猪八戒");
	}
};
*/
var hello = require("./helloModule");

/*
	exports 和 module.exports
		- 通过exports只能使用.的方式来向外暴露内部变量
			exports.xxx = xxx

		- 而module.exports既可以通过.的形式,也可以直接赋值
			module.exports.xxx = xxxx
			module.exports = {}
 */

console.log(hello.name);
console.log(hello.age);
hello.sayName();

所以,在项目的配置文件中,通常会使用到module.exports。


CommonJS-Node ※

项目结构:

在这里插入图片描述
package.json可以用npm init来创建。
在这里插入图片描述
"name"中不能有大写字母。(在package.json中,只保留了简单的信息,毕竟我们不需要上传项目)

在这里我们下载一个第三方模块用来演示:(在当前项目目录下安装)
在这里插入图片描述
npm install 包名 --save:用来安装包,并添加到依赖中

这其中的uniq的用法:(就是用来去除数组中重复的内容)
在这里插入图片描述
安装好之后:
在这里插入图片描述
在这里插入图片描述
module1.js

//使用module.exports = value向外暴露一个对象
module.exports = {
  msg: 'module1',
  foo() {
    console.log(this.msg);
  }
}

module2.js

//使用module.exports = value向外暴露一个函数
module.exports = function () {
  console.log('module2');
}

module3.js

//使用exports.xxx = value向外暴露一个对象
exports.foo = function () {
  console.log('module3 foo()')
}

exports.bar = function () {
  console.log('module3 bar()')
}

exports.arr = [2,4,5,2,3,5,1,11];

app.js

/**
  1. 定义暴露模块:
    module.exports = value;
    exports.xxx = value;
    但是exports不能使用exports = {}的方式,但module.exports可以
  2. 引入模块:
    var module = require(模块名或模块路径);
 */

let uniq = require('uniq');

//引用模块
let module1 = require('./modules/module1');
let module2 = require('./modules/module2');
let module3 = require('./modules/module3');

//使用模块
module1.foo();
module2();
module3.foo();
module3.bar();

let result = uniq(module3.arr);
console.log(result);

输出结果:
在这里插入图片描述
(uniq不仅能去掉重复元素,还能根据元素首位数字的大小进行排序)


CommonJS-Browserify ※

项目结构:
在这里插入图片描述
在当前项目目录下安装:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
(直接save加依赖,加的是’运行依赖’的包,save-dev加依赖,加的是’开发依赖’的包,-g是全局下载包,给计算机用的)

安装后:
在这里插入图片描述
package.json:

{
  "name": "commonjs-browserify",
  "version": "1.0.0",
  "dependencies": {
    "uniq": "^1.0.1"
  },
  "devDependencies": {
    "browserify": "^16.5.1"
  }
}

module1.js

//使用module.exports = value向外暴露一个对象
module.exports = {
  foo() {
    console.log('module1 foo()')
  }
}

module2.js

//使用module.exports = value向外暴露一个函数
module.exports = function () {
  console.log('module2()')
}

module3.js

//使用exports.xxx = value向外暴露一个对象
exports.foo = function () {
  console.log('module3 foo()')
}

exports.bar = function () {
  console.log('module3 bar()')
}

app.js

//引用模块
let module1 = require('./module1')
let module2 = require('./module2')
let module3 = require('./module3')

let uniq = require('uniq')

//使用模块
module1.foo()
module2()
module3.foo()
module3.bar()

console.log(uniq([1, 3, 1, 4, 3]))

在之前已经说过了,浏览器是无法识别require语法的,需要提前编译打包处理,Browserify就是帮我们做这个事情的。

在这里插入图片描述
这段指令分成三部分。除去browserify。-o:output,意味着输出。输出的左侧是需要处理的源文件,输出的右侧是作用后输出的文件位置及名字。(起什么名字都可以,只是bundle是打包的意思)

完成操作后:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script type="text/javascript" src="js/dist/bundle.js"></script>
</body>
</html>

然后打开页面,就可以看到输出结果了:
在这里插入图片描述


AMD-RequireJS ※

AMD(Asynchronous Module Definition):异步模块定义,专门用于浏览器端。模块的加载是异步的。AMD规范的出现比CommonJS-Browserify要早。

基本语法

  • 定义暴露模块:
    • 没有依赖的模块:define(function(){return 模块对象})
    • 有依赖的模块:define([依赖模块名], function(){return 模块对象})
      如:define(['module1','module2'],function(m1,m2){return 模块对象})
  • 引入使用模块:
    require(['module1', 'module2'], function(m1, m2){//使用模块对象})
    requirejs(['module1', 'module2'], function(m1, m2){//使用模块对象})

演示:未使用AMD

(首先,我们不使用AMD规范,自己来写)
项目结构:
在这里插入图片描述

dataService.js

//定义一个没有依赖的模块
(function (window) {
  let msg = 'dataService.js';
  function getMsg() {
    return msg;
  }
  window.dataService = {getMsg}
})(window)

alerter.js

//定义一个有依赖的模块
(function (window, dataService) {
  let msg = 'alerter.js';
  function showMsg() {
    alert(dataService.getMsg() + ', ' + msg);
  }
  window.alerter = {showMsg}
})(window, dataService)

app.js

(function (alerter) {
  alerter.showMsg();
})(alerter)

test1.html

<!DOCTYPE html>
<html>
<head>
  <title>Modular Demo 1</title>
</head>
<body>
<div>
  <h1>Modular Demo 1: 未使用AMD(require.js)</h1>
</div>
<script type='text/javascript' src='js/modules/dataService.js'></script>
<script type='text/javascript' src='js/modules/alerter.js'></script>
<script type="text/javascript" src="./app.js"></script>
</body>
</html>

很明显的,我们需要通过script标签,将所有依赖导入才能使用,显然很麻烦。

接下来使用 AMD 模块化规范👇


RequireJS ※

在使用之前,需要下载require.js,并引入:

(Node不需要下载是因为Node已经帮我们封装好了一切,这也就让我们感知不到它们底层细节的存在)

然后将require.js导入项目: js/libs/require.js
项目结构:
在这里插入图片描述
dataService.js

//定义没有依赖的模块
define(function () {
  let name = 'dataService.js';
  function getName() {
    return name;
  }
  return {getName}
});

alerter.js

//定义有依赖的模块
define(['dataService', 'jquery'], function (dataService, $) {
  let msg = 'alerter.js';
  function showMsg() {
    $('body').css('background', 'gray');
    alert(dataService.getName() + ', ' + msg);
  }
  return {showMsg}
});

main.js

(function () {
  //配置
  requirejs.config({
    //基本路径 (出发点在根目录下)
    baseUrl: 'js/',
    //映射: 模块标识名: 路径
    paths: {
      //注意:文件千万不要写 .js,规范会自动帮我们加
      //自定义模块
      alerter: './modules/alerter',
      dataService: './modules/dataService',
      //库模块
      jquery: './libs/jquery-1.10.1',
      angular: './libs/angular'
    },
    //暴露angular对象
    shim:{
      angular:{
        exports: 'angular'
      }
    }
  })

  //引入模块使用
  requirejs(['alerter','angular'], function (alerter,angular) {
    alerter.showMsg();
    console.log(angular);
  })
})()

index2.html

<!DOCTYPE html>
<html>
<head>
    <title>Modular Demo 2</title>
</head>
<body>
    <script type="text/javascript" src="js/libs/require.js" data-main="js/main.js"></script>
 </body>
</html>

其中需要注意的是,要引入jQuery模块时,一定要使用这个名字:jquery
因为jQuery的源码中写到了:
在这里插入图片描述
从AMD-RequireJS规范中已经不难看出,此时已经初具了目前webpack的雏形,只可惜AMD不能够支持所有第三方库,比如想使用AngularJS还得需要用shim来单独暴露。除此以外,AMD也不支持服务器端,没有更高的想法和扩展性,从而被现在的Node + vue-cli + webpack所取代。


CMD-SeaJS

CMD在市面上用的不多。CMD也是专门用于浏览器端,模块的加载也是异步的。模块只在使用时才会加载执行。

基本语法:(像是AMD和CommonJS的合并)

  • 定义暴露模块:
define(function(require, exports, module){
   //通过require引入依赖模块
   //引入依赖模块(同步)
   var module2 = require('./module2')
   //引入依赖模块(异步)
   require.async('./module3',function(m3){

   })
   //通过module/exports来暴露模块
   exports.xxx = value
   module.exports = value
})
  • 引入使用模块:
define(function(require){
	var m1 = require('./module1')
	var m4 = require('./module4')
	m1.show()
	m4.show()
})

在使用之前,需要下载sea.js,并引入:

(需要说明的是,因为sea.js好像已经被卖了,网站应该已经点不开了,同时应该也找不到官方下载途径了,所以sea.js听人说已经快属于文物了。而且sea.js是阿里的大佬写的,目前只有阿里少部分在用。不过下载不到sea.js也没关系,其他模块化方法也可行,这里了解即可)

然后将sea.js导入项目: js/libs/sea.js
项目结构:
在这里插入图片描述
module1.js

//定义没有依赖的模块
define(function (require, exports, module) {
  //内部变量数据
  let msg = 'module1';
  //内部函数
  function foo() {
    return msg;
  }
  //向外暴露
  module.exports = {foo};
});

module2.js

define(function (require, exports, module) {
  let msg = 'module2';
  function bar(){
    console.log(msg);
  }
  module.exports = bar;
});

module3.js

define(function (require, exports, module) {
  let data = 'module3';
  function fun(){
    console.log(data);
  }
  exports.module3 = {fun};
});

module4.js

define(function (require, exports, module) {
  let msg = 'module4';
  //引入依赖模块(同步)
  let module2 = require('./module2');
  module2();
  //引入依赖模块(异步)
  require.async('./module3', function (m3) {
    m3.module3.fun();
  });
  function fun2(){
    console.log(msg);
  }
  exports.fun2 = fun2;
})

main.js

define(function (require) {
  var m1 = require('./module1');
  console.log(m1.foo());
  var m4 = require('./module4');
  m4.fun2();
})

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<!--
使用seajs:
  1. 引入sea.js库
  2. 如何定义导出模块 :
    define()
    exports
    module.exports
  3. 如何依赖模块:
    require()
  4. 如何使用模块:
    seajs.use()
-->
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>
</body>
</html>

ES6_Babel_Browserify ※※

ES6规范很重要,目前使用最多的就是它。而且ES6要比CommonJS和AMD好用的多。

ES6的依赖模块也需要编译打包处理,这是因为有一些浏览器没有ES6的语法,所以需要把它编译成ES5,来让浏览器识别。当然我们之前也提到过,浏览器也不识别require语法,所以这就需要用到Browserify

基本语法

  • 导出模块:export
  • 引入模块:import

实现(浏览器端):使用Babel将ES6编译为ES5代码,再使用Browserify编译打包成JS。


项目结构:
在这里插入图片描述
①首先定义package.json文件:

{
  "name": "es6-babel-browserify",
  "version": "1.0.0"
}

②安装babel-cli, babel-preset-es2015browserify

babel-cli:command line interface (命令行接口),让我们能使用babel的命令。
babel-preset-es2015:下载ES6转为ES5的所有插件。

在这里插入图片描述

③定义.babelrc文件

{
  "presets": ["es2015"]
}

当babel运行的时候,它会先去读.babelrc文件,看看要做什么: "presets": ["es2015"]。数组中放es2015,它就会转ES6。(也能放其他参数,比如"react",它就会转JSX)

(以后可能看到很多rc文件,rc:run control ,即运行时控制文件:运行的时候读这个文件)

④ 定义js文件

module1.js

//暴露模块 分别暴露
export function foo() {
  console.log('module1 foo()');
}

export let bar = function () {
  console.log('module1 bar()');
}

export let arr = [1, 2, 3, 4, 5];

module2.js

//统一暴露
function fun1() {
  console.log('module2 fun1()');
}

function fun2() {
  console.log('module2 fun2()');
}

export {fun1, fun2};

module3.js

//这种方式可以暴露任意数据类型,暴露什么数据接收到的就是什么数据
export default {
  name: 'Tom',
  setName: function (name) {
    this.name = name
  }
}

app.js

//引入其他模块
import {foo, bar} from './module1'
import {arr} from './module1'
import {fun1, fun2} from './module2'

foo();
bar();
console.log(arr);
fun1();
fun2();

//上述用法如果出现重名,显然用不了
import person from './module3'
person.setName('JACK');
console.log(person.name);

//引入第三方模块(jQuery)
import $ from 'jquery'
$('body').css('background', 'red');

在这里插入图片描述
⑤ 编译
在这里插入图片描述
⑥ 页面中引入测试

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script type="text/javascript" src="js/lib/bundle.js"></script>
</body>
</html>

通过最终的这种JS模块化规范,不难看出,已经很接近目前的node + webpack 的形式了。之所以会出现webpack就是因为,在这种方式下,如果想要修改其中内容,每次都需要重新编译,先让其转化ES5,再让其编译js,最后才能执行,每次都需要反复敲命令,十分麻烦。而且在最终打包时,该如何处理css文件,又该如何处理图片等静态资源,又能否实现代码的压缩,实现懒加载等一系列复杂操作需要实现,这样就有了现在的webpack。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值