38道关于this的面试题,让你彻底解决this 的指向问题

3.隐式绑定的丢失


隐式绑定可是个调皮的东西,一不小心它就会发生绑定的丢失。一般会有两种常见的丢失:

  • 使用另一个变量作为函数别名,之后使用别名执行函数

  • 将函数作为参数传递时会被隐式赋值

隐式绑定丢失之后, this 的指向会启用默认绑定。

具体来看题目:

题目3.1:取函数别名

a = 1

var obj = {

a: 2,

foo() {

console.log(this.a)

}

}

var foo = obj.foo;

obj.foo();

foo();

JavaScript 对于引用类型,其地址指针存放在栈内存中,真正的本体是存放在堆内存中的。

上面将 obj.foo 赋值给 foo ,就是将 foo 也指向了 obj.foo 所指向的堆内存,此后再执行 foo ,相当于直接执行的堆内存的函数,与 obj 无关, foo 为默认绑定。笼统的记, 只要fn前面什么都没有,肯定不是隐式绑定 。

答案

不要把这里理解成 window.foo 执行,如果 foo 为 let/const 定义, foo 不会挂载到 window 上,但不会影响最后的打印结果

题目3.2:取函数别名

如果取函数别名没有发生在全局,而是发生在对象之中,又会是怎样的结果呢?

var obj = {

a: 1,

foo() {

console.log(this.a)

}

};

var a = 2;

var foo = obj.foo;

var obj2 = { a: 3, foo: obj.foo }

obj.foo();

foo();

obj2.foo();

obj2.foo 指向了 obj.foo 的堆内存,此后执行与 obj 无关(除非使用 call/apply 改变 this 指向)

答案

题目3.3:函数作为参数传递

function foo() {

console.log(this.a)

}

function doFoo(fn) {

console.log(this)

fn()

}

var obj = { a: 1, foo }

var a = 2

doFoo(obj.foo)

用函数预编译的知识来解答这个问题:函数预编译四部曲前两步分别是:

  1. 找形参和变量声明,值赋予 undefined

  2. 将形参与实参相统一,也就是将实参的值赋予形参。

obj.foo 作为实参,在预编译时将其值赋值给形参 fn ,是将 obj.foo 指向的地址赋给了 fn ,此后 fn 执行不会与 obj 产生任何关系。 fn 为默认绑定。

答案

Window {…}

2

题目3.4:函数作为参数传递

将上面的题略作修改, doFoo 不在 window 上执行,改为在 obj2 中执行

function foo() {

console.log(this.a)

}

function doFoo(fn) {

console.log(this)

fn()

}

var obj = { a: 1, foo }

var a = 2

var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)

console.log(this)

obj2.doFoo

xxx.fn

doFoo

this

obj2

{a: 3, doFoo: ƒ}

  • fn() : 没有于 obj2 产生联系,默认绑定,打印2

答案

{a: 3, doFoo: ƒ}

2

题目3.5:回调函数

下面这个题目我们写代码时会经常遇到:

var name=‘zcxiaobao’;

function introduce(){

console.log('Hello,My name is ', this.name);

}

const Tom = {

name: ‘TOM’,

introduce: function(){

setTimeout(function(){

console.log(this)

console.log('Hello, My name is ',this.name);

})

}

}

const Mary = {

name: ‘Mary’,

introduce

}

const Lisa = {

name: ‘Lisa’,

introduce

}

Tom.introduce();

setTimeout(Mary.introduce, 100);

setTimeout(function(){

Lisa.introduce();

},200);

setTimeout 是异步调用的,只有当满足条件并且同步代码执行完毕后,才会执行它的回调函数。

Tom.introduce()执行

console

setTimeout

this

window

Mary.introduce

setTimeout

题目3.3

this

Lisa.introduce

setTimeout

xxx.fn

this

答案

Window {…}

Hello, My name is zcxiaobao

Hello,My name is zcxiaobao

Hello,My name is Lisa

所以如果我们想在 setTimeout 或 setInterval 中使用外界的 this ,需要提前存储一下,避免 this 的丢失。

const Tom = {

name: ‘TOM’,

introduce: function(){

_self = this

setTimeout(function(){

console.log('Hello, My name is ',_self.name);

})

}

}

Tom.introduce()

题目3.6:隐式绑定丢失综合题

name = ‘javascript’ ;

let obj = {

name: ‘obj’,

A (){

this.name += ‘this’;

console.log(this.name)

},

B(f){

this.name += ‘this’;

f();

},

C(){

setTimeout(function(){

console.log(this.name);

},1000);

}

}

let a = obj.A;

a();

obj.B(function(){

console.log(this.name);

});

obj.C();

console.log(name);

本题目不做解析,具体可以参照上面的题目。

答案

javascriptthis

javascriptthis

javascriptthis

undefined

4.显式绑定


显式绑定比较好理解,就是通过 call()、apply()、bind() 等方法,强行改变 this 指向。

上面的方法虽然都可以改变 this 指向,但使用起来略有差别:

  • call()和apply() 函数会立即执行

  • bind() 函数会返回新函数,不会立即执行函数

call()和apply()

call

apply

题目4.1:比较三种调用方式

function foo () {

console.log(this.a)

}

var obj = { a: 1 }

var a = 2

foo()

foo.call(obj)

foo.apply(obj)

foo.bind(obj)

  • foo() : 默认绑定。

foo.call(obj)

foo

this

obj

  • foo.apply(obj) : 显式绑定

  • foo.bind(obj) : 显式绑定,但不会立即执行函数,没有返回值

答案

题目4.2:隐式绑定丢失

题目3.4 发生隐式绑定的丢失,如下代码:我们可不可以通过显式绑定来修正这个问题。

function foo() {

console.log(this.a)

}

function doFoo(fn) {

console.log(this)

fn()

}

var obj = { a: 1, foo }

var a = 2

doFoo(obj.foo)

  1. 首先先修正 doFoo() 函数的 this 指向。

doFoo.call(obj, obj.foo)

  1. 然后修正 fn 的 this 。

function foo() {

console.log(this.a)

}

function doFoo(fn) {

console.log(this)

fn.call(this)

}

var obj = { a: 1, foo }

var a = 2

doFoo(obj.foo)

大功告成。

题目4.3:回调函数与call

接着上一个题目的风格,稍微变点花样:

var obj1 = {

a: 1

}

var obj2 = {

a: 2,

bar: function () {

console.log(this.a)

},

foo: function () {

setTimeout(function () {

console.log(this)

console.log(this.a)

}.call(obj1), 0)

}

}

var a = 3

obj2.bar()

obj2.foo()

乍一看上去,这个题看起来有些莫名其妙, setTimeout 那是传了个什么东西?

做题之前,先了解一下 setTimeout 的内部机制:(关于异步的执行顺序,可以参考 JavaScript之EventLoop [6] )

setTimeout(fn) {

if (回调条件满足) (

fn

)

}

这样一看,本题就清楚多了,类似 题目4.2 ,修正了回调函数内 fn 的 this 指向。

答案

2

{a: 1}

1

题目4.4:注意call位置

function foo () {

console.log(this.a)

}

var obj = { a: 1 }

var a = 2

foo()

foo.call(obj)

foo().call(obj)

  • foo() : 默认绑定

  • foo.call(obj) : 显式绑定

foo().call(obj)

foo()

call

foo

undefined

call()

答案

2

1

2

Uncaught TypeError: Cannot read property ‘call’ of undefined

题目4.5:注意call位置(2)

上面由于 foo 没有返回函数,无法执行 call 函数报错,因此修改一下 foo 函数,让它返回一个函数。

function foo () {

console.log(this.a)

return function() {

console.log(this.a)

}

}

var obj = { a: 1 }

var a = 2

foo()

foo.call(obj)

foo().call(obj)

  • foo() : 默认绑定

  • foo.call(obj) : 显式绑定

foo().call(obj)

foo()

2

call

this

obj

1

这里千万注意:最后一个 foo().call(obj) 有两个函数执行,会打印 2个值 。

答案

题目4.6:bind

将上面的 call 全部换做 bind 函数,又会怎样那?

call是会立即执行函数,bind会返回一个新函数,但不会执行函数

function foo () {

console.log(this.a)

return function() {

console.log(this.a)

}

}

var obj = { a: 1 }

var a = 2

foo()

foo.bind(obj)

foo().bind(obj)

首先我们要先确定,最后会输出几个值? bind 不会执行函数,因此只有两个 foo() 会打印 a 。

  • foo() : 默认绑定,打印 2

  • foo.bind(obj) : 返回新函数,不会执行函数,无输出

foo().bind(obj)

foo()

2

bind

foo()

this

obj

答案

题目4.7:外层this与内层this

做到这里,不由产生了一些疑问:如果使用 call、bind 等修改了外层函数的 this ,那内层函数的 this 会受影响吗?(注意区别箭头函数)

function foo () {

console.log(this.a)

return function() {

console.log(this.a)

}

}

var obj = { a: 1 }

var a = 2

foo.call(obj)()

foo.call(obj) : 第一层函数 foo 通过 call 将 this 指向 obj ,打印 1 ;第二层函数为匿名函数,默认绑定,打印 2 。

答案

题目4.8:对象中的call

把上面的代码移植到对象中,看看会发生怎样的变化?

var obj = {

a: ‘obj’,

foo: function () {

console.log(‘foo:’, this.a)

return function () {

console.log(‘inner:’, this.a)

}

}

}

var a = ‘window’

var obj2 = { a: ‘obj2’ }

obj.foo()()

obj.foo.call(obj2)()

obj.foo().call(obj2)

看着这么多括号,是不是感觉有几分头大。没事,咱们来一层一层分析:

obj.foo()()

obj.foo()

foo:obj

inner:window

obj.foo.call(obj2)()

题目4.7

obj.foo.call(obj2)

call

obj.foo

this

obj2

foo: obj2

inner:window

obj.foo().call(obj2)

题目4.5

foo: obj

call

this

obj2

inner: obj2

题目4.9:带参数的call

显式绑定一开始讲的时候,就谈过 call/apply 存在传参差异,那咱们就来传一下参数,看看传完参数的this会是怎样的美妙。

var obj = {

a: 1,

foo: function (b) {

b = b || this.a

return function © {

console.log(this.a + b + c)

}

}

}

var a = 2

var obj2 = { a: 3 }

obj.foo(a).call(obj2, 1)

obj.foo.call(obj2)(1)

要注意 call 执行的位置:

  • obj.foo(a).call(obj2, 1) :

  • obj.foo(a) : foo的AO中b值为传入的a(形参与实参相统一),值为2,返回匿名函数fn

  • 匿名函数 fn.call(obj2, 1) : fn的this指向为obj2,c值为1

  • this.a + b + c = obj2.a + FooAO.b + c = 3 + 2 + 1 = 6

  • obj.foo.call(obj2)(1) :

  • obj.foo.call(obj2) : obj.foo的this指向obj2,未传入参数,b = this.a = obj2.a = 3;返回匿名函数fn

  • 匿名函数 fn(1) : c = 1,默认绑定,this指向window

  • this.a + b + c = window.a + obj2.a + c = 2 + 3 + 1 = 6

答案

麻了吗,兄弟们。进度已经快过半了,休息一会,争取把 this 一次性吃透。

5.显式绑定扩展


上面提了很多 call/apply 可以改变 this 指向,但都没有太多实用性。下面来一起学几个常用的 call与apply 使用。

题目5.1:apply求数组最值

JavaScript中没有给数组提供类似max和min函数,只提供了 Math.max/min ,用于求多个数的最值,所以可以借助apply方法,直接传递数组给 Math.max/min

const arr = [1,10,11,33,4,52,17]

Math.max.apply(Math, arr)

Math.min.apply(Math, arr)

题目5.2:类数组转为数组

ES6 未发布之前,没有 Array.from 方法可以将类数组转为数组,采用 Array.prototype.slice.call(arguments) 或 [].slice.call(arguments) 将类数组转化为数组。

题目5.3:数组高阶函数

日常编码中,我们会经常用到 forEach、map 等,但这些数组高阶方法,它们还有第二个参数 thisArg ,每一个回调函数都是显式绑定在 thisArg 上的。

例如下面这个例子

const obj = {a: 10}

const arr = [1, 2, 3, 4]

arr.forEach(function (val, key){

console.log(${key}: ${val} --- ${this.a})

}, obj)

答案

0: 1 — 10

1: 2 — 10

2: 3 — 10

3: 4 — 10

关于数组高阶函数的知识可以参考: JavaScript之手撕高阶数组函数

6.new绑定


使用 new 来构建函数,会执行如下四部操作:

  1. 创建一个空的简单 JavaScript 对象(即 {} );

  2. 为步骤1新创建的对象添加属性 __proto__ ,将该属性链接至构造函数的原型对象 ;

  3. 将步骤1新创建的对象作为 this 的上下文 ;

  4. 如果该函数没有返回对象,则返回 this 。

关于new更详细的知识,可以参考: JavaScript之手撕new [7]

通过new来调用构造函数,会生成一个新对象,并且把这个新对象绑定为调用函数的this。

题目6.1:new绑定

function User(name, age) {

this.name = name;

this.age = age;

}

var name = ‘Tom’;

var age = 18;

var zc = new User(‘zc’, 24);

console.log(zc.name)

答案

zc

题目6.2:属性加方法

function User (name, age) {

this.name = name;

this.age = age;

this.introduce = function () {

console.log(this.name)

}

this.howOld = function () {

return function () {

console.log(this.age)

}

}

}

var name = ‘Tom’;

var age = 18;

var zc = new User(‘zc’, 24)

zc.introduce()

zc.howOld()()

这个题很难不让人想到如下代码,都是函数嵌套,具体解法是类似的,可以对比来看一下啊。

const User = {

name: ‘zc’;

age: 18;

introduce = function () {

console.log(this.name)

}

howOld = function () {

return function () {

console.log(this.age)

}

}

}

var name = ‘Tom’;

var age = 18;

User.introduce()

User.howOld()()

  • zc.introduce() : zc是new创建的实例,this指向zc,打印 zc

  • zc.howOld()() : zc.howOld()返回一个匿名函数,匿名函数为默认绑定,因此打印18(阿包永远 18 )

答案

zc

18

题目6.3:new界的天王山

new 界的天王山,每次看懂后,没过多久就会忘掉,但这次要从根本上弄清楚该题。

接下来一起来品味品味:

function Foo(){

getName = function(){ console.log(1); };

return this;

}

Foo.getName = function(){ console.log(2); };

Foo.prototype.getName = function(){ console.log(3); };

var getName = function(){ console.log(4); };

function getName(){ console.log(5) };

Foo.getName();

getName();

Foo().getName();

getName();

new Foo.getName();

new Foo().getName();

new new Foo().getName();

  1. 预编译

GO = {

Foo: fn(Foo),

getName: function getName(){ console.log(5) };

}

  1. 分析后续执行
  • Foo.getName() : 执行Foo上的getName方法,打印 2

  • getName() : 执行GO中的getName方法,打印 4

  • Foo().getName()

// 修改全局GO的getName为function(){ console.log(1); }

getName = function(){ console.log(1) }

// Foo为默认绑定,this -> window

// return window

return this

复制代码

  • Foo().getName() : 执行window.getName(),打印 1

  • Foo() 执行

  • getName() : 执行GO中的getName,打印 1

  1. 分析后面三个打印结果之前,先补充一些运算符优先级方面的知识(图源: MDN [8] )

从上图可以看到,部分优先级如下: new(带参数列表) = 成员访问 = 函数调用 > new(不带参数列表)

  1. new Foo.getName()

首先从左往右看: new Foo 属于不带参数列表的new(优先级 19 ), Foo.getName 属于成员访问(优先级 20 ), getName() 属于函数调用(优先级 20 ),同样优先级遵循从左往右执行。

  • Foo.getName 执行,获取到Foo上的 getName 属性

  • 此时原表达式变为 new (Foo.getName)() , new (Foo.getName)() 为带参数列表(优先级 20 ), (Foo.getName)() 属于函数调用(优先级 20 ),从左往右执行

new (Foo.getName)()

2

Foo.getName()

这里有一个误区:很多人认为这里的 new 是没做任何操作的的,执行的是函数调用。那么如果执行的是 Foo.getName() ,调用返回值为 undefined , new undefined 会发生报错,并且我们可以验证一下该表达式的返回结果。

console.log(new Foo.getName())

// 2

// Foo.getName {}

可见在成员访问之后,执行的是 带参数列表格式的new 操作。

  1. new Foo().getName()

步骤4

new Foo()

Foo

Foo

getName

Foo.prototype.getName

3

  1. new new Foo().getName()

从左往右分析: 第一个new不带参数列表(优先级 19 ), new Foo() 带参数列表(优先级 20 ),剩下的成员访问和函数调用优先级都是 20

  • new Foo() 执行,返回一个以 Foo 为构造函数的实例

Foo

Foo.prototype

getName

new (new Foo().getName)()

Foo.prototype.getName()

3

  1. new Foo.getName()  与  new new Foo().getName() 区别:
  • new Foo.getName() 的构造函数是 Foo.getName

  • new new Foo().getName() 的构造函数为 Foo.prototype.getName

测试结果如下:

foo1 = new Foo.getName()

foo2 = new new Foo().getName()

console.log(foo1.constructor)

console.log(foo2.constructor)

输出结果:

2

3

ƒ (){ console.log(2); }

ƒ (){ console.log(3); }

通过这一步比较应该能更好的理解上面的执行顺序。

答案

兄弟们,革命快要成功了,再努力一把,以后this都小问题啦。

7.箭头函数


箭头函数没有自己的 this ,它的 this 指向外层作用域的 this ,且指向函数定义时的 this 而非执行时。

this指向外层作用域的this

this

this

  1. 指向函数定义时的this而非执行时 :  JavaScript 是静态作用域,就是函数定义之后,作用域就定死了,跟它执行时的地方无关。更详细的介绍见 JavaScript之静态作用域与动态作用域 [9] 。

题目7.1:对象方法使用箭头函数

name = ‘tom’

const obj = {

name: ‘zc’,

intro: () => {

console.log('My name is ’ + this.name)

}

}

obj.intro()

上文说到,箭头函数的 this 通过作用域链查到, intro 函数的上层作用域为 window 。

答案

My name is tom

题目7.2:箭头函数与普通函数比较

name = ‘tom’

const obj = {

name: ‘zc’,

intro:function () {

return () => {

console.log('My name is ’ + this.name)

}

},

intro2:function () {

return function() {

console.log('My name is ’ + this.name)

}

}

}

obj.intro2()()

obj.intro()()

  • obj.intro2()() : 不做赘述,打印 My name is tom

obj.intro()()

obj.intro()

this

this

obj

My name is zc

题目7.3:箭头函数与普通函数的嵌套

name = ‘window’

const obj1 = {

name: ‘obj1’,

intro:function () {

console.log(this.name)

return () => {

console.log(this.name)

}

}

}

const obj2 = {

name: ‘obj2’,

intro: ()=> {

console.log(this.name)

return function() {

console.log(this.name)

}

}

}

const obj3 = {

name: ‘obj3’,

intro: ()=> {

console.log(this.name)

return () => {

console.log(this.name)

}

}

}

obj1.intro()()

obj2.intro()()

obj3.intro()()

obj1.intro()()

题目7.2

obj1,obj1

obj2.intro()()

obj2.intro()

this

this

window

window,window

obj3.intro()()

obj3.intro()

obj2.intro()

intro

this

window

window,window

答案

obj1

obj1

window

window

window

window

题目7.4:new碰上箭头函数

function User(name, age) {

this.name = name;

this.age = age;

this.intro = function(){

console.log('My name is ’ + this.name)

},

this.howOld = () => {

console.log('My age is ’ + this.age)

}

}

var name = ‘Tom’, age = 18;

var zc = new User(‘zc’, 24);

zc.intro();

zc.howOld();

zc

new User

User

this

zc

  • zc.intro() : 打印 My name is zc

  • zc.howOld() :  howOld 为箭头函数,箭头函数 this由外层作用域决定,且指向函数定义时的this ,外层作用域为 User , this 指向 zc ,打印 My age is 24

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

img

最后

分布式技术专题+面试解析+相关的手写和学习的笔记pdf

还有更多Java笔记分享如下:

image

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
数的嵌套

name = ‘window’

const obj1 = {

name: ‘obj1’,

intro:function () {

console.log(this.name)

return () => {

console.log(this.name)

}

}

}

const obj2 = {

name: ‘obj2’,

intro: ()=> {

console.log(this.name)

return function() {

console.log(this.name)

}

}

}

const obj3 = {

name: ‘obj3’,

intro: ()=> {

console.log(this.name)

return () => {

console.log(this.name)

}

}

}

obj1.intro()()

obj2.intro()()

obj3.intro()()

obj1.intro()()

题目7.2

obj1,obj1

obj2.intro()()

obj2.intro()

this

this

window

window,window

obj3.intro()()

obj3.intro()

obj2.intro()

intro

this

window

window,window

答案

obj1

obj1

window

window

window

window

题目7.4:new碰上箭头函数

function User(name, age) {

this.name = name;

this.age = age;

this.intro = function(){

console.log('My name is ’ + this.name)

},

this.howOld = () => {

console.log('My age is ’ + this.age)

}

}

var name = ‘Tom’, age = 18;

var zc = new User(‘zc’, 24);

zc.intro();

zc.howOld();

zc

new User

User

this

zc

  • zc.intro() : 打印 My name is zc

  • zc.howOld() :  howOld 为箭头函数,箭头函数 this由外层作用域决定,且指向函数定义时的this ,外层作用域为 User , this 指向 zc ,打印 My age is 24

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-VQH93t7s-1711565188938)]

[外链图片转存中…(img-TJxFkzqn-1711565188938)]

[外链图片转存中…(img-Ld94lhe1-1711565188938)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

img

最后

分布式技术专题+面试解析+相关的手写和学习的笔记pdf

还有更多Java笔记分享如下:

[外链图片转存中…(img-J0RQpjpb-1711565188939)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值