从0到1学会TypeScript(二)

TypeScript 学习笔记(二)

朋友们,本期内容是关于 TypeScript 的学习,如果您学习过 javaScript ,或 java 等后端语言,我认为您学习的过程将会非常轻松!本期内容包括 webpack面向对象,后续会继续更新!本期参考 TypeScript 中文手册李立超老师的讲解视频和学习笔记特别鸣谢❤️



2. 使用 webpack 打包 typeScript 文件


具体四个步骤:

  • npm init -y 表示对项目进行初始化
  • 安装 webpack 所使用的项目依赖
  • 完成对 webpack 配置信息
  • 配置 tsconfig.json 文件
  • 修改 package.json 添加 build 命令

2.1 初始化项目


  • 使用 webpack 要使用 npm 包管理器
  • 进入项目目录,执行命令 npm init -y,创建 package.json 来管理项目,该 json 文件可以清楚知道项目有哪些依赖

在这里插入图片描述

  • 安装 webpack 所使用的依赖 cnpm 是国内的镜像,更快一些
  • cnpm:阿里巴巴为了众多开发者的便捷推出了淘宝镜像(即 cnpm),它把 npm 官方的“包”全部搬到国内,供广大开发者使用。
  • 下载构建工具
    • npm i -D webpack webpack-cli typescript ts-loader
    • i 表示 install,-D <完整为 (--save-dev) > 其意义表示配置开发环境依赖,将模块安装到项目目录下,并在 package文件的 devDependencies 节点写入依赖。
    • webpack 是打包代码时依赖的核心内容,
    • webpack-cli 是一个用来在命令行中运行 webpack 的工具。
    • typescript 编译器
    • ts-loader 是用于 webpack 的 TypeScript 加载器,将 TypeScript 编译成 JavaScript。

  • 安装完成,模块依赖的模块列表如下:
{
    //...
    "devDependencies": {
        "ts-loader": "^9.4.1",
        "typescript": "^4.8.4",
        "webpack": "^5.74.0",
        "webpack-cli": "^4.10.0"
    }
    //...
}

2.2 配置 webpack.config.js


  • 根目录下创建 webpack 的配置文件 webpack.config.js
  • 引入相关包
  • path 是一个和路径有关的模块,用于处理文件路径和目录路径
  • 指定入口文件 entry
  • 指定打包文件 output 所在目录,文件名,环境…
const path = require('path');
  • webpack 中所有的配置选项都应该写入 module.exports 中,声明这个模块对外暴露具体内容
// webpack 中所有的配置选项都应该写入 module.exports 中
const path = require('path');
module.exports = {
    //指定入口文件
    entry: "./src/index.ts",
    //打包文件 所在目录
    output: {
        //其路径
        path: path.resolve(__dirname, 'dist'),
        //打包后文件名 
        filename: 'bundle.js',
    },
// 指定要加载的规则
// mode 指定构建模式 development 开发模式 production 生产模式
	mode: 'development',
	rules: [
    	{
        	//test 指定规则生效的文件      
        	test: /\.ts$/,
        	//要使用的 loader 表示使用 ts-loader 去处理 ts 文件
        	use: 'ts-loader',
        	//要排除指定的文件
        	//node-modules 不需要编译
        	exclude: /node-modules/,
    	}
	]
}

2.3 为 typescript 指定编译规范


  • 根目录下创建 tsconfig.json,配置可以根据自己需要
  • vscode 快捷生成 tsconfig.json 方式 ( tsc -init )
{
    "compilerOptions": {
        "target": "ES2015",
        "module": "ES2015",
        "strict": true
    }
}
  • 打包在 package.json 文件中在“script”中加入 "build"命令
  • “build”: “webpack”,可通过 build 命令直接执行 webpack, 使用命令npm run build
  • webpack 编译成功

在这里插入图片描述


模块

问题情境:新增的 .ts 文件为 m1.ts,在 index.ts 引入(import)会发生报错。原因是 m1.ts 是引入模块,但 webpack 是“不知道” ts 可以作为模块,“不知道”哪些文件可以引入。

  • webpack 中使用 resolve 字段来配置模块的相关解析策略。本质上是通过对 resolve 库的使用来解析引入模块路径,帮助 webpack 找到 bundle.js 中以 require/import 引入的模块代码。

  • 配置 webpack.config.js 来设置哪些文件可以作为模块使用引入

  • resolve 用于设置引入模块,其属性 extensions 使用 extensions: 扩展名的语法格式,.ts 和 .js 作为扩展名都可以使用。

//...
//用来设置引用模块
resolve: {
    extensions: ['.ts', '.js']
}
//...

babel

  • babel是一个 js 编译器,用来转换最新的 js 语法,比如把 ES6, ES7 等语法转化成 ES5 语法,从而能够在大部分浏览器中运行(处理兼容性问题),像箭头函数,也可以做转换。

  • 需要安装 babel-core,babel-loader,babel-preset-env

    当我们在 webpack 中使用 babel 的时候,首先要安装 babel-core,这是 babel 编译库的核心包

    然后 webpack 中对 js 文件,要进行编译就需要继续配置,在webpack 中,需要用到 babel-loader 帮你来使用 babel。

    babel-preset-env 意思为 babel 预先设置环境,编译规则。

    IE 浏览器无法兼容 es6 语法,我们可以使用 core-js 进行兼容性处理,从而使 IE 浏览器也能够正常的解析 es6 语法

    这个年头应该没人使用 IE 吧 😂

  • 安装依赖包:npm i -D @babel/core @babel/preset-env babel-loader core-js

  • 修改 webpack.config.js 配置文件

//...
//webpack 打包时使用的模块
module: {
    //指定要加载的规则
    rules: [
        {
            //test 指定规则生效的文件
            test: /\.ts$/,
            //要使用的loader 表示使用 ts-loader去处理 ts 文件
            use: [
                //配置 babel
                {
                    //指定加载器
                    loader: "babel-loader",
                    options:{
                        //设置预定义环境
                        presets: [
                            [
                                "@babel/preset-env",
                                //配置信息
                                {
                                    "targets":{
                                        "chrome": "58",
                                        "ie": "11"
                                    },
                                    //指定 corejs 的版本
                                    //使用 corejs 的方式
                                    // "usage" 按需加载
                                    "corejs":"3",
                                    "useBuiltIns": "usage"
                                }
                            ]
                        ]
                    }
                },
                {
                    loader: "ts-loader",

                }
            ],
            exclude: /node_modules/
        }
    ]
}
//...

2.4 配置 Webpack 插件(注册)


问题情境:如果有个 html 文件 需要引入 index.js 文件,常规做法就在 html 文件中添加 script 标签并外部引入 js 文件,但是如果有多个 js 文件呢?手动引入不太现实,有点麻烦。

  • 使用插件,使用命令 npm i -D html-webpack-plugin
  • 该插件能生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle.js 文件,只需添加该插件到 webpack 配置中

  • 回到 webpack.config.js,引入 html 插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
  • 生效 html 插件
plugins: [
	new HTMLWebpackPlugin(),
]
  • 如何自定义? 比如 titile 不想要默认的 Webpack app
  • 传入参数 options
//配置 Webpack 插件(注册)
plugins: [
    new HTMLWebpackPlugin({
        title: '自定义title'
    }),
],

问题情境:网页结构复杂,自定义较为麻烦,希望网页有个结构,指定一个模板 src/index.html

//配置 Webpack 插件(注册)
plugins: [
    new HTMLWebpackPlugin({
        template: './src/index.html'
    }),
],

  • webpack 开发服务器
  • 使用命令 npm i -D webpack-dev-server 安装 webpack-dev-server
  • 相当于在项目里安装内置服务器,让我们的项目直接在这个服务器运,插件跟 webpack 关联,会根据项目改变自动刷新
  • 打包在 package.json 文件中在“script”中加入 "start"命令
  • “start”: “webpack serve --open”,可通过 npm start 命令开启服务器。

在这里插入图片描述


问题情境:编译不会删除原来的文件,只是替换相关文件,替换的问题:编译后的文件还在 dist 文件夹里,并没有一同删除。

现需求:编译前先将 dist 目录下文件全部清空,然后再生成新的文件,避免出现旧文件情况

  • 使用插件 clean-webpack-plugin
  • 使用命令安装 npm i -D clean-webpack-plugin

作用:更新代码时若文件不再被需要了,把其文件删除,清空就可以保证删除这些冗余文件。

  • 回到 webpack.config.js,引入 clean-webpack-plugin 插件并注册
//引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//...
plugins: [
    //...
    new CleanWebpackPlugin(),
],
//...

3. 面向对象


3.1 概述

  • 面向对象是程序中一个非常重要的思想,简而言之就是程序之中所有的操作都需要通过对象来完成。

  • 程序之中所有的操作都需要通过对象来完成。举例:

    • 操作浏览器要使用 window 对象
    • 操作网页要使用 document 对象
    • 操作控制台要使用 console 对象
  • 也就是所谓的面向对象,那么对象到底是什么呢?这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。

  • 在这里插入图片描述

  • 在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象


3.2 类

  • 要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象。要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person 类来创建人的对象,通过 Dog 类创建狗的对象,通过 Car 类来创建汽车的对象,不同的类可以用来创建不同的对象。

  • 定义类

  • class 类名 {
    	属性名: 类型;
    	
        //构造函数
    	constructor(参数: 类型){
    		this.属性名 = 参数;
    	}
    	
    	方法名(){
    		....
    	}
    
    }
    
  • class Person {
        name: string;
        age: number;
    
        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }
    
        sayHello() {
            console.log(`大家好,我是${this.name}`);
        }
    }
    
  • 使用类 (创建类的实例)

  • const p = new Person('孙悟空', 18);
    p.sayHello();
    

3.3 面向对象特点


3.3.1 封装
  • 对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装

  • 默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在 TS 中可以对属性的权限进行设置

  • 只读属性readonly):

    • 如果在声明属性时添加一个 readonly,则属性便成了只读属性,是无法修改
  • 静态属性

    • 静态属性(方法),也称为类属性。使用静态属性无需创建实例,通过类即可直接使用

    • 静态属性(方法)使用 static 开头

  • //使用 class 关键字 定义一个类
    
    class Person {
        /* 
            对象中主要包含两个部分
                属性
                方法
        */
    
        //实例属性
        name: string = 'zhangsan';
        //name = "zhangsan" 类型自动判断
        age: number = 20;
        //在属性前使用 static 关键字 可以定义类属性 (静态属性)
        //不需要创建对象就可以使用
        static gender: string = 'male';
        readonly address: string = 'xxx';//只读实例属性
        static readonly phone: string = 'xxx';//只读类属性
        //定义实例方法
        sayHi() {
            console.log('hi');
        }
        //类方法 直接通过类调用
        static sayHello() {
            console.log('hello');
        }
    }
    
    const person = new Person();
    console.log(person.age);//实例属性 可读可写
    console.log(Person.gender);
    //console.log(Person.gender); //类属性
    person.sayHi();
    Person.sayHello();
    
  • 构造函数

    • 构造函数会在对象创建时调用
    • this
      • 在类中,使用 this 表示当前对象
  • class Dog {
        // constructor 被称为构造函数
        // 构造函数会在对象创建时调用
        name: String;
        age: number;
    
        constructor(name: String, age: number) {
            // 在实例方法中 this 表示当前实例
            // 在构造函数中当前对象就是当前新建的那个对象
            // 可以通过 this 向新建的对象添加属性
    
            this.name = name;;
            this.age = age;
        }
    
        bark() {
            //alert('汪汪汪');
            //在方法中可以通过 this 来表示当前调用方法的对象
            console.log(this);
        }
    }
    
    const dog = new Dog("小黑", 4);//会调用constructor() 无参
    const dog1 = new Dog("小白", 2);
    dog.bark();
    dog1.bark();
    

3.3.2 继承
  • 继承是面向对象中的一个特性

  • 通过继承可以将其他类中的属性和方法引入到当前类中

  • 重写

    • 发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
  • 示例

  • (function () {
    
        class Animal {
            name: String;
            age: number;
    
            constructor(name: String, age: number) {
                this.name = name;
                this.age = age;
            }
    
            sayHello() {
                console.log('do Something');
            }
        }
    
        //定义一个表示狗的类
        // extends 继承 扩展
        // Animal 父类
        // Dog Cat 子类
        // 继承后 子类会拥有父类所有的方法和属性(不严谨) public 
        // 通过继承可以将多个类共有的代码写在父类中
        // 如果希望在子类添加一些父类中没有的方法 或 属性 直接写入子类
        // 如果在子类中添加了跟父类一样的方法,则子类方法会覆盖掉父类的方法
        // 子类覆盖掉父类的方法  --- 方法重写
        class Dog extends Animal {
            sayHello() {
                console.log(`${this.name}汪汪汪`);
            }
            run() {
                console.log(`${this.name}在跑`);
            }
        }
    
        class Cat extends Animal {
            sayHello() {
                console.log(`${this.name}喵喵喵`);
            }
        }
    
    
        const dog = new Dog("旺财", 3);
        console.log(dog);
        dog.sayHello();
        dog.run();
    
        const cat = new Cat("咪咪", 2);
        console.log(cat);
        cat.sayHello();
        //发现 Cat Dog 类的 属性 构造函数 一样 唯一不同只是方法内部
        //重复代码提取出来
    	//OCP 不能去修改 可以去扩展
    })();
    

  • super

    • 在子类中可以使用 super 来完成对父类的引用
  • 示例:

  • (function () {
        class Animal {
            name: string;
            constructor(name: string) {
                this.name = name;
            }
            sayHello() {
                console.log('动物在叫');
            }
        }
    
        class Dog extends Animal {
            age: number;
            //属性“age”没有初始化表达式,且未在构造函数中明确赋值。注意声明属性一定要初始化!
            //派生类的构造函数必须包含 "super" 调用。
            constructor(name: string, age: number) {
                //如果在子类写了构造函数 在子类的构造函数进行对父类构造函数的调用
                super(name);//调用父类的构造函数
                this.age = age;
            }
    
            sayHello() {
                // super 表示当前类的父类
                super.sayHello();
            }
        }
    
        const dog = new Dog("旺财", 3);
        console.log(dog);
        dog.sayHello();
    
    })();
    

3.3.3 多态
  • 父类定义一个方法不去实现,让继承他的子类去实现,每一个子类有不同的表现
  • 多态属于继承

  • 抽象类abstract class

    • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
    • 使用 abstract 开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要实现
  • (function () {
    
        // 以 abstract --> 抽象类
        // 抽象类 --> 用于被继承的类
        // 抽象类可以添加抽象方法
        // 抽象方法只能定义在抽象类,必须由子类去实现
        abstract class Animal {
            name: string;
            constructor(name: string) {
                this.name = name;
            }
            abstract sayHello(): void;
        }
    
        class Dog extends Animal {
            sayHello() {
                console.log(`${this.name}汪汪`);
            }
    
        }
    
        const dog = new Dog("旺财");
        console.log(dog);
        dog.sayHello();
        //不希望 通过Animal类创建对象
        //const animal = new Animal();//无法创建抽象类的实例
    
    })();
    

  • 接口Interface

  • 接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法

  • 接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  • 示例:

  • (function () {
        //描述一个对象的类型
        type myType = {
            name: string,
            age: number
        };
        const obj: myType = {
            name: 'zhangsan',
            age: 20
        };
        //类型声明不可以重复
        //接口 用于定义一个类的结构
        interface myInterface {
            name: string,
            age: number
        }
    
        interface myInterface {
            gender: string;
        }
        // 接口用来定义一个类的结构,用来定义一个类应该包含哪些属性和方法
        // 同时接口也可以当成类型声明来使用
        // 接口是可以重复声明的
        const o: myInterface = {
            name: 'zhangsan',
            age: 20,
            gender: 'male'
        }
        // 接口可以在定义类的时候去限制类的结构
        // 接口中所有的属性都不能由实际的值
        // 接口只定义对象的结构 而不考虑实际值
        // 在接口中所有的方法都是抽象方法
        interface myInter {
            name: string,
            sayHello(): void;
        }
        //定义类时 可以使类去实现一个接口
        class MyClass implements myInter {
            name: string;
            constructor(name: string) {
                this.name = name;
            }
            sayHello() {
    
            }
        }
    })();
    

  • TS 中属性具有三种修饰符:

    • public(默认值),可以在类、子类和对象中修改
    • protected ,可以在类、子类中修改
    • private ,可以在中修改
  • 属性存取器

    • 对于一些不希望被任意修改的属性,可以将其设置为 private

    • 直接将其设置为 private 将导致无法再通过对象修改其中的属性

    • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

    • 设置属性的方法叫做 setter 方法,读取属性的方法叫做 getter 方法

  • 示例:

  • (function () {
        class Person {
            // TS 可以在属性面前添加属性的修饰符
            /* 
            public 修饰的属性可以在任意位置访问|修改 (默认值)
            private 私有属性 -- 私有属性只能在类内部访问
            通过在类中添加方法使得私有属性可以在外部访问(暴露)
    
            protected 受包含的属性 只能在当前类和当前类的子类中访问
            不能通过实例去访问
            */
            private name: string;
            private age: number;
    
            constructor(name: string, age: number) {
                this.name = name;
                this.age = age;
            }
    
            //getter 获取name属性
            getName() {
                return this.name;
            }
    
            //setter
            setAge(value: number) {
                if (value >= 0) {
                    this.age = value;
                }
            }
            // TS 中 设置 setter 方法的方式
            get _name() {
                return this.name;
            }
    
            set _name(value: string) {
                this.name = value;
            }
    
        }
    
        class A {
            protected num: number;
            constructor(num: number) {
                this.num = num;
            }
        }
    
        class B extends A {
            test() {
                console.log(this.num);
            }
        }
    
        class C {
            //相当于语法糖
            //可以直接将属性定义在构造函数中
            constructor(public name: string, public age: number) {
    
            }
        }
    
        // 属性在对象中设置 属性是可以任意被修改的
        // 将对象中的数据变的非常不安全
        const per = new Person('zhangsan', 18);
        console.log(per);
        //per.name = 'lisi';//属性“name”为私有属性,只能在类“Person”中访问
        //per.age = 20;
        console.log(per.getName());
        per.setAge(20);
        console.log(per);
    
        console.log(per._name);
    
        per._name = 'wangwu';
        console.log(per);
    
        const b = new B(123);
        //b.num = 33;//属性“num”受保护,只能在类“A”及其子类中访问。
        const c = new C('xxx', 20);
    })();
    

  • 泛型(Generic)

  • 定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

  • 举个例子:

  • function test(arg: any): any{
    	return arg;
    }
    
  • 上例中,test 函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的

  • 由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用 any 会关闭 TS 的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型。

  • 使用泛型:

  • function test<T>(arg: T): T{
    	return arg;
    }
    
  • 这里的 <T>就是泛型,T 是我们给这个类型起的名字(不一定非叫 T,可以是 K,V …),设置泛型后即可在函数中使用 T 来表示该类型。所以泛型其实很好理解,就表示某个类型。

  • 使用方式

    • 方式一(直接使用)

    • test(10)
      
    • 使用时可以直接传递参数使用,类型会由 TS 自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式

    • 方式二(指定类型):

    • test<number>(10)
      
    • 可以在函数后手动指定泛型

  • 可以同时指定多个泛型,泛型间使用逗号隔开:

  • function test<T, K>(a: T, b: K): K{
        return b;
    }
    
    test<number, string>(10, "hello");
    
  • 使用泛型时,完全可以将泛型当成是一个普通的类去使用

  • 类中同样可以使用泛型:

  • class MyClass<T>{
        prop: T;
    
        constructor(prop: T){
            this.prop = prop;
        }
    }
    
  • 可以对泛型的范围进行约束:

  • interface MyInter{
        length: number;
    }
    
    function test<T extends MyInter>(arg: T): number{
        return arg.length;
    }
    
  • 使用 T extends MyInter 表示泛型 T 必须是 MyInter 的子类,不一定非要使用接口类和抽象类同样适用。

  • 具体示例:

  • // function fn(a: number): number {
    //     return a;
    // }
    
    //类型不明确可以使用泛型
    //在定义函数或类时,如果遇到类型不明确可以使用泛型
    
    // K T 表示任意类型
    // T 只有在函数执行的时候才可以确定
    function fn<T>(a: T): T {
        return a;
    }
    
    //可以直接调用具有泛型的函数
    let result = fn(10);//function fn<10>(a: 10): 10
    let res = fn<string>('hello');//function fn<string>(a: string): string
    // 不指定泛型 TS 可以自动对类型进行推断
    
    function fn2<T, K>(a: T, b: K) {
        console.log(b);
        return a;
    }
    
    fn2(123, 'hello');
    fn2<number, string>(123, 'hello');
    
    interface Inter {
        length: number;
    }
    
    
    //T是泛型必须实现Inter接口
    //T extends Inter 表示 泛型 T 必须使用 Inter 实现类(子类)
    function fn3<T extends Inter>(a: T): number {
        return a.length;
    }
    
    fn3('123');
    fn3({ length: 10 });
    //fn3({ name: 10 });//报错
    //类型“{ name: number; }”的参数不能赋给类型“Inter”的参数。
    //对象文字可以只指定已知属性,并且“name”不在类型“Inter”中。
    
    class MyClass<T>{
        name: T;
        constructor(name: T) {
            this.name = name;
        }
    }
    
    const mc = new MyClass<string>('xxx');
    

  • 下期预告:使用 typescript 编写游戏<贪吃蛇>

配置项目文件是挺麻烦的,也致使 “Webpack 配置工程师” 的出现🤣,但不可否认的是,Webpack 是现较为主流的前端构建工具,所以想要深造前端的同学一定好好静下心来,好好学习💪。

第二期 TypeScript 学习内容就这么多啦,如果内容不错的话,望您能关注🤞点赞👍收藏❤️一键三连!

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值