Javascript

Javascript入门

Javascript的作用

它是一种脚本语言,可以用来更改页面内容,控制多媒体,制作图像,动画等

例子

  • 修改页面内容

虽然javascript能够修改页面的内容但是点刷新之后页面又会重置成原来的状态

js代码位置
<script>
  // js代码
  </script>

引入js脚本
<script src="js脚本路径"></script>
  • 注意,到了框架之后,引入方式会有不同

document对象:代表着整个html文档

变量与数据类型

声明变量
  • let
let 变量名 =;

let声明的变量将来能够被多次赋值,例如

let a = 100;//初始值为100
a = 200;//重新赋值成200
  • const

与java中的final类似

const 变量名 =;

const修饰的叫常量,只能被赋值一次

const a = 200; //a被赋值成200
a = 400; //发生报错,const修饰的为常量不能再被赋值 error

const并不意味着它引用的内容不可修改,例如

const c = [1,2,3];
c[2] = 4; //ok 修改之后的数组为c=[1,2,4]
c = [5,6]; //error,不能被再次赋值
  • var
var 变量名 =;

var声明的变量可以多次被赋值,例如

var f = 100;
f = 200; //ok

能用let不要用var

基本类型

1,2)undefined和null⭐
  • 执行表达式或函数,没有返回结果,出现undefined
  • 访问数组不存在的元素,访问对象不存在的属性,出现undefined
  • 定义变量没有初始化,出现undefined

二者共同点

  • 都没有属性,方法
  • Nullish

二者区别

  • undefined由js产生
  • null由程序员提供

image.png

3.)string⭐

js字符串三种写法

let a = "hello"; //双引号
let b = "world"; //单引号
let c = `hello`; //反引号

html代码如下,用java和js中的字符串如何表示

<a href = "1.html">超链接</a>

java

String s1 = "<a href = \"1.html\">超链接</a>";

String s2 = """
    <a href = "1.html">超链接</a>
    """;

js

let s1 = '<a href = "1.html">超链接</a>';

let s2 = `<a href = "1.html">超链接</a>`;

模板字符串(Template strings)
需求:拼接URI的请求参数,如

/test?name=zhang&age=18
/test?name=li&age=20

传统方法拼接

let name = ; //zhang li ...
let age = ; //18 20 ...

let uri = "/test?name=" + name + "&age=" + age;

模板字符串方式

let name = ; //zhang li ...
let age = ; //18 20 ...

 let uri = `/test?name=${name}&age=${age}`;

4,5)number和bigint⭐

number类型标识的是双精度浮动小数,例如

10 / 3;  //结果 3.3333333333335

既然是浮点小数,那么可以除零

10 / 0;				//结果Infinity 正无穷大
-10 / 0;			//结果 -Infinity 负无穷大

浮点小数都有运算精度问题,例如

2.0 - 1.1; //结果 0.8999999999999999999

字符串转数字

parseInt("10");			//结果是数字10
parseInt("10.5");		//结果是数字10,去除了小数部分
parseInt("10") / 3; 	//结果仍然视为number浮点数,因此结果为3.33333333333

parseInt("abc");		//转换失败,结果是特殊值NaN(Not a Number)

要表示真正的整数,需要用bigint,数字的结尾用n表示它是一个bigint类型

10n / 3n;			//结果3n,按整数除法处理

6)boolean⭐
  • Truthy
  • Falsy

在js中,并不是boolean才能用于条件判断,你可以在if语句中使用【数字】,【字符串】…作为判断条件

let b = 1;
if(b){ // true
  console.log("进入了");
}

这时就有一个规则,当需要条件判断时,这个值被当作true还是false,当作true的值归类为truthy,当作false的值归类为falsy
falsy

  • false
  • Nullish(null, undefined)
  • 0, 0n, NaN
  • “”, ‘’, ``

7) symbol

对象类型

1)Function⭐⭐

定义函数:
function 函数名(参数){
  //函数体
  return 结果;
}

例子:

function add(a,b){
  return a + b;
}

调用函数:
函数名(实参);

例子:

add(1,2); 	//返回3

js中的函数调用特点:对参数的类型和个数都没有限制,例如

add('a','b');			//返回ab
add(4,5,6);				//返回9,第三个参数没有被用到,不会报错
add(1);						//返回NaN,这时b没有定义是undefined,undefined做的数学运算结果就是NaN

默认参数:

java中(Spring)要实现默认参数的效果

@RestController
public class MyController {
    @RequestMapping("/page")
    @ResponseBody
    public void page(
        @RequestParam(defaultValue = "1") int page,
        @RequestParam(defaultValue = "10") int size
    ){
        // ...
    }
}

js

function pagination(page = 1, size = 10){
  console.log(page,size);
}

匿名函数:

语法

(function(参数){
  //函数体
  return 结果;
})

(function(a,b){
  return a + b;
})

第一种场景:定义完毕之后立刻调用

(function(a,b){
  return a + b;
})(1,2)

第二种场景:作为其他对象的方法,例如
页面有元素

<p id="p1">点我啊</p>

此元素有一个onclick方法,会在鼠标单击这个元素之后被执行,onclick()方法刚开始是null,需要赋值后才能使用

document.getElementById("p1").onclick = (function(){
  console.log("鼠标单击了...");
});

箭头函数:

匿名函数更简化的语法

(参数) => {
  //函数体
  return 结果;
}
  • 如果只有一个参数,()可以省略
  • 如果函数体里面只有一行代码,{}可以省略
document.getElementById("p1") = () => {
  return console.log("鼠标单击了箭头函数")
}
function abc() {
  console.log("bbb")
}

document.getElementById("p1").onclick = abc;

函数是对象

以下形式在js中非常常见!
1.可以参与赋值,例,具名函数也能参与赋值

function abc() {
  console.log("bb");
}

document.getElementById("p1").onclick = abc;

2.有属性,有方法
3.可以作为方法参数

function a(){
  console.log('a')
}

function b(fn){		//fn任何对象 将来可以是一个函数对象
  console.log('b')
  fn();		//调用函数对象
}

b(a);

4.可以作为方法返回值

function c(){
  console.log("c");
  function d(){
    console.log("d");
  }
  return d;
}

c()()

函数的作用域

函数可以嵌套(js代码中很常见,只是嵌套的形式更多是匿名函数,箭头函数)

function a() {
  function b(){
    
  }
}

看下面例子

function c() {
  var z = 30;
}
var x = 10;
function a() {
  var y = 20;
  function b (){
    //看这里
    console.log(y);
    console.log(x);
  };
  b();
}
a();
  • 以函数为分界线划定作用域,所有函数之外是全局作用域
  • 查找变量时,由内向外查找
    • 在内层作用域找到变量,就会停止查找,不会再找外层
    • 所有作用域找不到变量,报错
  • 作用域的本质上是对函数对象的属性,可以通过console.dir来查看调试

闭包
var x = 10;
function a() {
  var y = 20;
  function b() {
    console.log(x,y);
  }
  return b;
}
a()(); //调用a函数之后再调用b函数 在外面执行了b
  • 函数定义时,它的作用域已经确定好了,因此无论函数将来去了哪,都能从它的作用域中找到当时那些变量
  • 别被概念忽悠了,闭包就是指函数能够访问自己作用域中变量

###### let,var与作用域 如果函数外层引用的是let变量,那么外层普通的{}也会作为作用域边界,最外层的let也占一个script作用域 ```javascript let x = 10; if(true){ let y = 20; function b() { console.log(x,y); } console.dir(b); } ``` 如果函数外层引用的是var变量,外层普通的{}不会视为边界 ```javascript var x = 10; if(true){ var y = 20; function b() { console.log(x,y) } console.dir(b); } ``` 如果var变量出现了重名,则他俩会被视为同一作用域中的同一个变量 ```javascript var e = 10; if(true) { var e = 20; console.log(e); } console.log(e); ``` 要想里面的e和外面的e能区分开来,最简单的办法是改成let,或者用函数来界定作用域范围 ```javascript var e = 10; if(true){ function b(){ var e = 20; console.log(e); //打印20 } b(); } console.log(e); ```

2)Array⭐

语法
//创建数组
let arr = [1,2,3];

//获取数组元素
console.log(arr[0]);

//修改数组元素
arr[0] = 5; //将索引为0的元素修改为5

//遍历数组元素,其中length是数组属性,代表数组长度
for(let i = 0; i < arr.length; i++){
	console.log(arr[i]);
}

API
  • push,shift,splice
let arr = [1,2,3];
//在数组最后添加元素
arr.push(4);
//数组从左往右删除元素
arr.shift
//也是数组删除两个参数:第一个参数是从第几个索引开始删除,第二个参数是指从该索引往后删除多少元素
arr.splice(1,2)
  • join
let arr = ['a','b','c']

//默认使用【,】做连接符,结果'a,b,c'
arr.join();
//结果'abc'
arr.join('');
//结果'a-b-c'
arr.join('-')
  • map,filter,forEach
let arr = [1,2,3,4,5,6];

//[10,20,30,40,50,60]

function a(i){//代表新旧元素之间的变换规则
  return i * 10;
}

arr.map(a) //具体函数,结果[10,20,30,40,50,60]

arr.map((i)=>{return i * 10} ); //箭头函数

arr.map(i=> i * 10 );//简化后的箭头函数
  • 传给map的函数,参数代表着旧元素,返回值代表着新元素

map的内部实现(伪代码)

function map(a) {//参数是一个函数
  let narr = [];
  for(let i = 0; i <= arr.length; i++){
    let o = arr[i]; //旧元素
    let n = a(o);//新元素
    narr.push(n);
  }
  return narr;
}

filter例子

let arr = [1,2,3,6];
arr.filter((i) => {return i % 2 == 1}); //结果[1,3]
  • 传给filter函数,参数代表旧元素

forEach例子

let arr = [1,2,3,6]

for(let i = 0; i < arr.length; i++){
	console.log(arr[i]);
}

arr.forEach((i) => console.log(i));

两个称呼

  • 高阶函数map,filter,forEach
  • 回调函数,例如作为参数传入的函数

3)Object⭐⭐

语法
let obj = {
  属性名:,
  方法名: 函数,
  get 属性名() {},
  set 属性名(新值) {}
}

例1

let student = {
  name: "小明",
  age: 18,
  study: function() {
    consloe.log(this.name + "我爱学习");
  }
}

例2

let name = "小黑";
let age = 20
let study = function(){
  console.log(this.name + "爱学习")
}


let stu = {
  name,age,study
}

例3(重点掌握这种方法)

let student = {
  name: "小明",
  age: 18,
  study() {
    consloe.log(this.name + "我爱学习");
  }
}
  • 注意:对象方法名这么写仅限于对象内部

例4

let stu4 = {
  _name: null,/*类似于java中的私有成员变量*/
  get name(){
    console.log("进入了get");
    return this._name;
  },
  set name(name){
    console.log("进入了set");
    this._name = name;
  }
  
}

调用get,set

stu4.name = "小白"

console.log(stu4.name)

特色:属性增删

对比一下Java中的Object

  • Java的Object是以类作为模板来创建,对象不能脱离类模板的范围,一个对象的属性,能用的方法都是确定好的
  • js的对象,不需要什么模板,它的属性和方法可以随时加减
let stu = {name:'张三'};

stu.age = 22;

delete stu.age;

stu.study = function() {
	console.log(this.name + "在学习")
}

添加get,set

let stu = {_name:null};

Object.defineProperty(stu, "name", {
  get(){
    return this._name;
  },
  set(name){
    this._name = name;
  },
});

特色:this

先来对java中的this有个理解:

public class TestMethod {

    static class Student {
        private String nmae;

        public Student(String name){
            this,name = name;
        }

        public void study(Student this, String subject){
            System.out.println(this.name + "在学习" +subject);
        
        }
    }

    public static void main(String[] args){
        Student stu = new Student("小明");

        //下面的代码,本质上是执行study(stu, "java"),因此this就是stu
        stu.study("java");
    }
}

在java中,我们用this代表的调用方法的对象

js中的this也是隐式参数,但它与函数运行时上下文相关
例如,一个"落单"的函数

function study(subject){
  console.log(this.name + "在学习" + subject)
}

测试一下

study("js"); 	//输出 在学习 js

这是因为此时函数执行,全局对象window被当作了this,window对象的name属性是空串

同样的函数,如果作为对象的方法

let stu = {
  name:"小白",
  study
}

这种情况下,会将当前对象作为this

stu.study('js'); 	//输出 小白在学习

还可以动态改变this

let stu = {name:"小黑"};
study.call(stu,"js"); //输出	小黑在学习js

这回study执行时,就把call的第一个参数stu作为this

一个例外就是,在箭头函数内出现的this,以外层this理解
用匿名函数

let stu = {
  name: "小花",
  friends: ["小白","小黑","小明"]play() {
    this.friends.forEach(function(e){
      console.log(this.name + "与" + e + "在玩耍");
    });
  }
}
  • this.name所在的函数是【落单】的函数,因此this代表window
与小白在玩耍
与小黑在玩耍
与小明在玩耍

用箭头函数

let stu = {
  name:"小花",
  friends:["小白","小黑","小明"],
  play(){
    this.friends.forEach(e => {
      console.log(this.name + "与" + e + "在玩耍");
    })
  }
}
  • this.name所在的函数是箭头函数,因此this要看它外层的play函数,play又是属于stu的方法,因此this代表stu对象
小花与小白在玩耍
小花与小黑在玩耍
小花与小明在玩耍

不用箭头函数的做法

let stu = {
  name: "小花",
  friends: ["小白","小黑","小明"]play() {
    let _this = this;
    this.friends.forEach(function(e){
      console.log(_this.name + "与" + e + "在玩耍");
    });
  }
}

特色:原型继承
let father = {
  f1:'父属性',
  m1:function(){
    console.log("父方法");
  }
}

let son = Object.create(father);

console.log(son.f1);	//打印	父属性
son.m1();		//打印	父方法
  • father是父对象,son去调用.m1或.f1时,自身对象没有,就到父对象找
  • son自己可以添加自己的属性和方法
  • son里面有特殊属性_proto_代表它的父对象,js术语:son的原型对象
  • 不同浏览器对打印的son的_proto_属性时显示不同
    • Edge打印console.dir(son)显示[[Prototype]]
    • Firefox打印console.dir(son)显示

特色:基于函数的原型继承

出于方便,js又提供了一种基于函数的原型继承
函数职责

  1. 负责创建子对象,给子对象提供属性,方法,功能上相当于构造方法
  2. 函数有个特殊的属性prototype,它就是函数创建的子对象的父对象
    1. 注意!名字有差异,这个属性的作用就是为新对象提供原型
function cons(f2){
  //创建子对象,给子对象提供属性和方法
  this.f2 = f2;
  this.m2 = function(){
    console.log("子方法");
  }
}
//cons.prototype 就是父对象
cons.prototype.f1 = "父属性";
cons.prototype.m1 = function(){
  console.log("父方法");
}

配合new关键字,创建子对象

let son = new cons("子属性");

子对象的_proto_就是函数的prototype属性

JSON

之前我们将http请求格式时,讲过json这种数据格式,它的语法看起来与js对象非常相似,例如:
一个json对象可以长这样:

{
  "name":"张三",
  "age":18
}

一个js对象长这样:

{
  name:"张三",
  age:18
}

那么它们的区别在哪里呢?总结以下几点

  • 本质不同
    • json对象本质上是字符串,它的职责是作为客户端与服务器之间传递数据的一种格式,它的属性只是样子货
    • js对象是切切实实的对象,可以有属性方法
  • 语法细节不同
    • json中只能由null,true|false,数字,字符串(只有双引号),对象,数组
    • json中不能有除以上的其它js对象的特性,如方法等
    • json中的属性必须用双引号引起来

json字符串与js对象的转换

JSON.parse(json字符串); //返回js对象
JSON.stringify(js对象); //返回json字符串

动态类型

静态类型语言,如Java,值有类型,变量也有类型,赋值给变量时,类型要相符

int a = 10;
String b = "abc";

int c = "abc";  //错误

而js属于动态类型语言,值有类型,但变量没有类型,赋值给变量,没要求
例如

let a = 200;

let b = 100;
b = 'abc';
b = true;

动态类型看起来比较灵活,但是变量没有类型,会给后期维护带来困难,例如

function test(obj){
  // obj的类型未知,必须根据不同类型做出各种容错处理
}

运算符和表达式

1)===

严格相等运算符,作用做逻辑判等

1 == 1		//返回true
1 == '1'	//返回true,会先将右侧的字符串转为数字,再做比较
1 === '1'	//返回false,类型不等,直接返回false

typeof查看某个值的类型

2)||

需求,如果参数n没有传递,给它一个【男】
推荐做法

function test(n='男'){
  console.log(n);
}

其他做法1

function test(n){
  if(n === undefined){
    n = '男';
  }
  console.log(n);
}

其他做法2

function test(n){
  n = (n === undefined) ? '男' : n;
  console.log(n);
}

一些老旧的代码中可能的做法(不推荐)

function test(n){
  n = n || '男'
  console.log(n)
}

它的语法是

1||2

如果值1是Truthy,返回值1,如果值1是Falsy 返回值2

3)??与?.

需求,如果参数n没有传递或是null,给它一个【男】
如果用传统办法

function test(n) {
  if(n === undefined || n === null){
    n='男';
  }
  console.log(n);
}

用??

function test(n){
  n = n ?? '男';
  console.log(n);
}

需求,函数参数是一个对象,可能包含有子属性
例如,参数可能是

let stu1 = {
  name:"张三",
  address: {
    city:'北京'
  }
};

  let stu2 = {
    name:"李四"
  }

let stu3 = {
  name:"李四",
  address:null
}

现在要访问子属性(有问题)

function test(stu){
  console.log(stu.address.city)
}

现在希望当某个属性是nullish时,短路并且返回undefined

function test(stu){
  console.log(stu.address?.city)
}

用传统办法

function test(stu){
  if(stu.address === undefined || stu.address === null){
    console.log(undefined);
    return;
  }
  console.log(stu.address.city)
}

4)…

展开运算符
作用一:打散数组传递给多个参数

let arr = [1,2,3];

function test(a,b,c){
  console.log(a,b,c);
}

测试

test(arr[0],arr[1].arr[2]);

展开运算符写法

test(...arr);
  • 打散可以理解为【去掉了】数组外侧的中括号,只剩下数组元素

作用二:复制数组或对象
数组

let arr1 = [1,2,3];

let arr2 = [...arr1]; //复制数组

对象

let obj1 = {name:'张三',age:18};

let obj2 = {...obj1};		//复制对象

注意:展开运算符复制属于浅拷贝,例如

let o1 = {name:'张三',address:{city:'北京'}}

let o2 = {...o1};

作用三:合并数组或对象
合并数组

let a1 = [1,2];
let a2 = [3,4];

let b1 = [...a1,...a2];		//结果:1,2,3,4
let b2 = [...a2,5,...a1];	//结果:3,4,5,1,2

合并对象

let o1 = {name:'张三'};
let o2 = {age:18};
let o3 = {name:'李四'};

let n1 = {...o1,...o2};

//如果出现属性相同(后面的同名属性会将前面的同名属性给替换掉)例如name,则后面的name就会覆盖前面的name
let n2 = {...o1,...o2,...o3};

let n3 = {...o3,...o2,...o1};

5)[] {}

解构赋值

[]

用在声明变量时

let arr = [1,2,3];

let[a,b,c] = arr;		//结果a=1,b=2,c=3

用在声明参数时

let arr = [1,2,3];

function test([a,b,c]){
  console.log(a,b,c)
}

test(arr);	//结果 a=1,b=2,c=3

{}

用在声明变量时

let obj = {name:"张三",age:18};
let {name,age} = obj;

用在声明参数时

let obj = {name:"张三",age:18};

function test({name,age}){
  console.log(name,age);
}

test(obj)

控制语句

for in

主要用来遍历对象对象

let father = {name:'张三',age:18,study:function(){}};

for(const n in father){
  console.log(n);
}
  • 其中const n 代表遍历出来的属性名
  • 注意1:方法名也能被遍历出来(它其实也算一种特殊属性)
  • 注意2:遍历子对象时,父对象的属性会跟着遍历出来
let son = Object.create(father);
son.sex="男";

for(const n in son){
  console.log(n);
}
  • 注意3:在for in 内获取属性值,要使用[]语法,而不能用.语法
for(const n in son){
  console.log(n,son.[n]);
}

for of

主要用来遍历数组,也可以是其它可迭代对象,如Map,Set等

let a1 = [1,2,3];

for(const n in a1){
  console.log(n);
}

let a2 = [
  {name:'张三',age:18},
  {name:'李四',age:20},
  {name:'王五',age:25},
]

for(const n in a2){
  console.log(n.name,n.age);
}

for(const {name,age} in a2){
  console.log(name,age);
}

try catch
let stu1 = {name:'张三',age:18,address:{city:'北京'}};

let stu2 = {name:'张三',age:18};

function test(stu){
  try{
     console.log(stu.address.city)
  }catch(e){
    console.log('出现了异常',e.message)
  } finally {
    console.log('finally')
  }
}

API

nvm

1)安装nvm

nvm即(node version manager),好处是方便切换node.js版本
安装注意事项

  1. 要卸载掉现有的nodejs
  2. 提示选择nvm和nodejs目录时,一定要避免目录中出现空格
  3. 选用【以管理员身份运行】cmd程序来执行nvm命令
  4. 首次运行前设置好国内镜像地址
node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/

首先查看有哪些可用版本

nvm list available

输出
image.png
建议安装LTS(长期支持版)

nvm install 16.16.0
nvm install 14.20.0

执行nvm list会列出已安装的版本

nvm list

切换到16.16.0

nvm use 16.16.0

切换到14.20.0

nvm use 14.20.0

安装后nvm自己的环境变量会自动添加,但可能需要手工添加node.js的PATH环境变量

2)检查npm

npm是js的包管理器,就类似于java界的maven,要确保它使用的是国内镜像
检查镜像

npm get registry

如果返回的不是[https://registry.npm.taobao.org/](https://registry.npm.taobao.org/),需要做如下设置

npm config set registry https://registry.npm.taobao.org/

3)搭建简单服务

新建一个保存项目的client文件夹,进入文件夹执行

npm install express --save-dev

修改package.json文件

{
  "type":"module",
  "devDependencies":{
    "express": "^4.18.1"
  }
}
  • 其中devDependencies是npm install --save-dev添加的

编写main.js代码

import express from 'express'
const app = express()

app.use('',express.static('./'))
app.listen(7070)

执行js代码(运行前端服务器)

node main.js

前后端分离架构
image.png

查找页面元素

1.document.selector(“.xxx”) 根据类别查找单个

document.selector(".col")

2.document.selectorAll(“.xxx”) 根据类别查找全部

document.selectorAll(".col")

3.document.getElementById(“xxx”) 根据id查找单个

document.getElementById("h")

4.document.querySelector(“#xxx”) 根据id查到单个(前面要加#)

document.querySelector("#h")

修改页面元素

innerHTML:获取标签的内容或者修改标签的内容

document.querySelector(".title").innerHTML

document.querySelector(".title").innerHTML = '您想修改的内容'

textContent:也能获取标签的内容或者修改标签的内容

document.querySelector(".title").textContent

document.querySelector(".title").textContent = '您想修改的内容'

Fetch API

Fetch API 可以用来或缺远程数据,它有两种方式接收结果,同步方式与异步方式
格式

fetch(url,options)

同步方式

const 结果 = await(url,options);
//后续代码
  • await关键字必须在一个标记了async的function内来使用
  • 后续代码不会再结果返回前执行

异步方式

fetch(url,options).then(结果 => {...});
//后续代码
  • 后续代码不必等待结果返回就可以执行

跨域问题

image.png

  • 只要协议,主机,端口之一不同,就不同源,例如
    • http://localhost:7070/a和https://localhost:7070/b就不同源
  • 同源检查是浏览器行为,而且只针对fetch,xhr请求
    • 如果是其他客户端,例如java http client,postman,它们是不做同源检查的
    • 通过表单提交,浏览器直接输入url地址这些方式发送的请求,也不会做同源检查
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.SuperHero.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值