前端小零识

开发技巧

需求分析

  • 输入查询条件得出结果的需求:留意是数据筛选还是查询

JavaScript

JS基本原理

  • javascript运行在单线程环境

数组操作

segmentfault.com/a/119000001…

this指向

  • 首先this指向的对象只有三个:windows、函数实例、所处对象
  • 如果函数有this,但是没有所处对象或函数实例调用,那么this指向windows
function a(){
    var user = "追梦子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();
复制代码
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();
复制代码
  • 如果this被所处对象或函数实例调用,this指向该对象
var o = {
    a:10,
    b:{
        //a:12,
        fn:function(){
            console.log(this.a); //undefind  有两个对象b和o,所以此this.a指向它的上一级
        }
    },
    fn1:function(){
        console.log(this.a);  //10 
    }
}
o.fn1();
o.b.fn();
复制代码
  • 当函数中有this,且最后使用了return,如果return的是对象,那么this指向该对象,否则this指向函数实例
function fn()  
{  
    this.user = '追梦子';  
    return {};  // 或function(){}
}
var a = new fn;  
console.log(a.user); //undefined
复制代码
function fn()  
{  
    this.user = '追梦子';  
    return 1;   
}
var a = new fn;  
console.log(a.user); //追梦子
复制代码
  • 虽然null也是对象,但是null比较特殊,return null,this依然指向函数实例,不指向null

bind、apply、call的区别

  • apply和call不同点在于,call需要一个个传入参数,apply可以数组形式传入参数;使用方法分别为funA.apply(funB, args)和funA.call(funB, arg1, arg2, arg3...)。
  • bind的传参方式与call相同,但永久改变this的指向,并返回新的函数;使用方法为var newFun = funA.bind(funB, arg1, arg2...);如果在使用bind时传入了参数,那么之后调用newFun时参数也是默认被传入,如var newFun = funA.bind(null, a, b); newFun(c, d); 这里相当于传入了a、b、c、d四个参数。

柯里化

  • 柯里化是创建一个已经设置好一个或多个参数的函数,是用于动态创建函数的强大功能
  • 柯里化函数的通用方式:
function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
        var inArgs = Array.prototype.slice.call(null, arguments);
        var finalArgs = args.concat(inArgs);
        return fn.apply(null, finalArgs);
    };
}
复制代码

不可扩展对象

  • 使用Object.preventExtensions()可以防止对象添加新成员
var ob = {name: 'tom'};
Object.preventExtensions(ob);
ob.age = 22;
alert(ob.age);  // undefined;
复制代码
  • 使用Object.preventExtensions()后的对象,非严格模式下,添加新成员静默失败;严格模式下,添加新成员抛错。
  • 使用Object.preventExtensions()后的对象,依旧可以删除修改成员
  • 可以使用Object.isExtensible()可判断是否可以扩展;

密封对象

  • 使用Object.seal(ob)密封对象ob,对象不可添加和删除成员。非严格模式默认失败,严格模式下抛错。但是可以修改成员。

冻结对象

  • 使用Object.freeze(ob)冻结对象ob,对象不可修改、添加、删除成员。非严格模式默认失败,严格模式下抛错。

定时器原理

  • 指定的时间间隔表示何时将定时器的代码放入队列,而不是何时立即执行代码,如果队列里面还有其它任务,那么这些排在前面的任务将会被优先执行,定时器代码会被滞后执行。
  • 异步队列中的任务会在进程空闲期执行

重复的定时器原理

  • 重复定时器主要是setInterval,但使用setInterval有很多问题
  • 如果再添加定时器代码到队列时,上一次添加到队列的代码还没有执行完,可能会导致定时器代码连续执行多次,没有停顿。为了解决这个问题,浏览器会判断队列里有无定时器代码实例,如果有则不添加代码到队列。
  • 上述浏览器的解决方案也存在另一问题:某些定时任务被跳过

数据属性

访问器属性

获取通用唯一识别码

https://blog.csdn.net/mr_raptor/article/details/52280753
复制代码

纯函数

  • 函数返回结果只依赖于参数
  • 函数没有副作用,不会更改参数

运算符

  • NaN === NaN // false
  • +0 === -0 // true (但这里本应该是不等的,是JS的缺陷)

字符串

  • replace(arg1, arg2):用于字符串内容替换,第一参数为正则表达式或字符串,第二参数为需替换的字符串或函数,函数为function(matchStr, group1[...groupN], index, sourceStr),matchStr为正则匹配到的字符串,group为捕获组,index为匹配项在字符串中的开始下标,sourceStr为原字符串

CSS

布局

  • clearfix
.clearfix {
  display: block;
  zoom: 1;

  &:after {
    content: " ";
    display: block;
    font-size: 0;
    height: 0;
    clear: both;
    visibility: hidden;
  }
}
复制代码

ES6

对象的新增方法

  • Object.is()方法可以准确比较NaN与其自身,+0和-0的比较,如:

  • Object.assign()参数为非对象则转换为对象并返回
  • Object.assign()参数为null或undefined则报错
  • 利用Object.assign()克隆对象并拷贝对象的原型链
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}
复制代码
  • 以下克隆方法可以解决get、set不能克隆的问题
function shallowMerge(target, source) {
    return Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))
}
复制代码
  • 利用Object.create()克隆对象,对象属性可以为访问器属性,并拷贝对象的原型链
function merge(ob) {
    return Object.create(Object.getPrototypeOf(ob), Object.getOwnPropertyDescriptors(ob));
};
复制代码
  • Obeject.entries()不能输出Symbol键值对
  • Symbol类型的实例具有唯一性,一般用作属性,可用于替换魔术字符串
  • 使用Symbol.for()可创建全局的唯一Symbol类型实例
  • 数据结构Set的成员唯一,可用于数组去重,如:
Array.from(new Set(arr))
[...new Set(arr)]
复制代码

也可以用于字符串去重,如:

[...new Set(str)].join('')
复制代码

Map和Set

  • Set的原型链属性有contructor、size,操作属性有add、delete、has、clear,遍历方法有keys、values、entries、forEach
  • WeakSet的成员只是对象,且对象都为弱引用,若对象引用次数为0,对象则被gc
  • WeakSet的实例操作方法有add、has、delete
  • Map数据结构的键可以为任意类型
  • Map数据类型+0和-0等同于一个键、NaN虽然等同于自身但是会把NaN视为一个键
  • Map数据类型具有继承属性size,操作方法set、get、delete、clear、has,遍历方法有keys、values、entries、forEach
  • WeakMap数据结构只接受对象作为键名

Proxy

  • 元编程即为对编程语言进行编程
  • proxy是一种元编程方法,机制是在操作对象前会作一层拦截
  • proxy中的get、set可以被继承
  • proxy可用于属性的链式操作
var ob = {
  n: 0,
  a: function(n) {
    return n + 1;  
  },
  b: function(n) {
    return n + 1;
  }
}
var pipe = function (ob, value) {
  var funcStack = [];
  var oproxy = new Proxy(ob , {
    get : function (pipeObject, fnName) {
      if (fnName === 'get') {
        return funcStack.reduce(function (val, fn) {
          return fn(val);
        }, value);
      }
      console.log('fnName', fnName)
      funcStack.push(ob[fnName]);
      console.log('funcStack', funcStack)
      return oproxy;
    }
  });
  return oproxy;
};


var proxy = pipe(ob, 1);
console.log(proxy.a.b.get)  // 3
复制代码
  • Reflect是使用内部语言方法操作对象

Promise

  • 概念:Promise是一个容器,保存着未来发生的事情。Promise是一个对象,可以获取异步操作的消息。
  • 优点:
    (1)状态不受外界影响:三种状态pending、fullfilled、reject (2)状态改变后不会再改变,什么时候都可以得到这个结果:从pending到fullfilled或从pending到reject后,状态resolved(定型),任意时刻可以得 到结果
  • 缺点:
    (1)Promise一旦创建,不可以取消
    (2)如果在pending状态,不可以知道是处于刚开始还是即将完成状态

Interator

  • Interator是一种接口,为不同的数据结构提供统一的访问机制,具备Interator的数据结构完成遍历操作
  • 具备Interator的数据结构:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList对象
  • 对象不具备Iterator接口,是因为属性的顺序是不确定的
  • 只有类数组对象可以使用[Symbol.iterator]方法,如:
let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // 'a', 'b', 'c'
}
复制代码
  • 普通对象使用[Symbol.iterator]方法无效
let iterable = {
  a: 'a',
  b: 'b',
  c: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
  console.log(item); // undefined, undefined, undefined
}
复制代码
  • [Symbol.iterator]方法的使用场合,在解构赋值、扩展运算符、yield*(yield*后面为可遍历结构时,会调用后面的可遍历结构的[Symbol.iterator]方法),其它场合:(for...of Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])) Promise.all() Promise.race())
  • 除了next方法外,还有return(),throw()方法
  • for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串

Generator

  • Generator是一个状态机,同时也是遍历器对象生成器
  • 特征:function和函数名之间有一个*号,函数内部使用field表达式,表示不同的状态
  • 返回:指向内部状态的指针对象
  • 可以把Generator赋值给对象的[Symbol.iterator]实现对象的iterator操作
  • Generator执行后返回的遍历器也具有[Symbol.iterator],并且Symbol.iterator指向其自身
  • 如果next带有参数,那么这个参数就是上一个field的返回值
function* generator() {
  yield 1;
  yield 2;
  return 3;
}

var g = generator();
console.log(g.next())
console.log(g.next())
console.log(g.next())
/**
 * Object {value: 1, done: false}
    Object {value: 2, done: false}
    Object {value: 3, done: true}
    Object {value: undefined, done: true}
 */
复制代码

如果调用next时遇到return,返回值中的done就会提前为true;如果没有return,当把generator的值遍历完后调用next,返回的done才为true

  • 实现斐波那契数列:
function* fibonacci() {
  let [prev, cur] = [0, 1];
  for (;;) {
    yield cur;
    [prev, cur] = [cur, prev + cur]
  }
}

for (let i of fibonacci()) {
  if (i > 10000) break;
  console.log(i)
}
复制代码
  • Generator可以作为对象属性:
const ob = {
    * generator () { //... }
}
等同于
const ob = {
   generator: function* () { //... } 
}

复制代码
  • 协程是指并行执行,可以交互执行权的线程
  • 求值策略,即是函数的参数何时取值的问题,有两种类型:传值调用和传名调用
  • 编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
  • 基于Thunk的流程自动化管理
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

function run(fn) {
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    console.log(result)
    if (result.done) return;
    result.value(next);
  }

  next();
}

var g = function* (){
  var f1 = yield readFileThunk('fileA');
  var f2 = yield readFileThunk('fileB');
  var fn = yield readFileThunk('fileN');
};

run(g);
复制代码

async

  • async是generator的语法糖,优点:(1)内置执行器
    (2)更好的语义
    (3)更广的适用性,await后面可以是Promise或原始类型值(若原始类型值为n,相当于Promise.resolve(n))
    (4)返回的值是Promise

class

  • 子类没有定义constructor方法,这个方法会被默认添加
  • 在子类的constructor方法中,需要调用super才可以使用this
  • super在静态方法中指向父类,在普通方法中指向父类的原型
  • 在子类普通方法中使用super调用父类的方法,方法中的this指向子类实例
  • 在子类的静态方法中使用super调用父类方法,方法中的this指向子类而不是子类的实例
  • 通过super为属性赋值,此时super就是this,导致赋值的是子类的属性
class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();
复制代码
  • Object.getPrototypeOf方法可以用来从子类上获取父类
  • 子类的构造函数必须执行一次super()
  • 若A为B的父类,super()相当于A.prototype.constructor.call(this)
  • new.target只能在constructor中使用,并且指向当前执行的函数
  • 子类的__proto__指向父类,子类的prototype的__proto__指向父类的prototype
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
复制代码
  • 如果只是单纯的类,没有继承,类的__proto__指向Function.prototype,类的prototype.__proto__指向Object.prototype
class A {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
复制代码
  • 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性
class Point {}
class ColorPoint extends Point {}

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

console.log(p2.__proto__.__proto__ === p1.__proto__ )  // true
console.log(p2.__proto__ === p1 )  // false
复制代码
  • ES的原生构造函数有如下:
    Boolean()
    Number()
    String()
    Array()
    Date()
    Function()
    RegExp()
    Error()
    Object()
    在ES5中不能通过继承内部属性和内部方法,在ES6中可以实现。注意在继承Object时会有异常:
class NewObj extends Object{
  constructor(){
    super(...arguments);
  }
}
var o = new NewObj({attr: true});
o.attr === true  // false
复制代码

因为如果不是使用new Object()新建对象,ES6会忽略传入的参数

  • Mixin混入模式是指多个对象合成新对象,新对象具有这些原对象成员的接口

Module

  • export用于定义模块对外的接口
  • export可以输出变量、函数、类,不能输出常量
  • 需要注意使用export输出函数的写法
function fn() { ... }
export fn;	// 错
export { fn };	// 对
复制代码
  • export 只能放在模块的顶层,可以放在顶层的任意位置,但不可以放在函数内部
  • export default A等同于将A赋值给default然后输出,所以可以
import { default as B } from 'xx.js';
复制代码

也可以

export { A as default };
复制代码
  • export和import的复合写法:
export { A } from 'xxx.js'
复制代码
  • import用于引入其他模块的功能
  • import引入的变量是动态绑定,如import a,如果输出的a是异步变化的,那么引入a的也是变化的
  • import命令具有提升效果,因为import再编译期间(代码运行之前)就会执行

ArrayBuffer

  • ArrayBuffer是内存中一段二进制数据
  • TypeArray包括9类视图:
    (1)Init8Array: 8位整型 (2)Uinit8Array: 8位无符号整型 (3)Uinit8CArray: 8位无符号整型(自动过滤溢出) (4)Init16Array: 16位整型 (5)Uinit16Array: 无符号16位整型 (6)Init32Array: 32位整型 (7)Uinit32Array: 无符号32位整型 (8)Float64Array: 64位浮点型 (9)Ufloat64Array: 无符号64位浮点型
  • DataArray: 自定义的复合格式的视图
  • ArrayBuffer.prototype.byteLength:用户获取ArrayBuffer的实例
  • ArrayBuffer.prototype.slice():用于将ArrayBuffer内存的一部分,拷贝为一段新的内存;过程为创建一段新的内存,然后拷贝
  • ArrayBuffer.prototype.slice():判断是否为ArrayBuffer的视图实例(如 DataArray、TypeArray的实例)
  • TypeArray与Array的区别:
    (1)成员连续
    (2)成员默认为0 (3)成员类型一致 (4)本身只是视图,实际数据存储在底层的ArrayBuffer

计算机基础

编码

  • 编码是信息从一种形式转换到另一种信息的过程,解码即是逆编码。
  • JavaScript有全局方法encodeURI()和encodeURIComponent();
  • encodeURI不对以下字符编码
    • 保留字符:; , / ? : @ & = + $
    • 非转义字符:字母 数字 - _ . ! ~ * ' ( )
    • 数字字符 #
  • encodeURIComponent不会对以下字符编码
    • 非转义字符:字母 数字 - _ . ! ~ * ' ( )
  • 两者的区别是encodeURI不能对保留字符和#转义

解码

  • encodeURI()和encodeURIComponent()对应的解码方法有decodeURI()和decodeURIComponent()

进程和线程

  • 进程是资源分配的最小单位,线程是程序执行的最小单位
  • 进程有独立的内存地址空间
  • 一个进程下可以有多个线程
  • 同一进程下的多个线程可共享进程的资源
  • 多进程程序更加健壮,因为一个线程死掉,进程即死掉,该进程下的所有所有线程都会死掉;而一个进程死掉不会影响另一进程,因为进程占用独立的内存地址空间

计算机网络

链路层

  • 每一个网卡都用一个mac地址
  • 广播:假设子网略内有若干台主机,一台主机发出一个数据包,接收到包的主机会读取数据包头部的MAC地址,并与自身的MAC地址比较,如果相同则做进一步处理,否则丢弃这个包

网络层

  • 网络层的存在是因为:链路层广播的效率低,通信双方需要在一个子网略,局限性太大
  • 网络层引进了“网络地址”,引入网络地址可以区分哪一些MAC地址处于同一个子网络
  • 规定网络地址的协议成为IP地址,由32位二进制数组成 11111111.11111111.11111111.00000000,可分为网络部分和主机部分,如果网络部分为前24位,主机部分位后8位,那么前24位相同的两个ip地址肯定在同一个子网络。
  • 子网掩码用于确定IP地址的网络部分,比如子网掩码位255.255.255.0,那么肯定是前24位为网络部分
  • ARP协议:用于同一子网络的通信,协议内容是:发出的包含有所要查询主机的ip地址和mac地址,mac地址为FF:FF:FF:FF:FF:FF(12位16进制),表示广播到所有主机。所在子网络的每一台主机会收到这个包,取出其中的IP地址并与自身比对,如果相同双方都做出回复,告诉对方自己的mac地址,否则丢弃这个包
  • 实现数据包的成功发送,需要获取接收方的MAC地址、IP地址、网关地址、DNP服务器IP地址

动态IP地址

  • 静态IP地址:每次开机都用一个IP地址上网,即是“静态IP地址上网”。为了更方便地为新接入的主机添加IP地址,一般会使用动态IP地址。DHCP协议(动态主机设置协议)可实现动态IP地址

获取动态IP地址过程

(1)新加入的主机会向DHCP服务器发送一个DHCP请求数据包,数据包的结构为 |以太网标头|IP标头|UDP标头|data(数据内容)|

  • 以太网标头含有发送方和接收方的MAC地址,前者为新加入主机自身的MAC地址,后者为FF.FF.FF.FF.FF.FF,即是广播地址
  • IP标头含有发送方和接收方的IP地址,因为两者都不知道,于是发送发IP地址设为0.0.0.0,接收方IP地址设为255.255.255.255
  • UDP标头含有发送方和接收方的端口,前者设为68,后者设为67
  • 请求数据包在子网络内广播,接收到数据包的主机首先解析数据包MAC地址,因为接收方MAC地址为FF.FF.FF.FF.FF.FF广播地址,所以会解析数据包的IP地址,接收方IP为255.255.255.255,将之与自身IP地址比对,发现不同则丢包
    (2)当DHCP服务器接收到请求数据包并解析接收方IP地址,发现为255.255.255.255,服务器知道需要去分配IP,发出响应数据包
    (3)响应数据包的数据内容中含有为新主机分配的IP地址、网关IP地址、DNS的IP地址
  • 响应数据包的以太网标头含有发送方和接收方的MAC地址,前者为DHCP服务器看的MAC地址,后者为新主机的MAC地址
  • IP标头含有发送方IP地址为DHCP服务器自身的IP地址,接收方IP地址依然为255.255.255.255
  • UDP标头的发送方端口为67,接收方端口为68 (4)新加入的数据包接收到响应数据包,拿到IP地址、子网掩码、网关IP、DNS服务器的IP。

传输层

  • 端口:表示数据包供哪个程序使用,端口号可以为0到65535,0到1023供系统使用
  • 传输层是"端口到端口"的通信
  • UDP的优点是简单,但是可靠性差,无法知道对方是否接收到。
  • TCP可靠性比UDP强,缺点是过程复杂、消耗较多资源

应用层

  • 主要用于规定好客户端和服务端的通信格式

一次网络通信的过程

比如说用我们的计算机访问一次百度(baidu.com):

  • 设置好本机IP、子网掩码、DNS_IP、网关IP
  • 本机向DNS_IP发送一个DNS数据包,通过DNS协议获得将baidu.com解析为IP地址
  • 接下来创建我们的数据包,首先将HTTP数据包嵌入TCP数据包,新数据包再嵌入IP数据包,这个由HTTP数据包、TCP数据包、IP数据包组成的新数据包最后会嵌入以太网数据包(它的结构如下图)
  • 通过子网掩码判断本机与百度服务器是否在同一网关(我们和百度服务器的必然是不同的)
  • 以太网标头的目标MAC地址改为本机网关的MAC地址,我们的数据包转由网关发送
  • 通过ARP协议将百度服务器的IP地址解析为MAC地址,得到了最终的数据包。假设这一个数据包大小为4600字节,因为以太网数据包的大小限制为1500字节,所以需要切割为大小分别为1500、1500、1500、100字节的四个以太网数据包,最后再发送到百度的服务。

JS替换语言

TypeScript

基础类型
  • 基础类型有:number、boolean、string、null、undefined、symbol、object
  • null和undefined是所有类型的子类型,因此可以把它们赋给任何已经定义类型的变量
  • never是那些不存在值的类型,如只会抛错和没有return值的函数,是所有类型的子类型,但它没有子类型(包括any)
  • 类型断言:类似于类型转换、不进行类型检查,可有两个方式:和as string
  • 如果想变量只读使用const,想对象的属性只读,可定义类型为只读类型,比如
interface Point {
    readonly x: number;
    readonly y: number;
}
复制代码
  • 处理额外属性可以使用索引签名,索引签名可以为数字也可以是字符串,但是前者值的类型必须是后者的子类型,如(propName为其余属性):
interface Ob {
    name: string;
    [rest: string]: any;    // string定义属性名类型、any定义值的类型
}
复制代码

模块化

AMD(异步模块规范)

define(['jquery', 'underscore'], function ($, _) {
// 方法
function a(){}; // 私有方法,因为没有被返回(见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
     //    暴露公共方法
    return {
        b: b,
        c: c
    }
});
复制代码

CommonJS

var $ = require('jquery');
复制代码

UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS之类的
        module.exports = factory(require('jquery'));
    } else {
        // 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    //    方法
    function myFunc(){};
 
    //    暴露公共方法
    return myFunc;
}));
复制代码

设计模式

单一指责原则

里氏替换

接口拆分

依赖倒置

开放封闭原则

转载于:https://juejin.im/post/5c3610ec6fb9a049d2363efa

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值