node.js基础

浏览器的工作原理

浏览器的组成

  • 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分
  • 浏览器引擎- 用来查询及操作渲染引擎的接口
  • 渲染引擎(浏览器内核)- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来
    网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作
  • UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口
  • JS解释器- 用来解释执行JS代码
    数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了Storage技术,这是一种轻量级完整的客户端存储技术
  • 主流的渲染引擎
    浏览器的渲染引擎也叫排版引擎,或者是浏览器内核

主流的 渲染引擎 有

  • Chrome浏览器: Blink引擎(WebKit的一个分支)。
  • Safari浏览器: WebKit引擎,windows版本2008年3月18日推出正式版,但苹果已于2012年7月25日停止开发Windows版的Safari。
  • FireFox浏览器: Gecko引擎。
  • Opera浏览器: Blink引擎(早期版使用Presto引擎)。
  • Internet Explorer浏览器: Trident引擎 。
  • Microsoft Edge浏览器: EdgeHTML引擎(Trident的一个分支)。

渲染引擎解析的基本流程:

  1. 解析HTML构建Dom树,DOM 是W3C组织推荐的处理可扩展置标语言的标准编程接口。
  2. 构建渲染树,渲染树并不等同于Dom树,因为像head标签 或 display: none这样的元素就没有必要放到渲染树中了,但是它们在Dom树中。
  3. 对渲染树进行布局,定位坐标和大小、确定是否换行、确定position、overflow、z-index等等,这个过程叫layoutreflow
  4. 绘制渲染树,调用操作系统底层API(UI Backend)进行绘图操作。
    性能优化:重绘与回流(重排)
    回流(reflow)与重绘(repaint),在性能优化的时候,经常会提起,因为涉及到浏览器底层的渲染,所以掌握的童鞋并不多,但是面试经常被提及。

回流(reflow):当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。

重绘(repaint):当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。

每个页面至少需要一次回流+重绘。
回流必将引起重绘

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;

如何性能优化? 尽量减少重绘与回流的次数

  • 直接使用className修改样式,少用style设置样式
  • 让要操作的元素进行”离线处理”,处理完后一起更新
  • 使用DocumentFragment进行缓存操作,引发一次回流和重绘
  • 使用display:none技术,只引发两次回流和重绘;
  • 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素为动画的 HTML 元素,例如动画,那么修改他们的 CSS 是会大大减小 reflow 。

nodejs基本概念

NodeJS是前端项目的基础设施,前端项目中用到的大量工具,都是基于nodejs实现的
nodejs在处理高并发上有得天独厚的优势
相关文档可以参考:
node.js官方网站
node.js 中文社区
Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。

  1. nodejs不是一门新的编程语言,nodejs是在服务端运行javascript的运行环境
  2. 运行环境:写得程序想要运行必须要有对应的运行环境
    php代码必须要有apache服务器
    在web端,浏览器就是javascript的运行环境
    在node端,nodejs就是javascript的运行环境
  3. javascript并不只是能运行在浏览器端,浏览器端能够运行js是因为浏览器有js解析器,因此只需要有js解析器,任何软件都可以运行js。
  4. nodejs可以在服务端运行js,因为nodejs是基于chrome v8的js引擎。
    Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
    Node.js 的包管理器 npm,是全球最大的开源库生态系统。
    nodejs的本质:不是一门新的编程语言,nodejs是javascript运行在服务端的运行环境,编程语言还是javascript

nodejs与浏览器的区别

  • 相同点:nodejs与浏览器都是浏览器的运行环境,都能够解析js程序。对于ECMAScript语法来说,在nodejs和浏览器中都能运行。

  • 不同点:nodejs无法使用DOM和BOM的操作,浏览器无法执行nodejs中的文件操作等功能

nodejs可以干什么?

  • 开发服务端程序
  • 开发命令行工具(CLI),比如npm,webpack,gulp,less,sass等
  • 开发桌面应用程序(借助 node-webkit、electron 等框架实现)

node -v
环境变量
当要求系统运行一个程序 而没有告诉它程序所在的完整路径时,

运行nodejs程序

  • 方式一:REPL介绍
    REPL 全称: Read-Eval-Print-Loop(交互式解释器)
    R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
    E 执行 - 执行输入的数据结构
    P 打印 - 输出结果
    L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。
    在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
    直接在控制台输入 node 命令进入 REPL 环境
    按两次 Control + C 退出REPL界面 或者 输入 .exit 退出 REPL 界面
    按住 control 键不要放开, 然后按两下 c 键
  • 方式二:使用node执行js文件
    创建js文件 helloworld.js
    写nodejs的内容:console.log(‘hello nodejs’)
    打开命令窗口 cmd
    shift加右键打开命令窗口,执行 node 文件名.js即可
    给vscode安装terminal插件,直接在vscode中执行
    执行命令:node helloworld.js
    注意:在nodejs中是无法使用DOM和BOM的内容的,因此document, window等内容是无法使用的。

ES6语法:
let与const
ES6中提供了两个声明变量的关键字:const和let

let的使用
ES6 新增了let命令,用来声明变量。它的用法类似于var。
let声明的变量只有在当前作用域有效

const的使用
const声明一个只读的常量。常量:值不可以改变的量
const声明的量不可以改变
const声明的变量必须赋值
其他用法和let一样

  1. 只能在当前代码块中使用
  2. 不会提升
  3. 不能重复
    let与const的使用场景
    如果声明的变量不需要改变,那么使用const
    如果声明的变量需要改变,那么用let

ES5-数组的新方法
forEach
forEach() 方法对数组的每个元素执行一次提供的函数。功能等同于for循环.

  • 第一个参数:element,数组的每一项元素
  • 第二个参数:index,数组的下标
  • 第三个参数:array,正在遍历的数组

map
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

  • 第一个参数:element,数组的每一项元素
  • 第二个参数:index,数组的下标
  • 第三个参数:array,正在遍历的数组
  • 返回值:一个新数组,每个元素都是回调函数的结果。

filter
filter用于返回满足条件的元素,返回一个新数组,如果在回调函数中返回true,那么就留下来,如果返回false,就扔掉

  • 第一个参数:element,数组的每一项元素
  • 第二个参数:index,数组的下标
  • /第三个参数:array,正在遍历的数组
  • 返回值:一个新数组,存储了所有返回true的元素

some
some用于遍历数组,如果有至少一个满足条件,就返回true,否则返回false。

  • 第一个参数:element,数组的每一项元素
  • 第二个参数:index,数组的下标
  • 第三个参数:array,正在遍历的数组
  • 返回值:布尔类型的值,只要有一个回调函数返回true,就返回true

every
every用于遍历数组,只有当所有的元素返回true,才返回true,否则返回false。

  • 第一个参数:element,数组的每一项元素
  • 第二个参数:index,数组的下标
  • 第三个参数:array,正在遍历的数组
  • 返回值:布尔类型的值,只有当所有的元素返回true,才返回true,否则返回false。

find
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

ES6语法-字符串新方法

  1. startsWith() 是否以谁开头 布尔类型
  2. endsWith() 是否以谁结尾
  3. includes() 是否包含
  
    let str = "abcdef"
    console.log(str.startsWith('ab'));  // true
    console.log(str.startsWith('bb'));  // false
    console.log(str.endsWith('ef'));    // true
    console.log(str.endsWith('ff'));    // false
    console.log(str.includes('ab'));    // true
    console.log(str.includes('bc'));    // true

ES6语法- 对象简写
1- 在对象中,如果属性名和变量名相同的话,可以省略一个
2- 在对象中,方法也可以简写, 不写function

    let name = 'zs'
    let age = 18 


3 在对象中,如果属性名和变量名相同的话,可以省略一个
4 在对象中,方法也可以简写

ES6语法-函数默认参数
1 es6的函数允许直接传递默认参数
2 当调用函数未传递参数时会使用默认参数

ES6语法-箭头函数
1 ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
2 为什么叫Arrow Function?因为它的定义用的就是一个箭头:

基本使用
var fn = function(x, y) {
    console.log(x + y);
}
​
相当于
//语法: (参数列表) => {函数体}
var fn = (x, y) => {
    console.log(x + y);
}
参数详解
如果没有参数列表,使用()表示参数列表
var sum = () => {
    console.log('哈哈')
};
// 等同于:
var sum = function() {    
    console.log('哈哈')
};
如果只有一个参数,可以省略()
// 等同于:
var sum = function(n1) {    
    console.log('哈哈')
};var sum = n1 => {
    console.log('哈哈')
};
如果有多个参数,需要使用()把参数列表括起来
var sum = function(n1, n2) {    
    console.log('哈哈')
};var sum = (n1, n2) => {
    console.log('哈哈')
};
返回值详解
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来
var sum = function(n1) {    
    console.log('哈哈')
};var sum = n1 => {
    console.log('哈哈')
};
如果函数体只有一行一句,并且需要返回这个值,那么可以省略{}return
var fn = function(n1, n2) {
    return n1 + n2;
}var fn = (n1, n2) => n1 + n2;

nodeJs

global模块-全局变量

  • JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

  • 在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

  • 在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

常用的global属性

  • console: 用于打印日志
  • setTimeout/clearTimeout: 设置清除延时器
  • setInterval/clearInterval: 设置清除定时器​
  • __dirname: 当前文件的路径,不包括文件名
  • __filename: 获取当前文件的路径,包括文件名
    fs模块
  • fs模块是nodejs中最常用的一个模块,
  • 文档地址:http://nodejs.cn/api/fs.html
  • 在nodejs中,提供了fs模块,这是node的核心模块
    注意:
    除了global模块中的内容可以直接使用,其他模块都是需要加载的。
    fs模块不是全局的,不能直接使用。因此需要导入才能使用。
var fs = require("fs");

读取文件

  • 语法:fs.readFile(path[, options], callback)
    - 方式一:不传编码参数
    参数1: 文件的名字
    参数2: 读取文件的回调函数
    回调函数参数1:错误对象,如果读取失败,err会包含错误信息,如果读取成功,err是null
    回调函数参数2:读取成功后的数据(是一个Buffer对象)
fs.readFile("data.txt", function(err, data){
  console.log(err);
  console.log(data);
});

方式二:传编码参数
参数1: 文件的路径
参数2: 编码,如果设置了,返回一个字符串,如果没有设置,会返回一个buffer对象
参数3: 回调函数

fs.readFile("data.txt", "utf8",function(err, data){
  console.log(err);
  console.log(data);
});

关于Buffer对象

  1. Buffer对象是Nodejs用于处理二进制数据的。
  2. 其实任意的数据在计算机底层都是二进制数据,因为计算机只认识二进制。
  3. 所以读取任意的文件,返回的结果都是二进制数据,即Buffer对象
  4. Buffer对象可以调用toString()方法转换成字符串。
    写文件
    语法:fs.writeFile(file, data[, options], callback)
    参数1:写入的文件名(如果文件不存在,会自动创建)
    参数2:写入的文件内容(注意:写入的内容会覆盖以前的内容)
    参数3:写文件后的回调函数
fs.writeFile("2.txt", "hello world, 我是一个中国人", function(err){
  if(err) {
    return console.log("写入文件失败", err);
  }
  console.log("写入文件成功");
});

注意:
写文件的时候,会把原来的内容给覆盖掉
追加文件
语法:fs.appendFile(path, data[, options], callback)
参数1:追加的文件名(如果文件不存在,会自动创建)
参数2:追加的文件内容(注意:写入的内容会覆盖以前的内容)
参数3:追加文件后的回调函数

fs.appendFile("2.txt", "我是追加的内容", function(err){
  if(err) {
    return console.log("追加文件内容失败");
  }
  console.log("追加文件内容成功");
})

文件同步与异步的说明
fs中所有的文件操作,都提供了异步和同步两种方式
异步方式:不会阻塞代码的执行

//异步方式
var fs = require("fs");​
console.log(111);
fs.readFile("2.txt", "utf8", function(err, data){
  if(err) {
    return console.log("读取文件失败", err);
  }
  console.log(data);
});
console.log("222");

同步方式:会阻塞代码的执行

//同步方式
console.log(111);
var result = fs.readFileSync("2.txt", "utf-8");
console.log(result);
console.log(222);

总结:同步操作使用虽然简单,但是会影响性能,因此尽量使用异步方法,尤其是在工作过程中。

其他api
文档:http://nodejs.cn/api/fs.html

方法名	描述
fs.readFile(path, callback)	读取文件内容(异步)
fs.readFileSync(path)	读取文件内容(同步)
fs.writeFile(path, data, callback)	写入文件内容(异步)
fs.writeFileSync(path, data)	写入文件内容(同步)
fs.appendFile(path, data, callback)	追加文件内容(异步)
fs.appendFileSync(path, data)	追加文件内容(同步)
fs.rename(oldPath, newPath, callback)	重命名文件(异步)
fs.renameSync(oldPath, newPath)	重命名文件(同步)
fs.unlink(path, callback)	删除文件(异步)
fs.unlinkSync(path)	删除文件(同步)
fs.mkdir(path, mode, callback)	创建文件夹(异步)
fs.mkdirSync(path, mode)	创建文件夹(同步)
fs.rmdir(path, callback)	删除文件夹(异步)
fs.rmdirSync(path)	删除文件夹(同步)
fs.readdir(path, option, callback)	读取文件夹内容(异步)
fs.readdirSync(path, option)	读取文件夹内容(同步)
fs.stat(path, callback)	查看文件状态(异步)
fs.statSync(path)	查看文件状态(同步)

path模块
路径操作的问题
在读写文件的时候,文件路径可以写相对路径或者绝对路径
data.txt是相对路径,读取当前目录下的data.txt, 相对路径相对的是指向node命令的路径
如果node命令不是在当前目录下执行就会报错, 在当前执行node命令的目录下查找
相对路径:相对于执行node命令的路径
绝对路径:__dirname: 当前文件的目录,__filename: 当前文件的目录,包含文件名
path模块的常用方法
关于路径,在linux系统中,路径分隔符使用的是/,但是在windows系统中,路径使用的
在我们拼写路径的时候会带来很多的麻烦,经常会出现windows下写的代码,在linux操作系统下执行不了,path模块就是为了解决这个问题而存在的。
常用方法:

path.join();//拼接路径​
//windows系统下
> path.join("abc","def","gg", "index.html")
"abc\def\gg\a.html"//linux系统下
> path.join("abc","def","gg", "index.html")
'abc/def/gg/index.html'
​
path.basename(path[, ext])  返回文件的最后一部分
path.dirname(path)  返回路径的目录名
path.extname(path)  获取路径的扩展名
​
var path = require("path");
var temp = "abc\\def\\gg\\a.html";
console.log(path.basename(temp));//a.html
console.log(path.dirname(temp));//abc\def\gg
console.log(path.extname(temp));//.html

path模块其他api


path.basename(path[, ext])	返回文件的最后一部分
path.dirname(path)	返回路径的目录名
path.extname(path)	获取路径的扩展名
path.isAbsolute(path)	判断目录是否是绝对路径
path.join([...paths])	将所有的path片段拼接成一个规范的路径
path.normalize(path)	规范化路径
path.parse(path)	将一个路径解析成一个path对象
path.format(pathObj)	讲一个path对象解析成一个规范的路径

http模块
创建服务器基本步骤

//1. 导入http模块,http模块是node的核心模块,作用是用来创建http服务器的。
var http = require("http");//2. 创建服务器
var server = http.createServer();//3. 服务器处理请求
server.on("request", function() {
  console.log("我接收到请求了");
});//4. 启动服务器,监听某个端口
server.listen(9999, function(){
  console.log("服务器启动成功了, 请访问: http://localhost:9999");
});

详细说明

给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
服务器监听的端口范围为:1-65535之间,推荐使用3000以上的端口,因为3000以下的端口一般留给系统使用
request对象详解
文档地址:http://nodejs.cn/api/http.html#http_message_headers

常见属性:
headers: 所有的请求头信息
method: 请求的方式
rawHeaders: 所有的请求头信息(数组的方式)
url: 请求的地址
注意:在发送请求的时候,可能会出现两次请求的情况,这是因为谷歌浏览器会自动增加一个favicon.ico的请求。
小结:request对象中,常用的就是method和url两个参数
response对象详解
文档地址:http://nodejs.cn/api/http.html#http_class_http_serverresponse

常见的属性和方法:


res.write(data): 给浏览器发送请求体,可以调用多次,从而提供连续的请求体

res.end();   通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成。

res.end(data); 结束请求,并且响应一段内容,相当于res.write(data) + res.end()

res.statusCode: 响应的的状态码 200 404 500

res.statusMessage: 响应的状态信息, OK Not Found ,会根据statusCode自动设置。

res.setHeader(name, value); 设置响应头信息, 比如content-type

res.writeHead(statusCode, statusMessage, options); 设置响应头,同时可以设置状态码和状态信息。

注意:必须先设置响应头,才能设置响应。

根据不同请求输出不同响应数据
request.url
req.url:获取请求路径
例如:请求http://127.0.0.1:3000/index 获取到的是:/index
例如:请求http://127.0.0.1:3000/ 获取到的是:/
例如:请求http://127.0.0.1:3000 获取到的是:/
服务器响应文件
注意:浏览器中输入的URL地址,仅仅是一个标识,不与服务器中的目录一致。也就是说:返回什么内容是由服务端的逻辑决定

server.on('request', function(req, res) {
  var url = req.url
  if(url === '/') {
    fs.readFile('./index.html', function(err, data) {
      if(err) {
        return res.end('您访问的资源不存在~')
      }
​
      res.end(data)
    })
  }
})

模拟Apache服务器
根据 req.url 读取不同的页面内容,返回给浏览器
MIME类型
MIME(Multipurpose Internet Mail Extensions)多用途Internet邮件扩展类型 是一种表示文档性质和格式的标准化方式
浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档;因此服务器将正确的MIME类型附加到响应对象的头部是非常重要的
mime模块
作用:获取文件的MIME类型
安装:npm i mime

var mime = require('mime')// 获取路径对应的MIME类型
mime.getType('txt')                    // ⇨ 'text/plain'
// 根据MIME获取到文件后缀名
mime.getExtension('text/plain')        // ⇨ 'txt'

npm - Node包管理工具

  1. npm 是node的包管理工具,
  2. 它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。
  3. 来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。

    npm 由三个独立的部分组成:
  • 网站
  • 注册表(registry)
  • 命令行工具 (CLI)
  • 作用:通过npm来快速安装开发中使用的包npm不,需要安装,只要安装了node,就自带了npm
    npm基本使用
  • 初始化包-
  • npm init; //这个命令用于初始化一个包,创建一个package.json文件,我们的项目都应该先执行npm init
  • npm init -y; //快速的初始化一个包, 不能是一个中文名
    安装包
  • npm install 包名; //安装指定的包名的最新版本到项目中(npm i 包名; //简写)
  • npm install 包名@版本号; //安装指定包的指定版本​

卸载包

  • npm uninstall 包名; //卸载已经安装的包

  • 全局安装:如果你想将其作为一个命令行工具,那么你应该将其安装到全局。这种安装方式后可以让你在任何目录下使用这个命令。比如less命令,webpack命令。

  • 本地安装:如果你自己的模块依赖于某个包,并通过 Node.js 的 require 加载,那么你应该选择本地安装,这种方式也是 npm install 命令的默认行为。
    // 全局安装,会把npm包安装到C:\Users\cc\AppData\Roaming\npm目录下,作为命令行工具使用

  • npm install -g 包名;​
    本地安装,会把npm包安装到当前项目的node_modules文件中,作为项目的依赖

  • npm install 包名;
    package.json文件
    package.json文件,包(项目)描述文件,用来管理组织一个包(项目),它是一个纯JSON格式的。

  • 作用:描述当前项目(包)的信息,描述当前包(项目)的依赖项

  • 如何生成:npm init或者npm init -y

  • 作为一个标准的包,必须要有package.json文件进行描述

  • 一个项目的node_modules目录通常都会很大,不用拷贝node_modules目录,可以通过package.json文件配合npm install直接安装项目所有的依赖项

//描述内容
{
  "name": "03-npm",  //描述了包的名字,不能有中文
  "version": "1.0.0",  //描述了包的的版本信息, x.y.z  如果只是修复bug,需要更新Z位。如果是新增了功能,但是向下兼容,需要更新Y位。如果有大变动,向下不兼容,需要更新X位。
  "description": "", //包的描述信息
  "main": "index.js", //入口文件(模块化加载规则的时候详细的讲)
  "scripts": {  //配置一些脚本,在vue的时候会用到,现在体会不到
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],  //关键字(方便搜索)
  "author": "",  //作者的信息
  "license": "ISC",  //许可证,开源协议
  "dependencies": {   //重要,项目的依赖, 方便代码的共享  通过 npm install可以直接安装所有的依赖项
    "bootstrap": "^3.3.7",
    "jquery": "^3.3.1"
  }
}
注意:一个合法的package.json,必须要有name和version两个属性

如果安装失败, 可以通过以下命令清除npm缓存:

npm cache clean -f  // -f强制清除

npm下载加速
nrm:npm registry manager(npm仓库地址管理工具)
安装:npm i -g nrm
带*表示当前正在使用的地址

查看仓库地址列表
nrm ls

切换仓库地址

  • nrm use taobao
  • nodemon 自动重启
  • 作用:监视到js文件修改后,自动重启node程序
  • 安装:npm i -g nodemon
  • 使用:nodemon app.js 运行node程序

art-template 模板引擎

  • npm install art-template
    核心方法
    基于模板路径渲染模板
    参数1:文件的路径
    参数2:数据
    返回值:返回渲染后的内容
  template(filename, data)
let html = template(path.join(__dirname, "pages", "index.html"), {name:"大吉大利,今晚吃鸡"});

注意点:文件的路径必须是绝对路径

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值