# Node.js 基础入门、npm、模块化、服务端调试 DeBug
TIP
从本节内容开始学习 Node.js 核心基础,掌握 Node 相关知识
- NodeJS 是什么
- NodeJS 环境安装
- 使用 npm、开发自己的 npm 包
- common.js 模块化
- 服务端调试 - debug
- NodeJS 和 前端 JS 区别
# 一、NodeJS 简介
TIP
深入浅出 NodeJS 什么,Chrome V8 引擎,运行时环境,NodeJS 出现
# 1、NodeJS 是什么
TIP
- 一个基于 Chrome V8 引擎的 Javascript 运行时
- 2009 年发布,现已更新到 V21.X 版本
- 现已广泛应用于开源社区 和 各种公司,特别是互联网公司
# 2、Chrome V8 引擎
TIP
- Chrome 是一个浏览器,它可以执行 JS 代码
- V8 就是 Chrome 的 JS 引擎,以速度著称
- NodeJS 也是基于 JS 语法的,因此也可以借用 V8 引擎
# 3、运行时
TIP
- 代码的运行环境
- 有了运行时,代码才能被执行
- 没有运行时,代码就是一堆静态的文本,就像 txt 文本一样
# 4、NodeJS 出现之前
TIP
- NodeJS 出现以前,只有浏览器可以执行 JS 代码
- 浏览器主要显示网页,所有 JS 也被当做网页的一部分
- 除此之外,没有其他应用场景,更别提做服务端
# 5、NodeJS 出现之后
TIP
- 除了浏览器,NodeJS 又是一个新的 JS 运行时
- 哪里安装 NodeJS ,哪里就可以运行 JS 代码
- 可以用在本机(如使用 Webpack 打包),也可以做服务端
# 6、NodeJS 的价值
TIP
- 让 JS “放飞自我”,不再是网页的一部分
- 让 JS 可以做更多的事情
- 也让前端工程师可以做更多的事情
# 7、如何使用 NodeJS 做服务端
TIP
- 安装 NodeJS
- 编写 JS 代码(处理 HTTP 请求)
- 使用 NodeJS 执行 JS 代码
# 8、总结
TIP
- NodeJS 是一个基于 Chrome V8 引擎的 JS 运行时
- NodeJS 使得 JS 语言能做更多的事情,而不仅仅是网页
- 安装 NodeJS,即可执行 JS 代码
# 二、NodeJS 的环境安装
TIP
深入浅出 NodeJS 的环境安装、常见问题、基础体验
# 1、NodeJS 安装 - Windows
TIP
安装 NodeJS 的最新稳定版本,点击 NodeJS 官方下载链接 (opens new window)
根据自己的电脑的系统型号只选择对应的版本,下载并安装即可。
# 2、在文件夹的地址栏中输入 CMD
TIP
在电脑的任意文件夹地址栏中输入 "cmd" 命令 -> 回车,会弹出 cmd 命令行窗口
# 输入命令 node -v 即可查看到安装成功后的 nodejs 的版本
node -v
# 3、Win + R 输入 cmd
TIP
按下键盘中的 Win + R
会弹窗,输入 cmd
命令,即可打开 cmd 命令行窗口。同样可以输入 node 相关命令
# 查看 node 版本
node -v
# 查看 npm 版本
npm -v
直接输入 node
命令,就可以进入 node 的运行环境,即可输入 JS 代码运行
node
# 4、在 VSCode 中体验 NodeJS
TIP
新建 icoding-node
文件夹,在 /src/index.js
中,输入 NodeJS 相关代码
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url; // '/index.html?a=123'
const path = url.split("?")[0]; // 'index.html'
res.end(path);
});
server.listen(3000);
在命令行终端中运行
node .\src\index.js
或 在 windows 的 cmd 中运行
通过 node 命令运行成功后,命令行窗口中的光标会一直闪烁,也没有报错,说明成功运行
在浏览器中运行,不论输入什么样的地址,返回的永远都是域名后边的 path 部分
注:
以上代码演示,暂时不用管代码的意思,只需要知道以上 JS 代码能通过 NodeJS 客户端运行并搭建一个服务端
- NodeJS 是一个 Javascript 运行时,可以运行 JS 代码
- NodeJS 可以运行一个服务端,供前端来访问
# 5、总结
TIP
- 下载安装 NodeJS
- 学习使用控制台
- 如何将控制台定位到一个文件夹
- NodeJS 运行 JS 代码
# 三、npm
TIP
深入浅出 npm 是什么,使用 npm 安装软件包,以及 package.json
配置文件相关内容
# 1、npm 是什么 ?
TIP
- node package manager ,即 NodeJS 软件包管理者 或 包管理器
- npm 官网:https://www.npmjs.com/ (opens new window)
- 有几百上千万的软件包,开源免费
# 2、软件包有何用
TIP
- 现代软件工程已经完善且成熟,项目不会从 0 开始写
- 必须搭配成熟的工具和框架才能满足需求,否则将无人使用
- 每个成熟的开发语言或者环境,都需要成熟的软件包体系
# 3、使用 npm
TIP
- npm 会随着 NodeJS 一起被安装
npm init
命令初始化环境- 使用
npm install lodash --save
命令 安装 lodash (opens new window)(包含了 JavaScript 中常用工具 和 函数)
# 3.1、安装 lodash
TIP
在 VSCode 命令行终端 或 cmd 中输入如下命令,初始化 package.json
配置文件
# 查看 node 版本
node -v
# 查看 npm 版本
npm -v
# 初始化 package.json 配置文件,npm init -y(完全跳过问卷,默认即可)
npm init
在项目根目录中生成 package.json
配置文件
{
"name": "icoding-node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
# --save 将安装的包指定为生产依赖
npm i lodash --save
安装成功后,package.json
配置文件中的 dependencies
节点中新增了 lodash
# 3.2、体验 lodash
TIP
在 /src/lodash-test.js
中,使用 lodash (opens new window) ( JavaScript 中常用工具 和 函数)
// 引入 lodash 工具
const _ = require("lodash"); // require 为 commonjs 的语法
// 定义一个数组
const arr = [1, 2, 3];
// 使用 concat 方法,合并数组
const otherArr = _.concat(arr, 4, 5, 6);
console.log(otherArr); // [ 1, 2, 3, 4, 5, 6 ]
成功运行后
# 4、nodemon 安装 和 使用
TIP
nodemon:用来监控 NodeJS 源代码有任何变化时,会自动重启服务器。
npm i nodemon --save-dev
# 或 简写形式,将安装的包指定为开发依赖包
npm i nodemon -D
使用 nodemon,在 package.json
中,定义启动运行脚本
"scripts": {
"dev": "nodemon ./src/index.js"
},
在命令终端中,运行命令
npm run dev
运行成功后,在浏览器中访问没问题,说明我们通过 nodemon 将 服务器启动成功了 !
注:
当修改了 ./src/index.js
文件中的代码时,只需要刷新浏览器即可看到修改后的结果,无需再次启动 node 服务。
# 5、总结
TIP
- npm 是什么,软件包有何作用
- 安装和使用 npm 包,
--save
和--save-dev
或-D
的区别 package.json
( scripts dependencies devDependencies )
# 6、npm 包 - 深入扩展
TIP
- npm 指定包的依赖
- npm 包卸载
package.json
文件配置说明- 解决 npm 包下载速度慢的问题,国内 npm 镜像服务器
- nrm 源管理器
- npm 常用命令
详细内容讲解,查看 前端工程化 - npm (opens new window)
# 7、开发自己的 npm 包
TIP
- 创建项目文件夹,用来存放项目用到的所有文件
- 初始化
package.json
文件 - 创建
index.js
文件 - 撰写
README.md
说明文档 - 上传包到 npm 平台:注册、登录 npm 账号,发布包到 npm 平台,删除发布的包
详细内容讲解,查看 前端工程化 - 开发自己的 npm 包 (opens new window)
# 四、commonJS 模块化
TIP
深入浅出 commonJS 语法、commonJS 和 ES6 Module 的区别、为何要使用模块
# 1、ES6 Module
TIP
在学习 commonJS 模块之前,先回顾下 ES6 Module
export ...
或export default ...
模块化 - 导出语法import ... from ...
模块化 - 导入语法- 一般用于前端开发,如:我们在 Vue 项目中经常用到这样的语法
在 TS 的学习中,有完整的回顾了 ES6 与 CommonJS 模块系统 (opens new window) 相关内容
# 1.1、在 Vue 项目中使用 ES6 模块化
TIP
借助 Vue 项目的环境来演示 ES6 模块化
创建 Vue 项目 vue-es6-module
,使用如下命令
npm create vue@latest
输入项目名,一路回车即可
使用 VSCode 打开项目,同时初始化依赖 和 运行项目
在浏览器中访问
# 1.2、ES6 模块在 Vue 项目中的应用
新建 /src/utils.js
function sum(a, b) {
return a + b;
}
// 导出 sum 函数
export default sum;
在 /src/App.vue
中,导入 sum 并使用
<script setup>
// import ... from 语法,即:ES6 模块化的语法
import HelloWorld from "./components/HelloWorld.vue";
import TheWelcome from "./components/TheWelcome.vue";
import { onMounted } from "vue";
// 导入 utils.js ,import ... from 语法,即:ES6 模块化的语法
import sum from "./utils";
// 组件渲染完成后执行
onMounted(() => {
console.log(`the component is now mounted.`);
// 使用 sum 函数
const res = sum(123, 456);
console.log(res);
});
</script>
<template></template>
<style scoped></style>
在浏览器的控制台中查看
注:
以上就是 ES6 模块化,通过 export default
导出,通过 import ... from
导入内容。
# 1.3、export 导出多个
在 /src/utils.js
中
export function sum(a, b) {
return a + b;
}
export function message() {
console.log("I like programming !");
}
// 或
// export {
// sum,
// message
// }
在 App.vue
中
<script setup>
// import ... from 语法,即:ES6 模块化的语法
import HelloWorld from "./components/HelloWorld.vue";
import TheWelcome from "./components/TheWelcome.vue";
import { onMounted } from "vue";
// 导入多个
import { sum, message } from "./utils";
// 组件渲染完成后执行
onMounted(() => {
console.log(`the component is now mounted.`);
// 使用 sum 函数
const res = sum(123, 456);
console.log(res);
// 使用 message 函数
message();
});
</script>
<template></template>
<style scoped></style>
# 2、commonJS 语法
TIP
module.exports
导出require( ... )
导入- 主要用于 NodeJS 开发,即:commonJS 是 NodeJS 的规范
# 3、require( ... )
的三个层级
TIP
- ①、系统自带模块,如:
require('http')
- ②、npm 包,如:
require('lodash')
- ③、自定义模块,如:
require('abc')
以上在 require 时,会分为三个层级来处理,会先找 ①、再找 ②、再找 ③
# 4、commonJS 应用实践
在 icoding-node
项目中,新建 /src/utils.js
function sum(a, b) {
return a + b;
}
// 使用 module.exports 导出 ,commonJS 的语法
module.exports = sum;
在 /src/test.js
中导入
// 使用 require 导入
const sum = require("./utils");
const res = sum(123, 345);
console.log(res); // 468
运行 test.js
,命令行终端中输入如下
node ./src/test.js
# 4.1、导出多个
在 /src/utils.js
中导出多个函数
function sum(a, b) {
return a + b;
}
// 增加一个函数
function message() {
console.log("I like programming !");
}
// 使用 module.exports 导出多个函数
module.exports = {
sum,
message,
};
在 /src/test.js
中导入多个函数,并使用
// 使用 require 导入多个函数
const { sum, message } = require("./utils");
// 执行 message 函数
message();
const res = sum(1, 2);
console.log(res); // 468
# 5、commonJS 的三个级别
TIP
在导入模块时,require('有三种形式')
- nodeJS 自带的模块
- 通过 npm 安装的第三方模块
- 自己手写的模块(一般在 require 中都会有一个相对路径的目录)
注:一般
require('只有一个名称的')
如:require('http')
、require('lodash')
是系统自带 或 npm 安装的第三方模块
// nodeJS 自带的模块
const http = require("http");
// 通过 npm 安装的模块
const _ = require("lodash");
// 自己手写的模块
const { sum, message } = require("./utils");
注:
这三种模块全部都通过 require 的方式去引用,这就是 require 的强大之处。require 会区分这三种层级:
- 当你给 require 中一个字符串时,require 会优先判断是不是 nodeJS 自带的模块;
- 如果不是,再判断是否是 npm 安装的模块;
- 如果不是,再判断是否是 自己手写的模块;
虽然这三种方式都是用 require 引入,但 require 会智能的区分改模到底是通过三种方式中的哪一种引入的。这样就交给 require 自己来判断,我们就不用关心这个事情了。
# 6、commonJS 和 ES6 Module 的区别
TIP
- 两者语法不一样
- CommonJS 是执行时引入,动态的
- ES6 Module 是打包时引入,静态的
# 6.1、commonJS
TIP
在 icoding-node
项目的 /src/test.js
中
将 commonJS 语法,放在 if 语句中执行
// 定义 flag
const flag = true;
// 将 commonJS 语法,放在 if 语句中执行
if (flag) {
// 使用 require 导入多个函数
const { sum, message } = require("./utils");
// 执行 message 函数
message();
const res = sum(2, 3);
console.log(res); // 5
}
运行打印结果
以上方式可以看到 commonJS 语法可以放到一个 if 语句中去执行
# 6.1、commonJS 动态引入
TIP
程序在执行到 if 语句中require('./utils')
时,才会去引用 ./utils
将 sum
和 test
拿出来。
因此,我们讲 commonJS 是执行时引入,是动态的。
# 6.2、ES6 Module
TIP
在 vue-es6-module
项目的 /src/vue.app
中
将 ES6 Module 语法,放在 if 语句中执行
<script setup>
import HelloWorld from "./components/HelloWorld.vue";
import TheWelcome from "./components/TheWelcome.vue";
import { onMounted } from "vue";
// 定义 flag
const flag = true;
// 将 ES6 Module 语法,放在 if 语句中执行
if (flag) {
// 导入多个
import { sum, message } from "./utils";
}
onMounted(() => {
console.log(`the component is now mounted.`);
const res = sum(123, 456);
console.log(res);
message();
});
</script>
<template></template>
<style scoped></style>
启动运行 Vue 项目
npm run dev
在浏览器中同样会出现报错
注:报错原因
在 ES6 Module 中使用 import
和 export
导入模块时,只能出现在顶层,不能出现在 if 语句块中。
因此,我们讲 ES6 模块化是 静态的,就没有 commonJS 灵活(放在语句块里外都可以)。
# 6.3、ES6 Module 静态引入
TIP
在使用模块时,必须要先将其引入进来后(或 打包时),才可以执行。
即:ES6 模块化要求必须在使用前 提前引入,而 commonJS 是在执行时引入。
# 7、为何使用模块化
TIP
- 模块拆分开,便于代码的组织和管理
- 便于多人协作开发,各写各的互不干扰
- 成熟的编程语言都支持模块化,如:Java、Python、C、C++、PHP、GO 包括 NodeJS
即:一个成熟的语言必须得有一个软件包的生态体系,同理一个成熟的语言也必须要有一个模块化的规范,否则这个语言就不能用。
# 五、服务端调试 - DeBug
TIP
深入浅出什么是 DeBug,DeBug 的重要性,以及相关应用
# 1、什么是 DeBug
TIP
- bug:音译:小虫子、小昆虫,在程序中通常理解为:程序错误
背后故事:
早期的计算机不像当今的 PC 电脑和移动设备这样精密,那时的计算机体积是非常庞大的,而且运算速度非常慢。通常一台计算机会占满整个屋子,有各种机械零件相互连接运转。
在运转过程中就会出现问题,通常问题是机器在运转过程中有 “虫子” 飞进去将齿轮 和 传动轴等卡主了导致的。因此一开始计算机问题和异常都是由于小虫子飞进去导致的机器异常导致的。
从那时起,bug 就慢慢地被叫为一个 计算机的错误,因此我们现在叫的 bug 就指:程序错误。
- debug:即 拍错,也叫 “调试”
- 编程语言必须有成熟的 debug 机制,否则将不可用
# 2、Debug 的重要性
TIP
- 程序出 bug 很常见,因此 debug 也很常见
- 出了 bug,得需要知道代码时如何运行出错的
- 就像一台机器异响,得拆开外壳看看里面如何运转的
这时候,就需要有 Debug 这种机制,否则只能靠猜了 !
# 3、inspect 调试法
TIP
- 修改 scripts ,增加
--inspect
,启动服务 - 打开
Chrome
浏览器,访问chrome://inspect
- 增加
debugger
重启服务,即可调试
在 icoding-node
项目中,打开 package.json
配置文件
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// 修改如下,增加了 --inspect=9229 ,9299 为默认端口号
"dev": "nodemon --inspect=9229 ./src/index.js"
}
}
在命令行终端中运行命令
npm run dev
在浏览器中输入访问地址 chrome://inspect
并回车
点击 inspect 链接后,弹出 DevTools 框
以上 Console 面板中没有打印输出任何内容,在 /src/index.js
中服务启动成功后,打印输出一个字符串测试
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url; // '/index.html?a=123'
const path = url.split("?")[0]; // 'index.html'
res.end(path + " - icoding");
});
server.listen(3000);
console.log("服务器正在侦听 3000 端口!");
再次运行
npm run dev
再次点击 inspect 链接后,弹出 DevTools 框
# 4、如何 Debug 调试
需要先在 /src/index.js
中故意制造一个 bug
const http = require("http");
const msg = "故意制造错误";
const server = http.createServer((req, res) => {
// debugger // 断点
const url = req.url; // '/index.html?a=123'
const path = url.split("?")[0]; // 'index.html'
// 故意将 msg 字符串 当作一个函数来调用
msg();
res.end(path + " - icoding");
});
server.listen(3000);
console.log("服务器正在侦听 3000 端口!");
在浏览器中访问,报错
查看 VSCode 终端,也报错了 “msg 不是一个函数”
# 4.1、通过 inspect 调试
TIP
在需要调试的代码中 打断点 ,添加一个 debugger
关键字
const http = require("http");
const msg = "故意制造错误";
const server = http.createServer((req, res) => {
debugger; // 断点:即 程序会断一下,等着一步步往下执行
const url = req.url; // '/index.html?a=123'
const path = url.split("?")[0]; // 'index.html'
// 故意将 msg 字符串 当作一个函数来调用
msg();
res.end(path + " - icoding");
});
server.listen(3000);
console.log("服务器正在侦听 3000 端口!");
再次重启 node 服务
npm run dev
在浏览器中输入访问地址 chrome://inspect
并回车,等待 inspect
链接出现,并点击进入 DevTools
界面
再次访问 http://localhost:3000/
会自动进入调试状态,并定位在 debugger
处
点击右边 “跳过下一个函数调用” 按钮,一步步执行,执行完毕后回到 Console
面板即可看到报错信息
注:
通过以上在 Sources
中一步步断点即可断出 Uncaught TypeError: msg is not a function
也就是说我们通过 debugger
关键字可以一行行的找出 bug,问题找到了即可解决。这就是我们打断点调试的思路和步骤
# 4.2、整个调试过程 及 步骤
TIP
- 代码有一个错误,即:bug
- 在错误代码之前添加
debugger
关键字,即:打断点 - 在命令行终端启动 node 服务,如:
npm run dev
- 在浏览器中输入访问地址
chrome://inspect
并回车,等待inspect
链接出现,并点击进入DevTools
界面,即:监听错误 - 在浏览器中访问
http://localhost:3000/
node 服务,会自动进入调试状态,并定位在debugger
处 - 点击右边 “跳过下一个函数调用” 按钮,一步步执行,执行完毕后回到
Console
面板即可看到报错信息(还会定位到具体哪个文件的第几行报错) - 此时,该 bug 就被找出来了,修改即可
# 5、总结
TIP
- debug 即排错、调试(是方法),debugger 关键字(作为代码语句出现在代码行中)是 断点
- debug 对于编程非常重要,必须学会
- inspect 调试方法
# 六、NodeJS 和 前端 JS 区别
TIP
- 两者都使用 JS 语法
- 前端 JS 使用浏览器提供的 Web API
- NodeJS 使用 NodeJS API
# 1、都用 JS 语法
TIP
- 变量的定义和类型
- 函数的定义和执行
- ES6 的 Class、Promise 等语法
JS 语法都是通用的,没有任何区别
# 2、Web API
TIP
- 前端网页的 DOM、BOM 事件、Ajax 等 这些前端 JS 可以使用,因为在浏览器环境中
- 而 NodeJS 则无法使用以上 WebAPI ,因为是 NodeJS 环境
# 2.1、浏览器中的 JavaScript
注:
将前端浏览器中常用的 window
、document
对象分别在 NodeJS 和 浏览器环境中测试观察
在 /src/test.js
中,即:NodeJS 环境
// 将 window 和 document 对象在 NodeJS 环境中运行
// BOM
console.log(window);
// DOM
// console.log(document)
在浏览器环境中查看
注:
因此,我们说前端浏览器中的 API 我们叫做 “WebAPI”,这些 API 在 NodeJS 中是无法使用的。因为是 NodeJS 环境 !
# 3、NodeJS API
TIP
- 如:处理 HTTP
- NodeJS 可以使用,因为是 NodeJS 环境
- 前端 JS 则无法使用,因为在浏览器环境
# 3.1、NodeJS 中的 JavaScript
注:
对比浏览器中的 JavaScript 我们发现,在 NodeJS 中并没有包含 BOM、DOM 这些 API 的。
因此我们在写 NodeJS 代码时,就不能编写 BOM 和 DOM 相关的代码。
简单验证,在浏览器控制台中输入 http
会直接报错
# 3.2、NodeJS 的顶级对象 global
TIP
- 在 NodeJS 中的顶级对象是 global,而不是 window
- global 顶级对象 相当于 浏览器环境中的 window 对象
在 /src/obj.js
中
// global NodeJS 顶级对象
// 打印输出 global 顶级对象的成员
console.log(global);
globalThis 是 ES2020 规范中引入新的特性,用 globalThis 指向 global 顶级对象,而 NodeJS 也支持该特性
// 打印输出 globalThis
console.log(globalThis);
// 两者对比是否指向同一对象
console.log(global === globalThis); // true
// global 和 globalThis 是指向同一对象的
# 4、总结
TIP
- 前端 JS = JS 语法 + WebAPI
- NodeJS = JS 语法 + NodeJS API
- 同样的语法,不同的工作(它们的 API 不一样,即:能力不一样)
- NodeJS 中不能使用 BOM 和 DOM 的 API,可以使用 console 和 定时器 API
- NodeJS 中的顶级对象为 global,也可以用 globalThis 访问顶级对象
一门编程语言只有语法 结合 不同的 API 才能具备不同的能力,去做不同的工作。只有 API 也是不行的,两者是相辅相成的。
同样的 JS 语法 结合 不同的 API 就可以用在不同场景、环境 去完成 不同的工作。
以上就是前端 JS 和 NodeJS 的区别 和 联系
# 七、总结 - 回顾
TIP
- NodeJS 是什么
- NodeJS 环境安装
- 使用 npm、开发自己的 npm 包
- common.js 模块化
- 服务端调试 - debug
- NodeJS 和 前端 JS 区别
# 1、NodeJS 是什么
TIP
- NodeJS 是一个基于 Chrome V8 引擎 的 JS 运行时
- NodeJS 使得 JS 语言能做更多的事情,而不仅仅是网页
- 安装 NodeJS ,即可执行 JS 代码
# 2、NodeJS 环境安装
TIP
- 下载、安装 NodeJS
- 学习使用控制台
- 如何将控制台定位到一个文件夹
- NodeJS 运行 JS 代码
# 3、npm
TIP
- npm 是什么,软件包有何作用
- 安装和使用 npm 包,
--save
(生产依赖)和--save-dev
或-D
(开发依赖包)的区别 package.json
( scripts dependencies devDependencies )- npm 包 - 深入扩展
- 开发自己的 npm 包
# 4、commonJS
TIP
- commonJS 语法
- commonJS 和 ES6 Module 的区别
- 为何要使用模块化
# 5、debug
TIP
- debug 即排错、调试(是方法),debugger 关键字(作为代码语句出现在代码行中)是 断点
- debug 对于编程非常重要,必须学会
- inspect 调试方法
# 6、NodeJS 和 前端 JS 区别
TIP
- 前端 JS = JS 语法 + WebAPI
- NodeJS = JS 语法 + NodeJS API
- 同样的语法,不同的工作(它们的 API 不一样,即:能力不一样)
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X