2020前端面试(十五)-前端模块化,前端性能优化,js设计模式,常见编程题

点这里,欢迎关注

文章目录

一.前端模块化

1.如何理解前端模块化

1.前端模块化就是将复杂的文件变成一个个独立的模块,比如单个js文件等等。

2.分成独立的模块有利于重用(复用性)和维护(版本迭代)。

3.但这样会引来模块之间相互依赖的问题,所以有了CommonJS,AMD,CMD规范等等,以及用于js打包(编译等处理)的工具webpack

2.说一下Commonjs、AMD和CMD

参考链接

Commonjs规范AMD规范 (Asynchronous Module Definition 异步模块定义)CMD规范 (Common Module Definition 通用模块定义)
实现NodeJS的模块系统RequireJS框架
AMD 是 requireJS框架 在推广过程中对模块定义的规范化产出,所以AMD是一种标准。
SeaJS框架(淘宝)
CMD 是 seaJS框架在推广过程中对模块定义的规范化产出,CMD也是属于一种标准。
应用场景服务器端浏览器浏览器
模块加载方式同步加载。服务器端加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不需太考虑异步加载。异步加载,如果采用同步加载的方式,可能会阻止页面的加载。异步加载
使用模块输出:modual.exports,exportsAMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
AMD在某个模块加载(下载)完后会立即执行该模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行-------用户体验好,因为没有延迟
CMD推崇就近依赖,只有在用到某个模块的时候再去require
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的-------性能好,因为只有用户需要的时候才执行
优缺点优点:解决了脚本并行加载的问题
缺点:代码太过臃肿

requireJS的例子:

通过define定义模块:

define(['dependency'], function(){
    var name = 'Byron';
    function printName(){
    	console.log(name);
    }
    return {
    	printName: printName
    };
});

通过require加载模块:

require(['myModule'], function (my){
	my.printName();
}

3.ES6与Commonjs的区别:

1.commonjs

commonjs属于运行时加载,即只有在运行时才能加载第三方模块的对象,且加载的是整个对象,然后再从该对象读取对应的方法。

模块导入:

// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

模块导出:

module.exports={};
exports.xxx={}

2.ES6

自带模块化,属于编译时加载(或称静态加载),即在编译阶段就会完成模块加载,而且加载的只是该模块对应的方法。这样的话效率比commonjs的高。缺点就是没法引用 ES6 模块本身,因为它不是对象

模块导入:

// ES6模块
import { stat, exists, readFile } from 'fs';

模块导出:

export { .... };

二.前端性能优化

1.性能优化的方式:

1.减少http请求次数和资源的大小:

使用雪碧图将多张小图片合并为一张图片,然后根据定位来显示对应的元素。

字体图标代替传统的png图片,字体图标是矢量图,代码编写出来的,不仅渲染速度快,放大之后还不会变形。

利用打包工具将css和js文件进行压缩

图片懒加载,即延时加载,减少页面第一次加载时http的请求数。

不轻易使用第三方插件,能用css做的效果,不要用js做;能用js做的,不轻易使用第三方插件。

2.代码优化相关:

减少闭包的使用,因为闭包所在的上下文不会被释放。

减少dom的重绘和回流

js中避免死循环

css样式表放在顶部,script标签放在尾部。

用link代替@import,因为@import属于同步加载,而link属于异步加载。

异步加载js文件,防止页面阻塞。

3.缓存:

HTTP 协议缓存请求,本地缓存 manifest,web缓存localStorage

4.加快加载速度:DNS预解析,CDN内容分发网络

2.懒加载:

https://zhuanlan.zhihu.com/p/55311726

页面开始加载时不去发送http请求,而是放置一张占位图。
当页面加载完时,并且图片在可视区域再去请求加载图片信息。

(1)图片的懒加载和预加载的区别

预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。

懒加载:懒加载的主要目的是用作性能优化,减少请求次数。

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是延迟加载

懒加载对服务器端有一定的缓解压力作用,预加载则会增加服务器端压力。

3.什么是按需加载:

即根据需要去加载对应的资源。

当用户触发了某个动作时,才去加载需要的html,css,js或图片。

图片的按需加载也就是懒加载,当图片进行可视区域的使用才加载。

4.异步加载js的方法

注意不要把异步加载js和js执行异步任务搞混了,当加载完js后,在执行代码的过程中才会遇到异步的问题。

为什么需要异步加载js

同步加载是指浏览器在加载js文件时,会阻塞后续内容的加载。

​ 通常可以将script标签放在底部来解决阻塞的问题,但这样也存在一个问题,就是dom加载完毕后有一段时间页面虽然能看到,但是和用户的交互却很差,因此需要让一些脚本与页面异步加载。

异步加载就是指,在加载js文件,不会阻塞后续内容加载。

异步加载js文件有四种方式:

方法一:defer延时加载:

<script type="text/javascript" defer="defer"> 
alert(document.getElementById("p1").firstChild.nodeValue); 
</script> 

defer 属性规定是否对脚本执行进行延迟,直到页面加载完毕为止。

如果您的脚本不会改变文档的内容,可将 defer 属性加入到

注意:兼容所有浏览器。这种方法可以确保所有设置defer属性的脚本按顺序执行。

方法二:async:

<script type="text/javascript" src="xxxxxxx.js" async="async"></script> 

是HTML5的属性,async 属性规定一旦脚本可用,就会异步执行。

仅适用于外部脚本,注意:如果在IE中,同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。

注意:HTML5中新增的属性,支持IE9以上。这种方法不能保证脚本按顺序执行。

方法三:动态创建script标签,插入到DOM中

动态创建的srcipt标签在加载js代码的时候是异步的,加载完后会触发onload事件。

为了确保多个js文件按顺序加载,需要通过回调函数的形式进行处理

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript">
            function loadScript(file, callbackFn) {
                var script = document.createElement('script');
                script.src= file;
                script.type='text/javascript';
                // 监听onload时间,当前js文件加载完成后,再加载下一个
                script.onload = callbackFn;
                document.getElementsByTagName('head')[0].appendChild(script)
            }

            loadScript('calc1.js', function () {
                loadScript('calc2.js');
            } );
        </script>
    </head>
    <body>
    </body>
</html>

方法四:AJAX + eval

使用AJAX得到脚本内容,然后通过eval(xhr.responseText)来运行脚本,或者还是通过添加script标签script.src=xhr.responseText来运行js脚本

eval()是做什么的

https://blog.csdn.net/lxcao/article/details/52782771

作用1:把字符串参数解析成JS代码并运行,并返回执行的结果。

eval("2+3");//先解析为加法表达式,然后执行加法操作,并返回运算值。
eval("var age=10");//先解析为赋值表达式,然后执行,即初始化了一个age变量

注意:应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。

作用2:处理Ajax返回的json字符串的第二种方式

//eval()来解析JSON格式字符串的时候会将大括号{}直接解析为代码块,而不是表达式,而在代码块中不可能有name:xxx的写法的,所以会报错。所以必须使用()包起来,这样解析出来的就是表达式了。
var json="{name:'Mr.CAO',age:30}";
var jsonObj=eval("("+json+")");//将json字符串包装为一个可以执行的表达式
console.log(jsonObj);

5.游戏卡顿的原因以及解决方法:

有一个游戏叫做Flappy Bird,就是一只小鸟在飞,前面是无尽的沙漠,上下不断有钢管生成,你要躲避钢管。然后小明在玩这个游戏时候老是卡顿甚至崩溃,说出原因(3-5个)以及解决办法(3-5个):

原因可能是:

1.内存溢出问题。

2.资源过大问题。

3.资源加载问题。

4.canvas绘制频率问题

解决办法:

1.针对内存溢出问题,我们应该在钢管离开可视区域后,销毁钢管,让垃圾收集器回收钢管,因为不断生成的钢管不及时清理容易导致内存溢出游戏崩溃。

2.针对资源过大问题,我们应该选择图片文件大小更小的图片格式,比如使用webp、png格式的图片,因为绘制图片需要较大计算量。

3.针对资源加载问题,我们应该在可视区域之前就预加载好资源,如果在可视区域生成钢管的话,用户的体验就认为钢管是卡顿后才生成的,不流畅。

4.针对canvas绘制频率问题,我们应该需要知道大部分显示器刷新频率为60次/s,因此游戏的每一帧绘制间隔时间需要小于1000/60=16.7ms,才能让用户觉得不卡顿。

(注意因为这是单机游戏,所以回答与网络无关)

6.click在ios上有300ms延迟,原因及如何解决?

原因:

  • 移动端双击屏幕时会有 300ms 的判断时间,若第二次点击的时间距第一次小于了 300ms,则判断为双击事件,此时会缩放屏幕,而不是触发两次点击事件。

解决方法:

  • 方法一:粗暴型,禁止浏览器的双击事件,禁用缩放:
  • 方法二:写一个封住函数,若触摸和松开的时间小于 300ms,则立即调用回调函数

  • 方法三:使用 fastclick 插件,原理是检测到 touchend 事件后,立刻触发模拟 click 事件,并且把浏览器 300 毫秒之后真正触发的事件给阻断掉

二.js设计模式

1.单例模式:

概念:确保一个类仅有一个实例,并提供了一个访问它的全局访问点。

实现思路:创建该实例之前,检查该实例是否存在。若不存在,则创建,否则直接返回该实例。

原理:立即执行函数+闭包

实际应用:创建一个遮罩框

//规范的单例模式:
var Singleton = (function(){
    var instance;
    var CreateSingleton = function (name) {       
        if(instance) {
            return instance;
        }
        this.name = name;       
        instance = this;
        return instance;
    }
    return CreateSingleton;
})();
var a = new Singleton('a');
var b = new Singleton('b');
console.log(a===b);//true	


// 实现了一个通用的singleton包装器
var singleton = function (fn) {
    let result = null;
    return function () {
        return result || (result = fn.apply(this, arguments));
    };
};
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 实际需要的创建的单例对象通过回调函数的方式传人到 singleton 包装器中的
var createPerson = singleton(function () {
    return new Person(...arguments);
});
var person1 = createPerson("liu", 21);
var person2 = createPerson("xiao", 22);
console.log(person1 === person2); //true
console.log(person1); //Person { name: 'liu', age: 21 }

2.工厂模式:

设计一个统一的接口(工厂),然后根据对应的参数生成对应的实例对象(产品)。

实际应用:前文中new的实现体现的就是一种工厂模式;在一些封装的ajax库中使用的就是工厂模式,根据参数来决定发送get请求还是post请求。

代码见new的模拟实现部分

3.观察者模式(发布者-订阅者模式):

概念:当一个对象(发布者)被修改时,则会自动通知依赖它的对象(订阅者)。相当于建立了一套触发机制。观察者模式可以很好的实现模块之间的解耦。

实际应用:JS中的事件监听和事件触发就是经典的观察者模式

let observer = {
    // 订阅集合
    subscribes: [],
    // 订阅
    subscribe: function (type, fn) {
        //   判断订阅集合中是否存在订阅的对象
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        this.subscribes[type].push(fn);
    },
    //   发布消息
    publish: function (type, info) {
        let fns = this.subscribes[type];
        //   判断发布的消息类型是否存在,以及判断该消息所在的订阅回调函数是否为空
        if (!fns || !fns.length) {
            return;
        }
        //   挨个处理对应的订阅回调函数
        for (var i = 0; i < fns.length; i++) {
            fns[i].call(this, info);
        }
    },
    //   删除订阅
    remove: function (type, fn) {
        //   若未提供移除的订阅类型,则删除所有的订阅信息
        if (!type) {
            this.subscribes = [];
            return;
        }
        var fns = this.subscribes[type];
        // 如果没有提供对应的订阅回调,则删除该类型下的所有订阅回调
        if (!fn) {
            fns.length = 0;
            return;
        }
        //   如果指定了具体的订阅回调,则删除该类型下具体的回调
        for (var i = 0; i < fns.length; i++) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    },
};
// 订阅adele的消息
observer.subscribe("adele", function (value) {
    console.log("接收到的adele的新消息是:", value);
});
function fn(value) {
    console.log("接收到的王菲的新消息是:", value);
}
// 订阅王菲的消息
observer.subscribe("wangfei", fn);
// 发布adele和王菲的消息
observer.publish("adele", "这是adele的最新消息");
observer.publish("wangfei", "这是王菲的最新消息");
// 输出:
// 接收到的adele的新消息是: 这是adele的最新消息
// 接收到的王菲的新消息是: 这是王菲的最新消息
console.log("-------------------");
observer.remove("wangfei", fn);
observer.publish("wangfei", "这是王菲的最新消息");
// 输出:
// 空

4.装饰者模式:

动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

function Person() {}
Person.prototype.skill = function () {
    console.log("数学");
};
// 创建装饰类(装饰器)
function MusicDecorator(person) {
    this.person = person;
}
// 装饰了音乐这个技能
MusicDecorator.prototype.skill = function () {
    this.person.skill();
    console.log("音乐");
};
// 创建装饰类(装饰器)
function RunDecorator(person) {
    this.person = person;
}
// 装饰了跑步这个技能
RunDecorator.prototype.skill = function () {
    this.person.skill();
    console.log("跑步");
};
let person = new Person();
let musicMan = new MusicDecorator(person);
let runMan = new RunDecorator(musicMan);
runMan.skill(); //数学 音乐 跑步

三. 利用js实现的功能

1.js监听对象属性的改变

这个问题也可以理解为如何实现MVVM库/框架的数据绑定。

常见的数据绑定的实现有 1.基于ES5gettersetter; 2.ES6中添加的Proxy;3.脏值检测

方法一:ES5中的getter和setter:

利用Object.defineProperty为对象的属性添加访问器getter/setter

var obj5 = {};
Object.defineProperty(obj5, "a", {
    get() {
        return a;
    },
    set(value) {
        a = value;
        // 需要触发的渲染函数写在这里,这样在属性改变的时候就不需要手动调用渲染函数了
    },
});
obj5.a = "liu";
console.log(obj5.a); //liu

方法二:在ES6中可以通过Proxy来实现:

原理是利用proxy代理对象拦截对对象属性的赋值操作。

// 利用proxy实现监听js对象属性变化的功能
// 下面的例子是在监听object对象中message属性的变化
let objct = {
  message: "liu",
};
let proxy2 = new Proxy(objct, {
  set(target, prop, value) {
    if (prop == "message") {
      console.log("这里可以执行渲染相关的函数");
    }
  },
});
proxy2.message = "liu2"; //这里可以执行渲染相关的函数
proxy2.message = "liu2"; //这里可以执行渲染相关的函数

方法三:脏值检测

2.js怎么控制一次加载一张图片,加载完后再加载下一张

通过Image构造函数创建img标签,动态加载图片,加载成功后会触发onload回调函数,在该函数中可以加载下一次图片。

<body>
    <script>
        let images = ["./avatar1.jpg", "./avatar2.jpg", "./avatar3.jpg"];
        function loadImage() {
            if (images.length != 0) {
                let imageSrc = images.shift();
                let image = new Image();
                image.style.width = "200px";
                image.style.height = "200px";
                image.src = imageSrc;
                image.onload = function () {
                    document.body.appendChild(image);
                    setTimeout(() => {
                        loadImage();
                    }, 1000);
                };
            }
        }
        loadImage();
    </script>
</body>

3.如何实现sleep的效果(es5、es6)

题目解释:让当前的代码休眠一段时间再执行后面的代码。

不能使用setTimeout,因为setTimeout属于异步任务,后面的同步代码会立即执行。

方法一:(ES5)while循环的方式

function sleep(sleepTime) {
  var start = Date.now();
  var end = start + sleepTime;
  while (Date.now() < end) {}
}
console.log("这里是前面的代码");
sleep(2000);
console.log("这里是后续的代码");

方式二:(ES6)promise

将后续的同步代码放在then方法指定的回调函数中

function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}
console.log("这里是sleep前的同步代码");
sleep(2000).then(function () {
    console.log("这里是后续的同步代码");
});

方式三:(ES6) generator生成器

利用yield表达式+next来实现,原理同直接使用promise是一样的,核心就是返回一个promise,且这个Promise在指定的时间后才会调用指定的回调函数。

function* sleep(time) {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}
console.log("这里是前面的同步代码");
sleep(2000)
    .next()
    .value.then(function () {
    console.log("这里是后续的同步代码");
});

方法四:(ES6) async语法

其实跟generator是一样的,只是这样便于阅读一些

//写法1:
async function sleep(time) {
  let rs = await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
  console.log("这里是后续的代码");
}
console.log("这里是前面的代码");
sleep(2000);


//写法2:
async function sleep(time) {
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}
console.log("这里是前面的代码");
sleep(2000).then(function () {
    console.log("这里是后续的代码");
});

方法一的缺点:容易造成死循环

后面三种方法的缺点:后续的同步代码必须写在回调函数中。要是按照这种做法的话还不如直接写在setTimeout中,反正都是将想要后续的同步代码放在了异步任务中执行。。。

4.如何实现点击下载图片

  • 在a标签中添加download属性
<a href="1.png" download="重命名.png">下载图片</a>
  • 使用js实现点击下载图片的功能
function downloadImage(url){
    var a=document.createElement('a');
    a.download="默认图片名称.png"
    a.href=url;
    a.click();
}

四.职业修养

作为前端开发,如果遇到资源无法加载,会是什么问题,如何解决:

首先会打开开发者工具看报错情况,根据http状态码来确认是服务器还是客户端的错误,然后再具体问题来分析。

五.编程题

1.关于游戏中打怪,回复血量,积蓄能量(sleep一会儿)

题目描述:

(1)Hero(“37er”);执行结果为 Hi! This is 37er

(2)Hero(“37er”).kill(1).recover(30);执行结果为 Hi! This is 37er Kill 1 bug Recover 30 bloods

(3)Hero(“37er”).sleep(10).kill(2)执行结果为 Hi! This is 37er //等待10s后 Kill 2 bugs //注意为bugs (双斜线后的为提示信息,不需要打印)

function Hero(name) {
    var obj = new Object();
    obj.name = name; //角色名字
    console.log(`Hi! This is ${name}`);
    obj.time = 0; //延时发起进攻的的时间
    obj.bloods = 100; //自身的血量
    obj.kill = function (bugs) {
        if (bugs > 1) {
            setTimeout(() => {
                console.log(`Kill ${bugs} bugs`);
            }, this.time * 1000);
        } else {
            console.log(`Kill ${bugs} bug`);
        }
        return this;
    };
    obj.recover = function (bloods) {
        console.log(`Recover ${bloods} bloods `);
        this.bloods += bloods;
        return this;
    };
    obj.sleep = function (time) {
        this.time = time;
        return this;
    };
    return obj;
}
Hero("37er"); //Hi! This is 37er
Hero("37er").kill(1).recover(30); //Hi! This is 37er   Kill 1 bug   Recover 30 bloods
Hero("37er").sleep(10).kill(2); //Hi! This is 37er  Kill 2 bugs

2.写一个函数,第一秒打印1,第二秒打印2:

方法一:ES6中使用let+块级作用域

function print(num) {
  for (let i = 0; i < num; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  }
}
print(5);

方法二:ES5中使用立即执行函数形成闭包模拟块级作用域,开辟独立的作用域

function print(num) {
  for (var i = 0; i < num; i++) {
    (function (i) {
      setTimeout(() => {
        console.log(i);
      }, i * 1000);
    })(i);
  }
}
print(5);

3.正则表达式相关

匹配01-12的字符:

let regExp = /^0[1-9]|1[0-2]$/;
//注意:这里需要将数字转换为字符串再传入
console.log(regExp.test("06")); //true
console.log(regExp.test("12")); //true
console.log(regExp.test("13")); //false
console.log(regExp.test("6")); //true

匹配长度为6-13位的数字(qq号码):

//注意:精确匹配
var regExp = /^[1-9]\d{5,12}$/;
console.log(regExp.test("123456")); //true
console.log(regExp.test("12345")); //false
console.log(regExp.test("023456")); //false
console.log(regExp.test("12345678901111")); //false

匹配这种形式的座机号: 010-12345678 0530-1234567

//注意:精确匹配
var regExp = /^(\d{3}-\d{8})|(\d{4}-\d{7})$/;
console.log(regExp.test("010-12345678")); //true
console.log(regExp.test("0101-12345678")); //false
console.log(regExp.test("0101-1234567")); //true
console.log(regExp.test("0101-123456781")); //false

去掉字符串中的数字:

//注意:全局匹配
var str = "adadas321413sas";
console.log(str.replace(/\d/g, "")); //adadassas

去除字符串首尾空格:

console.log("  asbsbas   ".trim())


//trim()的实现原理:正则表达式
String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, '');
};

var str = "     #id div.class  ";
console.log(str.replace(/^\s+|\s+$/, "")); //#id div.class
//错误示例:
console.log(str.replace(/^\s|\s+$/, "")); //    #id div.class

将border-left-top转换成驼峰形式:

//方法一:正则表达式替换
let str = "border-left-top";
console.log(
  str.replace(/-\w/g, function (match) {
    return match.slice(1).toUpperCase();
  })
); //borderLeftTop

//方法二:转换为数组切割后再拼接
function toCamel(str) {
  let arr = str.split("-");
  let rs = arr[0];
  for (let i = 1; i < arr.length; i++) {
    rs += arr[i].substring(0, 1).toUpperCase() + arr[i].substring(1);
  }
  return rs;
}
console.log(toCamel("border-left-top")); //borderLeftTop

⭐️ 模板字符串的替换:

<body>
    <div id="app">
        <h1>{{name}}</h1>
        <h2>{{age}}</h2>
    </div>
</body>
<script>
    let obj = {
        name: "liu",
        age: 21,
    };
    let text = app.innerHTML;
    console.log(typeof text);
    let newText = text.replace(/\{\{(.*?)\}\}/g, function (match, $1) {
        return obj[$1];
    });
    console.log(newText);
    app.innerHTML = newText;
</script>

4.计算一年中有多少周?

// 判断是否为闰年,能被4整除且不能被100整除,或者能被400整除的年份是润年
function isLeapYear(year) {
    if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
        return true;
    }
    return false;
}
const total = isLeapYear(2020) ? 366 : 365;
// 获取这一年前面几天未参与到一个完整的周的天数
const front = 7 - new Date("2020-1-1").getDay() + 1;
// 获取这一年后面几天未参与到一个完整的周的天数
const end = new Date("2020-12-31").getDay();
console.log(front, end);
const weeks = (total - front - end) / 7;
console.log(weeks);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值