js中this是什么,代表谁,详解看懂了却总碰到难以解释的现象?

做前端开发,难免会遇到使用this的场景,它无处不在,却又总是产生一些奇怪的现象困扰着我们,每次学完之后都感觉自己已经掌握了它的用法,但是一使用的时候还是摸不准它的行为。

其实在制定this的时候,是会为它约定一些准则的,并不是动态变化的,我们认为的this表现时常变化这只是表象,只要我们摸准了它的这条脉路,很多的疑问也就迎刃而解了。

先看一下这个例子:

var b = 1
function a() {
  this.b = 2
  console.log(b)
  return function () {
    console.log(this.b)
  }
}
let m = a()
console.log(b)
m()
function c() {
  this.b = 3
  function d() {
    console.log(this.b)
  }
  d()
}
new c()
console.log(b)

如果你对上面的代码输出了然于胸,并且能指出其中的运行原理,那么关于this的理解也基本足够处理大部分问题了。

检验一下输出结果跟你想的一样嘛?

输出

概要说明

虽然我们在日常开发中遇到this的难以理解的行为,可以通过各种方法去变相解决,但深入的去理解它的行为机制,有利于我们设计出更好的程序代码。

我们不去说概念、说定义,也不用去背场景、背例子,更不会去长篇大论的深入展开,因为这些网上都是一大堆,而且跟记英语一样,没过几天就又蒙圈了。

简单来说,我们只需记住this就是一个对象,其他的都是通过这一点来引申发散的。

有这个调用对象那么this就指向它,如果没有那么就指向undefined

PS:本篇所讲基于严格模式的正常行为,浏览器中全局环境的this指向全局对象,也就是window

请注意,虽然是在全局下面使用this,但是它不属于window的属性,this是一个特殊的关键字,只在执行环境生成的时候也自动生成它。可以通过下面这个例子简单理解下:

例子

下面我们通过几个方面来印证一下,看看在不同情景下他们的表现形式,相信读完之后你就能轻松的应付面试(工作)了。

主要有:普通函数、箭头函数、构造函数、call/apply、bind、对象、类、事件处理这八种环境,不要被吓到,看着很多,其实只要牢记上面的口诀,一切都变的简单,它们都是一样的,其他的场景也是如此,只不过列出来是让你在各个点上肯定自己。

开始

普通函数

function a1() {
  console.log(this)
}
a1()
window.a1()

这是一个简单的例子,我们来分析一下,根据上面所说,很容易能得出第5行window.a1()的输出结果,由于a1方法是由window对象调用的,那么它的this就应该指向window。

那么对于第4行的输出是什么呢?由于这是在非严格模式下,那么如果找不到调用对象,就会把this指向全局,也就是window。

看下输出结果:

输出

那不禁要问,如果要是在严格模式呢?有什么不一样的嘛?

function a2() {
  "use strict";
  console.log(this)
}
a2()
window.a2()

是的,如果在严格模式下,如果找不到调用对象,那么就指向undefined,因此第5行将打印undefined,而第6行虽然处于严格模式,但是指定了调用对象,因此第6行能找到这个对象,因此就会输出window。

输出

所以我们也能知道,严格模式限定了:调用方法的时候必须显示指定调用对象,它的this才会有值,否则找不到调用对象的时候将会指向undefined。因此严格模式下需要我们手动去指定调用的对象,以此来指定this。

之后的例子,我们就不再两种情况都列出来了,都是一样的道理,就不浪费篇幅了,我们都将以严格模式来讲解。

再看个例子:

let t = 1
function a() {
  "use strict";
  let t = 2
  console.log(this.t)
  function b() {
    console.log(this.t)
  }
  b()
}
window.a()
a()

我们分析:执行第11行时,由于指定了调用对象为window,那么它的this应该指向window,因此第5行就相当于window.t,因为t使用let定义的,所以不会自动添加到window中,因此第5行应该输出undefined,如果使用var定义的,那么就会输出1了;在执行第9行的时候,由于没有指定调用对象,因此它的this应该是undefined(千万注意,这里的this不会自动往上层寻找,去使用外层的this,也就是window,而是按照咱们之前说的,直接指向全局,在严格模式下也就是undefined),所以这时的第7行会抛出错误。

在执行第12行的时候,由于没有指定调用对象,那么同理,第5行的this就指向了undefined,因此也会直接抛出错误,不会再往下执行。

看下我们的分析对吗?

执行window.a()时:

输出

直接执行a()时:

输出

也就是说,不管函数是否嵌套,都符合我们之前的认知,也符合我们的预期输出。

箭头函数

箭头函数与普通函数的this绑定方式不同,这么说其实不准确,普通函数有自己的this绑定,但是箭头函数自身不提供this绑定,都是使用的跟外层同样的this。

举个例子:

let a1 = function() {
  "use strict";
  console.log(this)
  function b() {
    console.log(this)
  }
  b()
}
a1()
let a2 = () => {
  "use strict";
  console.log(this)
  let b = () => {
    console.log(this)
  }
  b()
}
a2()

在这段代码中,执行第9行时,按照我们之前的逻辑分析,第3行的this应该指向undefined,同样第5行的this也是指向undefined。

而执行第18行时,由于a2是箭头函数,它不提供this绑定,也就是说第12行的this其实是指向它外层的this,那也就是window,而第14行同样的道理,b是箭头函数,里面的this指向的是外层函数a2的this指向,而a2的this指向又是外层的window,因此第14行也是输出window。

看下结果:

输出

可以看到,跟我们分析的结果一样,相比于普通函数,箭头函数不会在当前执行环境自动生成this,而是使用外层的this。

也就是说,不管箭头函数是否嵌套,都符合我们之前的认知,也符合我们的预期输出。

构造函数

我们可以通过new关键字,来对一个函数进行实例化,当一个函数使用new关键字来执行的时候,它会生成一个实例对象,这个函数就叫做构造函数,这个实例对象就是这个构造函数的一个实例,注意:这里生成的实例是一个对象,你看我们都已经叫它实例对象了。

来看个代码:

function A(name) {
  "use strict";
  this.name = name
  this.sayHi = function() {
    console.log(`Hi ${this.name}`)
    return this
  }
}
let a1 = new A('Tom')
let b = a1.sayHi()
console.log(b === a1)

执行第8行代码,实例化一个对象a1,那么这个a1就有了一个属性name,值为Tom,还有个方法sayHi。

调用它的实例方法sayHi,可以找到它的调用对象是a1,那么第4行中的this应该就是指向的a1,因此它应该输出Hi Tom。那么第10行比对一下this和a1,它俩是一个,应该是true。

看下结果:

输出

根据上面讲的,你可能会问,那如果sayHi方法改成箭头函数呢?将会是什么样子?

function A(name) {
  "use strict";
  this.name = name
  this.sayHi = () => {
    console.log(`Hi ${this.name}`)
    return this
  }
}
let a1 = new A('Tom')
let b = a1.sayHi()
console.log(b === a1)

同样,我们来分析一下,在第9行调用sayHi方法的时候,虽然找到了a1这个调用对象,但是sayHi是一个箭头函数,因此不会自己绑定this,那么第4行的this就会使用外层的this,而外层的this就是a1,所以第4行还是同样输出Hi Tom,那么返回的this跟a1是同一个,第10行也就还是true。

输出

如果你还带着点将信将疑的态度,我们来稍微把代码改一下,再看看是什么样的,你就会明白了。

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = function() {
    console.log(`Hi ${this.name}`)
    return this
  }
}
let a1 = new A('Tom')
let b = a1.sayHi
b()

我们把实例对象a1的sayHi方法赋值给b,而不是直接调用,然后通过直接调用b,这时按照之前的逻辑,由于第11行执行的时候找不到调用对象,那么第5行的this就会是undefined,那么取name属性的时候就会报错。

看下是不是这样:

输出

那么如果给它换成是箭头函数呢?

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = () => {
    console.log(`Hi ${this.name}`)
    return this
  }
}
let a1 = new A('Tom')
let b = a1.sayHi
b()

同样的分析,箭头函数不会自己绑定this,那么第5行的this就会使用外层的this,也就是a1,因此就会正常输出Hi Tom。

输出

构造函数会自动把this指向当前生成的实例,并且默认返回它,我们也可以手动修改返回值,来重新指定this的指向。

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = function() {
    console.log(`Hi ${this.name}`)
    return this
  }
  return {
    name: 'Jack',
    sayHi: this.sayHi
  }
}
let a1 = new A('Tom')
a1.sayHi()

由于我们显示返回了一个对象,那么现在调用a1的sayHi方法,它的name值就变成了Jack:

输出

这样看起来,第3行的代码好像就没什么作用了,是的,除非你使用箭头函数:

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = () => {
    console.log(`Hi ${this.name}`)
    return this
  }
  return {
    name: 'Jack',
    sayHi: this.sayHi
  }
}
let a1 = new A('Tom')
console.log(a1)
a1.sayHi()

因为箭头函数的this绑定不是在执行时才确定,而是解析的时候就默认使用外层this,因此这时候虽然a1的name已经是Jack了,但是已经改变不了箭头函数中的this了,绑定了外面的name,也就是Tom:

输出

你可能会问,那这也太迷惑了,一个函数里面是不是出现了两个this啊,一会指向这个,一会指向那个,还有这个a1跟第3行的this是不是一个呀?我们用代码输出一下:

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = () => {
    console.log(`Hi ${this.name}`)
    console.log(a1 === this)
    return this
  }
  return {
    name: 'Jack',
    sayHi: this.sayHi
  }
}
let a1 = new A('Tom')
console.log(a1)
a1.sayHi()

输出

可以看到,已经不是同一个对象了,其实这个并不跟我们之前说的冲突,用我们刚才的分析仍然是正确合理的。

出现这样的现象是因为第3行执行的时候,this还是指向的当前实例,接着往下执行的时候,虽然第4行的sayHi这时还没有被执行,但是它在代码分析阶段,由于箭头函数的特性,已经将函数体内的this绑定成跟第三行this一样的对象,而我们在第9行手动重新指定了返回值,也就是更改了实例对象a1的值和它的this,但是已经绑定过的箭头函数中的第5行和第6行中的this,并不会发生改变。

对于这一点,我们可以用闭包的概念来理解,第5行和第6行中的this还保留着对滴3行的this的引用。

我们再通过一个例子来理解一下:

function A(name) {
  "use strict"
  this.name = name
  this.sayHi = () => {
    console.log(`Hi ${this.name}`)
    return this
  }
  return {
    name: 'Jack',
    sayHi: () => {
      console.log(`Hello ${this.name}`)
      return this
    }
  }
}
let a1 = new A('Tom')
console.log(a1)
let b = a1.sayHi
console.log(b())

这里第17行的a1应该是我们手动返回的对象,因此第19行执行的是第10行的sayHi,并且由于找不到调用对象,第10行又是箭头函数,那么第11行的this默认指向外层的name,也就是Tom。

输出

同样,如果第10行改成是普通函数,那么就会因为找不到调用对象而将this指向undefined,从而执行到第11行时,会抛出错误。

也就是说,不管构造函数是否手动指定返回值,实例属性是否为箭头函数,都符合我们之前的认知,也符合我们的预期输出。

call/apply

将这两个放在一块说,是因为它俩的作用都是一样的,只不过用法不同,上面我们讲的都是默认情况下this的指向问题,那么有没有方法在代码执行的时候由我们来手动指定,改变this 的指呢?

call和apply就是用来干这个事情的,它俩可以不经过任何人的同意,直接毫无道理的掰弯this指针,让它指向另一个对象。

由于call和apply只是调用形式上的不同,因此我们只拿call来讲解说明。

function a2() {
  "use strict";
  console.log(this.m)
}
a2()

毫无疑问,第3行将会报错,因为找不到第5行的调用对象,而且严格模式,因此第3行的this是undefined。

输出

那我们如果手动改变this指向呢?

let a1 = {
  m: 1,
  n:2
}
function a2() {
  "use strict";
  console.log(this.m)
}
a2.call(a1)

函数的自调用改成了使用call来调用,其中将a2方法体中的this强行指定为a1,让我们再来看下结果:

输出

已经正常输出了,是不是一点都不讲道理,这就是call和apply的神奇之处。

我们再用一个构造函数的例子,感受一下它俩的魔力:

let a1 = {
  name: 'Bob'
}
function A2(name) {
  "use strict";
  this.name = name
  this.sayHi = function() {
    console.log('call: Hi ' + this.name)
  }
}
let a2 = new A2('Tom')
a2.sayHi()
a2.sayHi.call(a1)

根据我们之前的逻辑,很容易得出第12行输出call: Hi Tom,而执行到第13行的时候,这里也是能找到调用对象,但由于它不是自执行,而是通过call来执行,那么既然麻烦了人家,因此也要以人家为准,this的指针由指向a2变成了指向a1,因此第13行应该输出call: Hi Bob。

输出

因此我们知道了,不管你在执行方法的时候找没找到调用对象,它都是以call中的第一个参数为this的值。

我们把sayHi改成箭头函数试试:

let a1 = {
  name: 'Bob'
}
function A2(name) {
  "use strict";
  this.name = name
  this.sayHi = () => {
    console.log('call: Hi ' + this.name)
  }
}
let a2 = new A2('Tom')
a2.sayHi()
a2.sayHi.call(a1)

还是同样的分析,第13行中this已经被指定成了a1,但是我们之前说过,箭头函数中的this不是在执行的时候确定的,而是在代码分析(代码会先分析,然后创建环境,再执行)的时候就已经绑定外层的this了,那么在执行第8行的时候,它的this已经绑定为跟第6行中的this一样的值了:

输出

可以看到,还是箭头函数厉害,无论外面的世界怎么变,我自岿然不动,而其实有时候这样才是我们想要的合理的效果,所以能用箭头函数就用箭头函数吧,省的产生一些令自己疑惑的问题。

也就是说,无论能否找到调用对象,只要使用call或者apply来调用,也符合我们之前的认知,也符合我们的预期输出。

bind

bind和call/apply的功能差不多,都是用来修改this的(其实这么说也不准确,this是不允许被赋值的,我们只能通过bind、call、apply来指定this的指向),只不过bind不会马上执行指定的函数,我们可以理解为预修改,并返回一个新的函数,当执行这个新的函数时,函数体内的this就指向了传给bind的第一个参数。

"use strict";
let a = {
  name: 'li'
}
function b() {
  "use strict";
  console.log(this.name)
}
let c = b.bind(a)
c()

如果直接执行b()的话,我们会得到一个错误,因为没有找到调用函数的时候,第7行的this为undefined,但是我们现在通过bind将b函数体内的this指向了a,那么在执行第10行代码的时候,this就指向了a对象,因此会输出li:

输出

同样,如果使用构造函数或者改成箭头函数也都跟call和apply一样。

对象

我们在判断this指向的时候,其实就是找的调用对象,所以对于一个对象来说,调用它的方法,默认的this就是指的自己本身。这一点来说,相对很容易理解。

"use strict";
let a = {
  age: 18,
  tellAge: function() {
    console.log(this.age)
  }
}
a.tellAge()

根据我们之前的逻辑,这里tellAge的调用对象就是a,因此第5行里面的this就是指的a对象,那么就应该输出18:

输出

我们再稍微复杂一点:

"use strict";
let a = {
  age: 18,
  tellAge: function() {
    console.log(this.age)
  },
  b: {
    age: 20,
    tellAge: function() {
      console.log(this.age)
    }
  }
}
a.tellAge()
a.b.tellAge()

第14行不用说了,在执行第15行的时候,由于tellAge函数是用b调用的,所以b才是它的调用对象,即使它也是a对象的一个属性,但是根据调用原则,只关心直接调用者,因此第10行的this应该是指向的b对象,那么应该输出20:

输出

我们再稍微变换一下:

"use strict";
var age = 25
let a = {
  age: 18,
  tellAge: function() {
    function m() {
      console.log(this.age)
    }
    m()
  }
}
a.tellAge()
let b = a.tellAge
b()

在执行第12行时,我们发现调用对象为a,那么tellAge中的this应该指向a,但是在执行第9行时,由于找不到m的调用者,那么第7行的this就是undefined,因此会抛出错误,同样第14行也是这个道理:

输出

如果把m变成箭头函数呢?

"use strict";
var age = 25
let a = {
  age: 18,
  tellAge: function () {
    let m = () => {
      console.log(this.age)
    }
    m()
  }
}
a.tellAge()

因为m是箭头函数,执行时不会自动绑定this,在分析阶段(代码会先经过分析阶段,然后创建环境,才会去执行)就将函数体内的this指向了外层的this,因此第7行的this其实就是指向了tellAge中的this,在第12行调用的时候,由于调用对象是a,那么tellAge中的this也就指向了a对象,因此将会输出18:

输出

这样其实也符合我们的程序设计和自己的认知,表现也是合理的,所以建议要使用箭头函数,以此来避免产生一些令人困惑的问题。

如果将tellAge也改为箭头函数,那么this将继续向外层指向:

"use strict";
var age = 25
let a = {
  age: 18,
  tellAge: () => {
    let m = () => {
      console.log(this.age)
    }
    m()
  }
}
a.tellAge()

这是由于tellAge也没有了自己的this绑定,虽然第12行调用是指定了并且能找到这个调用对象,但是由于箭头函数的特殊机制,它会绑定为外层this,也就是全局对象,浏览器下就是window,那么第7行应该输出25,我把第2行的age用var来声明,主要是为了能让它自动将这个变量作为属性添加到window上面:

输出

顺便多说一句,如果我们执行的是异步任务,那么默认的this指向会有一点不同。

如果是setTimeout,那么它的this指向将是window:

"use strict";
var age = 18
var b = {
  age: 19,
  tellAge: function() {
    console.log(this.age)
  }
}
setTimeout(b.tellAge)

虽然第9行是tellAge是通过对象b来访问的,感觉就是它的调用对象,但是在这里它是没有被执行的,而是由是setTimeout异步回调来执行的,会默认它是由全局对象来执行的,也就是调用对象变成了window,因此就默认把this指向window,从而输出18:

输出

如果是promise,那么它的this默认将变成undefined:

"use strict";
var age = 18
let a = {
  age: 19,
  tellAge: function() {
    console.log(this.age)
  }
}
Promise.resolve().then(a.tellAge)

由于this是undefined,所以第6行将会抛出错误:

输出

还有一种情况:

"use strict";
var age = 18
let a = {
  age: 19,
  tellAge: function() {
    console.log(this.age)
  }
}
;(0, a.tellAge)()

第9行的逗号表达式其实是返回了一个函数,因此找不到调用对象,跟let b = a.tellAge是一样的道理,因此第6行的this指向了undefined,导致会抛出错误:

输出

也就是说,无论对象嵌套多少层,我们都只需看函数的直接调用对象即可,也符合我们之前的认知,和符合我们的预期输出。

类中的this,其实跟函数差不多,因为类的本质就是函数,类中的非静态方法和变量都是默认添加到实例对象的原型中,类的构造函数中的this和非静态方法中的this也都会默认的指向当前的实例对象,除非你使用bind、call、apply等手段强制指定。

事件处理

在DOM元素的事件处理中,this默认指向当前元素,也就是当前绑定事件的dom对象:

"use strict";
let a = document.getElementById('myDiv')
a.addEventListener('click', function(e) {
  console.log(a)
  console.log(this === a)
})

由于this和a指向的是同一个元素,都代表这个div对象,因此它俩是相同的:

输出

this格式说明

其实说this是对象有点绝对了,我们在使用call等方法时,第一个参数其实可以指定为任意类型,this都会自动指向这个参数。无论是字符串也好,或者数值也好,只要调用的方法能找到它的调用者,那么this就会指向它,这里为了方便,同意成为了调用对象,看个示例:

"use strict";
function bar() {
  console.log(this.toFixed(2))
}
bar.call(123);
bar.call('123');

第5行将this手动指定为了123,因此第3行将会正确执行,并输出123.00,而第6行由于将this指定为了字符串’123’,但是字符串并没有toFixed方法,因此执行它将会抛出错误:

输出

其他类型同样,这里不再一一列出。

案例与经典例题

简单解两道网上关于this的题目,看看你现在能自己解答出来了吗?

第一道题:

"use strict";
var name = 'window'
function Person(name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = () => console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return () => console.log(this.name)
  }
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()
personA.show1.call(personB)
personA.show2()
personA.show2.call(personB)
personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()
personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()

下面是答案:

第20行 //personA -- 调用对象为personA
第21行 //personB -- 手动指定this指向personB
第22行 //personA -- 箭头函数绑定为外层this,指向实例对象personA
第23行 //personA -- 箭头函数绑定为外层this,指向实例对象personA
第24行 //报错 -- 先执行show3,返回一个匿名函数,这时再执行,找不到调用对象
第25行 //personB -- 手动指定this指向personB
第26行 //报错 -- 虽然将show3的this手动指向personB,但仍返回一个匿名函数从而找不到调用对象
第27行 //personA -- 先返回一个箭头函数,绑定为外层this,指向实例对象personA
第28行 //personA -- 先返回一个箭头函数,绑定为外层this,指向实例对象personA
第29行 //personB -- 先指定show4的this为personB,返回的箭头函数绑定为外层this也就是show4的this,所以为personB

第二道题:

"use strict";
var length = 4;
function callback() {
  console.log(this.length); // 输出什么 
}
const object = {
  length: 5,
  method() {
    arguments[0]();
  }
};
object.method(callback, 1, 2);

执行第12行的时候,由于method方法中没有this,因此我们先不考虑它,这里的arguments指代了传进来的类数组参数对象,也就是类似这样:

arguments

因此arguments[0]()其实也就相当于是这样:

"use strict";
let a = {
  age: 22,
  0: function() {
    console.log(this.age)
  }
}
a[0]()
a['0']() //或者这样,只是调用方式不一样
//还可以是a['b']或a.b的形式,它们都是一样的

这里的输出就是:

输出

因此arguments[0](),就是调用arguments的key为0的方法,它的调用对象就是arguments,因此第4行的输出就是3,答案如下:

输出

再回到我们最初开篇的那道题,相信不用我讲解你也能看明白啦。

var b = 1
function a() {
  this.b = 2
  console.log(b)
  return function () {
    console.log(this.b)
  }
}
let m = a()
console.log(b)
m()
function c() {
  this.b = 3
  function d() {
    console.log(this.b)
  }
  d()
}
new c()
console.log(b)

由于我们使用了非严格模式,在执行第9行时,直接执行a函数,那么第三行的this将指向window,因此第4行的b向上查找的时候,就会找到window下面的b的值,也就是2,因此第4行输出2。

返回了一个匿名函数赋值给了m,在执行到第10行时,全局的b已经被重新赋值为2,因此第10行输出2。

在执行第11行时,由于没有调用对象,并且在非严格模式,因此第6行this指向window,所以第6行同样输出2。

执行到第19行时,实例化了一个对象,进入函数体内,第17行由于没有调用对象,因此第15行的this指向window,同样也会输出2。

然后第20行还是访问window下的b属性,也会输出2。

怎么样?是不是觉得简单了很多呢!

总结

从我们一开始的逻辑,到最后我们的各个示例的印证,我们发现找到this的指向其实很简单,如果是箭头函数,那么就直接去找它外层的this,否则如果有bind、call、apply等手动指定的操作,就去看它们的第一个参数,否则就去找它的调用对象,如果找不到,那么指向undefined,在非严格模式下就指向window,最后异步任务下,setTimeout默认将this指向全局对象window,promise默认将this指向undefined。

大体优先级:箭头函数 > (bind、call、apply) > 调用对象

除非是箭头函数,直接指向外层this,其他的情况直接看调用的地方的代码就可以推断出来了。

由于this使用广泛,场景比较多,因此大家可能对它比较畏惧,也容易产生困惑,其实只要记住this永远都是对象、对象、对象,你就会发现变的容易的多。

希望这篇文章能够给予你帮助!谢谢!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值