JavaScript性能优化

JavaScript性能优化 如何编写高性能的JavaScript
  • 性能优化是不可避免的
  • 哪些内容可以看作是性能优化
  • 无所不在的前端性能优化
本阶段的核心是JavaScript语言的优化
  • 内存管理
  • 垃圾回收与常见的GC算法
  • V8引擎的垃圾回收
  • Performance工具(垃圾回收的监控)
  • 代码优化实例
JavaScript内存管理(Memory Management)
  • 内存: 由可读写的单元组成, 表示一片可操作空间
  • 管理: 认为的去操作一片空间的申请、使用和释放
  • 内存管理: 开发者主动申请空间、使用空间、释放空间
  • 管理流程: 申请-使用-释放
// 申请空间
let obj = {}
// 使用空间
obj.name = 'liuchao'
// 释放空间
obj = null
JavaScript中的垃圾回收
  • JavaScript中内存管理是自动的
  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾
可达对象
  • 可达对象: 可以访问到的对象(引用、作用域链)
  • 可达的标准就是从根出发时候能够被找到
  • JavaScript中的跟可以理解为是全局变量对象
let obj = {name: 'liuchao'}

let ali = obj 

obj = null//ali变量还是有值的
JavaScript中的引用与可达
function objGroup (obj1, obj2){
    obj1.next = obj2 
    obj2.prev = obj1 
    return {
        o1: obj1,
        o2: obj2
    }
}
let obj = objGroup({name: 'obj1'}, {name: 'obj2'})

console.log(obj)



const user1 = {age: 1}
const user2 = {age: 2}
const user3 = {age: 3}

const nameList = [user1.name, user2.name, user3.name, ]

// function fn(){
//     num1 = 1
//     num2 = 2
// }
function fn(){
    const num1 = 1
    const num2 = 2
}
fn()
GC算法介绍
  • GC就是垃圾回收机制的简写
  • GC可以找到内存中的垃圾、并释放和回收空间
    • 程序中不再需要使用的对象
    • 程序中不能在访问到的对象
  • 算法就是工作时查找和回收所遵循的规则
  • 常见GC算法
    • 引用计数
    • 标记清除
    • 标记整理
    • 分代回收
引用计数算法实现原理
  • 核心思想: 设置引用数, 判断当前引用数是否为0
  • 引用计数器(被弃用的原因, 性能不好)
  • 引用关系改变时修改引用数字
  • 引用数字为0时立即回收
  • 引用计数的优点
    • 发现垃圾时立即回收
    • 减少程序卡顿时间
    • 最大限度减少程序暂停(内存即将占满,立即清除)
  • 引用计数的缺点
    • 无法回收循环引用的对象
    • 时间(资源)开销大
// circular reference 对象循环引用

function fn(){
    const obj1 = {}
    const obj2 = {}
    obj1.name = obj2
    obj2.name = obj1
    return 'A circular reference.'
}

fn()

标记清除算法的实现原理
  • 核心思想: 分为标记和清除两个阶段
  • 遍历所有对象找标记活动对象(对象、子对象递归标记)
  • 遍历所有对象清除没有标记对象(也会清除未清除对象的标记)
  • 回收相应的空间(回收的空间放到空闲链表上方便后续使用)
  • 标记清除算法优点
    • 解决对象循环引用
  • 标记清除算法缺点
    • 不会立即回收垃圾对象
    • 空间碎片化: 回收空间地址不连续(分散在各个角落)
标记整理算的实现原理
  • 标记整理可以看作是标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段会执行整理, 移动对象位置(为了地址上连续)
  • 不会立即回收垃圾对象
常见GC算法总结
  • 引用计数的优点
    • 发现垃圾时立即回收
    • 减少程序卡顿时间
    • 最大限度减少程序暂停(内存即将占满,立即清除)
  • 引用计数的缺点
    • 无法回收循环引用的对象
    • 时间(资源)开销大
  • 标记清除算法优点
    • 解决对象循环引用
  • 标记清除算法缺点
    • 不会立即回收垃圾对象
    • 空间碎片化: 回收空间地址不连续(分散在各个角落)
  • 标记整理的优点
    • 减少碎片化空间
  • 标记整理的缺点点
    • 不会立即回收垃圾对象
认识V8
  • V8是一款主流的JavaScript执行引擎
  • V8采用即时编译
    • V8快: 1. 优秀内存管理机制 2. V8采用即时编译(原先源码转换成字节码, 然后字节码转换成机器码执行; 即时编译: 源码转换成机器码直接执行)
  • V8内存设限(64X: 1.5G; 32X: 800M)
    • V8本身为了浏览器而制造, 现有大小对网页应用来说足够使用了
    • V8内部的垃圾回收机制决定了再用此设置是合理的
      • 垃圾内存达到1.5G的时候使用增量标记进行垃圾回收是50ms,如果使用非增量标记进行垃圾回收需要1s
V8垃圾回收策略
  • 采用分代回收的思想
  • 内存分为新生代、老生代
  • 针对不同对象采用不同GC算法
    • 分代回收
    • 空间复制
    • 标记清除
    • 标记整理
    • 标记增量
V8如何回收新生代对象
  • V8内存分配
    • V8内存空间一分为二
    • 小空间用于存储新生代对象(32M(64X)|16M(32X))
    • 新生代对象指存活时间比较短的对象(eq: 局部变量)
  • 新生代对象回收实现
    • 回收过程采用复制算法 + 标记整理
    • 新生代内存去分为二个等大小空间
    • 使用空间为From, 空闲空间为To
    • 活动对象存储于From空间
    • 标记整理后将活动对象拷贝至To
    • From与To交换空间, 完成释放
  • 新生代对象回收实现—说明
    • 拷贝过程中可能出现晋升
    • 晋升就是将新生代对象移动至老生代
      • 一轮GC还存活的新生代需要晋升
      • To空间的使用率超过25%(整体移动至老生代: From和To会交换)
V8如何回收老生代对象
  • 老生代对象存放在右侧老生代区域
  • 内存限制(64位操作系统1.4G, 32位操作系统700M)
  • 老生代对象就是指存活时间较长的对象(闭包等)
  • 老生代对象回收实现
    • 主要采用标记清除、标记整理、增量标记算法
    • 首先使用标记清除完成垃圾空间的回收
    • 采用标记整理进行空间优化(晋升的时候并且老生代存储空间不足以存放新生代移动过来的对象)
    • 采用增量标记进行效率优化
新生代与老生代回收的细节对比
  • 新生代区域垃圾回收使用空间换时间(复制算法)
  • 老生代区域篮机回收不适合复制算法(空间大浪费奢侈、对象数据比较多消耗时间多)
  • 标记增量如何优化垃圾回收
    • 垃圾回收会终止程序执行
    • 标记增量: 整个垃圾回收过程拆分成多个小步,替代整个垃圾回收, 实现程序执行和垃圾回收交替执行
V8垃圾回收总结
  • V8是一款主流的JavaScript执行引擎
  • V8内存设限(64X: 1.5G; 32X: 800M)
  • 采用分代回收的思想
  • 内存分为新生代、老生代
  • 针对不同对象采用不同GC算法
Performance工具介绍

为什么使用Performance

  • GC的目的是为了实现内存空间的良性循环
  • 良性循环的基石是合理使用
  • 时刻关注才能确定是否合理
  • Performance提供多种监控方式
  • 通过Performance时刻监控内存
Performance使用步骤
  • 打开浏览器输入牧鞭网址
  • 进入开发人员工具面板, 选择性能
  • 开启录制功能, 访问具体页面
  • 执行用户行为, 一段时间后停止录制
  • 分析界面中记录的内存信息
内存问题的外在体现
  • 频繁垃圾回收: 页面出现延迟加载或经常性暂停(网络正常下)
  • 内存膨胀: 页面持续性出现糟糕的性能(网络正常下)
  • 内存泄漏: 页面性能随时间延长越来越差(网络正常下)
监控内存的几种方式
  • 内存泄漏: 内存使用持续升高
  • 内存膨胀: 在多数设备上都存在性能问题(主流设备上测试)
  • 频繁垃圾回收: 通过内存变化图进行分析
  • 方式
    • 浏览器任务管理器
    • Timeline时序图记录
    • 堆快照查找分离DOM
    • 判断是否存在频繁的垃圾回收
浏览器任务管理器监控内存
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>浏览器任务管理器监控内存变化</title>
</head>
<body>
    <button id='btn'>Add</button>
    <script>
        const oBtn = document.getElementById('btn')
        oBtn.onclick = function (){
            let arrList = new Array(1000000)
        }
    </script>
</body>
</html>
Timeline记录内存
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Timeline记录内存</title>
</head>
<body>
    <button id='btn'>Add</button>
    <script>
        // 1. 创建大量DOM节点模拟内存消耗
        // 2. DOM节点不够,数组配合其他方法, 形成非常长的字符串,模拟内存消耗
            
        const arrList = []
        function test(){
            for(let i = 0; i < 100000;i++){
                document.body.appendChild(document.createElement('p'))
            }
            arrList.push(new Array(1000000).join('x'))
        }
        document.getElementById('btn').addEventListener('click', test)
    </script>
</body>
</html>
堆快照查找分离DOM
  • 界面元素存活在DOM树上
  • 垃圾对象时的DOM节点(从DOM树上脱离, 并且程序中无引用)
  • 分离状态的DOM节点(分离节点: 从DOM树上脱离, 但是程序中有引用; 页面上看不见, 但是占据内存, 导致内存泄漏)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>堆快照查找分离DOM</title>
</head>
<body>
    <button id='btn'>Add</button>
    <script>
        // 1. 创建大量DOM节点模拟内存消耗
        var tmpELe 
        function  fn() {
            var ul = document.createElement('ul')
            for(var i = 0;i < 10;i++){
                var li = document.createElement("li")
                ul.appendChild(li)
            }
            tmpELe = ul
            tmpELe = null
        }
        document.getElementById('btn').addEventListener('click', fn)
    </script>
</body>
</html>
判断是否存在频繁的垃圾回收
  • 原因
    • GC工作时应用程序时停止的
    • 频繁且过长的GC会导致应用假死
    • 用户使用中感知应用卡顿
  • 方法
    • Timeline中频繁的上升和下降
    • 任务管理器中数据频繁的增大减小
Performance使用总结
  • Performance使用步骤
  • 内存问题的相关分析
  • Performance时序图监控内存变化
  • 浏览器任务管理器监控内存
  • 堆快照查找分离DOM
代码优化介绍
  • 如何精准测试JavaScript性能
    • 本质上就是采集大量的执行样本进行数学统计和分析
    • 使用基于Benchmark.js的https://jsperf.com/ 完成
Jsperf使用流程
  • 使用GitHub账号登陆
  • 填写个人信息(非必须)
  • 填写详细的测试用例信息(title、slug)
  • 填写准备代码(DOM操作室经常使用)
  • 填写必要的setup和teardown代码
  • 填写测试代码片段
慎用全局变量
  • 全局变量定义在全局执行上下文, 是所有作用域链的顶端(时间长)
  • 全局执行上下文一直存在于上下文执行栈, 知道程序退出(GC工作不力, 降低内存使用)
  • 如果某个局部作用域出现了同名变量则护额遮蔽或者污染全局
    // 全局变量
    var i, str = ''
    for(i = 0; i < 100; i++ ){
        str += i
    }

    for(let i = 0; i < 100; i++ ){
        let str = ''
        str += i
    }
    // 局部变量有很大性能提升
    // Jsperf中查看结果 
缓存全局变量
  • 将使用中无法避免的全局变量缓存到局部
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>缓存全局变量</title>
</head>
<body>
    <input type="button" value='btn' id='btn1'>
    <input type="button" value='btn' id='btn2'>
    <input type="button" value='btn' id='btn3'>
    <p>111</p>
    <input type="button" value='btn' id='btn4'>
    <input type="button" value='btn' id='btn5'>
    <p>222</p>
    <input type="button" value='btn' id='btn6'>
    <input type="button" value='btn' id='btn7'>
    <input type="button" value='btn' id='btn8'>
    <p>33333</p>
    <input type="button" value='btn' id='btn9'>
    <input type="button" value='btn' id='btn10'>
    <script>
        function getBtn() {
            let oBtn1 = document.getElementById('btn1')
            let oBtn3 = document.getElementById('btn3')
            let oBtn5 = document.getElementById('btn5')
            let oBtn7 = document.getElementById('btn7')
            let oBtn9 = document.getElementById('btn9')
        }
        function getBtn2() {
            let obj = document
            let oBtn1 = obj.getElementById('btn1')
            let oBtn3 = obj.getElementById('btn3')
            let oBtn5 = obj.getElementById('btn5')
            let oBtn7 = obj.getElementById('btn7')
            let oBtn9 = obj.getElementById('btn9')
        }
        // Jsperf中查看结果
    </script>
</body>
</html>
通过原型新增方法
  • 在原型对象上新增实例对象需要的方法
var fn1= function(){
    this.foo = function(){
        console.log(11111)
    }
}
let f1 = new fn1()

var fn2 = function(){}
fn2.prototype.foo = function(){
    console.log(1111)
}
let f2 = new fn2()

// Jsperf中查看运算速度
避开闭包陷阱
  • 闭包特点
    • 外部具有指向内部的引用
    • 在‘外’部作用域访问‘内’部作用域的数据
  • 闭包
    • 闭包是一种强大的语法
    • 闭包使用不当很容易出现内存泄漏
    • 不要为了闭包而闭包
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>闭包陷阱</title>
</head>
<body>
    <button id="btn">btn</button>
    <script>
        // function foo() {
        //     var el = document.getElementById('btn')
        //     el.onclick = function () {
        //         console.log(el.id)
        //     }
        // }
        // foo()

        // 闭包优化
        function foo() {
            var el = document.getElementById('btn')
            el.onclick = function () {
                console.log(el)
                console.log(el, el.id)
            }
            el = null
        }
        foo()
    </script>
</body>
</html>
避免属性访问方法使用
  • JavaScript中的面向对象
    • JS不需要属性的访问方法, 所有属性都是外部可见的
    • 书用属性访问方法只会增加一层重定义, 没有访问的控制力
function Person (){
    this.name = 'Person'
    this.age = 19
    this.getAge = function(){
        return this.age
    }
}

const p1 = new Person()
const a = p2.getAge()

function Person1 (){
    this.name = 'Person'
    this.age = 19
}
const p2 = new Person1()
const b = p2.age
// Jsperf上查看运行速度
For循环优化 length提取
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>For循环优化</title>
</head>
<body>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <p class="btn"></p>
    <script>
        var aBtns = document.getElementsByClassName('btn')
        for(var i = 0; i < aBtns.length; i++){
            console.log(i)
        }
        for(var i = 0, len = aBtns.length; i < len; i++){
            console.log(i)
        }
        // Jsperf上查看运行速度
    </script>
</body>
</html>
采用最优循环方式
// 采用最优循环方式

const arr = new Array(1, 2, 3, 4, 5)

arr.forEach(function(item){
    console.log(item)
})
for(var i = arr.length; i; i--){
    console.log(arr[i])
}
for(var i in arr){
    console.log(arr[i])
}
// Jsperf中查看运行速度

节点添加优化
  • 节点添加操作必然会有回流和重绘
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>优化节点添加</title>
</head>
<body>
    <script>
        for(var i = 0; i < 100; i++){
            var oP = document.createElement('p')
            oP.innerHTML = i
            document.body.appendChild(oP)
        }

        const fragEle = document.createDocumentFragment()
        for(var i = 0; i < 100; i++){
            var oP = document.createElement('p')
            oP.innerHTML = i

            fragEle.appendChild(oP)
        }
        document.body.appendChild(fragEle)
        // Jsperf 查看运行速度
    </script>
</body>
</html>
克隆优化节点操作
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>克隆优化节点操作</title>
</head>
<body>
    <p id="box1">old</p>
    <script>
        // 创建新节点时 : 先clone一个已有的节点, 再在clone节点上作修改
        for(var i = 0; i< 5; i++){
            var oP = document.createElement("p")
            oP.innerHTML = i
            document.body.appendChild(oP)
        }
        var oldP = document.getElementById('box1')
        for(var i = 0; i< 5; i++){
            var newP = oldP.cloneNode(false)
            newP.innerHTML = i
            document.body.appendChild(newP)
        }
        // Jsperf 查看运行速度
    </script>
</body>
</html>
直接量替换Object操作
var a = [1, 2, 3]
var a1 = new Array(3)
a1[0] = 1
a1[1] = 2
a1[2] = 3

// Jsperf中查看运算速度
JavaScript性能提升2 空间换时间 或者 时间换空间
JSBench的使用(JSBench.me)
堆栈中代码执行流程
let a = 10
function foo(b){
    let a = 2 
    function baz(c){
        console.log(a+b+c)
    }
    return baz
}
let fn = foo(2)
fn(3)//=> 7 存在闭包 导致函数foo作用域没有被释放 


// 减少判断层级  
减少判断层级
// function doSome(part, chapter){
//     const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
//     if(part){
//         if(parts.includes(part)){
//             console.log('属于前端课程')
//             if(chapter > 5){
//                 console.log('需要提供Vip身份')
//             }
//         }
//     }else{
//         console.log('请确认模块信息')
//     }
// }
// doSome('ES2016', 6)


function doSome(part, chapter){
    const parts = ['ES2016', '工程化', 'Vue', 'React', 'Node']
    if(!part){
        console.log('请确认模块信息')
        return
    }
    if(!parts.includes(part)) return
    console.log('属于前端课程')
    if(chapter > 5){
        console.log('需要提供Vip身份')
    }
}
doSome('ES2016', 6)
减少作用域链查找层级
// var name = 'foo'
// function foo(){
//     name = 'foo666'//全局的name变量
//     function baz(){
//         var age = 39
//         console.log(age)
//         console.log(name)
//     }
//     baz()
// }
// foo()


var name = 'foo'
function foo(){
    var name = 'foo666'//全局的name变量
    function baz(){
        var age = 39
        console.log(age)
        console.log(name)
    }
    baz()
}
foo()
减少数据读取次数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>减少数据读取次数</title>
</head>
<body>
    <div id="skip" class="skip"></div>
    <script>
        var oBox = document.getElementById('skip')
        // function hasEle(ele, cls){
        //     return ele.className == cls
        // }
        function hasEle(ele, cls){
            var clsname = ele.className
            return clsname == cls
        }
        console.log(hasEle(oBox, 'skip'))
    </script>
</body>
</html>
字面量与构造式
// let test = () => {
//     let obj = new Object()
//     obj.name = 'liuchao'
//     obj.age = 39
//     obj.slogan = '我为前端而活'
//     return obj
// }
// let test = () => {
//     let obj = {
//         name : 'liuchao',
//         age : 39,
//         slogan : '我为前端而活'
//     }
//     return obj
// }
// console.log(test())




// ----------------------
var str1 = '我为前端而活'
var str2 = new String('我为前端而活')
console.log(str1)
console.log(str2)
减少循环体活动
// var test = () => {
//     var i 
//     var arr = ['liuchao', 39, '我为前端而活']
//     for( i =0;i<arr.length;i++){
//         console.log(arr[i])
//     }
// }
// var test = () => {
//     var i 
//     var arr = ['liuchao', 39, '我为前端而活']
//     var len = arr.length
//     for( i =0;i<len;i++){
//         console.log(arr[i])
//     }
// }
var test = () => {
    var arr = ['liuchao', 39, '我为前端而活']
    var len = arr.length
    while(len--){
        console.log(arr[len])
    }
}
test()
减少声明及语句数–词法分析消耗时间
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>减少声明及语句数-词法分析消耗时间</title>
</head>
<body>
    <div id="box" style="width: 100px;height: 200px;"></div>
    <script>
        var oBox = document.getElementById('box')
        // var test = function (ele){
        //     let w = ele.offsetWidth
        //     let h = ele.offsetHeight
        //     return w * h
        // }
        // var test = function (ele){
        //     return ele.offsetWidth * ele.offsetHeight
        // }
        // console.log(test(oBox))

        // var test = () => {
        //     var name = 'liucaho'
        //     var age = 39
        //     var slogan = '我为前端而活'
        //     return name + age + slogan
        // }
        var test = () => {
            var name = 'liucaho',
                age = 39,
                slogan = '我为前端而活'
            return name + age + slogan
        }
        console.log(test())
    </script>
</body>
</html>
惰性函数与性能
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>惰性函数与性能</title>
</head>
<body>
    <button id="btn">点击</button>
    <script>
        var oBtn = document.getElementById('btn')
        function foo(){
            console.log(this)
        }
        // function addEvent (obj, type, fn){
        //     if(obj.addEventListerer){
        //         obj.addEventListerer(type, fn, false)
        //     }else if(obj.attachEvent){
        //         obj.attachEvent('on' + type, fn)
        //     }else{
        //         obj['on' + type ] = fn
        //     }
        // }
        function addEvent (obj, type, fn){
            if(obj.addEventListerer){
                addEvent = obj.addEventListerer(type, fn, false)
            }else if(obj.attachEvent){
                addEvent = obj.attachEvent('on' + type, fn)
            }else{
                addEvent = obj['on' + type ] = fn
            }
            return addEvent
        }
        addEvent(oBtn, 'click', foo)
    </script>
</body>
</html>
采用事件委托
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用事件委托</title>
</head>
<body>
    <ul id="ul">
        <li>liuchao</li>
        <li>28</li>
        <li>male</li>
        <li>我为前端而活</li>
    </ul>
    <script>
        var list = document.querySelectorAll('li')
        // function showTxt(ev){
        //     console.log(ev.target.innerHTML)
        // }
        // for(let item of list){
        //     item.onclick = showTxt
        // }
        var oUl = document.getElementById('ul')
        oUl.addEventListener('click', showTxt, false)
        function showTxt(ev){
            var obj = ev.target 
            if(obj.nodeName.toLowerCase() === 'li'){
                console.log(ev.target.innerHTML)
            }
        }
    </script>
</body>
</html>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值