ES6 笔记 part2

Symbol

Symbol 类型数据的声明

{
  let a1 = Symbol ();
  let a2 = Symbol ();
  console.log(a1 === a2)

  // Symbol.for ,检查key值,不存在就新建一个Symbol,存在就返回已经创建的那个Symbol
  let a3 = Symbol.for('a3')
  let a4 = Symbol.for('a3')
  console.log(a3 === a4)
}

Symbol 类型作为对象的key

{
  let a1 = Symbol('abc')
  let obj = {
    [a1]: 123,
    'abc': 345,
    'c': 456
  }
  console.log(obj)
  
  // 无法遍历到Symbol类型的key
  for (let key in obj) {
    console.log(key, obj[key])
  }

  for (let [key, value] of Object.entries(obj)) {
    console.log(key, value)
  }

  // 取一个对象中所有的Symbol类型的对象
  console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol(abc) ]

  // 获取所有key,(包括Symbol)
  console.log(Reflect.ownKeys(obj)) // [ 'abc', 'c', Symbol(abc) ]

}

Set 类型

Set 类型的声明

{
  let list = new Set ()
  list.add(5)
  list.add(7)
  // 重复添加不会生效
  list.add(5)
  console.log(list) // Set { 5, 7 }
  console.log(list.size) // 2
}

Set 可用于去重

{
  // 可用于去重
  let arr = [1, 2, 3, 4, 5, 6, 6,'6']
  let list = new Set (arr)
  console.log(list) // Set { 1, 2, 3, 4, 5, 6, '6' }
}

Set 的方法:add、delete、clear、has

{
  let arr = ['add', 'delete', 'clear', 'has']
  let list = new Set (arr)
  
  console.log('has', list.has('add'))
  console.log('delete', list.delete('add'))
  console.log(list)
  console.log('add', list.add('add'))
  console.log(list)
  console.log('clear', list.clear())
  console.log(list)

}

WeakSet

  • WeakSet 和 Set 支持的数据类型不一样, weakSet 只支持对象
  • 无clear 方法,无size属性
  • 不能遍历
{
  let weakList = new WeakSet ()
  weakList.add({})

  console.log(weakList)
}

Map 类型

Map 类型的声明

{
  let map = new Map ()
  let arr = [1, 2, 3]
  map.set(arr, 456)
  map.set('a', 1)

  console.log(map)
  console.log(map.get(arr))
  console.log(map.get('a'))
}
{
  let map = new Map ([['a', 123], ['b', 456]])
  let obj = {a: 1}
  map.set(obj, 'ok')
  console.log(map)
  console.log(map.get(obj))
  console.log('map.szie', map.size)
  console.log(map.delete('a'), map)
  console.log(map.clear(), map)
}

map 对象没有.add方法,新增方法位.set

WeakMap

  • 无size属性,无法clear
  • 无法遍历
  • key必须是对象
{
  let weakmap = new WeakMap ()
  let obj = {}
  weakmap.set(obj, 123)
  console.log(weakmap)
  console.log(weakmap.get(obj))
}

各数据结构之间的对比

  • 优先使用map
  • 对数据唯一性要求较高,使用set
  • 放弃使用数组和对象

Map 和 Array 的对比

{
  let map = new Map ()
  let arr = []
  
  // 增
  map.set('t', 1)
  arr.push({t: 1})
  console.info(map, arr)
  
  // 查
  let map_exists = map.has('t')
  // let arr_exists = arr.find(function (item) {
  //   return item.t
  // })
  let arr_exists = arr.find(item => item.t)
  console.log(map_exists, arr_exists)

  // 改
  map.set('t', 2)
  arr.forEach(item => item.t ? item.t=2 : '')
  console.info(map, arr)

  // 删
  map.delete('t')
  arr.splice(arr.findIndex(item => item.t), 1)
  console.info(map, arr)

}

Set 和 Array 的对比

{
  let set = new Set()
  let arr = []
  let obj = {t: 1}

  // 增
  set.add(obj)
  arr.push(obj)
  console.info(set, arr)
  
  // 查
  let set_exists =  set.has(obj)
  let arr_exists = arr.find(item => item.t)
  // let arr_exists = arr.includes(obj)
  console.log(set_exists, arr_exists)

  // 改
  set.forEach(item => item.t ? item.t = 2 : '')
  arr.forEach(item => item.t ? item.t = 2 : '')
  console.info(set, arr)

  // 删
  // set.delete(obj)
  set.forEach(item => item.t ? set.delete(item) : '')
  arr.splice(arr.findIndex(item => item.t), 1)
  console.info(set, arr)
}

Map、Set 和 Object的互相对比

{
  let set = new Set()
  let arr = []
  let obj = {t: 1}

  // 增
  set.add(obj)
  arr.push(obj)
  console.info(set, arr)
  
  // 查
  let set_exists =  set.has(obj)
  let arr_exists = arr.find(item => item.t)
  // let arr_exists = arr.includes(obj)
  console.log(set_exists, arr_exists)

  // 改
  set.forEach(item => item.t ? item.t = 2 : '')
  arr.forEach(item => item.t ? item.t = 2 : '')
  console.info(set, arr)

  // 删
  // set.delete(obj)
  set.forEach(item => item.t ? set.delete(item) : '')
  arr.splice(arr.findIndex(item => item.t), 1)
  console.info(set, arr)
}


{
  let item = {t: 1}
  let map = new Map ()
  let set = new Set ()
  let obj = new Object ()

  // 增
  map.set('t', 1)
  set.add(item)
  obj['t'] = 1
  console.info(obj, map, set)

  // 查
  console.info({
    map_exists: map.has('t'),
    set_exists: set.has(item),
    obj_exist: 't' in obj
  })

  // 改
  map.set('t', 2)
  item.t = 2
  obj['t'] = 2
  console.info(obj, map, set)

  // 删
  map.delete('t')
  set.delete(item)
  delete obj['t']
  console.info(obj, map, set)

}

Proxy 和 Reflect

Proxy (代理)

可以通过Proxy 为一个对象创建代理,从而设置外界对改对象访问的拦截

{
  // 一个类似于供应商的原始数据对象 obj
  let t = Symbol.for('t')
  let obj = {
    time: '2020-04-04',
    name: 'net',
    _r: 123,
    [t]: 't'
  }

  // 通过Proxy 新生成一个对象,映射obj
  let monitor = new Proxy(obj, {
    // 拦截对象属性的读取
    // get (target, key) {
    //   return target[key].replace('2020', '2000')
    // },
    get: (target, key) => {
      if(key === 'time') return target[key].replace('2020', '2000')
      return target[key]
    },
    // 拦截对象设置属性
    set (target, key, value) {
      if (key === 'name') {
        return target[key] = value
      } else {
        return target[key]
      }
    },
    // 拦截 key in object 操作 
    has (target, key) {
      if (key === 'name') {
        return target[key]
      } else {
        return false
      }
    },
    // 拦截删除操作
    deleteProperty (target, key) {
      if (key.startsWith('_')) {
        delete target[key]
        return true
      } else {
        return target[key]
      }
    },
    // 拦截Object.keys、Object.getOwnPropertySymbols、Object.getOwnPropertyNames
    // 注意Object.keys 不会取到Symbol类型的key,所以下面的结果需要.concat拼接上Symbol类型的key的列表
    ownKeys (target) {
      return Object.keys(target).filter(item => item!='time')
        .concat(Object.getOwnPropertySymbols(target))
    }
  })
  
  console.log(monitor.time)
  monitor.time = 2018
  console.log(monitor)
  monitor.name = 'test'
  console.log(monitor)
  
  console.log('name' in monitor)
  console.log('time' in monitor)

  // delete monitor.time
  // console.log(monitor)
  // delete monitor._r
  // console.log(monitor)

  console.log(Object.keys(monitor))
  console.log(Object.values(monitor))
  console.log(Object.getOwnPropertyNames(monitor))
  console.log(Object.getOwnPropertySymbols(monitor))

}

Reflect(映射)

可以通过Reflect对某个对象进行操作

Reflect

{
  let t = Symbol.for('t')
  let obj = {
    time: '2020-04-04',
    name: 'net',
    _r: 123,
    [t]: 't'
  }

  console.log(Reflect.get(obj, 'time'))
  console.log(Reflect.get(obj, t))

  Reflect.set(obj, 'name', 'test')
  console.log(obj)
  console.log(Reflect.has(obj, 'name'))
}

Proxy 的应用

以下代码实现:

  • 函数validator 返回一个代理对象,并设置一些set验证,能避免外界错误地修改对象
  • personValidators 对象中定义了各属性的验证规则
  • Person类的构造函数,返回的实际上不是真正的实例对象,而是通过validator 函数得到一个实例对象的代理对象,从而将实例对象保护起来,这样就能避免误操作实例对象
{
  function validator (target, validator) {
    return new Proxy (target, {
      _validator: validator,
      set (target, key, value, proxy) {
        if (target.hasOwnProperty(key)) {
          let va = this._validator[key](value)
          // console.log('va', va)
          if (!! va) {
            Reflect.set(target, key, value, proxy)
          } else {
            throw Error(`${key} 不满足条件,无法设置`)
          }
        } else {
          throw Error(`${key} 不存在`)
        }
      }
    })
  }
  const personValidators = {
    name (val) {
      return typeof val === 'string'
    },
    age (val) {
      return typeof val === 'number' && val >= 18
    }
  }

  class Person {
    constructor (name, age) {
      this.name = name
      this.age = age
      // 返回的是一个Proxy对象,代理了this,即代理了这个Person对象
      // 在外面操作的是代理对象,不是peoson对象
      return validator(this, personValidators)
    }
  }

  let per = new Person('Tom', 25)
  console.info(per)
  //per.age = 8 // 报错,不满足条件,无法设置
}

类和对象

类的基本定义和生成实例

{
  class Parent {
    constructor (name = 'test') {
      this.name = name
    }
  }

  let parent1 = new Parent('v')
  console.info(parent1)
}

继承

{
  class Parent {
    constructor (name = 'test') {
      this.name = name
    }
  }

  class Child extends Parent {
    
  }

  console.log(new Child())
}

继承 传递参数

{
  class Parent {
    constructor (name = 'test') {
      this.name = name
    }
  }


  class Child extends Parent {
    constructor (name = 'child', age = 18) {
      // 一定要把 super 放第一行
      super(name)
      this.type = 'child'
      this.age = age
    }

  }

  console.log(new Child('Jack', 20))
}

getter setter

{
  class Parent {
    constructor (name = 'test') {
      this.name = name
    }

    get longName () {
      return this.name
    }

    set longName (value) {
      this.name = value
    }
    // 注意区分 getter 和 自定义的get属性
    // get () {
    //   console.log('hhh')
    // }
  }

  let parent = new Parent ()
  console.log(parent.longName)
  console.log(parent)
  parent.longName = 'Tom'
  console.log(parent)
}

静态方法 和 静态属性

  • 静态方法: static 关键字
  • 静态属性,创建类后,直接.出来
{
  class Parent {
    constructor (name) {
      this.name = name
    }

    static say () {
      console.log('hello!')
    }
    
  }
  Parent.type = 'test'

  Parent.say()
  console.log(Parent.type)

}

Promise

为了解决回调地狱嵌套,EcmaScript 6 中新增了一个API,promise

基本用法, 以nodejs中读文件为例

var fs = require('fs')

// 返回一个Promise对象
var pReadFile = function (filePath) {
  return new Promise (function (resolve, reject) {
    fs.readFile(filePath, 'utf8', function (err, data) {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

pReadFile('./a.txt')
  .then(function (data) {
    console.log(data)
    return pReadFile('./b.txt')
  })
  .then(function (data) {
    console.log(data)
    return pReadFile('./c.txt')
  })
  .then(function (data) {
    console.log(data)
  })

基本用法,以setTimeout为例

{
  let test = (num) => {
    return new Promise ((resolve, reject) => {
      setTimeout(() => {
        if (num > 5) {
          resolve(num)
        } else {
          reject(`${num} <= 5`)
        }
      },500)
    })
  }
  // 可以用.catch方法捕获错误
  test(3).then((num)=>{
    console.log(num)
  }).catch((err)=>{
    console.log('catch', err)
  })
  // 也可以给.then方法传入第二个callback参数
  test(3).then((num)=>{
    console.log(num)
  },(err)=>{
    console.log('then arg2', err)
  })

  test(6).then((num)=>{
    console.log(num)
  }).catch((err)=>{
    console.log('catch', err)
  })
}

Promise.all 方法

Promise.all()传入一个数组,成员为Promise实例对象,则返回一个新的Promise实例对象。

.then()则传入一个数组,表示所有的结果

所有的都处理完了,再一起处理

参考以下代码:

{
  let loadImg = (src) => {
    return new Promise((resolve, reject) => {
      let img = document.createElement('img')
      img.src = src
      img.onload = () => {
        resolve(img)
      }
      img.onerror = (err) => {
        reject(err)
      }
    })
    
  }

  let showImg = (img) => {
    document.body.appendChild(img)
  }

  let showImgs = (imgs) => {
    imgs.forEach((img) => {
      document.body.appendChild(img)
    })
  }

  loadImg('https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4224877969,2952280193&fm=26&gp=0.jpg').then((img) => {
    showImg(img)
  })
    
  // 把所有图片都加载完,再一起显示出来
  Promise.all([
    loadImg('https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4224877969,2952280193&fm=26&gp=0.jpg'),
    loadImg('https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4224877969,2952280193&fm=26&gp=0.jpg'),
    loadImg('https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4224877969,2952280193&fm=26&gp=0.jpg')
  ]).then((imgs) => {
    showImgs(imgs)
  })
}

Promise.race 方法

与Promise.all 类似, 传入的Promise实例中,有一个处理完了,就开始后续处理

{
  let loadImg = (src) => {
    return new Promise((resolve, reject) => {
      let img = document.createElement('img')
      img.src = src
      img.onload = () => {
        resolve(img)
      }
      img.onerror = (err) => {
        reject(err)
      }
    })
    
  }

  let showImg = (img) => {
    document.body.appendChild(img)
  }

  let showImgs = (imgs) => {
    imgs.forEach((img) => {
      document.body.appendChild(img)
    })
  }

  Promise.race([
 	loadImg('https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4224877969,2952280193&fm=26&gp=0.jpg'),
    loadImg('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586016141886&di=731198488c2a55235d4805d94005943d&imgtype=0&src=http%3A%2F%2Fp4.music.126.net%2F1tsSSkZKQqqahfKlfnnoFA%3D%3D%2F18648816720560140.jpg'),
    loadImg('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586016141887&di=1461c894cf4a8486b2d7097eaa728510&imgtype=0&src=http%3A%2F%2Fimg3.doubanio.com%2Flpic%2Fs21354179.jpg')
  ]).then((img) => {
    showImg(img)
  })
}

Promise 实现长轮询

{
  let ajax = function () {
    return new Promise ((resolve, reject) => {
      // 把setTimeout改写成具体的ajax请求
      setTimeout(()=>{
        resolve({err_code: 0})
      }, 200)
    })
  }
  let pull = () => {
    
    ajax().then((data)=>{
      if (data.err_code ===  0) {
        setTimeout(() => {
          console.log('wait')
          pull()
        }, 20)
      } else {
        console.log(data)
      }
    })
  }
  pull()
}

jquery支持promise方式的ajax

除了常用的callback的方式,jquery还支持promise方式的ajax

$.get('/data1')
      .then(function (data) {
        console.log(data)
        return $.get('/data2')
      })
      .then(function (data) {
        console.log(data)
      })
// jquery  
let pull = () => {
  $.get('/data1')
      .then(function (data) {
        if (data.err_code === 0) {
          setTimeout(()=>{
            console.log('wait')
            pull()
          }, 50)
        } else {
          console.log(data)
        }
      })
}
pull()

封装原生ajax get函数(promise版本)

function get (url, callback) {
      return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('get', url)
        xhr.onload = function () {
          // console.log(xhr.responseText)
          callback && callback(xhr.responseText)
          resolve(xhr.responseText)
        }
        xhr.onerror = function (error) {
          // console.log(error)
          reject(error)
        }
        xhr.send()
      })
    }
	// callback 调用方式
    get('/data1', function (data) {
      console.log(data)
    })
	// promise 调用方式
    get('/data1')
    .then(function (data) {
        console.log(data)
      })

mongoose 数据库查询,promise方式

// callback 方式
User.findOne({
  username: {$regex: '^a'}
}, function (err, ret) {
  if (err) {
    console.log('查询失败')
  } else {
    console.log(ret)
  }
})
// promise 方式
User.findOne({
  username: {$regex: '^a'}
})
  .then(function (ret) {
    console.log(ret)
  }, function (err) {
    console.log(err)
  })

// 先找到一个,再更新
User.findOne({
  username: {$regex: '^a'}
})
  .then(function (ret) {
    console.log(ret)
    return User.findOneAndUpdate({
        username: {$regex: /^a/}
      },{
        password: '0101001'
      })
  })
  .then(function (ret) {
    console.log('更新成功')
    console.log(ret)
  })
// 先查询,没查询到就新增
User.findOne({
    username: 'Jason'
  })
  .then(function (ret) {
    if (ret) {
      console.log('存在该用户')
    } else {
      console.log('不存在该用户,新增一个!')
      return new User({
        username: 'Jason',
        password: '123456',
        email: 'admin@admin.com'
      }).save()
    }
  })
  .then(function (ret) {
    if(ret) console.log('新增成功!')
    console.log(ret)
  }, function (err) {
    console.log('新增失败')
  })

lterator 和 for of

数组创建可迭代对象

数组可以使用for of,是因为 Array.prototype[Symbol.iterator]

{
  let arr = ['hello', 'world']
  let iter = arr[Symbol.iterator]()
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
}

为对象编写自定义 Symbol.iterator 属性

对象没有 Symbol.iterator 属性,若编写 Symbol.iterator 方法,则可以对对象使用 for of 遍历

{
  let obj = {
    start: [1, 3, 2],
    end: [7, 9, 8],
     // Symbol.iterator 属性是一个函数,实际上就是一个generator函数
    [Symbol.iterator] () {
      // let self = this
      let index = 0
      // let arr = self.start.concat(self.end)
      let arr = this.start.concat(this.end)
      let len = arr.length
      // 这个函数要返回一个对象,返回的对象中要有next方法
      return {
        // next方法应该返回一个对象,有 value 和 done 属性
        next () {
          if (index < len) {
            return {
              value: arr[index++],
              done: false
            }
          } else {
            return {
              value: arr[index++],
              done: true
            }
          }
        }
      }
    }
  }

  let iter = obj[Symbol.iterator]()

  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())

  for (let value of obj) {
    console.log(value)
  }
}

Generator

解决异步编程: 回调、Promise、Generator

Generator 的基本定义

{
  let tell = function* () {
    yield 'a'
    yield 'b'
    return 'c'
  }

  let k = tell()
  console.log(k.next())
  console.log(k.next())
  console.log(k.next())
  console.log(k.next())
  console.log(k.next())
  console.log(k.next())
  console.log(k.next())
}

设置对象的 Symbol.iterator 属性为一个Generator函数

{
  let obj = {}
  obj[Symbol.iterator] = function* () {
    yield 1
    yield 2
    yield 3
  }
  
  var iter = obj[Symbol.iterator]()
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())
  console.log(iter.next())

  for (let value of obj) {
    console.log(value)
  }
}

应用 状态机

// 状态机
{
  let state = function* () {
    while(1) {
      yield 'A'
      yield 'B'
      yield 'C'
    }
  }

  let status = state()
  console.log(status.next())
  console.log(status.next())
  console.log(status.next())
  console.log(status.next())
  console.log(status.next())
}

抽奖次数

通过Generator将抽奖逻辑和抽奖次数控制逻辑分开

{

  let draw = function (count) {
    // 抽奖逻辑
    console.log(`剩余${count}次`)
  }
  
  let residue = function* (count) {
    while (count > 0){
      count --
      yield draw(count)
      // draw(count)
    }
  }

  var star = residue(5)
  star.next()
  star.next()
  star.next()
  star.next()
  star.next()
  star.next()
  star.next()
  star.next()
  star.next()
}

Decorators

安装babel 以及 transform-decorators-legacy 插件

[Windows系统带你一步一步无脑使用babel]

npm i babel-plugin-transform-decorators-legacy --save-dev
// 修改.babelrc文件, 加入插件
{
 "presets": ["es2015"],
 "plugins": ["transform-decorators-legacy"]
}
npx babel-node es6.decorators.js // 运行js脚本

Decorators修饰器 修改对象属性为只读

{
  // 定义一个修饰器
  let readonly = function (target, name, descriptor) {
    descriptor.writable = false
    return descriptor
  }

  class Test {
    @readonly // 让time方法为只读
    time () {
      return '2020-04-05'
    }
  }

  let test = new Test()

  console.log(test.time())
  // test.time = function () { // TypeError: Cannot assign to read only property 'time' of object '#<Test>'
  //   console.log('reset time')
  // }

  let obj = {
    @readonly // 让a属性为只读
    a: 1,
    b: 2
  }
  // obj.a = 3 // TypeError: Cannot assign to read only property 'a' of object '#<Object>'
}

为类增加一个静态属性

{
  let typename = function (target, name, descriptor) {
    target.myname = 'hello'
  }

  // 为类增加一个静态属性
  @typename
  class Test {

  }
  console.log(Test)
}

有一个第三方修饰器的js库,core-decorators,可以不用自己手动写修饰器函数

日志埋点从业务中抽离出来,提高代码复用性

{
  // 把埋点系统从业务逻辑中抽离出来,提高复用性
  let log = (type) => {
    return (target, name, descriptor) => {
      // descriptor.value就是被修饰的对象的方法, 把 descriptor.value 拷贝一份,方便后面修改和调用
      let src_method = descriptor.value
      // 在执行之前输出 begin
      console.log(type, 'begin')
      // 修改被修饰的方法,在执行完毕后输出 end
      descriptor.value = (...arg) => {
        // src_method()
        // target 为实例对象,arg为方法调用时的参数列表
        src_method.apply(target, arg)
        console.log(type, 'end')
      }
    }
  }

  // 业务逻辑
  class AD {
    @log('show')
    show () {
      console.log('ad is show')
    }
    @log('click')
    click () {
      console.log('ad is click')
    }
  }

  let ad = new AD ()
  ad.show()
  ad.click()
}

模块化

node中仍不支持es6的模块化,需要 npx babel-node xxx.js

导出方

export let A = 123

export function test () {
  console.log('test')
}

export class Hello {
  test() {
    console.log('class')
  }
}
let A = 123

let test = () => {
  console.log('test')
}

class Hello {
  test () {
    console.log('class')
  }
}

export default {
  A,
  test,
  Hello
}

引入方

// import {A, test, Hello} from './es6.17.js'
import * as obj from './es6.17.js'
console.log(obj.A)
console.log(obj.test)
console.log(obj.Hello)
let hello = new obj.Hello()
hello.test()
import obj from './es6.17.js'
console.log(obj.A)
console.log(obj.test)
console.log(obj.Hello)
let hello = new obj.Hello()
hello.test()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值