前端笔记一

原型,原型链

原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个”[[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。

“[[Prototype]]”作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应创建所有指向该原型的实例的构造函数。

因为每个对象和原型都有原型,对象的原型指向原型对象,

而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。 

prototype是函数才有的属性,_proto_是每个对象都有的属性,但_proto_不是一个规范的属性,只有部分浏览器实现了这个属性,标准的属性是[[prototype]]。


this

this是javascript的一个关键字,随着函数使用场合不同,this的值会发生变化。但是总有一个原则,那就是this指的是调用函数的那个对象。

在最外层代码中,this引用 引用的是全局对象。

在函数内,this引用根据函数调用的方式的不同而有所不同。如下

1)构造函数的调用--this引用 引用的是所生成的对象

2)  对象方法的调用,this指向当前对象。

3)   通过apply或call方法调用,this指向传入的对象。

4)  闭包中的this --闭包:写在function中的function,this指向全局对象window。


APPLY,CALL

call方法: 
语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 
定义:调用一个对象的一个方法,以另一个对象替换当前对象。 
说明: 
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。 

apply方法: 
语法:apply([thisObj[,argArray]]) 
定义:应用某一对象的一个方法,用另一个对象替换当前对象。 
说明: 
如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。


闭包

闭包就是一个函数中嵌套一个函数,返回值设为嵌套函数就可以得到函数中的局部变量。

闭包可以用在许多地方。它的最大用处有两个,个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


跨域问题解决

第一: 使用 跨域资源共享(CORS)

跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

使用方法也很简单,在后端设置 Access-Control-Allow-Origin 头即可。

第二:使用jsonp 

什么是jsonp?维基百科的定义是:JSONP(JSON with Padding).

JSONP也叫填充式JSON,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:callback({"name","name1"});

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。

jsonp的原理是:

就是利用<script>标签没有跨域限制,来达到与第三方通讯的目的。

当需要通讯时,本站脚本创建一个<script>元素,地址指向第三方的API网址,并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。 
第三方产生的响应为json数据的包装(故称之为jsonp,即json padding),形如: 
callback({"name":"hax","gender":"Male"}) 
这样浏览器会调用callback函数,并传递解析后json对象作为参数。本站脚本可在callback函数里处理所传入的数据。 

(我们知道 <link href   <img src   <script src   请求的数据都不受域的限制)

jsonp的使用方法:

客户端指明使用jsonp的方式,服务器接受参数,并外包裹要返回的数据,再一并返回。

jquery的ajax简单描述:

前端指明data:jsonp , 在标明自定义的参数名 jsonp:jsoncallback

 第三 在本地搭建一个Web站点(代理),该站点可以向目标服务器发送HTTP请求,接收响应。它的行为跟浏览器类似,因此目标服务器是不用区分对待的。然后将本地的前端站点也部署到这个Web站点中,这样它们就属于同一个域了。所有针对目标服务器的Ajax请求,都发送到这个代理,然后由代理负责转发和接收响应。这样就避开了跨域之名,却有跨域之实。

Ajax

原理:ajax通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。

实现步骤:

1.创建XMLHTTPRequest对象

2.注册回调函数 xhr.onreadystatechange=callback;

3.设置和服务器端的链接信息。xhr.open(http请求方式(get,post),url,设置异步或同步方式交互(true,false));

4.发送数据开始交互。xhr.send(null); 

5.接受响应数据。


Ajax的优缺点:

 1.ajax进行异步通信,不会打断用户操作。

 2.页面无刷新,在页面中可以和服务器通信。

 3.“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。

 4.基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。


open()有三个参数,第一个是要发送的请求类型(get或post),第二个:请求的URL  第三个:是否异步发送请求的布尔值


局部刷新:从服务器获得数据,然后用javascript来操作DOM而更新页面


浏览器中输入url后

1.解析URL

2. DNS解析

3. 客户端与服务端建立TCP连接(三次握手)

4.请求和传输数据

5.浏览器渲染页面


浏览器的渲染过程

浏览器会解析三个东西: 
(1) HTML/SVG/XHTML,解析这三种文件会产生一个 DOM Tree。 
(2) CSS,解析 CSS 会产生 CSS 规则树。 
(3) JavaScript脚本,主要是通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree.

当浏览器获得一个html文件时,会“自上而下”加载,并在加载过程中进行解析渲染。 
解析: 
1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。 
2. 将CSS解析成 CSS Rule Tree 。 
3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。

4.有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。 
5.再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。


前端优化

请求数量——合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域 请求带宽——开启Gzip,精简JavaScript,移除重复脚本,图像优化 缓存利用——使用CDN,使用外部JavaScript和CSS,添加Expires头,减少DNS查找,配置ETag,使Ajax可缓存 页面结构——将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出 代码校验——避免CSS表达式,避免重定向 图片懒加载

1、实现思路:

     页面加载后只让文档可视区内的图片显示,其它不显示,随着用户对页面的滚动,判断其区域位置,生成img标签,让到可视区的图片加载出来

2、相关技术掌握:

     给img的父级加属性 (例如data-src),将图片的地址赋值给他,这样就生成img标签后再把data-src的值赋给img的src(通过dataset.src或者getAttribute('src'),再赋值给img.setAttribute('src'))

安全

XSS攻击原理

  特点:能注入恶意的HTML/JavaScript代码到用户浏览的网页上,从而达到Cookie资料窃取、会话劫持、钓鱼欺骗等攻击。

CSRF攻击

CSRF攻击跨站请求伪造。 本质:重要操作的所有参数都是可以被攻击者猜测到的。攻击者预测出URL的所有参数与参数值,才能成功地构造一个伪造的请求。     

常见前端框架对XSS攻击的防范    
 
 React 默认会转义所有字符串。
 
  AngularJS    使用AngularJS中的SCE来防御XSS攻击。

- XSS(Cross Site Script,跨站脚本攻击)是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式。跨站脚本攻击分有两种形式:反射型攻击(诱使用户点击一个嵌入恶意脚本的链接以达到攻击的目标,目前有很多攻击者利用论坛、微博发布含有恶意脚本的URL就属于这种方式)和持久型攻击(将恶意脚本提交到被攻击网站的数据库中,用户浏览网页时,恶意脚本从数据库中被加载到页面执行,QQ邮箱的早期版本就曾经被利用作为持久型跨站脚本攻击的平台)。XSS虽然不是什么新鲜玩意,但是攻击的手法却不断翻新,防范XSS主要有两方面:消毒(对危险字符进行转义)和HttpOnly(防范XSS攻击者窃取Cookie数据)。

 

- SQL注入攻击是注入攻击最常见的形式(此外还有OS注入攻击(Struts 2的高危漏洞就是通过OGNL实施OS注入攻击导致的)),当服务器使用请求参数构造SQL语句时,恶意的SQL被嵌入到SQL中交给数据库执行。

SQL注入攻击需要攻击者对数据库结构有所了解才能进行,攻击者想要获得表结构有多种方式:

(1)如果使用开源系统搭建网站,数据库结构也是公开的(目前有很多现成的系统可以直接搭建论坛,电商网站,虽然方便快捷但是风险是必须要认真评估的);

(2)错误回显(如果将服务器的错误信息直接显示在页面上,攻击者可以通过非法参数引发页面错误从而通过错误信息了解数据库结构,Web应用应当设置友好的错误页,一方面符合最小惊讶原则,一方面屏蔽掉可能给系统带来危险的错误回显信息);

(3)盲注。防范SQL注入攻击也可以采用消毒的方式,通过正则表达式对请求参数进行验证,此外,参数绑定也是很好的手段,这样恶意的SQL会被当做SQL的参数而不是命令被执行,JDBC中的PreparedStatement就是支持参数绑定的语句对象,从性能和安全性上都明显优于Statement。

 

- CSRF攻击(Cross Site Request Forgery,跨站请求伪造)是攻击者通过跨站请求,以合法的用户身份进行非法操作(如转账或发帖等)。CSRF的原理是利用浏览器的Cookie或服务器的Session,盗取用户身份。


前端安全问题主要有XSS、CSRF攻击
XSS:跨站脚本攻击
它允许用户将恶意代码植入到提供给其他用户使用的页面中,可以简单的理解为一种javascript代码注入。
XSS的防御措施:

  1. 过滤转义输入输出
  2. 避免使用evalnew Function等执行字符串的方法,除非确定字符串和用户输入无关
  3. 使用cookiehttpOnly属性,加上了这个属性的cookie字段,js是无法进行读写的
  4. 使用innerHTMLdocument.write的时候,如果数据是用户输入的,那么需要对象关键字符进行过滤与转义

CSRF:跨站请求伪造
其实就是网站中的一些提交行为,被黑客利用,在你访问黑客的网站的时候进行操作,会被操作到其他网站上
CSRF防御措施:

  1. 检测http referer是否是同域名
  2. 避免登录的session长时间存储在客户端中
  3. 关键请求使用验证码或者token机制

双向数据绑定

//脏检查  
我们说Angularjs(这里特指AngularJS 1.x.x版本,不代表AngularJS 2.x.x版本)双向数据绑定的技术实现是脏检查,大致的原理就是,  
Angularjs内部会维护一个序列,将所有需要监控的属性放在这个序列中,当发生某些特定事件时(注意,  
这里并不是定时的而是由某些特殊事件触发的),Angularjs会调用 $digest 方法,这个方法内部做的逻辑就是遍历所有的watcher,  
对被监控的属性做对比,对比其在方法调用前后属性值有没有发生变化,如果发生变化,则调用对应的handler。   
这种方式的缺点很明显,遍历轮训watcher是非常消耗性能的,特别是当单页的监控数量达到一个数量级的时候。  
              
//观察机制  
Object.observe()带来的数据绑定变革 ,说的就是使用ECMAScript7中的 Object.observe 方法对对象  
(或者其属性)进行监控观察,一旦其发生变化时,将会执行相应的handler。  
这是目前监控属性数据变更最完美的一种方法,语言(浏览器)原生支持,没有什么比这个更好了。唯一的遗憾就是目前支持广度还不行,有待全面推 
              
//封装属性访问器  
 国产mvvm框架vue.js实现数据双向绑定的原理就是属性访问器。  
 它使用了ECMAScript5.1(ECMA-262)中定义的标准属性 Object.defineProperty 方法。针对国内行情,  
 部分还不支持 Object.defineProperty 低级浏览器采用VBScript作了完美兼容,不像其他的mvvm框架已经逐渐放弃对低端浏览器的支持。 

ES6新特性

1.箭头函数

它简化了函数的书写。操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs

我们知道在JS中回调是经常的事,而一般回调又以匿名函数的形式出现,每次都需要写一个function,甚是繁琐。当引入箭头操作符后可以方便地写回调了。

2.类的支持

JS本身就是面向对象的,ES6中提供的类实际上只是JS原型模式的包装。现在提供原生的class支持后,对象的创建,继承更加直观了,并且父类方法的调用,实例化,静态方法和构造函数等概念都更加形象化。

3.增强的对象字面量

  • 可以在对象字面量里面定义原型
  • 定义方法可以不用function关键字
  • 直接调用父类方法

4.解构
自动解析数组或对象中的值。比如若一个函数要返回多个值,常规的做法是返回一个对象,将每个值做为这个对象的属性返回。但在ES6中,利用解构这一特性,可以直接返回一个数组,然后数组中的值会自动被解析到对应接收该值的变量中。
5.let,const
可以把let看成var,只是它定义的变量被限定在了特定范围内才能使用,而离开这个范围则无效。const则很直观,用来定义常量,即无法被更改值的变量。
6.模块

在ES6标准中,JavaScript原生支持module了。这种将JS代码分割成不同功能的小块进行模块化的概念是在一些三方规范中流行起来的,比如CommonJS和AMD模式。

将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。


7.promise
Promises是处理异步操作的一种模式,之前在很多三方库中有实现,比如jQuery的deferred 对象。当你发起一个异步请求,并绑定了.when().done()等事件处理程序时,其实就是在应用promise模式。

H5新特性
1. input type的扩展,
(1)email: 提供了邮箱格式的验证,形如:\w{1}@\w{1}
(2)url:提供了URL格式的验证,形如:[a-zA-Z]{1}:
(3)number:提供了数字格式的验证,形如:数字,提供了三个新的属性可用: max(最大) min(最小) step(步长)
(4)tel:在手机中弹出数字键盘,而不是字母键盘
(5)search:可能显示一个X用于清空已有输入
(6)range:范围选择、滑块选择,提供了三个新的属性可用: max(最大) min(最小) step(步长) value(初始值)
(7)color:颜色选择
(8)date:日期选择,弹出一个窗口,FF没有实现
(9)month:月份选择
(10)week:周选择

2.视频,音频
HTML5之后,播放视频和音频使用:
<video></video> 行内块元素
<audio></audio> 行内块元素

3.Canvas

4.拖放api

5.存储

客户端存储:
应用:浏览器历史记录、内容定制、样式定制、多久之内不再登录...
客户端存储的实现方法:

(1)Cookie :服务器保存在客户端的键值对 浏览器兼容性最好,最长可保存
只能保存4kb数据(400多个汉字)
(2)Flash:依赖于Flash播放器
(3)HTML5 sessionStorage/localStorage 会话存储/本地存储
HTML5 Wbe Storage 存储 :HTML新特性 ,最长不能超过8MB 
(4)IndexedDB :保存在客户端的“索引数据库”

 

HTML5 Web Storage 分为两个对象:
(1)window.sessionStorage:会话级存储
键值对形式的字符串,存储在浏览器进程的内存空间中,一个页面中保存的
数据可以供后续的所有页面使用;一旦浏览器窗口关闭、操作系统
重启,sessionStorage中的数据都会消失,其他浏览器进程不能访问
当前浏览器进程中的sessionStorage数据

总结:sessionStorage的用途:在某个浏览器窗口先后打开的多个页面间共享数据。

(2)window.localStorage:本地存储,跨会话级存储
键值对形式的字符串,存储在本地的文件系统(磁盘的文件)中,
这些数据在浏览器关闭后,甚至操作系统重启后,仍然可以使用。没有过期时间,
是永久存储

6.websocket


存储

Cookie

Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右,是网景公司的前雇员 Lou Montulli 在1993年3月的发明。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。

localStorage

localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你的 polyfill 的方案是种不错的选择。

sessionStorage

sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。

应用场景

有了对上面这些差别的直观理解,我们就可以讨论三者的应用场景了。

因为考虑到每个 HTTP 请求都会带着 Cookie 的信息,所以 Cookie 当然是能精简就精简啦,比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户,服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录啦。曾经还使用 Cookie 来保存用户在电商网站的购物车信息,如今有了 localStorage,似乎在这个方面也可以给 Cookie 放个假了~

而另一方面 localStorage 接替了 Cookie 管理购物车的工作,同时也能胜任其他一些工作。比如HTML5游戏通常会产生一些本地数据,localStorage 也是非常适用的。如果遇到一些内容特别多的表单,为了优化用户体验,我们可能要把表单页面拆分成多个子页面,然后按步骤引导用户填写。这时候 sessionStorage 的作用就发挥出来了。

安全性的考虑

需要注意的是,不是什么数据都适合放在 Cookie、localStorage 和 sessionStorage 中的。使用它们的时候,需要时刻注意是否有代码存在 XSS 注入的风险。因为只要打开控制台,你就随意修改它们的值,也就是说如果你的网站中有 XSS 的风险,它们就能对你的 localStorage 肆意妄为。所以千万不要用它们存储你系统中的敏感数据。




设计模式

工厂模式

  工厂模式是软件工程领域一种广为人知的设计模式,而由于在ECMAScript中无法创建类,因此用函数封装以特定接口创建对象。其实现方法非常简单,也就是在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。

?
1
2
3
4
5
6
7
8
9
10
11
functioncreateBlog(name, url) {
  varo = newObject();
  o.name = name;
  o.url = url;
  o.sayUrl=function() {
    alert(this.url);
  }
  returno;
}
 
varblog1 = createBlog('wuyuchang','http://www.jb51.net/');

可以看到工厂模式的实现方法非常简单,解决了创建多个相似对象的问题,但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,因此出现了构造函数模式。

  构造函数模式

  ECMAScript中构造函数可以创建特定类型的对象,类似于Array、Date等原生JS的对象。其实现方法如下:

?
1
2
3
4
5
6
7
8
9
10
functionBlog(name, url) {
  this.name = name;
  this.url = url;
  this.alertUrl = function() {
    alert(this.url);
  }
}
 
varblog = newBlog('wuyuchang','http://www.jb51.net/');
console.log(bloginstanceofBlog); // true, 判断blog是否是Blog的实例,即解决了工厂模式中不能

这个例子与工厂模式中除了函数名不同以外,细心的童鞋应该发现许多不同之处:

函数名首写字母为大写  (虽然标准没有严格规定首写字母为大写,但按照惯例,构造函数的首写字母用大写
没有显示的创建对象
直接将属性和方法赋值给了this对象
没有return语句
使用new创建对象
能够识别对象(这正是构造函数模式胜于工厂模式的地方)

  构造函数虽然好用,但也并非没有缺点,使用构造函数的最大的问题在于每次创建实例的时候都要重新创建一次方法(理论上每次创建对象的时候对象的属性均不同,而对象的方法是相同的),然而创建两次完全相同的方法是没有必要的,因此,我们可以将函数移到对象外面(也许有些童鞋已经看出缺点,嘘!)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
functionBlog(name, url) {
  this.name = name;
  this.url = url;
  this.alertUrl = alertUrl;
}
 
functionalertUrl() {
  alert(this.url);
}
 
varblog = newBlog('scjb51','http://sc.jb51.net/'),
  blog2 = newBlog('jb51','http://www.jb51.net/');
blog.alertUrl(); //http://sc.jb51.net/
blog2.alertUrl(); //http://www.jb51.net/

我们将alertUrl设置成全局函数,这样一来blog与blog2访问的都是同一个函数,可是问题又来了,在全局作用域中定义了一个实际只想让Blog使用的函数,显示让全局作用域有些名副其实,更让人无法接受的是在全局作用域中定义了许多仅供特定对象使用的方法,浪费空间不说,显然失去了面向对象封装性了,因此可以通过原型来解决此问题。

  原型模式

  我们创建的每个函数都有prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所有对象实例共享它所包含的属性及方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
functionBlog() {
}
 
Blog.prototype.name = 'wuyuchang';
Blog.prototype.url = 'http://tools.jb51.net/';
Blog.prototype.friend = ['fr1','fr2','fr3','fr4'];
Blog.prototype.alertInfo = function() {
  alert(this.name + this.url + this.friend );
}
 
// 以下为测试代码
varblog = newBlog(),
  blog2 = newBlog();
blog.alertInfo(); //wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4
blog2.alertInfo(); //wuyuchanghttp://tools.jb51.net/fr1,fr2,fr3,fr4
 
blog.name = 'wyc1';
blog.url = 'http://***.com';
blog.friend.pop();
blog2.name = 'wyc2';
blog2.url = 'http://+++.com';
blog.alertInfo(); //wyc1http://***.comfr1,fr2,fr3
blog2.alertInfo(); //wyc2http://+++.comfr1,fr2,fr3

原型模式也不是没有缺点,首先,它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得了相同的属性值,这样非常不方便,但这还是不是原型的最大问题,原型模式的最大问题在于共享的本性所导致的,由于共享,因此因此一个实例修改了引用,另一个也随之更改了引用。因此我们通常不单独使用原型,而是结合原型模式与构造函数模式。







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值