JS对象与函数,以及闭包

目录

1.JS对象学习

1.1.总述

1.2.对象属性

1.3.对象方法

1.4.对象访问器

1.5.对象构造器

1.6.对象原型

1.7.ES5对象方法

2.函数定义

2.1.总述

2.2.函数参数

2.3.函数调用

2.4.函数Call

2.5.函数Apply

2.6.闭包

3.闭包个人总结

3.1.参考资料

3.2.官方代码解析

4.ECMAScript继承机制实现

4.1.基本概念

4.2.对象冒充

4.3.对象冒充可以实现多重继承

4.4.对象冒充扩展应用:call()、 apply()

4.5.原型链prototype

4.6.混合方式


1.JS对象学习

1.1.总述

1.【所有JS值,除了原始值,都是对象】,那么也就是除了原始值之外,都是JS对象;

2.既然原始值除外,那么什么叫做原始值呢?【原始值指的是没有属性或方法的值】;

3.【原始数据类型指的是拥有原始值的数据】;

4.JS定义了 5 种原始数据类型:string、number、boolean、null、undefined;

5.【原始值是一成不变的(它们是硬编码的,因此不能改变)】,比如:x = 3.14,你只能修改x的值,但是无法改变3.14的值;

6.既然JS中,除了原始值之外都是对象,常见的对象形式有:

布尔是对象(如果用 new 关键词定义)

数字是对象(如果用 new 关键词定义)

字符串是对象(如果用 new 关键词定义)

日期永远都是对象

算术永远都是对象

正则表达式永远都是对象

数组永远都是对象

函数永远都是对象

对象永远都是对象

7.既然除了原始值都是对象,那么,什么叫做对象呢?【对象是包含变量的变量】、【JS变量只能包含一个值】、【对象也是变量,但对象能包含多个值】、【值按照名称 : 值对的形式编写(名称和值以冒号分隔)】、【JS对象是命名值的集合】;

8.对象属性就是对象中的命名值,对象属性可以是原始值、其他对象、函数;

9.对象方法是可以在对象上执行的动作,对象方法是包含函数定义的对象属性,JS对象是被称为属性和方法的命名值的容器;

10.那么,如何创建JS对象呢:通过对象文字来创建、通过关键词new来创建、通过构造器来创建、通过Object.create()来创建;

11.如何通过对象文字创建对象:对象文字指的是花括号{}中的名称:值对,例如:var person = {firstName:"Bill", lastName:"Gates", age:62, eyeColor:"blue"};

12.如何通过new关键字创建对象:var person = new Object();person.firstName = "Bill";

13.通过对象文字就直接创建对象并指定对象属性/方法,而new则是先创建对象,然后再去新建属性/方法,建议使用对象文字,这样更简单;

14.对象是易变的,其是通过对象引用来寻址,跟JAVA语言类似;

1.2.对象属性

1.JS对象是无序属性的集合;

2.属性通常可以被修改、添加、删除,但某些属性是只读的;

3.如何访问属性:objectName.property/objectName["property"]/objectName[expression];这里的expression是一个表达式,表达式结果必须是属性的名字,其实本质与前2个表达式一致;

4.可以通过for...in遍历对象属性;

5.如何新增属性:通过简单的赋值,向已存在的对象添加新属性;

6.如何删除属性:delete可以从对象中删除属性;

7.delete会同时删除属性的值和属性本身,删除完成后,属性在被添加回来之前是无法使用的;

8.delete被设计用于对象属性,它对变量或函数没有影响;

9.delete不应被用于预定义的JavaScript对象属性,因为这样做会使应用程序崩溃;

10.JS对象继承了它们的原型的属性;delete不会删除被继承的属性,但是如果删除了某个原型属性,则将影响到所有从原型继承的对象;

1.3.对象方法

1.JS方法是能够在对象上执行的动作;

2.JS方法是包含函数定义的属性;

3.在JS中,被称为this的事物,指的是拥有该JS代码的对象;

4.访问对象方法:methodName : function() { 代码行 }

5.访问对象方法:objectName.methodName();

6.注意:假如fullName是对应的方法,要想调用方法则必须通过()调用后会以函数形式执行,否则将返回函数定义:person.fullName()表示会以函数形式执行,而person.fullName将返回函数定义;

1.4.对象访问器

1.所谓访问器就是指Getter、Setter;

2.ECMAScript 5 (2009)引入的概念;

3.Getter/Setter定义的是被计算的属性;

4.说白了,还是属性,只不过这属性叫做【被计算的属性】,样子长得跟方法似的,但还是属性,访问普通属性一样;

1.5.对象构造器

1.用大写首字母对构造器函数命名是个好习惯;

2.通过new关键词调用构造器函数可以创建相同类型的对象;

3.对象添加新属性:myFather.nationality = "English";

4.对象添加新方法:myFather.name = function () {代码行};

5.无法动态为对象构造器添加新属性,只能去修改对应的代码;

6.无法动态为对象构造器添加新方法,只能去修改对应的代码;

7.JS提供用于原始对象的构造器:

var x1 = new Object();    // 一个新的 Object 对象

var x2 = new String();    // 一个新的 String 对象

var x3 = new Number();    // 一个新的 Number 对象

var x4 = new Boolean();   // 一个新的 Boolean 对象

var x5 = new Array();     // 一个新的 Array 对象

var x6 = new RegExp();    // 一个新的 RegExp 对象

var x7 = new Function();  // 一个新的 Function 对象

var x8 = new Date();      // 一个新的 Date 对象

8.可以用对象文字来取代构造器:

请使用对象字面量 {} 代替 new Object()。

请使用字符串字面量 "" 代替 new String()。

请使用数值字面量代替 Number()。

请使用布尔字面量代替 new Boolean()。

请使用数组字面量 [] 代替 new Array()。

请使用模式字面量代替 new RexExp()。

请使用函数表达式 () {} 代替 new Function()。

1.6.对象原型

1.所有JS对象都从原型继承属性和方法;

2.日期对象继承自Date.prototype(Date是对象构造器的名字,可以理解为Date(){XXXX});

3.数组对象继承自Array.prototype(Array是对象构造器的名字,可以理解为Array(){XXXX});

4.Object.prototype位于原型继承链的顶端(Object是对象构造器的名字,可以理解为Object(){XXXX});

5.日期对象、数组对象都继承自 Object.prototype;

6.可以通过prototype属性为对象构造器添加新属性;

7.可以通过prototype属性为对象构造器添加新方法;

8.请只修改您自己的原型。绝不要修改标准JS对象的原型;

1.7.ES5对象方法

ECMAScript 5为JS添加了大量新的对象方法,这里自己看官网吧,估计开发中要到的机会不是很大:https://www.w3school.com.cn/js/js_object_es5.asp

2.函数定义

2.1.总述

1.JS函数是通过function关键词定义的;

2.可以使用函数声明或函数表达式;

3.函数声明:function functionName(parameters) {要执行的代码}

4.函数表达式:var x = function (a, b) {return a * b};

5.被声明的函数不会直接执行,她们表示“保存供稍后使用”,当它们被调用时才被执行;

6.分号用于分隔可执行的JS语句,由于函数声明不是可执行的语句,以分号结尾并不常见;

7.函数也可以使用表达式来定义,函数表达式可以在变量中存储;

8.匿名函数(没有名称的函数):存放在变量中的函数不需要函数名。他们总是使用变量名调用;

9.函数表达式使用分号结尾,因为它是可执行语句的一部分;

10.函数也可以通过名为Function()的内建JavaScript函数构造器来定义;

11.函数提升:提升是JS将声明移动到当前作用域顶端的默认行为提升应用于变量声明和函数声明,如此,JS函数能够在声明之前被调用,但使用表达式定义的函数不会被提升;

12.自调用函数:函数表达式可以用作“自调用”,自调用表达式是自动被调用的,函数表达式会自动执行,但函数声明无法进行自调用,对函数表达式后面跟着(),来表示它是一个函数表达式;(函数声明会提升,函数表达式会自调用)

(function () {

  document.getElementById("demo").innerHTML = "Hello! I called myself";

  return function(){console.log('111111111111111111')}

})();

这里,函数自调用执行了document.getElementById("demo").innerHTML赋值语句,同时也执行了return语句,只不过function是一个对象,只是执行了方法的定义,没有执行方法体内部的逻辑;

(总结:如果自调用函数内部还有一个函数声明,则这个函数不会随着外部自调用函数执行,而被执行,因为没有“某物”来触发其执行,必须通过“某物”.方法名的方式来触发)

13.函数可用作值:JS函数可被用作值、JS函数可用在表达式中;

14.函数是对象:JS函数都有属性和方法;

15.对象方法:定义为对象属性的函数,被称为对象的方法;

16.对象构造函数:为创建新对象而设计的函数,被称为对象构造函数/对象构造器;

17.箭头函数:箭头函数允许使用简短的语法来编写函数表达式,不需要function关键字、return关键字、花括号;

18.箭头函数未被提升,因此必须在使用前进行定义;

19.使用const比使用var更安全,因为函数表达式始终是常量值;

20.箭头函数没有自己的this;

2.2.函数参数

1.JS函数不会对参数值进行任何检查;

2.函数参数:函数参数指的是在函数定义中列出的名称、函数参数指的是传递到函数或由函数接收到的真实值;

3.参数规则:①JS函数定义不会为参数规定数据类型;②JS函数不会对所传递的参数实行类型检查;③JS函数不会检查所接收参数的数量;

4.参数默认:如果调用参数时省略了参数(即少于被声明的数量),则丢失的值被设置为:undefined,最好给参数指定默认值;

5.如果函数调用的参数太多(超过声明),则可以使用arguments对象来达到这些参数;

6.arguments对象:JS函数有一个名为arguments对象的内置对象,arguments对象包含函数调用时使用的参数数组;

7.参数通过值传递:①函数调用中的参数是函数的参数;JS参数通过值传递:函数只知道值,而不是参数的位置;③如果函数改变了参数的值,它不会改变参数的原始值④参数的改变在函数之外是不可见的;

这个知识点的意思是,我举个例子,let a = 1;let b = 2; test(a,b),那么这里是把a和b的值传入了test,test拿到的是a和b的值,即test方法内的a和b,与方法外的a和b是两个独立的事务了,彼此不再关联

8.对象是由引用传递的:①JS中对象引用是值;如果函数改变了对象属性,它也改变了原始值;③对象属性的改变在函数之外是可见的

(这个知识点的意思是,对象是把对象引用传入到函数中,因此,函数中的对象,与函数外的对象都通过引用指向了内存的同一个地址,因此,一处改了,对象也就变了)

2.3.函数调用

1.JS函数内部的代码会在“某物”调用它时执行;

注意,这里用到了“某物”,即必须有个东西来触发函数执行,这个“某物”会贯彻整个本章节

2.调用JS函数:①在函数被定义时,函数内部的代码不会执行;②在函数被调用时,函数内部的代码会被执行;③调用函数通常也可以说“启动函数”或“执行函数”;

3.以函数形式调用函数:myFunction(10, 2); 在JS中,始终存在一种默认的全局对象,在HTML中默认全局对象是HTML页面本身,在浏览器中,这个页面对象就是浏览器窗口,上面的函数自动成为一个窗口函数,即myFunction()和window.myFunction()是同一个函数;

这里,“某物”就是JS默认的全局对象,放在浏览器上就是window对象,即这里的“某物”就是window对象,也就是window对象触发了函数执行

4.this:在JS中,被称为this的事物,指的是“拥有”当前代码的对象,this的值,在函数中使用时,是“拥有”该函数的对象,请注意this并不是变量,您无法改变this的值;

在函数中使用this时,也就是能触发该函数的“某物”,只不过函数中用this来表了这个“某物”,然后函数代码也算是定义到了这个“某物”内部,因此,你可以把“某物”理解成一个非常大的对象即可

5.全局对象:①当不带拥有者对象调用对象时,this的值成为全局对象;②在web浏览器中,全局对象就是浏览器对象;

像这种方式调用myFunction()时,这里没有明确点名“某物”具体是什么,因此,这里的“某物”就是全局对象,函数中的this就是“某物”,“某物”就是全局对象

6.调用一个函数作为一个全局函数,会导致this的值成为全局对象;

7.作为方法来调用函数:在JS中,可以把函数定义为对象方法,按照方法的定义:即作为某个对象属性的函数,叫做对象的方法,以对象方法来调用函数,会导致 this 的值成为对象本身;

这里的“某物”,就是这个对象了

8.通过函数构造器来调用函数:如果函数调用的前面是new关键字,那么这是一个构造函数调用,但由于JS函数是对象,你实际上创建一个新对象,构造器调用会创建新对象。新对象会从其构造器继承属性和方法,因此,如果构造器函数内的this会成为调用函数时创建的新对象;

new就意味着新建一个对象,这个“某物”就是新创建的对象

2.4.函数Call

1.方法重用:使用call()方法,可以编写能够在不同对象上使用的方法;

call()方法,就是可以把A对象的方法,应用到B对象上,仿佛A对象上的方法就是B对象上似的

2.函数是对象方法:一个函数,要么是JS对象的方法,要么是全局对象的函数

3.call()方法:①call()方法是预定义的JS方法;②call()方法可以用来调用所有者对象作为参数的方法;③通过call()能够使用属于另一个对象的方法;

(通俗理解:call()是JS默认自带的方法,随时随地可以调用,另外你可以把任何一个对象作为参数传入到call()方法中,然后函数中的this默认被替换为传入的对象

 

person通过【.】运算符调用了fullName方法,而fullName方法没有参数,但fullName是一个Function对象,然后调用call()方法传入person1对象,然后fullName方法中的this被替换成了person1对象;

4.带参数的call()方法:call()方法可接受参数;person.fullName.call(person1, "Seattle", "USA");

2.5.函数Apply

1.方法重用:通过apply()方法,能够编写用于不同对象的方法;

2.call()和apply()之间的区别:apply()方法与call()方法非常相似,但也有不同之处:call()方法分别接受参数,而apply()方法接受数组形式的参数,即apply()把参数都放入一个数组中,然后再把数组传入apply()中;

3.带参数的apply()方法:apply()方法接受数组中的参数;

4.JS严格模式:在严格模式下,如果apply()方法的第一个参数不是对象,则它将成为被调用函数的所有者(对象)。在“非严格”模式下,它成为全局对象;

2.6.闭包

1.JS变量,要么属于本地作用域,要么属于全局作用域;

2.全局变量能够通过闭包实现局部私有

3.全局变量:在网页中,全局变量属于window对象的,全局变量能够被页面中(以及窗口中)的所有脚本使用和修改;

4.局部变量:局部变量只能用于其被定义的函数内部,对于其他函数和脚本代码来说它是不可见的;

5.拥有相同名称的全局变量与局部变量是不同的变量,修改一个,不会改变其他;

6.不通过关键词var创建的变量总是全局的,即使它们在函数中创建;

7.变量的生命周期:全局变量活得和您的应用程序(窗口、网页)一样久,而局部变量活得不长,局部变量在函数调用时创建,在函数完成后被删除;

8.JS嵌套函数:①所有函数都有权访问全局作用域;②在JS中,所有函数都有权访问它们“上面”的作用域;③JS支持嵌套函数,嵌套函数可以访问其上的作用域;

9.闭包

// 初始化计数器

var counter = 0;

// 递增计数器的函数

function add() {

  counter += 1;

}

// 调用三次 add()

add();

add();

add();

这种写法存在一个问题:counter属于window对象的全局变量,任何一个第三方方法都可以绕过add()去直接操作counter变量,因此,必须想办法把window.counter由全局变量变成局部变量,放置被其他第三方方法直接操作;

// 初始化计数器

var counter = 0;

// 递增计数器的函数

function add() {

  var counter = 0;

  counter += 1;

}

// 调用三次 add()

add();

add();

add();

这种改进还是存在一个问题:add()方法内部定义的counter是局部变量,而window中的counter是全局变量,这2个变量彼此独立,任何一个第三方方法访问window.counter时永远得到0,因此,把把window.counter由全局变量变成局部变量目标没有得到实现;

// 递增计数器的函数

function add() {

  var counter = 0;

  counter += 1;

  return counter;

}

// 调用三次 add()

add();

add();

add();

这种改进,通过return counter方式把window.counter冲洗掉了,也算是把window.counter由全局变量变成局部变量了,但每次执行add()方法总是把counter置为0,因此,add()总是返回1,因此,必须想办法把局部counter的值,不能随着add()方法执行结束而销毁;

function add() {

    var counter = 0;

    function plus() {counter += 1;}

    plus();     

    return counter;

}

这里也算是在某种程度上解决了counter的生命周期问题:嵌套函数plus(),一方面可以访问add.counter变量,另外一方面plus方法执行的计算结果保存在add.counter变量中,当plus方法生命周期结束时,add.counter变量并不会因此而被销毁,但是add.counter的生命周期,还是会随着add执行结束而被销毁;

终极效果如下:

var add = (function () {

    var counter = 0;

    return function () {return counter += 1;}

})();

add();

add();

add();

变量 add 的赋值是自调用函数的返回值。

这个自调用函数只运行一次。它设置计数器为零(0),并返回函数表达式。

这样 add 成为了函数。

最“精彩的”部分是它能够访问父作用域中的计数器。

这被称为 JavaScript 闭包。它使函数拥有“私有”变量成为可能。

计数器被这个匿名函数的作用域保护,并且只能使用 add 函数来修改。

闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。

3.闭包个人总结

3.1.参考资料

https://developer.mozilla.org/zh-CN/docs/Glossary/Scope

当一个函数(foo)执行返回一个内部函数(bar)引用时,bar将会保存foo的作用域引用。例如:

function foo() {

    const str = "bar in foo";

    return function bar() {

        return str;

    }

}

var fun = foo();

fun(); // "bar in foo"

3.2.官方代码解析

var add = (function () {

    var counter = 0;

    return function () {return counter += 1;}

})();

官方语言

个人语言

变量add的赋值是自调用函数的返回值

通过();来指定自调用函数,然后这里return function()返回的是一个匿名函数,按照官方解释,这里是给add赋值了一个function()匿名函数的引用

这个自调用函数只运行一次。它设置计数器为零(0),并返回函数表达式。

通过();定义一个自调用函数,该函数执行2个操作:counter变量初始化、return赋值语句给add赋值

这样 add 成为了函数

因为return返回的是一个匿名函数的引用,因此,add最终指向了function () {return counter += 1;}

最“精彩的”部分是它能够访问父作用域中的计数器。

因为这个匿名函数是父作用域(function(){})中的一个嵌套函数,因此,能访问父作用域的counter变量

这被称为 JavaScript 闭包。它使函数拥有“私有”变量成为可能。

因为counter是局部变量,唯一能访问该变量的方式只能是其内部的嵌套函数,即拿到嵌套函数的add对象

计数器被这个匿名函数的作用域保护,并且只能使用 add 函数来修改。

现在add引用指的是function () {return counter += 1;},而它的父作用域消失了,任何其他人都无法访问到这个父作用域,因此,可以看做父作用域的counter消失了

闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。

这里随着自调用函数执行结束,add拿到了function () {return counter += 1;}引用,而这个引用对应的父函数随着自调用函数执行结束而结束;

4.ECMAScript继承机制实现

4.1.基本概念

1.所有开发者定义的类都可作为基类;

2.本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码,因为这些代码可以被用于恶意攻击;

3.你可能想创建一个不能直接使用的基类,它只是用于给子类提供通用的函数,在这种情况下,基类被看作抽象类;

4.创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现;

5.记住:所有属性和方法都是公用的,因此子类可直接访问这些方法。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法;

6.因为JS中的继承机制并不是明确规定的,而是通过模仿实现的,这意味着所有的继承细节并非完全由解释程序处理,因此,ECMAScript实现继承的方式不止一种,需要开发者自己去选择;

4.2.对象冒充

构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使ClassA构造函数成为ClassB的方法,然后再去调用它,然后ClassB就会收到ClassA的构造函数中定义的属性和方法,毕竟ClassA中的this指向了ClassB;

function ClassA(sColor) {

    this.color = sColor;

    this.sayColor = function () {

        alert(this.color);

    };

}

function ClassB(sColor, sName) {

    this.newMethod = ClassA;

    this.newMethod(sColor);

    delete this.newMethod;

    this.name = sName;

    this.sayName = function () {

        alert(this.name);

    };

}

在这段代码中,为 ClassA 赋予了方法 newMethod(请记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是 ClassB 构造函数的参数 sColor。最后一行代码删除了对 ClassA 的引用,这样以后就不能再调用它;

注意:所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法;

4.3.对象冒充可以实现多重继承

function ClassZ() {

    this.newMethod = ClassX;

    this.newMethod();

    delete this.newMethod;

    this.newMethod = ClassY;

    this.newMethod();

    delete this.newMethod;

}

这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举;

4.4.对象冒充扩展应用:call()、 apply()

对象扩展,其实是开发者开始理解函数的工作方式,尤其是如何在函数环境中使用this关键字后才发展出来,后来随着使用次数增多,ECMAScript的第三版为Function对象加入了2个方法:call()、 apply();

①call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身:

function sayColor(sPrefix,sSuffix) {

    alert(sPrefix + this.color + sSuffix);

};

var obj = new Object();

obj.color = "blue";

sayColor.call(obj, "The color is ", "a very nice color indeed.");

②apply()方法有两个参数,用作 this 的对象和要传递给函数的参数的数组:

function sayColor(sPrefix,sSuffix) {

    alert(sPrefix + this.color + sSuffix);

};

var obj = new Object();

obj.color = "blue";

sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));

注意:只有超类中的参数顺序与子类中的参数顺序完全一致时才可以传递参数对象arguments 。如果不是,就必须创建一个单独的数组,按照正确的顺序放置参数,此外,还可使用call()方法;

4.5.原型链prototype

原型链扩展了原型方式,以一种有趣的方式实现继承机制,其原理就是:prototype对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制;

function ClassA() {

}

ClassA.prototype.color = "blue";

ClassA.prototype.sayColor = function () {

    alert(this.color);

};

function ClassB() {

}

ClassB.prototype = new ClassA();

①想要ClassA的所有属性和方法,但又不想逐个将它们赋值给ClassB的prototype属性,还有比把ClassA的实例赋予prototype 属性更好的方法吗?

②调用ClassA的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数;

③子类的所有属性和方法都必须出现在prototype属性被赋值后,因为在它之前赋值的所有方法都会被删除。这是因为prototype属性被替换成了新对象,原始prototype对象将被销毁;

④原型链的弊端是不支持多重继承。记住,原型链会用另一类型的对象重写类的prototype属性;

4.6.混合方式

对象冒充方式的缺点在于必须使用构造函数方式;

原型链方式的缺点在于无法使用带参数的构造函数;

可以使用混合这2种方式来实现继承,总的原则就是:

①用构造函数定义属性,即用对象冒充继承构造函数的属性;

②用原型定义方法,即用原型链继承prototype对象的方法;

function ClassA(sColor) {

    this.color = sColor;

}

ClassA.prototype.sayColor = function () {

    alert(this.color);

};

function ClassB(sColor, sName) {

    ClassA.call(this, sColor);

    this.name = sName;

}

ClassB.prototype = new ClassA();

ClassB.prototype.sayName = function () {

    alert(this.name);

};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值