JavaScript高级

JavaScript高级

1.面向对象编程思想

  • 面向过程(POP): 分析解决问题的步骤,一步步的写出来
    • 优点: 性能比面向对象高,适合跟硬件联系很紧密
    • 缺点: 没有面向对象易维护,易扩展,易复用
  • 面向对象(OOP): 分解成一个个的对象,由对象分工合作; 特性: 封装性,继承性,多态性
    • 优点: 易维护,易复用,易扩展,由于面向对象有封装,继承,多态性,可以设计出低耦合高内聚
    • 缺点: 性能比面向对象低

2. constructor构造函数

constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法,如果没有显示定义,内部会自动给我们创建一个constructor()

对象原型(proto)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor属性,我们称为构造函数,因为指回构造函数本身

constructor主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数;
如果修改了原来的原型对象,给原型对像赋值的是一个对象,则必须手动利用constructor指回原来的构造函数

class A {   //创建类
    constructor(uname){//有参构造函数
    // this指的是创建实例的对象
        this.uname = uname;
    }
    say(){ //类的方法
    //this,谁调用指向谁,A
        console.log('我是类中的方法' + this.uname);
    }
}
class B extends A {//B继承A
    constructor(uname){
        super(uname); //调用父类构造函数,必须放在第一位
    }
    say(){ //类的方法
        console.log(super.say() + '我是类中的方法');
    }
}
//利用类创建对象 new
var ss = new A('ldh');// 会自动调用带参构造方法

function Fn(uname, age) {
    this.name = name;
    this.age = age;
}
Fn.prototype = {
    constructor: Fn, //指回原来对象
    say: function(){

    }
    sing: function(){
        
    }
}
console.log(Fn.prototype);//没有指回: 返回{say:f, sing:f),展开没有constructor属性;指回: 里面有构造函数constructor
console.log(Fn._proto_); // 没有指回: 返回{say:f, sing:f),展开没有constructor属性;指回: 里面有构造函数constructor
console.log(Fn.prototype.constructor);//如果没有指回返回object;指回,返回Fn函数
console.log(Fn._proto_.constructor); // 如果没有指回返回object;指回,返回Fn函数

3. 构造函数和原型

new 在内存中做四件事

  • 在内存中创建一个空对象
  • 让this指向这个新对象
  • 执行构造函数里面的代码,给这个新对象添加属性和方法
  • 返回这个新对象(所以构造函数里面不需要return)
//利用 new Object()创建对象
var obj1 = new Object();
// 利用对象字面量创建对象
var obj2 = {};
//构造函数
function Star(uname, age){
    //实例成员: 构造函数内部通过this添加的成员, uname, age, sing,只能实例化对象访问
    this.uname = uname;
    this.age = age;
    this.sing = function() {

    }
// 静态成员: 在构造函数本身上添加的成员, sex就是静态成员, 只能通过构造函数访问
Star.sex = '男';
}

4. 原型

构造函数方法很好用,但是非常浪费内存空间,每创建一个对象就要为方法多开辟一个空间

构造函数原型(prototype): 通过原型分配的函数是所有对象所共享的

JavaScript规定,每个构造函数都有一个prototype属性,指向另一个对象.注意prototype就是一个对象,这个对象的所有属性和方法都会被构造函数所用

一般情况,我们的公共属性定义在构造方法里面,公共属性定义在原型对象身上

function Star(uname, age){
    this.uname = uname;// this指向对象实例ldh,zxy
    this.age = age;
    /*this.sing = function() {

    }*/
}    
    var that;
    Star.prototype.sing = function(){   把方法放在原型上实现: 共享
        that = this
    }
var ldh = new Start('123',17);
var zxy = new Start('454',18);
console.log(ldh.sing === zxy.sing);
console.log(that === ldh); //true
// 方法在构造函数内: 输出false
//  方法在构造函数外的原型上: 输出true
console.dir(Start);// f Start(uname, age)
//可以把不变的方法,直接定义在prototype对象上,这样所有的实例就可以共享这些方法
ldh.sing()// 调用
console.log(ldh);// 对象身上系统自动添加一个_proto_指向我们构造函数的原型对象prototype
//方法查找规则: 首先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing,如果没有sing这个方法,因为有_proto_的存在,就去构造函数原型对象prototype身上查找sing这个方法
console.log(Star.prototype);// Object原型对象
console.log(ldh._proto_ === Star.prototype);//true  _proto_对象原型 === 原型对象prototype
console.log(Star.prototype._proto_ === Object.prototypr);  //true
// Start原型对象里面的_proto_原型指向的是Object.prototype
console.log(Object.prototypr._proto_);// null

对象原型(proto): 对象都有一个属性_proto_ 指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在

注意: proto 对象原型的意义就在于对象的查找机制提供的一个方向,或者说是一条路线,但是它是一个非标准的属性,因此实际开发不可以使用这个属性,它只是内部指向原型对象prototype

5 构造函数, 实例. 原型对象关系

在这里插入图片描述

JS成员查找机制

  • 当访问一个对象属性(包括方法时),首先查找这个对象自身有没有改属性
  • 如果没有就查找它的原型(_proto_指向prototype原型对象)
  • 如果还没有就查找原型对象的原型
  • 依次类推一直找到Object位置(null)
  • _proto_对象原型的意义就在于对象成员查找机制提供的方向或者说一条路线

原型对象this的指向

  • 构造函数中的this指向 对象实例
  • 原型对象函数里面的this指向 实例对象

6 继承

  • call()

调用这个函数并且修改函数运行时的this的指向

fun.call(thisArg,arg1,arg2, …)

  • 参数1: thisArg: 当前调用函数this的指向
  • 参数2-n: 传递其他的参数
function Father(x,y){
    this.x = x;
    this.age = age;
}
Father.prototype.money = function(){

}
function Son(x,y){
    // this执行Son
    Father.call(this,x,y);
}
// Son.prototype指向Father对象,Son原来的原型构造函数对象变成Father
Son.prototype = new Father();
Son.prototype.constructor = Son; //重新指回原来的构造函数
var son = new Son('lds', 18);

7 数组方法

迭代(遍历)方法: forEach(), map(), filter(), some(), every()

array.forEach(function(currentValue, index, arr))

  • currentValue: 数组当前项的值
  • index: 数组当前项的索引
  • arr: 数组对象本身

array.filter(function(currentValue, index, arr))

  • filter()方法创建一个新数组,新数组中的元素是通过检测指定数组中符合条件的所有元素,主要用于筛选数组
  • 注意它直接返回一个新数组

array.some(function(currentValue,index,arr))

  • some()方法检测数组中的元素是否满足指定条件,查找数组中满足条件的元素
  • 注意它返回值的布尔值,如果找不到返回false
  • 如果满足第一个条件的元素,则终止循环,不在继续
var arr = [1,2,3];
arr.forEach(function(value, index, array){console.log(value +' '+ index + ' ' + array);});  // 遍历
var newArr = arr.filter(function(value, index, array){return value >= 2});  // 返回满足条件的元素,形成新的数组
var flag = arr.some(function(value){return value >= 2});// 查找满足条件的就中止,返回true

8 字符串方法

str.trim() 删除字符串两端字符,返回新的字符串

9 对象方法

Object.keys(obj); 获取自身的所有属性名,类似for-in,返回一个由属性名组成的数组

Object.defineProperty(obj, prop, descriptor); 定义对象中新属性或修改原有的属性

  • obj 必须,目标对象
  • prop: 必须,需定义或修改的属性名字
  • descriptor: 必须,目标属性所拥有的特性: 以对象形式书写
    • value: 设置属性值,默认undefined
    • writable: 值是否可以重写,true | false(默认)
    • enumerable: 目标属性是否可以被枚举. true | false(默认)
    • configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false(默认)
var  obj = {
    id = 1,
    pname: '小米',
    price: 1999
};
// 以前对象添加和修改方式
obj.num = 10000;
obj.price = 99;

// 现在
Object.defineProperty(obj, 'num', {
    value: 10000 // 后面加的,默认enumerable: false
}); // 等价于 obj.num = 10000
Object.defineProperty(obj, 'price', {
    value: 9.9
}); // 等价于 obj.price = 9.9
Object.defineProperty(obj, 'id', {
    writable: false,//不允许修改这个值
    enumerable: false // 不能被枚举(遍历),默认为false
});

10 函数

10.1 函数定义

自定义函数

function fn(){};

函数表达式(匿名函数)

var fn = function(){}

利用new Function(‘参数1’, ‘参数2’, ‘函数体’,…);

var fn = new Function(‘a’, ‘b’, ‘console.log(a+b);’); fn(1,2);//调用

注意: 所有的函数都是new Function的实例对象

10.2 函数调用

  • 普通函数

function fn(){}; fn(); fn.call();

  • 对象方法

var o = {sayHi: function(){}}; o.sayHi();

  • 构造函数

function Start(){}; new Start();

  • 绑定事件函数

btn.onclick = function(){}; 点击按钮调用

  • 定时器函数

setInterval (function(){},1000);

  • 立即执行函数

(function(){}()); (function(){})();

10.3 this指向

调用方式this指向
普通函数调用window
构造函数调用实例对象, 原型对象里面的方法也指向实例对象
对象方法调用该方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即持行函数window

更改this指向: bind(), call(), apply()

  • 区别

    • call,apply会调用函数,bind不会
    • call,apply传参不同,call传参(ars1,args2,args3,…); apply必须数组形式
    • call一般做继承; apply对数组(伪数组)操作; bind不调用函数,更改内部指向,比如定时器this指向window
  • call()

    • 调用函数
    • 更改this指向
    • 继承(上面有实例)

fun.call(thisArg, arg1,arg2,…)

  • apply()
    • 调用函数
    • 更改this指向

fun.apply(thisArg, [argsArray])

  • thisArg: 在fun函数运行时指定this值
  • argsArray: 传递值,必须包含在数组里面
  • 返回值就是函数的返回值,因为它就是调用函数
var o = {
    name: 'andy'
};
function fn(arr) {

}
fn.apply(o,['1','2']);
// 利用apply,借助数学内置对象求最大值
var arr = [1,2,3,4,5];
Math.max.apply(Math,arr);// null 不改变this指向,  this指向Math
  • bind()
    • 不会调用函数,但是会更改this指向

fun.bind(thisArg, arg1, arg2, …)

  • thisArg: 在fun函数运行时指定的this的值
  • arg1,arg2: 传递的参数
  • 返回由指定的this值和初始化参数改造的原函数拷贝
var o = {
    name: 'andy'
};
function fn(a, b) {

}
//fn.bind(o);// 绑定不会执行
var f = fn.bind(o, 1, 2);
f();

// 应用
<button></button>
var btn = document.querySelector('button');
btn.onclick = function() {
    this.disabled = true; // 这个this本身指向btn
    setTimeout(function(){
        this.disabled = false; // 这个this本来指向window,更改指向btn
    }.bind(this),3000)// 不更改this指向window,更改this指向btn
}

10.4 严格模式

‘use strict’;

  • javascript提供严格模式(IE10+)和非严格模式
  • 严格模式消除了js语法的不合理性,不严谨之处,减少了一些怪异行为
  • 消除代码运行的一些不安全之处,保证代码运行的安全
  • 提高编译器效率,增加运行速度
  • 禁用ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的JavaScript做好铺垫,比如一些保留字: class, enum, export, extends, import, super不能做变量名
  • 开启严格模式应用到整个脚本或个别函数中.因此在使用时,我们可以将严格模式分为 "为脚本开启严格模式"和"为函数开启严格模式"两种情况
  • 不要在if,for里面声明函数

严格模式限定

  • 变量必须先声明在使用
  • 不能随意删除声明好的变量
  • 全局作用域中函数中的this指向undefined
  • 如果构造函数不加new调用,this会报错
  • new实例化的构造函数指向创建的对象实例
  • 定时器的this还是指向window
  • 事件,对象还是指向调用者

10.5 高阶函数

高阶函数: 对其他函数进行操作的函数,它"接收函数作为参数"或"将函数作为返回值输出"


    function fn(calllback){
        callback&&callback();
    }
    fn(function(){alert('hi')});

    function fn() {
        return function() {};
    }
    fn(); 
    // fn就是高阶函数, 函数也是一种数据类型,同样也是可以作为参数,传递给另外一个参数使用,最经典的事就作为回调函数使用

10.6 闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数; 闭包作用: 延伸了作用范围

//1
function fn(){
    var num = 10;
    function fun(){
       console.log(num); // 一个作用域访问了另外一个作用域的局部变量就会产生闭包
    }
    fun();
}
fn();
//2
function fn(){
    var num = 10;
    function fun(){
       console.log(num); 
    }
    return fun;
}
var f = fn();// 运行fn函数,return返回fun函数相当于 var f = fun(){console.log(num);}; 然后f()调用
f();
//3
function fn(){
    var num = 10;
    
    return function(){ // return匿名函数
       console.log(num); 
    };
}
var f = fn();
f();

10.7 递归

如果一个函数在内部自己调用自己那么这个函数就是递归函数

由于递归很容易发生"栈溢出"错误(stack overflow),所以必须要加退出条件return;

10.8 拷贝

  1. 浅拷贝只是拷贝一层, 对象级别只是拷贝引用地址,改一个对象属性,两个对象属性都改
  2. 深拷贝,堆中再开辟一个空间放入要拷贝的内容,每一级别的数据都会拷贝,改一个对象数据,不会两个都改

不用for-in循环浅拷贝,可以使用es6的Object.assign(targetObject, startObject)

11 正则表达式

11.1 概述

正则表达式(regular Expression)适用于匹配字符串中字符组合的模式,在JavaScript中,正则表达式也是对象

  • 特点
    • 灵活性, 逻辑性和功能性非常强
    • 可以迅速的用极简单的方式达到字符串的复杂控制
    • 不易上手, 邮箱正则: ^\w+()@\w+([-.]\w+).\w+([-.]\w+)*$

1.创建正则表达式

1.1 利用调用regExp对象的构造函数创建 正则表达式

var 变量名 = new regExp(/表达式/);

1.2 利用字面量创建正则表达式

var 变量名 = /表达式/;

2.测试正则表达式 test

test()正则对象方法,用于检测字符串是否符合该对象规则,该对象会返回true或false,其参数是测试字符串

regexObj.text(str);

11.2 特殊字符

一个正则表达式可以由简单的字符构成,比如/abc/,也可以是简单和特殊字符组合,比如/ab*c/.其中特殊字符也被称为元字符,在正在表达式中是具有特殊意义的专用符号,如^, $, + 等

1. 边界符

  • ^ 行首,以什么开始(开始)
    • var reg = /^123/; 判断是否以123开始
    • var reg = /[^a-zA-Z0-9]/; 中括号里面的^,是取反
  • $ 行位,以什么结尾(结束)
    • var reg = /^123$/; 判断是否以123开始,123结尾
  • [] 表示一系列字符可供选择,只要匹配其中之一就可以
    • var reg = /[123]/; //只要包含1,2,3其中一个都可以
    • var reg = /1$/; //只要是a | b | c单个都可以,也就是多选一,其他全为false
  • - 范围符
    • var reg = /[a-z]/; // 只要包含26个小写字母任何一个就可以
    • var reg = /[a-zA-Z0-9]/;// 26字母不区分大小写,0-9 10个数字
    • var reg = /[^a-zA-Z0-9]/; 中括号里面的^,是取反
  • * >= 0 可以出现的次数
    • var reg = /^a*$/; //a可以出现[0,Infinity)
  • + >= 1 可以出现的次数
    • var reg = /^a+$/; //a可以出现[1,Infinity)
  • ? 重复0次还是1次
    • var reg = /^a?$/; //a可以出现[0,1]
  • {n} 重复n次
    • var reg = /^abc{3}$/; //c只能出现3次,多一次少一次都不行
  • {n,} 重复n次或更多次
    • var reg = /^a{3,}$/; //a只能出现[3,Infinity)
  • {n,m} 重复n-m次
    • var reg = /^a{3,5}$/; //a只能出现[3,5]次
  • () 优先级
    • var reg = /^(abc){3}$/; // abc只能出现3次,多一次少一次都不行
  • \d 匹配0-9之间的任意数字,相当于[0-9]
  • \D 匹配0-9以外的字符,相当于[^0-9]
  • \w 匹配任意的字母,数字,下划线,相当于[A-Za-z0-9_]
  • \W 除所有任意的字母,数字,下划线以外,相当于[^A-Za-z0-9_]
  • \s 匹配空格(包括换行符, 制表符, 空格符等),相当于[\t\r\n\v\f]
  • \S 匹配非空格的字符,相当于[^\t\r\n\v\f]
  • | 或者
//座机号码验证: 全国座机号码 两种格式: 010-12345678  0530-1234567
var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/g
// 中文昵称昵称: 2-8位
var regs = /^[\u4e00-\u9fa5]{2,8}$/gi;

11.3 替换

stringObject.replace(regexp/substr,replacement) 只会替换第一个

  • 第一个参数: 被替换的字符串或者正则表达式
  • 第二个参数: 替换为的字符串
  • 返回值是一个替换完毕的新字符串

11.4 正则参数

/表达式/[switch]

  • g: 全局匹配
  • i: 忽略大小写
  • gi: 全局匹配和忽略大小写

12 ES6

ES(ECMAScript),它是由ECMA国际标准化组织制定的"一想脚本语言的标准化规范"

  1. let ES6声明变量的关键字
  • let声明的变量只在所处于的块级作用域有效,var没有
  • 防止循环的变量变成全局变量
  • 不存在变量提升,var具有
  • 暂时性死区
  1. const 声明常量,常量值就是值(内存地址)不能变化的量
  • 具有块级作用域 if(true){const a = 10};
  • 声明常量时必须赋值
  • 常量赋值后,值(内存地址)不能更改
const PI = 3.14;
PI = 1000; // Assignment to constant variable

const arr = [100, 200];
arr[0] = 'a'; // 没有更改ary在内存中的地址,只是更改了值
arr[1] = 'b';
console.log(arr);// ['a','b'];
arr = ['a','b']; // // Assignment to constant variable 这个更改了地址
    1. 使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
    1. 使用let声明变量,其作用域为该语句所在的代码内,不存在变量提升
    1. 使用const声明的常量,在后面出现的代码中不能再修改该常量的值
  1. 解构赋值: ES6中允许从数组中或对象中提取值,按照对应的位置,对应赋值.对象也可以实现解构
//数组解构
let [a,b,c] = [1,2,3];
console.log(a);
console.log(b);
console.log(c);
// 对象解构1
let person = {
    name: 'zhangsan',
    age: 20
};
let {name, age} = person;
console.log(name);// zhangsan
console.log(age);// 20

// 对象解构2
let person = {
    name: 'zhangsan',
    age: 20
};
let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
  1. 箭头函数: const fn = (形参) => {函数体}
  • 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
  • 如果形参只有一个,可以省略小括号
  • 不绑定this关键字,指向的是函数定义位置的上下文this
// 1
const fn = () => {

}
fn();

// 2
function sum(num1, num2){
    return num1 + num2;
}
// 简化
const sum = (num1, num2) => num1 + num2;
const result = sum(10, 20);

// 3
function fn(v){
    return v;
}
fn(v);

// 简化
const fn = v => {
    alert(v);
}
fn(20);

//4
const obj= {name: '张三'};
function fn() {
    console.log(this);
    return() => {
        console.log(this);
    }
}
const resFn = fn.call(obj);
resFn();
  1. 剩余参数: 允许我们将一个不定数量的参数表示为一个数组
/*function sum (first,...args){
    console.log(first); // 10
    console.log(args); // [20,30]
} */
const sum = (...args) => {
    let total = 0;
    /*args.forEach(item => {
        total += item;
    })*/
    args.forEach(item => total += item;);
    return total;
};
sum(10, 20, 30)
sum(10, 20)

// 剩余参数与解构配合
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1,...s2] = student;
console.log(s1); // wangwu
console.log(s2); //['zhangsan', 'lisi']
  1. 扩展运算符: 可以将数组或者对象转为用逗号分隔的参数序列
let ary = [1,2,3];
...ary //1,2,3
console.log(...arg);// 1,2,3

// 合并数组1
let ary2 = [3,4,5]
let ary3 = [...ary1, ...ary2]; // [1,2,3,3,4,5]
// 合并数组2
ary1.push(...ary2);

// 将伪数组转换为真正的数组
let oDiv = document.getElementsByTagName('div');
oDiv = [...oDivs]
  1. 构造函数方法: Array.from() 将类数组或可比案例对象转换为真正的数组
  • 第一个参数: 需要转换的伪数组转成数组
  • 第二个参数: 作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组
let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike);//['a','b','c']

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike, item => * 2);//['a','b','c']
  1. find() 找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [{
    id = 1,
    name: '张三'
},{
    id = 2,
    name: '李四'
}];
let target = ary.find((item,index) => item.id == 2);
  1. findIndex() 用处找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let arr = [1, 5, 10, 15];
let index = ary.findIndex((value,index) => value > 9);
console.log(index);//2
  1. includes() 表示某个数组是否包含给定的值,返回布尔值
[1,2,3].includes(2);//true
[1,2,3].includes(4);//false
  1. 模板字符串 ES6新增的创建字符串的方式,使用反引号
  • 可以解析变量
  • 可以换行
  • 调用函数
let name = `123`;
// 解析变量
let sayHello = `hello,my name is ${name}`;// ${str} 
//输出: hello,my name is 123

let result = {
    name: 'zhangsan',
    age: 20,
    sex: '男'
}
let html = `<div>
                <span>${result.name}</span>
                <span>${result.age}</span>
                <span>${result.sex}</span>
            </div>`;


/*const sayHello = function() {
    return 'hahahaha';
};*/
const sayHello = () => {
    return 'hahahaha';
}
let greet = '${sayHello()}hihihi`;
console.log(greet); //hahahahahihihi
  1. startsWith()/endsWith(): 表示参数字符串是否在原字符串的头部/尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello'); // true
str.endsWith('!');// true
  1. repeat(): 表示将原字符串重复n次,返回一个新的字符串
'x'.repeat(3); // 'xxx'
'hello'.repeat(2); // hellohello

13. Set数据结构

ES6提供了新的数据结构Set.类似于数组,但是成员的值都是唯一的,没有重复值

Set本身是一个构造函数,用来生成Set数据结构

const s = new Set();

Set函数可以接受一个数组作为参数,用来初始化

const set = new Set([1,2,3,4,4]);

  • 方法
    • add(value): 添加某个值,返回Set结构本身
    • delete(value): 删除某个值,返回一个布尔值,表示删除是否成功
    • has(value): 返回一个布尔值,表示该值是否为Set的成员
    • clear(): 清除所有的成员,没有返回值
    • forEach(): 遍历,用于对每个成员执行某种操作,没有返回值
//数组去重
const s3 = new Set(['a','b','a','b']);
console.log(s3.size);// 2
const ary = [...s3];
console.log(ary);//['a','b']
ary.add(1).add(2); // 向set结构中添加值
ary.delete(2);    // 删除set结构中的2值, 返回blue值
ary.has(1);   // 表示set结构中是否有1这个值,返回布尔值
ary.forEach(value => console.log(value));
ary.clear();//清除set结构中的所有值

  1. 123 ↩︎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值