js面试题

1. Ajax

定义和用法:

AJAX用来与后端进行数据交互, 能够在不刷新页面的情况下,局部更新网页的内容。ajax出现之前的网页,如果需要更新页面内容,必须刷新整个页面。

优点:

1.减轻服务器压力, 按需获取数据, 最大程度的减少冗余请求。

2.异步通信, 局部刷新页面, 减少用户等待时间, 带来更好的用户体验。

3.促进了页面和数据的分离。

缺点:

1.Ajax对外暴露了数据接口, 容易造成一些安全问题,如:sql注入。

2.不能使用浏览器前进后退按钮

AJAX的工作原理:

1.创建XMLHttpRequest对象

        var ajax = new XMLHttpRequest();

2.初始化请求:设置请求的类型、请求路径、是否异步(默认异步)

        ajax.open('GET', url, true);

3.设置请求头的内容编码类型

        ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

4.发送请求

        ajax.send(null);

        // ajax.send("username=杨&pwd=123456"); // post

5.接收服务器响应数据

        ajax.onreadystatechange = function () {

                if (ajax.readyState == 4 && (ajax.status == 200 || ajax.status == 304)) {

                        // …

                }

        };

2. 什么是跨域,如何实现跨域访问?

跨域是浏览器的同源策略限制,只有在同源的情况下,才允许ajax请求成功。协议、ip、端口任何一个不同,就是不同源,就会造成跨域。

解决跨越:

(1)JSONP跨域:利用script标签的src属性不受浏览器同源策略的限制,传入本地回调方法,服务器返回结果时回调。

(2)跨域资源共享(CORS): 在服务端设置响应头信息,允许浏览器跨域访问。

(3)反向代理

(4)iframe跨域

CORS与JSONP对比:

        a、JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。

        b、使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。

        c、JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。

3. get和post的区别,何时使用post?

(1) get是从服务器上获取数据,post是向服务器发送数据。

(2) get是把参数添加到URL中, 在URL中可以看到参数。post把参数放置在HTTP包的body体中, 在路径中看不到, 相对于get更安全。

(3)get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般不受限制。

4.什么是"use strict"?它的好处和坏处分别是什么?

在代码中使用"use strict",代码将按照严格模式解析,如:

1)变量必须声明后使用

2)同一作用域中不能有同名变量

3)增加了一些保留字(比如protected、static和interface)

4)禁止this指向全局对象

5)不能使用arguments.callee,不能使用arguments.caller,不能使用fn.caller

6)fn.arguments不能获取函数调用的堆栈

7)不能删除变量delete prop,只能删除属性delete global[prop]

好处:

        消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

        消除代码运行的一些不安全之处,保证代码运行的安全;

        提高编译器效率,增加运行速度;

        为未来新版本的Javascript做好铺垫。

坏处:严格模式下,一些代码可能会出现问题。

5.拖拽会用到哪些事件?

ondragstart: 拖拽开始, 执行一次。

ondrag: 拖拽期间在被拖拽元素上连续触发

ondragend: 拖拽结束, 执行一次。

ondragenter: 拖拽进入, 执行一次。

ondragover: 悬停于目标盒子之上,连续触发。

ondragleave: 拖拽离开, 执行一次。

ondrop: 在一个拖动过程中,释放鼠标键时触发此事件, 该事件绑给document, 需要阻止document.ondragover的默认事件。

6.谈谈你对闭包的理解?

(1)闭包可以理解为函数嵌套函数, 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免变量污染,缺点是会常驻内存,延长变量的生命周期,使用不当很容易造成内存泄露。

(2)闭包的三个特性:

        a、函数嵌套函数

        b、内部函数可以使用外部函数的参数和变量

        c、延长参数和变量的生命周期, 容易造成内存泄漏

7.谈谈你this对象的理解?

1.在一般函数方法中, this指代全局对象window。

2.作为对象方法调用, this指代上级对象。

3.作为构造函数调用, this指代new出的对象。

4.在事件中, this指向事件源。

5.箭头函数没有this,它里面的this就是其外层函数的this。

6.总结: 谁调用的函数, 函数中的this就指向谁。

8.浏览器多个标签页之间的通信

BroadcastChannel

广播频道,官方文档里的解释为,用于同源不同页面之间完成通信的功能,在其中某个页面发送的消息会被其他页面监听到

/*
用于同源不同页面之间完成通信的功能:
    使用构造函数创建一个实例,可以接收一个string作为频道的name标识。
    在其他页面,可以通过new相同的name的BroadcastChannel来使用同一个广播频道。
*/
const bc = new BroadcastChannel("broadcast");

// 监听消息,也可以使用addEventListener来添加"message"监听。
bc.onmessage = (data) => {
    console.log("BroadcastChannel data:", data);
};

// 错误也可以绑定监听
bc.onmessageerror = (error) => {
    console.warn("BroadcastChannel error:", error);
};

function postMessage() {
    // 发送消息
    bc.postMessage("BroadcastChannel hello world");
}

/* 关闭,关闭后将无法调用postMessage,如果之后又再需要广播,
则可以重新创建一个相同 name 的 Broadcast Channel
*/
// bc.close();

localStorage

localStorage 是浏览器多个标签共用的存储空间,所以可以用来实现多标签之间的通信(session 是会话级的存储空间,每个标签页都是单独的)

window.addEventListener('storage', () => {
  // callback
});

9. 请写出js内存泄漏的问题?

1)移除或替换元素时,若元素绑定的事件还没被移除,就会造成内存泄露, 要先手动移除事件, 再移除或替换dom或者采用事件委托。

<div id="myDiv">
    <button id="myBtn">Click me</button>
</div>

<script type="text/javascript">
    myBtn.onclick = function () {
        // 不加这一句, 就会造成内存泄漏, 或者使用事件委托, 移除子元素时就不会造成内存泄漏.
        myBtn.onclick = null;
        document.getElementById("myDiv").innerHTML = "123";
    }
</script>

2)闭包容易造成内存泄漏,下面的闭包就把变量a泄漏了, 因为函数func的返回值可以多次调用。

function func() {
    var a = [1, 2, 3];
    return function () {
        console.log(a);
    }
}

3)意外的全局变量, 下面函数里的变量a的空间就不会被释放掉。

function foo() {
    a = [1, 2, 3];
}

4)setInterval定时器忘记被关闭时, 会造成内存泄漏。

10. promise方法的理解和使用?

promise是es6提出的异步处理方案,他有三个状态:pending、resolved、rejected

Promise构造函数是同步执行,.then或.catch是异步执行。

Promise优点:让异步回调变成规范的链式调用,程序流程变很更清楚。

Promise缺点:

        1.一旦创建就无法取消

        2.pending状态时,不知道在哪个阶段了

        3.try {} catch (error) {} 只能捕获同步错误,捕获不了Promise异步代码里的错误

11.eval()的作用?

eval的作用: 把字符串解析成JS代码并运行;应该避免使用eval, 不安全,而且非常耗性能(2次,一次解析成js语句,一次执行)。

        eval("2+3"); // 执行加法运算,并返回运算值。

        eval("var age = 10"); // 声明一个age变量

由JSON字符串转换为JSON对象的时候可以用eval,var obj = eval('('+ str +')')。 

eval作用域问题:

        eval("var x=1"); // 相当于 var x = 1;

        console.log(x); // 输出1

12.什么是事件委托,事件委托优点是什么?

利用了事件冒泡原理,把事件绑定到父级上,代替子元素执行事件。

优点:

     1:减少事件注册,节省内存。

     2:简化了dom节点更新时相应事件的更新

缺点:

     1:事件委托基于冒泡, 对于不冒泡的事件不支持。

     2:层级过多时, 冒泡过程中可能会被某层阻止掉。

13.new操作符具体干了什么呢?

1.创建一个对象

2.将this指向创建的对象

3.使这个对象继承该函数的原型

如何确保你的构造函数只能被 new 调用,而不能被普通调用:

// 使用 instanceof 实现,代码如下:
function Person(firstName, lastName) {
    if (!(this instanceof Person)) {
        throw new TypeError('Function constructor A cannot be invoked without "new"')
    }
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = this.firstName + this.lastName;
}

14.暂时性死区

        暂时性死区是针对const和let这两个关键字而产生的概念。首先变量提升这个基本概念在js中无法撼动,const和let声明的变量也不例外。和var不同的是,const和let将作用域限制在了块中。在块中,const和let声明的变量已经被分配了内存,但当代码未执行到声明处时,访问变量是会报错的,这就是'暂时性死区'。通俗来说就是该变量虽然已经存在了,但暂时还不能访问,只有等声明变量的那一行代码出现后,才可以访问。

15.什么是作用域?有几种作用域?什么是作用域链?

作用域: 函数和变量的作用范围,有:全局作用域、函数作用域、块级作用域

作用域链:

        当代码在一个作用域中执行时,对变量的获取有一个作用域链。它保证了对执行环境中有访问权限的变量和函数进行有序的访问。

16.Es6中箭头函数与普通函数的区别?

1)箭头函数没有自己的this,它里面的this指的是其外层函数的this。

2)箭头函数没有原型对象prototype,不可以当成构造函数使用, 也不可以使用new操作符。

3)箭头函数内部不存在arguments对象, 可以用rest参数来代替。

4)箭头函数不可以使用yield命令, 不能用做generator函数。

5)箭头函数通过let或const关键字声明,不通过function关键字

17.javascript几部分组成?

1.核心(ECMAScript)

2.文档对象模型(DOM)

3.浏览器对象模型(BOM)

18.js判断数据类型方法?

typeof 判断基本数据类型:console.log(typeof 1);

instanceof 判断是否为基于某个构造函数的实例

constructor 返回相对应的构造函数, 可用来判断所有的数据类型

Object.prototype.toString.apply([])  可用来判断所有的数据类

function type(param) { // 可用来判断所有的数据类型
    var str = Object.prototype.toString.call(param);
    str = str.split(' ')[1];
    str = str.substring(0, str.length - 1);
    return str;
}

var a = [];
console.log(a instanceof Array); // true
console.log(a.constructor === Array); // true
console.log([].constructor === Array); // true
console.log(({}).constructor === Object); // true
console.log('a'.constructor === String); // true

function F() {this.a = 1}
const b = new F();
console.log(b instanceof F); // true
console.log(b instanceof Object); // true
console.log(b.constructor === F); // true
console.log(b.constructor === Object); // false

19.undefined和null区别

undefined是个值,当声明变量还未被初始化时,变量的默认值为undefined。

null是个空对象,表示什么都没有,垃圾回收机制会回收掉变量原来占的内存。

typeof null === ‘object’          typeof undefined === ‘undefined’

20.事件冒泡和事件捕获

    事件冒泡:从子到父逐级向上触发, 直到document结束。

    事件捕获:从根节点到目标节点, 逐级向下触发。

21.Object.is()与原来的比较操作符 "===" 、"==" 的区别?

== 先转换类型再比较,=== 先比较类型, 再比较值。

用 Object.is 进行相等判断时,一般情况下和===的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 认定为是相等的。

22.arguments是什么?arguments是数组还是对象?怎么将arguments转换数组?

在js的函数内可以访问一个特别变量arguments, 这个变量维护着所有传递到这个函数中的参数。arguments不是一个数组, 尽管在语法上它有数组相关的属性length,但它不从 Array.prototype继承,实际上它是一个对象。

转换成数组: Array.prototype.slice.call(arguments);

arguments对象有一个重要的属性callee, 值为当前正执行的函数本身。

23.什么是面向对象?

        面向对象是对面向过程的封装, 它是基于对象的概念, 以对象为中心, 以类、封装、继承、多态为构造机制来描述客观世界并设计和构建相应的软件系统,面相对象通俗一点就是模拟现实。面向对象是一种编程思想, 它不是某一种编程语言所特有的。

24.简述一下js原型链继承原理?

原型对象:

        每一个函数(箭头函数除外)都有一个prototype属性, 该属性对应的值就是函数的原型对象。构造函数new出来的对象是实例对象, 原型对象里的属性和方法是所有实例对象所公有的,构造函数里面的属性和方法是每个实例对象私有的。原型对象里的属性和方法不属于实例对象, 但实例对象可以使用。

原型链:

        每一个实例对象都有一个隐性属性__proto__, 就是原型链, 该属性的值为创建该实例对象的构造函数的原型对象。

原型继承、构造函数继承、组合继承、寄生继承、寄生组合继承

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = { // 原型对象
    print() {
        console.log('我叫' + this.name + ', 我今年' + this.age + '岁了。');
    },
    hobby: { a: '敲代码' }
}

// 1.原型继承:只继承一家, 公有的私有的都继承。私有部分也继承到原型里了。
function Student(sex) {
    this.sex = sex;
    this.say = function () {
        console.log('I am a student.');
    }
}
// 这样Student就继承了Person, 公有的私有的都继承了。
Student.prototype = new Person('张三', 18);

// 2.构造函数继承: 能继承多家, 只继承私有部分。
function Monitor(name, age, sex, skill) {
    this.skill = skill;
    Person.apply(this, [name, age]); // 这样Monitor就继承了Person的私有部分
    Student.call(this, sex); // 这样Monitor就继承了Student的私有部分
}

// 3.组合继承(原型链继承 + 构造函数继承)。两次调用父类构造函数,耗性能。

// 4.寄生式继承。只能继承公有部分,只继承一家。
function GroupLeader(group) {
    this.group = group;
}
// new创建一个对象,执行构造函数。Object.create也是创建一个对象,但不执行构造函数。
GroupLeader.prototype = Object.create(Person.prototype);
GroupLeader.prototype.constructor = GroupLeader;

// 5.寄生组合式继承(常用),私有和公有都继承了。
function Father(name) {
    this.name = name;
    this.age = 21;
}
Father.prototype = {
    sayName() {
        console.log(this.name);
    },
    address: '上海',
    hobby: ['唱歌'],
};
function Sub(name) {
    this.aa = 'aa';
    Father.call(this, name); // 继承父类构造函数的属性
}
Sub.prototype = Object.create(Father.prototype); // 继承父类的原型
Sub.prototype.constructor = Sub;

25.什么是构造函数?与普通函数有什么区别?

        构造函数是一种特殊的函数, 总与new操作符一起使用, 是用来创建对象的。其实构造函数本质上与普通函数并没有区别, 为了区分二者, 人为规定构造函数首字母必须大写, 普通函数首字母则必须小写。构造函数构造出来的对象称为实例对象, 构造函数自带的对象称为原型对象。

26.call和apply 还有bind

共同点:三者都是函数的方法,可以让其它对象调用自己,并把this指向其它对象。箭头函数能使用这三个方法,但箭头函数没有this。

不同点:

        apply接收数组参数, 立即执行。

        call接收连续参数, 立即执行。

        bind接收连续参数, 返回对应函数, 需调用才能执行。

27.js中数组和对象浅拷贝和深拷贝?

浅拷贝: 浅拷贝只是单纯的将引用地址赋值给这个元素

var arr = ["One", "Two", "Three"];

var arrToo = arr; // 单纯的地址赋值, 他们两个指向了同一个地址。

深拷贝: 深拷贝是开辟一块新的内存空间, 深拷贝的引用类型和原来的没有关联了。

// 数组的深拷贝

var arr = ["One", "Two", "Three"];

var arr1 = arr.slice(0);

var arr2 = arr.concat();

// 对象深拷贝

通过JSON方法深拷贝, 存在问题:

        1:无法复制函数

        2:原型链没了,对象就是object,所属的类没了。

封装方法深拷贝对象

28.实现javascript在String中写一个trim,要求能够去除一个字符串开始和结尾的空格?

function replaceStr(str) {

    var reg = /(^\s*)|(\s*$)/g;

    return str.replace(reg, "");

}

29.SVG、Canvas 和 WebGL三者之间的区别

SVG是矢量图,是给数据就可以绘制点、线、图形的,基于XML的标记语言;

Canvas是位图,是HTML5新增的一个元素对象,名副其实就是一个画布,配有相应的js操作api,可以不依赖库直接绘图,相当于2D的API。

WebGL是3D位图,是基于Canvas的3D框架。Three.js、Babylon.js、Blender4Web等是几种知名的WebGL开发框架,对WebGL基础操作做了大量的封装,可以拿来就用。

30.Event loop(事件轮询)

        js是一门单线程语言,是从上往下执行的。首先执行主线程js代码,主线程上是同步执行的。当主线程上遇到了异步操作,就会把它交给异步线程处理,然后继续执行主线程上的任务。在异步线程里,会先判断异步任务的类型。异步任务可分成宏任务和微任务,像setTimeout、setInterval属于宏任务(js主线程代码也属于宏任务),promise.then属于微任务。不同的任务类型进入不同的队列,等到主线程空闲的时候,就会进入主线程执行。当主线程里的同步任务都执行完后,就会执行主线程里的所有微任务,执行完主线程里的微任务,就执行宏任务队列里的其他宏任务。对于每个宏任务,都是先执行它里面的同步任务,等同步任务都执行完之后,就执行它里面的微任务,等微任务也都执行完了,就开始执行下一个宏任务。按以上步骤重复执行就是事件轮询。

宏任务:Js(主程序代码)  setTimeOut  setInterVal  UI渲染  

微任务:promise.then()

执行顺序:

        1.先执行第一个宏任务:js主程序代码

        2.在上一个宏任务执行完毕之后,如果这个宏任务中有微任务队列,就执行这里的所有微任务队列。Promises.then()

        3.执行完第一个宏任务中的微任务,接着执行剩余的宏任务队列。

                setTimeout—>setInterval—>UI rendering

                注意:new Promise(xx)属于主程序代码, 会立即执行, .then后面的是微任务。

31.为什么JavaScript是单线程?

        JS的单线程,这与它的用途有关。作为浏览器脚本语言,JS的主要用途就是与用户进行交互,以及操作DOM。这决定了它只能是单线程,否则会带来非常复杂的同步问题。假如JS同时有两个线程,一个线程要在某个DOM节点上添加内容,另一个线程要删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从刚诞生开始,JS就是单线程,这是这门语言的核心特征,将来也不会改变。

        为了利用多核CPU的计算能力,HTML5提出Web Workers标准,允许JS创建多个子线程,但是子线程完全受主线程控制,且不得操作DOM。所以这个新标准并没有改变JS单线程的本质。

32.setInterval明明写的间隔300ms,实际却是350ms、400ms秒才执行,这是为什么?

        setInterval每次把任务添加到任务队列之前,会先判断上一个任务是否仍在队列中,如果在就等待,如果不在才添加。这就导致setInterval在做定时轮训时,如果调用的异步操作耗时比较多,会导致异步任务不能按照期待的时间间隔来执行。为了解决setInterval的这个缺陷,常用setTimeout封装方法来解决,因为setTimeout不存在等待上一个任务的问题,会直接添加到队列里,setTimeout 保证调用的时间间隔是一致的。

33.为什么for循环比forEach性能高?

        for循环没有任何的额外函数调用栈和上下文,而forEach是数组方法的调用,并且传递一个回调函数,在循环的过程中每次都在执行这个回调函数,那么就会有诸多的参数、函数调用栈、执行上下文要考虑进来,这些内容都有可能拖慢forEach的效率,所以for循环的性能要高于forEach。

34.axios和fetch区别对比

axios是基于Promise实现的,对原生ajax的封装。

        它支持 Promise API

        客户端支持防止CSRF(跨站请求伪造)

        提供了一些并发请求的接口(重要,方便了很多的操作)

fetch被称为下一代ajax技术,采用Promise方式处理数据。它的API简洁明了,比XMLHttpRequest更加简单易用,fetch()返回的是一个Promise对象。

fetch存在问题:

        fetch不支持abort,不支持超时控制,请求一旦发出就不能撤回,造成了流量的浪费。

        fetch没有办法原生监测请求的进度,而XHR可以。

        服务器返回400,500错误码时,fetch并不会reject,只有网络错误导致请求不能完成时,fetch才会被reject。

        fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})

35.堆和栈

栈内存由操作系统自动分配和释放,用于基本数据类型。

堆内存需要通过垃圾回收机制和程序员自己来管理内存。若不主动释放,程序结束时由系统回收,用于存储引用类型。

36.async/await

async函数是Generator函数的语法糖,它是将Generator函数的星号( * )替换成async,将yield替换成await。async函数解决了异步操作深度嵌套的问题。

asyncpromise对比:

async/await可以用同步的方式写异步代码,Promise是链式调用。

async/await需要通过try/catch来抓取错误,Promise自带catch方法。

async/await可以让try/catch支持抓取异步错误。

多个await之间写条件语句很容易,Promise.then的链式调用较麻烦,上一个then要用分支判断return语句,下一个then还要用分支判断传过来的参数。

async对Generator的改进

1. 内置执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行,简化了Generator函数处理异步的写法。

2. 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

3. 返回值是Promise。这比Generator函数的返回值是Iterator对象方便多了。可以用then方法指定下一步的操作。

37.一个网站的js文件过多的情况下,会出现变量名被污染和函数名冲突。有什么解决方法?

1.使用闭包, 一个js文件用一个自执行函数包括着

2.把函数和变量封装到一个对象里

3.规定命名空间

4.使用模块化加载js库

38.script标签的async属性与defer属性

1.浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的script标签 ,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当js代码执行完毕后恢复HTML解析。

2.给script标签加上defer或async属性,都能异步下载外部的JS脚本,都不会阻塞页面的解析。

3.defer与async的区别:

        defer是在JS下载完成后,继续等待所有DOM加载完之后,再去执行js代码

        async是在JS下载完成后,立即执行js代码

        加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载

39.实现一个 cacheRequest 方法,保证发出多次同一个 ajax 请求时都能拿到的数据,而实际上只发出一次请求

const cache = new Map();
const request = (url, option) => {
    return new Promise((res) => {
        setTimeout(() => {
          res({ data: option });
        }, 2000);
    });
}
const cacheRequest = (url, option) => {
  let key = `${url}:${option.method}`;
  if (cache.has(key)) {
    if (cache.get(key).status === "pending") {
      return cache.get(key).myWait;
    }
    return Promise.resolve(cache.get(key).data);
  } else {
    // 无缓存,发起真实请求
    const requestApi = request(url, option);
    cache.set(key, { status: "pending", myWait: requestApi });
    return requestApi
      .then((res) => {
        cache.set(key, { status: "success", data: res });
        Promise.resolve(res);
      })
      .catch((err) => {
        cache.set(key, { status: "fail", data: err });
        Promise.reject(err);
      });
   }
};

40.使用 Symbol 函数都有哪些要注意的点? 

1.Symbol 类型的 key 是不能通过 object.keys() 或者 for…in 来枚举的,它未被包含在对象自身的属性名集合之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。

2.使用 JSON.stringify() 将对象转换成 JSON 字符串的时候,Symbol 属性也会被排除在输出内容之外

获取以 Symbol 方式定义的对象属性:
// 使用Object的API
Object.getOwnPropertySymbol(obj); // [Symbol(name)]

// 使用新增的的反射API
Reflect.ownKeys(obj); // [Symbol(name),"age","title"]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值