JS问题(1)

1.简述一个ajax的请求过程,以及注意事项

//1.通过XMLHttpRequest()构造函数创建httprequest对象
        var httpRequest = new XMLHttpRequest();
        //2.通过 XMLHttpRequest.open()方法打开链接,第一个参数为请求方式,第二个方式为请求的服务器地址,第三个参数为boolean,
        httpRequest.open('get','http://120.26.185.239:5588/category/findAll')
        //3.通过XMLHttpRequest.send()方法来发送请求,send()方法可以传递参数,如 在post请求中httpRequest.send(JSON.stringify(obj));
         //XMLHttpRequest.setRequestHeader()方法设置请求头的信息
        httpRequest.send();
       
        //httpRequest.setRequestHeader("Content-Type", "application/json")
        //4.请求成功了,通过XMLHttpRequest.onreadystatechange事件来处理响应
        //每当 readyState 属性改变时,就会调用XMLHttpRequest.onreadystatechange
        //XMLHttpRequest.readyState属性存有XMLHttpRequest对象的状态,从0到4发生变化
        /*
        0   表示请求未初始化
        1   服务器连接已经建立
        2   请求已经接收
        3   请求处理中
        4    请求已经完成,并且响应已经就绪
        */
        //注意: onreadystatechange 事件被触发 4 次(0 - 4), 分别是: 0-1、1-2、2-3、3-4,对应着 readyState 的每个变化。
        httpRequest.onreadystatechange=function(){
            if(httpRequest.readyState==4&&httpRequest.status==200){
                var data=httpRequest.responseText
                console.log('请求成功++++++++++',data)
            }
            else if(httpRequest.readyState==4&&httpRequest.status==500){
                console.log('请求失败')
            }
        }
注意点1:XMLHttpRequest.open()中的open函数中参数为:
  1. method 表示请求方式
  2. url 请求服务器的地址
  3. async true表示异步也就是脚本执行send()方法后不等待
    服务器的执行结果,而是继续执行脚本代码;false表示同步,也就是脚本执行send()方法后等待服务器的执行结果的返回,若在等待过程中超时,则不再等待,继续执行后面的脚本代码!;默认为true
注意点2:post请求不同于get请求
  • send()方法的post请求才使用字符串参数,否则不需要带参
  • get请求直接将参数使用?拼接在url的后面,send()方法中不用带参
  • post请求一定要根据后端需要的请求格式设置请求头的格式内容

2.post请求和get请求的区别

  • get请求的参数是暴露在url地址栏中,所以从安全性上来说,post请求比get请求安全一些
  • get请求的参数放置url字段中,以?开始,服务器端用Request.QueryString获取变量的值;而post请求的参数放置在请求体中,服务器端用Request.Form获取提交的数据
  • get请求的传参数量有限制,因为不同浏览器对url 的长度是有限制的,而post则没有
  • get请求的参数会跟随着url保留在浏览器的历史记录中,而post请求则不会
  • get请求能被缓存,但是post则不会
    在自己做项目中,get请求主要是用于做查询数据操作,而删除,修改,更新等操作都是使用post请求

3.JSON和JSONP的区别

答:JSON是一种远程数据传输格式,而JSONP是一种数据的获取方式,JSONP可以跨域

4.为什么会出现跨域?跨域的解决办法有?

答:由于浏览器同源策略的限制,非同源下的请求,都会产生跨域问题。即同一协议,同一域名,同一端口号。当其中一个不满足时,我们的请求即会发生跨域问题。 看下代码:

  1. http://www.demo.com与https://www.demo.com http 协议不同,所以跨域
  2. http://www.demo.com与http://www.demo.com:81 默认端口是80,所以请求端口不同,跨域
  3. http://www.demo.com与http://search.demo.com 域名不同,跨域
    非同源的限制:
  • 无法读取非同源下网页的Cookie、localstorage和indexed DB
  • 无法向非同源地址发送ajax请求

跨域的解决办法:

  1. 设置document.domain解决无法读取非同源网页的cookie问题

因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要通过设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同的跨域应用场景。)

// 两个页面都设置
document.domain = 'test.com';
  1. 跨文档通信 API:window.postMessage()

调用postMessage方法实现父窗口http://test1.com向子窗口http://test2.com发消息(子窗口同样可以通过该方法发送消息给父窗口)

//原网页
// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'title');
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Nice to meet you!', 'http://test2.com');

调用message事件,监听对方发送的消息:

    // 监听 message 消息
    window.addEventListener('message', function (e) {
      console.log(e.source); // e.source 发送消息的窗口
      console.log(e.origin); // e.origin 消息发向的网址
      console.log(e.data);   // e.data   发送的消息
    },false);
  1. JSONP
    JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。
    实现原理:我们的script标签的src还是img标签的src,或者说link标签的href他们没有被同源策略所限制,网页通过添加一个<script>元素,向服务器请求JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。当GET请求从后台页面返回时,可以返回一段JavaScript代码,这段代码会自动执行,可以用来负责调用后台页面中的一个callback函数
    <script src="http://test.com/data.php?callback=dosomething"></script>
    // 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
    // 处理服务器返回回调函数的数据
    <script type="text/javascript">
        function dosomething(res){
            // 处理获得的数据
            console.log(res.data)
        }
    </script>

JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

  1. CORS
    CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

  2. 普通的跨域请求:只需要服务器端设置Access-Control-Allow-Origin

  3. 带cookie的跨域请求:前后端都需要进行设置
    前端设置:XMLHttpRequest.withCredentials字段判断是否带有cookie

  • 原生AJAX
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
 
// 前端设置是否带cookie
xhr.withCredentials = true;
 
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); //post请求一定要设置请求头
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

  • axios
axios.defaults.withCredentials = true

服务器端设置
node.js

const http=require('http');
//创建服务器对象
let server = http.createServer((req,res)=>{
    //1. 封装参数获取的函数
    //2.不同的接口路径做不同的事情
    
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
    res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.setHeader("X-Powered-By", '3.2.1');
    res.end('123');
})
server.listen(3000,()=>{
    console.log('3000端口启动。。。。')
})

/*
 * 导入包:import javax.servlet.http.HttpServletResponse;
 * 接口参数中定义:HttpServletResponse response
 */
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "*"); 
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true"); 
  1. 用代理的方式来解决跨域问题
    同源策略是针对浏览器端进行的限制,所以我们跨域通过Proxy这个中间件来起到一个代理的作用,我是这样理解的,在我发送请求的时候,这条请求会被Proxy拦截,它再将我的请求按照我的配置,转发到真正的地址上去,然后再将结果返回给请求的源地址。本质上来说,是再本地起了一个node服务器去做请求的转发代理。
    在我实际运用中,我是使用http-proxy-middleware这个中间件,即在用vue-cli脚手架搭建的vue项目中,找到vue.confihg.js中这样设置
module.exports = {
    devSever:{
        proxy:"要转发到的实际的url地址"
    }
}

设置完成后,重启服务即可生效。

5. let和var和const的区别

都是用来定义变量
let对比var有如下特点:

  • let声明的变量不会被提升,即在变量声明之前无法使用该变量,否则产生“暂时性死区”
  • 具有局部作用域,即let声明的变量只能在对应代码块中使用
  • 不允许重复声明
    而const对比如下特点:
  • 跟let对比var类似,除此之外,const声明的变量在声明时就需要给予初始值,并且只能赋值一次,不能修改,它时用来定义常量的

6.简述一下=====的区别

  • == 是相等符,如果相等符两边的类型不同,那么将会进行隐式转换,例如’100’100,会将字符串’100’转换为数值100,即100100
  • ===是全等符,两边的类型和数值必须完全相同

7.javascript各个类型在转换为boolean类型的值的转换规则?

铭记:

  • " "(空字符串)、0NaNnull(空指针对象)、undefinedNullfalse用Boolean()包装器转都为false
  • 任意非空字符串、任意非零数字值、任何对象、symbol用Boolean()包装器转都为true
    注意点Boolean(undefined)报错ReferenceError: Null is not definedBoolean(Null)都会报错ReferenceError: Undefined is not defined,因为这两者都不适用于Boolean()包装器

8.Object.creat()和new Object()有什么区别

  • Object.create() Object.create()方法是ECMAScript5中新增的,用来规范化原型式继承的。创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法.这个方法接收两个参数,第一个参数是用作新对象原型的对象,和一个为新的实例对象本身定义额外属性的(可选)对象。
  • new Object() new Object()方法的实质是,使用引用类型Object的构造函数创建了一个新的实例,这个实例拥有Object默认的方法如toString、toLocaleString等。并且{}是javascript对象字面量创建的形式,其本质和new Object()并无区别,默认都是继承了Object对象上的prototype

首先要深入理解原型链 ,其次理解es6对象的API

//1
let obj={
    name:'zhangsan',
    age:12
}
console.log(obj)  //{ name: 'zhangsan', age: 12 }
console.log(obj.name) //zhangsan
console.log(obj.__proto__.name)  //undefined
console.log(obj.__proto__==Object.prototype) //true
//console.log(Object.prototype)
console.log(Object.getPrototypeOf(obj)) //{}
console.log(obj.toString()) //

let obj4=new Object(obj)
console.log(obj4) //{ name: 'zhangsan', age: 12 }
console.log(obj4.__proto__.name)//undefined
console.log(obj4.__proto__==Object.prototype)  //true
//2. 
let obj2={
    name:'lisi',
    age:15
}
//如果是上述方法创建的实例对象,那么其上的属性就是可修改,可枚举的
let obj3=Object.create(obj2,{
    name2:{
        enumerable:true , //表示该属性是否可枚举  ,默认为false
        configurable:true, //表示该属性是否可以修改或者删除,默认为false
        value:'lisi'   //该属性对应的值。默认为undefined
    }
})  //这样可以实现对象的浅拷贝
console.log(obj3) //{ name2: 'lisi' }
console.log(obj3.name) //lisi
console.log(obj3.age)  //15
console.log(obj3.__proto__.name)//lisi
console.log(obj3.__proto__.age)//15
console.log(obj3.__proto__==Object.prototype)  //false
console.log(Object.getPrototypeOf(obj3))// { name: 'lisi', age: 15 }

console.log(Object.keys(obj3)) // [ 'name2' ]  只能拿取实例本身可枚举的属性名的数组,
for (const key in obj3) {  //for in 拿到的原型链上可枚举的属性
    console.log(key)
    //obj3.hasOwnProperty()判断是否是该对象自己本身的属性
    if(obj3.hasOwnProperty(key)){
        console.log(obj3[key])
    }
}
//之所以可以调用是因为它调用的obj2中的原型对象中的方法,这里原型链接了两层,
//也就是说obj3的原型指向了obj2,而obj2的原型对象上有toString()方法,故可以调用
console.log(obj3.toString())  

//浅拷贝
let sourceObj = { a: { b: 1}};
let targetObj = {c: 3};
Object.assign(targetObj, sourceObj);
//当我们去修改targetObj.a.b=20000时,因为targetObj.a.b和sourceObj.a.b指向的同一个引用地址,
//那么我们变更targetObj.a.b的值,那么sourceObj.a.b的值也会随之改变,这就称为浅拷贝
targetObj.a.b = 20000;
console.log(targetObj.a.b)  //20000
console.log(sourceObj.a.b)  //20000

9. for in 和Object.keys()

  • for in 常常用来枚举对象的属性,for in 拿到的原型链上可枚举的属性。某些情况下可能会按照随机顺序遍历数组元素
  • Object.keys() 返回一个给定对象自己实例对象本身的所有可枚举属性名的数组

10.Object.assign()函数的作用及用法

答:Object.assign(target, ...sources)方法用于使得源对象(source)的所有可枚举的属性及它的值复制到目标对象(target),可以用来实现对象的浅拷贝
该函数的特性:

  • 如果该目标对象和源对象有同名属性或者说多个同源对象具有同名属性,则后面的属性会覆盖前面的属性
  • 如果该函数只有一个参数,当参数为对象时,将直接返回该函数,当参数不是对象时,会将该参数转为对象再返回
    可以使用该函数来实现对象的浅赋值
// 对象的浅拷贝 1.通过Object.assign()
 let sourceObj = { a: { b: 1}};
let targetObj = {c: 3};
Object.assign(targetObj, sourceObj);
//当我们去修改targetObj.a.b=20000时,因为targetObj.a.b和sourceObj.a.b指向的同一个引用地址,
//那么我们变更targetObj.a.b的值,那么sourceObj.a.b的值也会随之改变,这就称为浅拷贝
targetObj.a.b = 20000;
console.log(targetObj.a.b)  //20000
console.log(sourceObj.a.b)  //20000

//实现浅拷贝,2.Object.creat()
let obj={
    name:'wangwu',
    age:15
}
let obj2=Object.create(obj);
console.log(obj2.name)
obj.name="lisi"
console.log(obj.name)
console.log(obj2.name)

不过对于深复制来说,Object.assign()方法无法实现
拓展点:深复制
在实际的开发项目中,前后端进行数据传输,主要是通过JSON实现的。
JSON对象下有两个方法:

  • 一是将JS对象转换成字符串对象的JSON.stringify()方法;
  • 一个是将字符串对象转换成JS对象的JSON.parse()方法。
    这两个方法结合使用可以实现对象的深复制。也就是说,当我们需要复制一个obj对象时,可以先调用JSON.stringify(obj),将其转换为字符串对象,然后再调用JSON.parse()方法,将其转换为JS对象。就可以轻松的实现对象的深复制

11.数组的常用方法有哪些

可以这样记忆,9改变,8不变,12遍历
9改变:

  • Array.pop() 移除数组中的末尾项,并返回该项,同时数组的长度减一
  • Array.push() 可接受任意参数,并将它们添加到数组的末尾,并返回数组的长度
  • Array.shift() 移除数组中的首项,并返回该项,同时数组长度减一
  • Array.unshift() 可接受任意参数,并将它们添加到数组的首项,返回数组的长度
  • Array.reverse() 反转数组的顺序,改变原数组
  • Array.splice() 向数组中删除数组项,并返回被删除后的数组或插入数组返回插入后的数组项或替换数组项,返回被替换后的数组

当为删除时,那么Array.splice()指定两个参数(起始位置,要删除的项数)
当为插入时,指定三个参数(起始位置,0,要插入的任意数量的项)
当为替换时,指定三个参数(起始位置,要删除的项数,要插入的任意数量的项)

  • Array.sort() 对数组元素进行排序,并返回排序后的数组,参数可选:规定排序顺序的比较函数

没有可选参数时,默认按字母升序,如果元素不是字符串,会调用toString()方法来将其转为字符串的Unicode码再做比较

  • es6中的Array.fill() 用指定的数据填充满数组所有的内容

参数为三个:

  • 第一个参数为用来填充的值,如果只有一个参数那么,这个函数的作用就是用指定的数据填充满数组所有的内容
  • 第二个参数为可选,表示被填充的起始索引
  • 第三个参数为可选,被填充的结束索引,默认为数组末尾下标+1
  • es6中的Array.copyWithin() 在当前数组内部,将指定位置的成员复制到其他位置,并返回这个数组

参数为三个,并且这三个参数都为数值,如果不是将自动转为数值

  • 第一个参数为target(必需),表示从该位置开始替换数据,如果为负值,表示倒数
  • 第二个参数为start(可选),表示从该位置开始读取数据,默认为0,如果为负值表示倒数
  • 第三个参数为end(可选),表示到该位置停止读取数据,默认等于数组的长度,使用负数,可以从数组结尾处,规定位置

不改变原数组的8个方法

  • Array.join() 用于将数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串

参数str(可选):指定要使用的分隔符,默认使用逗号作为分隔符

  • Array.toLocaleString() 返回一个表示数组元素的字符串。该字符串由数组中的每个元素的toLocaleString()返回值经调用join()方法链接(由逗号隔开)组成
  • Array.toString() 用于将数组转为字符串的,默认以逗号分隔,但是与join方法相比没有优势,不能自定义分隔符,因此不推荐使用
  • Array.slice() 数组切割,浅拷贝数组的元素,返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个一个新数组对象,且原数组不会被修改

参数为二个:

  1. begin(可选):索引数值,接受负值,表示从该索引处开始提取原数组中的元素,默认为0
  2. end(可选):索引数值(不包括),接受负值,表示从该索引处结束提取原数组元素,默认值为数组末尾(包括最后一个元素)
  • Array.cancat() 数组拼接,用于合并两个或多个数组,返回一个新的数组。即先创建当前数组的一个副本,然后将接受到的参数添加到这个副本的末尾,返回副本,不改变原数组。

参数arrayX(必须):表示该参数可以时具体的值,也可以时数组对象。可以任意多个

  • indexOf() 从数组前面开始查找数组是否存在某个元素,返回下标
  • lastIndexOf() 从数组后面查找数组元素在数组中的最后位置,返回下标,如果不存在返回-1

参数为:

  1. searchElement(必须):被查找的元素
  2. fromIndex(可选): 逆向查找开始位置,默认值为数组的长度-1,即查找整个数组
  • es6中Array.includes() 用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

参数为:Array.includes(searchElement,fromIndex=0)

  1. searchElement(必须):被查找的元素
  2. fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。

12个遍历方法

  • Array.forEach()
  • Array.every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)
  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果没有满足条件的元素,则返回false。

注意点:

  1. 不会对空数组进行检测
  2. 不会改变原始数组
  • Array.some() 方法用于检测数组中的元素是否满足指定条件(函数提供)
  • 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
  • 如果没有满足条件的元素,则返回false。

注意点:

  1. 不会对空数组进行检测
  2. 不会改变原始数组
  • Array.filter() 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。
    注意点:
  1. 不会对空数组进行检测
  2. 不会改变原始数组
  • Array.map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
//map和filter的使用
let arr=[{name:'zhangsan',age:15},{name:'lisi',age:15},{name:'wangwu',age:18}]
let newArr=arr.map((item,index)=>{
    console.log(index) //0 1 2
    //返回一个新数组,数组为对象中name的属性值的数组
    return item.name   
})
let newArr2=arr.filter(item=>{
    //返回一个新数组,数组中的元素为name为wangwu这个对象
    return item.name=='wangwu'
})
console.log(newArr) //[ 'zhangsan', 'lisi', 'wangwu' ]
console.log(newArr2)  //[ { name: 'wangwu', age: 18 } ]

  • Array.reduce() 对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。一般用于数组求和

参数为:
第一个参数function(必须): 数组中每个元素需要调用的函数。为回调函数,回调函数中的参数为,第一个为求和的总值,初始值或上一次调用回调返回的值,第二个参数为数组元素当前的值,第三个参数为当前数组元素的索引值,第四个参数为数组元素本身
第二个参数initialValue(可选):为指定第一次回调 的第一个参数。

  • 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
  • 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
  • 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。
  • Array.reduceRight() 对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值。一般用以数组求和
  • es6中的Array.find() 用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。
  • es6中的Array.findIndex() 返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
  • es6中的Array.keys() 返回一个包含数组中每个索引键的Array Iterator对象。
  • es6中的Array.values() 返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值
  • es6中的Array.entries() 返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对

let arr = [2, 3, 4, 5, 6, 2]
//遍历
//这三个方法,返回的是迭代器对象,该迭代器实现了Iterator接口,只要有Iterator接口就可以用for-of遍历
//for of 常用于遍历数组
let keys = arr.keys();
let values = arr.values();
let entries = arr.entries();
//结果为:Object [Array Iterator] {} Object [Array Iterator] {} Object [Array Iterator] {}
console.log(keys, values, entries);
//keys,values,entries变量当前是迭代器对象
for (const iterator of keys) {
    /**
     * 0 keys
    * 1 keys
    * 2 keys
    * 3 keys
    * 4 keys
    * 5 keys
     */
    console.log( iterator,"keys")
}
for (const iterator of values) {
    /**
     * 2 values
    * 3 values
    * 4 values
    * 5 values
    * 6 values
    * 2 values
     */
    console.log( iterator,"values")
}
for (const iterator of entries) {
   /*
 [ 0, 2 ] entries
[ 1, 3 ] entries
[ 2, 4 ] entries
[ 3, 5 ] entries
[ 4, 6 ] entries
[ 5, 2 ] entries
*/
    console.log( iterator,"entries")
}

14.js中的内置对象有哪些?

答:有Object、Array、Date、RegExp、Function、Boolean、Number、String、Math、JSON

15.控制台undefined和is not defined异常区别

答:

  • 打印undefined说明该变量已经声明,但没有赋值,即初始化
  • 打印is not defined异常说明该变量连声明都没有
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值