万能图片编辑器
免费制作生成二维码的网站
在线图片压缩工具
灵动的扫描识别神器
支持多种格式的在线文件加密网站
在线手绘流程图画板
好用丰富的在线工具箱
在线桌面翻页时钟
在线高颜值的支持压感的白板
在线辅助写作网站
在线英语写作语法纠正平台
在线PDF编辑网站
安全/快捷/好用的工具箱
支持处理音频/PDF的多功能工具箱
无需安装的在线录屏网站
随时随地开启在线会议
免费下载浏览器插件的神器
专注于发现与分享的音乐平台
海量版权的网络音乐服务产品
专业的影视推荐平台
每天推荐一部优秀电影
国内知名的视频弹幕网站
混合场景发出白噪音助眠音乐
发现你的私属好音乐
有声小说/相声/评书/脱口秀/广播剧/听书
为您找到每一天的精神食粮
努力让找电影变得简单
音效爱好者的声音分享下载平台
网易旗下支持整个文档翻译工具
字节跳动出品的翻译工具
支持即时翻译文本的专业翻译器
小众简洁的翻译网站
专业的笔记网站
新一代知识管理与协作平台
多人编辑便捷协同的笔记工具
提供优秀的文档与知识库工具
万事皆有后续
具有代表性的实力型科技新媒体与信息服务平台
数据图表说明热点问题
提供各站热榜聚合追踪全网热点
每天三分钟的科技新闻聚合阅读
不错过互联网的每个重要时刻
独家的视角为用户深度剖析最前沿的资讯
提供临时安全匿名免费的一次性电子邮件地址
便捷的思维工具
完全免费的在线思维导图工具
新一代免费思维导图协作软件
快速安全的存储云盘
下载速度慢并且摆脱不掉的云盘
在线跨设备文件传输网站
在线大文件传输工具
飞书全家福中专业的文档
免费的在线问卷工具
收集各种中国的传统颜色
炫酷的对称光束生成网站
定时轮播濒危灭绝物种的抽象图片
在线生成光束射线的网站
收集介绍中国古今传说的妖怪
360全景视感受故宫浑厚的文化
故宫数字文物博物馆
指尖上的全球360全景
知识图谱为核心引擎多维度呈现历史知识
科普有趣的知识频道
支持emoji图标融合制成新图标
定时轮播濒危灭绝物种的抽象图片
一个基于梗文化的娱乐科普社区
免费在线FC小游戏
在线联网贪吃蛇小游戏
在线娱乐桌球游戏
边玩边记单词小游戏
酷似消消乐的益智小游戏
在线架子鼓打击乐器
模拟遥控操作小车
圈住小猫的益智游戏
荒庙逃亡跑酷游戏
俄罗斯方块经典怀旧版
相同颜色相连的益智小游戏
我的游戏在线联机版本
支持在线弹奏钢琴
即时通讯的小蝌蚪聊天室
在线开始音乐创作之旅
敲木鱼攒功德
在线欣赏烟花的网站
可爱魔性的在线手鼓猫
专注图片模板在线设计
创意图文设计神器
AI辅助涂鸦创作网站
一站式动效制作平台
炫酷背景图片生成器
炫酷的对称光束生成网站
多端设备样机展示制作平台
智能图文创作平台
支持图片一键像素化
支持一键生成不规则形状图片
绘画稿智能上色网站
陪你做生活的设计师
专业的设计创意互动平台
原创插画内容创作平台
收集各种中国的传统颜色
设计师和艺术家的调色板
颜色策划搭配方案神器
线性渐变球体配色网站
线性渐变背景配色网站
可调节的渐变配色网站
通过不同色彩规则来提供配色参考
支持选择颜色生成配色方案
国内优秀摄影师交流社区
提供令人惊叹的免费库存照片下载平台
汇聚千上万的无版权库存图片
资源丰富的壁纸网站
各类动漫二次元插画
高质量的壁纸免费下载平台
免费优质的图片分享网站
专业的矢量图标库
可商用的免费图标库下载平台
风靡全球的图标字体库
AI智能语音转写听翻平台
在线音视频编辑工具
超好用在线智能创作平台
在线制作编辑图片工具
免费强大在线图片编辑工具
专业的图片背景去除工具
视频去除背景工具
人工智能无损修复老旧图片
在线提高图像分辨率网站
在线视频片段转化GIF工具
在线制作炫酷视频片头网站
支持动漫风格图片无损放大
在线修图调色工具
产品爱好者学习交流平台
优质的产品经理学习网站
产品设计一体化在线协同办公平台
安全稳定的云产品及服务
为企业和开发者提供安全稳定的云计算服务
查询快递物流信息
快递公司的不法行为进行投诉
中国铁路出行信息平台
在线制作简历网站
模版丰富的简历制作平台
实习生招聘信息网站
高时效更新校园招聘信息的平台
回复率较高的工作招聘平台
应届生招聘信息发布平台
专注于医学生的求职网站
古诗文网作为传承经典的网站专注于古诗文服务
传播有温度有态度的新科学观
丰富实用的科学信息服务以及交流互动的网络平台
每天静下心来读一篇文章
高效生活视频书
为您全方位提供财经资讯及全球金融市场行情
美观且优秀的设计专业导航
聚法科技旗下专为法律人定制的网址导航工具箱
工业自动化专业导航
共产党员上网助手
轻便的解压缩神器
简洁的文本编辑工具
支持各种音频文件的播放器
摆脱烦恼的广告屏蔽工具
支持多线程任务下载工具
鼠标丝滑滚动神器
免费而强大的Mac图床客户端
体积小无广告的解压缩工具
超好用的卸载工具
一款强力的垃圾清理软件
截图神器
良心的安全电脑管理软件
功能齐全的小白必备浏览器
支持名称快速定位文件和文件夹
支持选择文件后空格预览
办公必备的小工具百宝箱
好用的GIF图片录屏工具
远程操控软件
快速提升翻译效率
鼠标手势控制软件
支持实时语音识别软件
一个适用于创意行业从业者的图像工具箱
界面简洁的桌面日历软件
桌面便签/任务清单/备忘录的效率工具
鼠标党的指尖工具箱
十分好用的录屏工具
键盘党的自由组合工具集
桌面整理收纳工具(下载独立版)
开源/免费/功能强大的图床软件
多平台局域网文件传输文件网站
专注于分享优秀的正版软件
苹果软件推荐下载平台
国外资源丰富的MAC软件下载平台
CPU版本参数天梯榜
显卡参数性能天梯榜
电源性能排行榜单
专注于文件格式转换的免费网站
幻灯片背景/模版/素材下载网站
优质/高效/安全的PPT模板下载网站
有情怀的免费PPT模板下载网站
专注于收集可商用字体的网站
在线转换书法字体网站
支持在线即时预览和编写的代码编辑器
支持CSS解析添加浏览器厂商前缀
支持在线进行JS语法转换
支持在线生成预览CSS动画函数
支持多种语言解析并导出AST树
支持拟态风格阴影生成预览
分享纯CSS和HTML实现自定义元素的网站
免费的前端开源项目 CDN 加速服务
支持前端开发者查看API的兼容性
Web技术标准及实施指南
Web技术开发文档的免费网站
支持在线格式化和压缩JSON字符串
支持在线绘制导出SVG图片
正则表达式在线可视化工具
支持各种编译语言在线运行
国外搭建的最全面的CDN资源站点
支持在线校验正则表达式的网站
在线生成loading图片的网站
在线loading图标生成工具
一个简单的公共 IP 地址 API 库
网络ip
网络ip
全球极客热爱的技术成长平台
较为全面的编程入门教程网站
前端入门技术学习网站
Nodejs的包管理和分发工具
GtiHub仓库文件夹下载
Github文件代理加速下载服务
基于 JavaScript 的开源可视化图表库
渐进式 JavaScript 框架
最受欢迎的网页3D渲染JS引擎
企业级 Node.js 开发框架
优秀的CSS动画库
基于JavaScript语言的跨平台桌面应用开发工具
使用Webpack和Nodejs进行封装的基于Vue的SSR框架
开发文档查询工具
原子化CSS框架
模块化高性能的JavaScript实用工具库
兼容IE6和移动端的交互图表库
掘金技术交流论坛的前端板块
网站快速成型组件工具
一套企业级UI设计语言和React组件库
字节跳动出品的企业级设计系统
免费轻量友好的前端开发工具
针对JavaScript和相关技术的集成开发环境
免抠设计元素下载网站
免费的开源插图库
精美的扁平插画网站
国外的免抠图片分享平台
插画套图分享平台
支持输入图片/文字/类型的智能作画工具
文本描述生成视频
增强图片清晰度智能工具
涂抹消除图片内容的智能工具
黑白照片自动着色工具
在线智能视频创作平台
免费的电影生成字幕工具
文字描述生成图片
使用语音增强技术去除语音记录中的噪音
改进你的麦克风设置的建议
免费分离音频中的人声和乐器
使用AI快速生成PPT模版
一个智能聊天机器人助手
一种基于机器学习的聊天机器人
手机扫描智能生成人物模型智能工具
网络测速工具
智能的代码编程助手
日常文案写作模版网站
简洁的端口扫描工具
Parser HTML/XML to PostHTMLTree
Render PostHTMLTree to HTML/XML
DIY爱好者的必备工具合集
简洁的ip地址查询网站
支持上传svg转换字体的图标库
语音转文字软件本地离线版
收集免费的软件替代品网站
前端工程化的主流构建工具
下一代的前端工具链
基于流的自动化构建工具
基于任务的自动化构建工具
安全高效的服务器运维面板
新一代的 Linux 服务器运维管理面板
安全可靠极稳定的服务器面板
极具潜力的效率启动器
科大讯飞新一代认知智能大模型
百度全新一代知识增强大语言模型
国产剪辑神器
超流畅远程控制软件
阿里巴巴推出的响应人类指令大模型
智能创意与创作
一站式智能数据分析与应用平台
AI换脸开源神器
逼真的数字互动人物模型制作平台
机器学习模型托管平台
智能编程补全助手
搭建专属的智能知识库
免费的线上人声分离工具
在线打字练习网站
支持在线协作的流程图
多场景综合办公作图软件
支持团队协作的无边记白板
在线可视化显示npm包依赖关系
百度智能搜索助手
功能齐全的开源远程控制替代方案
AI视频生成工具
强大的图片搜素引擎
微软出品的智能助手
动态壁纸下载平台
在线生成文字阴影网站
生成盒子阴影CSS样式代码
抖音推出的AI智能体
vue2.js官方文档
电影、短剧找台词,一站搞定!
docker容器镜像代理服务
免费的共享ChatGPT账号
免费在线接收短信到一个新的临时电话号码
注册送免费GPT4额度的优质AI工具
编程语言转换网站
自动生成API文档工具
在线练习英语网站
一款学生和职场人的新质生产力工具
国内权威的气象平台
纸张设计网站
开源且强大的四维导图
精品MacOS资源下载平台
阿里巴巴提供的免费镜像站
红白机/FC/NES游戏,童年的回忆。
在线JSON转Excel工具
高性价比的云服务提供商
提供免费和可自动续https证书
好用的在线局域网文件传输平台
轻量且出色的智能助手
一个功能强大的在线网络诊断工具
万能图片编辑器
腾讯元宝是基于腾讯混元大模型的AI应用
网页样式窗口图片生成器
智能修复模糊图片
万能视频图片解析下载
网站推荐/成为友链/合作申请
最新官方会计准则
财务人员必备的会计行业网址导航
没有广告的免费在线小游戏
阿里巴巴旗下的智能助手
用于视频录制和直播的免费和开源软件
使用微信文件传输助手,手机电脑轻松互传文件!
线下聚会必备桌游小游戏
优化Prompt提示词
# 🚧 大事件 ### 2023/03/15 由于 **huasen.cc** 域名将于2024年2月26日过期,需要支付**500¥**赎回。为了缩减网站开销,含泪将花森系列网站域名更换为 **huasenjio.top**,请大家奔走相告,感谢支持! ### 2022/02/01 > 2022年春节 晴 10°C 西北风 步入社会,自由支配的时间一点点地被工作侵蚀。开发维护网站的时间变少,可偏偏是屋漏偏逢连夜,2月份网站遭到严重的攻击,联系了腾讯云的工程师仍然无法解决,最终不得已重置服务器,被迫关停网站。网站关停的一段时间里,我寻找原因,针对地加固服务器,同时吸取之前使用者提出的建议,反正网站都被攻击到重置服务器了,一不做二不休,干脆直接升级网站! # 📷 版本快照 ### 2024年 未来可期,扬帆远航。 ### 2023年 冒烟开发,不断的迭代更新。引入容器化,重构底层代码。正式开源项目,注册bilibili账号,开始运营网站,结识不少益友,收获颇丰!  ### 2022年 我逐渐开始意识到,光有一身前端技术,却搞不出像样的产品,就是白瞎了满身的好手艺。我开始购买原型工具,学习做竟品分析,才有现版本的雏形。  ### 2021年 我疯狂学习前端周边技术,羽翼逐渐丰满,然后站在巨人(前辈)的肩膀上,从0到1,设计开发导航网站,把学习的知识实践到项目中,该版本简直就是技术大杂烩。  ### 2020年 由于对前端技术的热爱,我开始接触到不少优秀的开源作品,考虑到喜欢简洁风格,最终我选择小呆的开源导航项目。后来我申请域名,线上部署,从此入坑前端世界。 ### 2019年末 在校期间,第一次接触web课程,使用纯html、css、js开发的页面,虽然很丑,但是很有成就感。  ### 2019年中 梦开始的痕迹... 
# 不负相遇 > Ctrl + D 收藏本站 ```javascript 你好 - 普通话 mwngz ndei - 壮语 hello - 英语 console.log('你好') - Javascript ``` ## 🎈 初衷 深知如今使用百度引擎获取优质的内容困难性,祸源在于是百度搜索存在严重的**竞价排名**的现象,花钱就能让我的内容排名靠前,如此,你可能错过靠后的优质资源,甚至你会搜索到大量的**伪劣内容以及捆绑软件**。好巧不巧,小生喜欢收集实用的资源站点,涵盖了生活、娱乐、学习、影视、考研、工作、科技、等诸多领域。于是花森酱网址导航孕育而生,发现并分享优质的内容,愿大家在繁杂喧闹的互联网信息时代,舒适愉快地上网冲浪! ## 🌿 心境 > 森林覆盖的岛屿花开遍地美好都将与约而至 从大三决然的跳入前端大坑,到如今已是毕业近三年的杭州社畜。脑子依旧清晰地记得,我选择前端时的孤独,周围都是清一色的后端方向,图书馆里我与灯为伴,以书为友,只道是不悔梦归处,空恨时间太匆匆。花森酱主页已运营三年有余,不长不短,**念念不忘,必有回响**。 ## 🥳 联系我们 企鹅 🐧:184820911 邮箱 📮:184820911@qq.com WeChat:huasencc
# 花森网站功能白皮书 花森网站已维护**3**年之余,历时多次迭代升级,网站目前包含**主页**、**文章**两个模块,完美适配手机、平板、电脑等移动设备。网站非常注重使用体验和隐私,用户**无需登录**即可使用**全部功能**,并且我们不会私自上传您的数据。因为网站全部数据优先存储于浏览器本地,所以**清空浏览器缓存会导致数据丢失**,建议**定期**通过**数据管理面板**拷贝并保存数据,或者登录账号备份数据至云端。 ## 极简模式 花森主页存在**正常**、**极简**两种模式,正常模式下,平铺展示所有站点信息。极简模式下,隐藏绝大多数功能,仅保留搜索框,非常干净整洁,并且刷新、重启浏览器不会重置模式。  | 序号 | 功能描述 | 备注 | | ---- | ------------------------------------------------ | ---- | | 1 | 点击"全屏",进入极简模式。 | | | 2 | 点击右下角全屏图标,进入正常模式,退出极简模式。 | | ## 搜索框 目前搜索框适配**7**种常见引擎,支持切换搜索,输入关键词时,下方展开热词提示面板,使用百度提供的能力进行开发。特别说明,**站内搜索**功能比较独立,属于本站的亮点之一,用户可以通过网站名称和描述,搜索收录的链接,达到快速跳转的效果。  | 序号 | 功能描述 | 备注 | | :--- | ------------------------------------------------------------ | ---- | | 1 | 搜索关键词,支持按下"/"快捷输入,按下回车键,立即搜索。 | | | 2 | 绿色小圆点,表示当前使用的引擎,可以点击切换,同时支持`tab、tab+shift`组合键上下切换引擎。 | | | 3 | 站内搜索,支持通过名称、描述,搜索站内收录的网站,然后选中直接跳转。 | | | 4 | 热词提示面板,支持鼠标点击直接搜索(站内跳转),同时可以 ⬆️⬇️ 键,上下切换。 | | ## 背景墙 没有弯绕,童叟无欺,**无需登录**,即可定义专属背景墙,目前选择纯色、图片墙纸(后续支持视频),并且调节字体颜色、模糊度、遮罩,另外上传的壁纸尺寸建议**1920*1080**,大小不超过2m!  | 序号 | 功能描述 | 备注 | | ---- | :-------------------------------------------------- | ---- | | 1 | 点击"个性",弹出个性定制面板。 | | | 2 | 拖拽设置墙纸模糊度 | | | 3 | 拖拽设置墙纸明暗度 | | | 4 | 选择纯色作为墙纸 | | | 5 | 选择预设图片作为墙纸 | | | 6 | 上传图片作为墙纸,强烈建议1920*1080,文件越小越好。 | | ## 数据备份恢复 网站非常注重使用体验和隐私,所以我们不会私自上传您的数据,您的所有自定义数据,均存储在本地浏览器(**清理浏览器数据会造成数据丢失**),只有您登录账号,并且手动备份数据至云端(实现多设备数据共享),服务器才会感知您的数据。  | 序号 | 功能描述 | 备注 | | ---- | ------------------------------------------------------------ | ---- | | 0 | 点击"数据",弹出数据管理弹窗。 | | | 1 | 数据备份tab | | | 2 | 点击"拷贝数据",数据将复制到剪贴板,您可以手动管理数据,请妥善保管!另外,非专业人员,**不要擅自修改数据**,如果数据损坏,网站无法解析恢复。 | | | 3 | 登录账号情况下,点击"数据上云",数据将会备份到云端,意味着您可以在不同设备上一键同步数据到本地,而不是拷贝离线数据,然后恢复。 | | | 4 | 数据备份tab | | | 5 | 粘贴之前拷贝的离线数据,点击"粘贴数据恢复",网站将会解析数据,然后页面自动刷新之后,完成数据恢复。 | | | 6 | 粘贴网站离线数据,您需要保证数据无误,否则网站无法打开。 | | | 7 | 登录账号情况下,点击"应用云端数据",将会从云端拉取数据,同步到浏览器本地。 | | ## 自定义网站 ### 新增 新增的自定义网站,不会自动同步更新到云端,请您按照实际需求,选择数据是否备份上云。  | 序号 | 功能描述 | 备注 | | ---- | :----------------------------------- | ---- | | 1 | 点击"添加",弹出添加自定义网站弹窗。 | | | 2 | 输入网站数据后点击"确认" | | | 3 | 添加的自定义网站展示位置 | | ### 修改 更新的自定义网站,不会自动同步更新到云端,请您按照实际需求,选择数据是否备份上云。  | 序号 | 功能描述 | 备注 | | ---- | ------------------------------------------------------------ | ---- | | 1 | 点击"编辑",进入编辑模式(再次点击退出),弹出编辑自定义网站弹窗。 | | | 2 | 点击需要编辑的自定义链接 | | | 3 | 更新网站数据后点击"确认" | | ### 删除 删除的自定义网站,不会自动更新到云端,请您按照实际需求,选择数据是否备份上云。  | 序号 | 功能描述 | 备注 | | ---- | ------------------------------------------------- | ---- | | 1 | 点击"管理",进入管理模式,再次点击退出。 | | | 2 | 点击自定义网站右上角 ❌ 按钮,即可删除自定义网站。 | | ## 登录注册 目前网站仅支持邮箱注册登录(日后计划开发QQ一键扫码登录),并且建议使用QQ、网易邮箱,请您务必保证您的邮箱正常接收验证码,否则无法完成注册和找回密码。另外,如果您**7**天内不访问网站,登录状态将会失效,需要重新登录。  | 序号 | 功能描述 | 备注 | | ---- | ------------------------------------------------------------ | ---- | | 1 | 点击"注册登录",弹出登录注册弹窗。如果账号已登录成功,那么此处显示您的昵称。 | | | 2 | 按需选择登录、注册、找回,输入信息。 | | | 3 | 退出账号,网站将会清理所有本地数据(线上数据不受影响),回到最初的模样。 | |
# ECMAScript JavaScript 官方名称是 `ECMAScript` 是一种属于网络的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。 # 编辑器 Vscode是一款微软出品的强大前端编辑器,具有很强的拓展性,常用插件有如下类型: 1. live server服务器形式运行代码,ctrl+s保存自动刷新代码; 2. auto rename tag标签更改后自动补全; 3. Bracket Pair Colorizer 2代码语法高亮; 4. Prettier Code formatter代码格式化,需要进入vscode设置,搜索save关键词,勾选format on save选项; 5. vue代码高亮; # 代码执行顺序 由于程序由上往下执行,避免程序页面延迟,将js代码放入body的尾部。 script标签按顺序执行,引入模块代码延后执行。 # 命名规则 数字、字母、下划线、$符号均可以作为变量名的组成部分,但是要注意不能以数字开头和关键名命名,例如:while、class等关键词均不可以使用。 # 声明关键字 ### var 1. var声明的变量没有块级作用域,仅有函数作用域,重复命名会覆盖; ```javascript if (1) { var a = 6; let b = 9; } console.log(a); // 6 console.log(b); // b is not defined ``` 2. var声明变量造成变量提升(先使用后申明),环境在执行代码之前优先将解析变量并放入堆中,未定义的变量优先定义为undefined,不会发生报错的现象,打开严格模式也不会进行报错; ```javascript a = 3; alert(a); var a; // 真实的执行顺序 1.var a; 2.a = 3; 3.alert(a); ``` ### let 1. let需要先声明后否则使用会造成TDC暂时性死区(报错); 2. 存在块级作用域; 3. 同一作用域下不可以重复声明; ### const 1. 定义后的常量不可改变; 2. 声明的同时必须赋值; 3. 当const是对象等引用类型常量时只要地址不变即可; 4. 拥有块、函数、全局作用域; ### 无关键词 无关键词定义全局变量会造成全域污染,导致变量冲突难以调试的问题。 ### object.freeze变量冻结 非严格模式下变量被冻结后再次被使用不会报错,而开启严格模式后控制台会进行报错。 ```javascript "use strict" const INFO = { url: 'https://n.huasenjio.top/', port: '8080' }; Object.freeze(INFO); INFO.port = '443'; //Cannot assign to read only property console.log(INFO); ``` # 作用域 ### 1.块级作用域 if、while、for语句形成块级作用域,var声明的变量是没有块级作用域,可以穿透块级作用域。 ```javascript <script> if (1) { var a = 9; console.log("1是逻辑值为真"); } console.log(a); // 输出9 </script> ``` ### 2.函数作用域 当声明函数时会在函数的内部形成一个函数的作用域,let和const声明的变量有块级作用和函数作用域,var也不能穿透函数作用域。所以可以用函数作用域来解决var无块级作用域的缺点。 ```javascript <script> function b() { var a = 9; } console.log(a); // a is not defined </script> ``` # 传值与传址 ### 1.传值 函数传递参数时,基本数据类型的赋值是直接传递值。 ### 2.传址 引用类型的复合对象的赋值是传递地址 # 严格模式 1. 变量需要先声明再使用; 2. 必须使用关键词声明变量; 3. 匿名函数的this不再指向window对象而是undefined; 4. 非规范写法会在严格模式下报错; # 运算符 ### 1.数学运算符 ```javascript let a = 4; let b = 5 console.log(3 % 2); // 1,取余; console.log(3 / 2); // 1.5,除法; console.log(a++); // 5,自加运算符,此时a=4,但这条指令执行完成后a+1; console.log(--b); // 6,自减运算符,此时a=6,执行这段代码前b+1; ``` ### 2.比较运算符 比较运算符分为两个,`“==”`和`“===”`,双等号的运算符比较时会将等式两边的数据变成相同的数据类型再比较,这个特点叫隐式转换;三等号运算符称为严格比较,不会转变为相同的数据类型来比较,数据类型不同就返回false。 ```javascript <script> // undefined仅与null和本身相等 console.log(0 == undefined); //false; console.log("" == undefined); //false; console.log([] == undefined); //false; console.log({} == undefined); //false; console.log(undefined == null); //true </script> ``` ### 3.三目运算符 `表达式 ? 表达式为真时 :表达式为假时`,可以嵌套使用。 # 循环控制 ### 1.if ```javascript if (1) { // 表达式为真的代码块 } else { // 表达式为假的代码块 } ``` ### 2.for ```javascript for (let i = 0; i < 10; i++) { // 执行代码块 } ``` ### 3.for-in for-in遍历数组时,根据下标遍历,遍历对象时是根据属性名遍历。 ```javascript let arr = ["森", "酱", "与", "猪"]; let obj = { name: "花森", sex: "男", }; // 遍历数组 for (const key in arr) { console.log(key); //下标 console.log(arr[key]); } // 遍历对象 for (const key in obj) { console.log(key); // name sex console.log(obj[key]); // 花森 男 } ``` ### 4.for-of for-of是根据数组内的元素进行遍历,可以遍历迭代器对象。 ```javascript let arr = ["森", "酱", "与", "猪"] for (const item of arr) { console.log(item); // 森 酱... } ``` ### 5.forEach ```javascript let arr = ["森", "酱", "与", "猪"]; arr.forEach((item, index, arr) => { console.log(item); // 元素 console.log(index); // 下标 console.log(arr); // 原数组 }); ``` ### 6.while ```javascript var a = 0; while (1) { a++; if (a == 10) { continue; // 跳过a = 10并直接进入a=11循环 } console.log(a); if (a == 20) { break; // 跳出结束循环 } } ``` ### 7.switch 不设置break会造成穿透效果 ```javascript let name = '视频'; switch (name) { case '产品': console.log('huasen'); break; case '视频': console.log('zhuqi'); break; default: console.log('hs'); } ``` ### 8.break和continue 1. break跳出当前循环体; 2. continue跳过本次循环; 3. 通过label跳出父级循环; ```javascript huasen: for (let i = 1; i <= 10; i++) { zhuqi: for (let n = 1; n <= 10; n++) { if (n % 2 != 0) { continue huasen; } console.log(i, n); if (i + n > 15) { break zhuqi; } } } ``` # this指向 匿名函数中的this指向的是window对象(严格模式下指向undefined),可以通过作用域链的方式获取到外部的this,同样也可以通过ES6箭头函数取到父级作用域的this指针。 ```javascript <script> let huasen = { names: "花森", sex: "男", showSex: function () { let that = this; function getName() { console.log(this.names); //this指向window,输出 undefined。 console.log(that.names); //that指向huasen,作用域链向上查找。 } getName(); //调用getName是一个函数getName,它没有没有依靠,因为匿名函数中的this是指向window,所以调用者就是window,所以this就指向window。 }, showName: () => { // 箭头函数默然会指向父级作用域,此处调用showName方法的是huasen,它的父级作用域就是window,this指向window。 console.log("方法是箭头函数", this); }, }; huasen.showSex(); // 男 huasen.showName(); </script> ``` #### <u>Call与Apply</u> `函数a.call(对象b,参数1,参数2...)`,函数a将它的this指针指向对象b,同时传递参数的形式是一个一个传递;`函数a.apply(对象b,[参数1,参数2...])`,函数a将它的this指针指向对象b,传递参数的形式是数组,两者更改this指向后不会立刻执行函数,bind的方式绑定this会立即执行该函数。 ```javascript function User(name, type = "人类") { this.name = name; this.type = type; showType = function () { console.log(this.name); }; // 默认会返回一个生成后的实例 return this; } function Pig(name, type = "猪") { this.name = name; this.type = type; showType = function () { console.log(this.name); }; } console.log(new User("花森", "人类")); let zhuqi = { name: "李琦", sex: "女", }; // User.call(zhuqi, "猪琦", "猪"); // 通过call将User函数的this指向zhuqi对象,call通过一个一个参数传入,最后返回this再次指向zhuqi,相同属性名会遭到覆盖。 User.apply(zhuqi, ["猪琦", "猪"]); // apply的不同点是通过数组直接传入参数,其他效果一致。 console.log(zhuqi); //{sex: "女", name: "猪琦", type: "猪"} ``` #### <u>Bind</u> bind()是将函数绑定到某个对象,比如 a.bind(hd) 可以理解为将a函数绑定到hd对象上即 hd.a()。绑定后会立即执行函数,bind是赋值函数行为会返回一个新的函数。 ```javascript function hs(a, b) { console.log(this); return this.f + a + b; } //使用bind会生成新函数 let newFunc = hs.bind({ f: 1 }, 3); // 将函数hs绑定给了对象{f:1} //1+3+2 参数2赋值给b即 a=3 b=2 console.log(newFunc(2)); ``` # 数据类型检测 ### 1.typeof 基本数据类型检测,无法辨别数组和对象,合适基本的数据类型辨别,具体可以辨别如下类型: 1. number/string/boolean; 2. function; 3. object; 4. undefined; ```javascript let a = 1; console.log(typeof a); //number let b = "1"; console.log(typeof b); //string //未赋值或不存在的变量返回undefined var huasen; console.log(typeof huasen); function run() {} console.log(typeof run); //function let c = [1, 2, 3]; console.log(typeof c); //object let d = { name: "n.huasenjio.top" }; console.log(typeof d); //object ``` ### 2.instanceof `instanceof` 运算符用于检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上,就是它是否存于某一个继承的链,如果是数组,那么他的祖先就必定存在Array构造函数,如此进行精准判断数据类型; ```javascript let hs = []; let huasen = {}; console.log(hs instanceof Array); //true console.log(huasen instanceof Array); //false let c = [1, 2, 3]; console.log(c instanceof Array); //true let d = { name: "n.huasenjio.top" }; console.log(d instanceof Object); //true function User() {} let hd = new User(); console.log(hd instanceof User); //true ``` # 字面量与对象 当我们声明一个数组`let a =[]`时,同样可以使用`a.push()`方法,只有对象才可以调用方法而Array构造函数的prototype原型上面拥有这个`push`方法,所以推论内部将[]转换成为对象。 ```JavaScript let hd = "huasen"; // 字面量形式 let h = new String("hs"); // 对象的形式 console.log(huasen.length); //6个字符 console.log(h.length); //2个字符 ``` # Number Number用于表示整数和浮点数,数字是 `Number`实例化的对象,可以使用对象原型上提供的丰富方法。 ### 1.数字变量的命名 ```javascript let huasen = 3; //字面量形式 let h = new Number(3); //对象的形式 console.log(h+3); //6 ``` ### 2.判断书否是整数 ```javascript console.log(Number.isInteger(1.2)); ``` ### 3.NaN无效数值 ```javascript console.log(Number("huasen")); //声明传参传递数字无效产生NaN console.log(2 / 'huasen'); //无效的数值计算会产生NaN Number.isNaN(变量) // 判断是否是NaN Object.is(对象1,对象2) // 判断两个对象是否相等 ``` ### 4.类型转换 使用Number构造函数基本上可以转换所有类型 ```javascript console.log(Number('huasen')); //NaN console.log(Number(true)); //1 console.log(Number(false)); //0 console.log(Number('9')); //9 console.log(Number([])); //0 console.log(Number([5])); //5 console.log(Number([5, 2])); //NaN console.log(Number({})); //NaN ``` ### 5.常用方法 1)字符串转数字parseInt: 提取的字符串并去除空白数字字串转变为整数 ```javascript console.log(parseInt('1' * 1)); //1 隐式转换 console.log(parseInt(' 99huasen')); //99 console.log(parseInt('18.55')); //18 小数点被忽略 ``` 2)字符串转为浮点数parseFloat: ```javascript console.log(parseFloat(' 99huasen')); //99 console.log(parseFloat('18.55')); //18.55且不会忽略小数点 ``` 3)浮点数四舍五入toFixed: ```javascript console.log(1.55667.toFixed(2)); //1.56 ``` # String 字符串类型是使用非常多的数据类型 ### 1.字符串变量的声明 ```javascript let hs = new String('huasen'); // 获取字符串长度 console.log(hs.length); // 获取字符串 console.log(hs.toString()); ``` ### 2.转义符号 有些字符有双层含义,需要使用 `\` 转义符号进行含义转换,下例中引号为字符串边界符,如果输出引号时需要使用转义符号。 ```javascript let content = '花森 \'huasenjio.top\''; // 内部两个'是需要转义 console.log(content); // 花森 'huasenjio,top' ``` 常见转义符号表: | 符号 | 说明 | | ---- | -------------- | | \t | 制表符 | | \n | 换行 | | \\ | 斜杠符号(转义) | | \' | 单引号 | | " | 双引号R | ### 3.连接运算符 ```javascript let year = 2019, name = '花森导航'; console.log(name + '成立于' + year + '年'); ``` ### 4.模板字面量 使用\`\`符号包裹的字符串中可以写入引入变量与表达式,变量使用${}放入,并且可以换行而不产生错误。 ```javascript let url = 'huasenjio.top'; console.log(`花森导航网址是${url}`); //花森导航网址是huasenjio.top ``` ### 5.标签模板 通过tag方法将字符串和变量分离到两个数组中 ```javascript let lesson = 'css'; let web = '花森'; tag `访问${web}学习${lesson}前端知识`; function tag(strings, ...values) { console.log(strings); //["访问", "学习", "前端知识"] console.log(values); // ["花森", "css"] } ``` ### 6.常用方法 1)使用`length`属性可以获取字符串长度: ```javascript console.log("huasenjio.top".length) ``` 2)字母大小写转换(toLowerCase和toLowerCase): ```javascript console.log('HUASENJIO.top'.toLowerCase()); //huasenjio.top 转小写 console.log('huasenjio.top'.toLowerCase()); //HUASENJIO.TOP 转小写 ``` 3)移除字符空白trim: ```javascript let str = ' huasenjio.top '; console.log(str.length); console.log(str.trim().length); console.log(name.trimLeft()); // 删除左空白 console.log(name.trimRight()); // 删除又空白 ``` 4)抓取单个字符chatAt: ```javascript console.log('huasenjio'.charAt(3)) // s 通过下标(0开始)获取到对于的字符 ``` 5)数组形式获取字符: ```javascript console.log('huasenjio'[3]) // 3 数字索引获取字符串 ``` ### 7.字符串的截取 #### <u>slice</u> - 截掉 ```javascript let hs = "12345678"; console.log(hs.slice(3)); //45678 slice(截掉的个数) ``` - 截取 ```javascript // 参数为正数情况 let hs = "12345678"; console.log(hs.slice(3, 6)); //456 [开始下标,结束下标) // 参数为负数情况 console.log(hs.slice(-2)); //78 末尾取两个 console.log(hs.slice(1, -2)); //23456 [开始下标,-2(倒数第二个开始)) 第二个参数是负数说明从尾部数 ``` #### <u>substring</u> - 截掉 ```javascript let hs = "12345678"; console.log(hs.substring(3)); //45678 substring(截掉的个数) ``` - 截取 ```javascript let hs = "12345678"; console.log(hs.substring(3, 6)); //456 [开始下标,结束下标) console.log(hs.substring(3, 0)); //123 参数中选最小值作为开始下标进行截取 console.log(hs.substring(3, -9)); //123 负数转为0再按最小值作为开始下标进行截取 ``` #### <u>substr</u> - 截掉 ```javascript let hs = "12345678"; console.log(hs.substr(3)); //45678 substr(截掉的个数) ``` - 截取 ```javascript let hs = "12345678"; console.log(hs.substr(3, 4)); //4567 [开始下标,截取个数] console.log(hs.substr(-3, 2)); //78 [-3,个数] ``` ### 8.查找字符串 #### <u>indexOf检索</u> 从开始(下标为零)进行查找,若查找到返回第一个目标的下标,查找不到返回-1,可以指定开始查找的位置。 ```javascript console.log("12345678".indexOf("3")); //1 console.log("12345678".indexOf("5", 3)); //4 从第3个字符向后搜索 ``` #### <u>search检索</u> search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索进行搜索,返回子串的开始下标。 ```javascript let str = "huasenjio.top"; console.log(str.search("top")); console.log(str.search(/top/i)); ``` ### 9.字符串替换 #### <u>replace</u> replace方法用于字符串的替换操作,默认替换第一个匹配的字符串,如果想要通过全局替换需要配合正则表达式使用。 ```javascript let name = "1122331122"; web = name.replace("22", "**"); console.log(web); // 配合正则完成替换 let str = "2023/02/12"; console.log(str.replace(/\//g, "-")); ``` ### 10.重复生成repeat 通过已有的字符串生成重复的字符串 ```javascript function star(num = 3) { return '*'.repeat(num); } console.log(star()); ``` ### 11.字符串分割split 通过某一个符号对字符串进行分割并返回分割后的数组 ```javascript console.log("1,2,3".split(",")); //[1,2,3] ``` ### 12.类型转换 ```javascript let hs = 99; console.log(hs.toString()); //99 String类型 let arr = ["1", "2", "3"]; console.log(arr.toString()); //1,2,3 String类型 let a = { name: "huasen", }; console.log(a.toString()); //[object Object] String类型 ``` # Math math数学对象,提供了很多数学相关的计算方法。 ### 1.最大值最小值 ```javascript console.log(Math.min(1, 2, 3)); // 1 console.log(Math.max(1, 2, 3)); // 3 ``` ### 2.向上取整ceil ```javascript console.log(Math.ceil(1.111)); // 2 ``` ### 3.向下整数floor ```javascript console.log(Math.floor(1.555)); // 1 ``` ### 4.四舍五入处理round ```javascript console.log(Math.round(1.5)); //2 ``` ### 5.生成随机数random `random` 方法用于返回 >=0 且 <1 的随机数(包括0但不包括1) ```javascript const number = Math.floor(Math.random() * 5); // [0,5) console.log(number); const number = Math.floor(Math.random() * (5+1)); // [0,5] console.log(number); const number = Math.floor(Math.random() * (5 - 2)) + 2; // [2,5) console.log(number); const number = Math.floor(Math.random() * (5 - 2+1)) + 2; // [2,5] console.log(number); X+Math.floor(Math.random()*(Y-X)) // [X,Y) X+Math.floor(Math.random()*(Y-X+1)) // [X,Y] ``` # Date 处理日期的方法,通过 `Date` 类型提供的丰富功能可以非常方便的操作。 ### 1.时间戳 定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数 ### 2.获取当前日期 ```javascript let now = new Date(); console.log(now); // Mon Oct 19 2020 21:15:23 GMT+0800 (中国标准时间) console.log(typeof now); //object console.log(now * 1); //获取时间戳 //直接使用函数获取当前时间 console.log(Date()); console.log(typeof Date()); //string //获取当前时间戳单位毫秒 console.log(Date.now()); ``` ### 3.时间格式的封装 ```javascript function dateFormat(date, format = "YYYY-MM-DD HH:mm:ss") { const config = { YYYY: date.getFullYear(), MM: date.getMonth() + 1, DD: date.getDate(), HH: date.getHours(), mm: date.getMinutes(), ss: date.getSeconds(), }; for (const key in config) { format = format.replace(key, config[key]); } return format; } console.log(dateFormat(new Date(), "YYYY年MM月DD日HH时mm分ss秒")); ``` # Boolean 布尔类型包括 `true` 与 `false` 两个值 ### 1.声明定义 ```javascript console.log(new Boolean(true)); //true 对象形式 let hs =true; // true 字面量形式 ``` ### 2.隐式转换 逻辑运算符"=="进行两个是比较是否相等时,不同的数据类型会造成隐式转换后再比较,属于js中比较难的部分,具体情况如下: 1. 数组和布尔值比较,`[] == true;//false `,空数组转为""再转为0,逻辑值true直接转为1; 2. 数组符串比较,`[1,2,3] == '1,2,3' // true`,[1,2,3]转换成字符串"1,2,3"最后跟字符串比较; 3. 字符串和数字进行比较,`'1' == 1 // true`,字符串转为数字后与数字1比较; 4. 字符串和布尔值进行比较,`'1' == true; // true `,字符串转为数字1,布尔值转为数字1,比较相等; 5. 布尔值和数字比较,`true == 1;/ true`,布尔值转为数字1再比较;  有趣的事情是`[] == false和![] == false`的结果都是true,第一个[]数组转为""再转成0,false直接转为0,比较相等;第二个同理,!符号优先级高所以先执行,所以式子就变成`(![]) == false `,估计就能理解了!另外有几个比较的特殊`undefined == null // true`、`NaN == XXX //NaN和任意类型都不等(包括自己)`。 ### 3.显示转换 使用 `!!` 转换布尔类型 ```javascript let hs = ""; console.log(!!hs); //false hs = 0; console.log(!!hs); //false hs = null; console.log(!!hs); //false hs = new Date("2020-2-22 10:33"); console.log(!!hs); //true hs = []; console.log(!!hs); //false hs = {}; console.log(!!hs); //false ``` # Array 数组是多个变量值的集合,数组是`Array` 对象的实例,Array原型对象上系统预设很多方式可以供我们调用。 ### 1.数组的声明 ```javascript console.log(new Array(1, '花森', 'hs')); //[1, "花森", "hs"] const array = ["花森", "huasen"]; const array = [["花森","hs"], ["猪琦","zhuqi"]]; console.log(array[1][0]); // 猪琦 let hs = Array.of(3); console.log(hs); // [3] hs = new Array(3); // 创建长度为3的空数组 console.log(hs); hs = Array.of(1, 2, 3); console.log(hs); //[1, 2, 3] ``` ### 2.类型检测isArray ```javascript console.log(Array.isArray([1, "花森", "hs"])); //true console.log(Array.isArray(9)); //false ``` ### 3.类型转换 #### <u>数组2字符串</u> 大部分数据类型都可以使用`.toString()`和join 函数转换为字符串 ```javascript console.log(([1, 2, 3]).toString()); // 1,2,3 toSring() console.log(String([1, 2, 3])); //1,2,3 构造函数 console.log([1, 2, 3].join("-"));//1-2-3 join() ``` #### <u>伪数组2数组</u> 使用`Array.from`可将类数组转换为数组,类数组指包含 `length` 属性或可迭代的对象,伪数组不具备数组中的某一个方法,一般使用时都要转为数组再进行使用。 ```javascript let str = '花森酱'; console.log(Array.from(str)); //["花", "森", "酱"] let user = { 0: '花森酱', '1': 18, length: 2 }; console.log(Array.from(user)); //["花森酱", 18] let divs = document.querySelectorAll("div"); let hs = [...divs] ``` #### <u>字符串2数组</u> 使用`split`,将数组中的元素连接成字符串。 ```javascript let price = "99,78,68"; console.log(price.split(",")); //["99", "78", "68"] ``` ### 4.数组合并拆分 #### <u>展开语法</u> 使用展开语法来合并数组相比 `concat` 要更简单,使用`...` 可将数组展开为多个值。 ```javascript et a = [1, 2, 3]; let b = ['a', '花森', ...a]; console.log(b); //["a", "花森", 1, 2, 3] ``` #### <u>concat</u> `concat`方法用于连接两个或多个数组,返回是一个数组,元素是值类型的是复制操作,如果元素是引用类型还是指向同一对象,属于浅拷贝的操作。 ```javascript let arr = ["hs", "花森"]; let hs = [1, 2]; let cms = [3, 4]; console.log(arr.concat(hs, cms)); //["hs", "花森", 1, 2, 3, 4] ``` #### <u>copyWithin</u> 使用 `copyWithin` 从数组中复制一部分到同数组中的另外位置,语法说明`array.copyWithin(target, start, end)`,target复制到的索引地址位置,start元素复制的开始索引,end结束的索引。 ```javascript const arr = [1, 2, 3, 4,5]; console.log(arr.copyWithin(2, 0, 2)); //[1, 2, 1, 2, 5] ``` ### 5.解构赋值 解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构。 ```javascript //数组使用 let [name, url] = ['花森', 'hs']; console.log(name); // 解构函数返回值 function huasen() { return ['hs', '花森']; } let [a, b] = huasen(); console.log(a); //hs // 函数参数解构 function hs([a, b], c) { console.log(a, b); console.log(c); } hs(["花森", "hs"], "猪琦"); // 解构数组 let [a, ...b] = ['花森', 'hs', '猪琦']; console.log(b); // ['hs', '猪琦']; // 解构字符串 "use strict"; const [...a] = "huasenjio"; console.log(a); //Array(9) // 解构单个变量 let [,url]=['花森','huasenjio']; console.log(url);//huasenjio ``` ### 6.操作元素 ```javascript // 数组追回元素 let arr = [1, "花森", "hs"]; arr[arr.length] = "huasenjio"; console.log(arr); //[1, "花森", "hs", "huasenjio"] // push批量压入元素,改变数组长度,返回值是元素的数量。 let arr = [1, "花森", "hs"]; arr.push("huasenjio","zhuqi"); console.log(arr); //[1, "花森", "hs", "huasenjio","zhuqi"] // pop尾弹一个元素,改变数组长度,返回被弹出的元素。 let arr = [1, "花森", "hs"]; arr.pop(); console.log(arr); //[1, "花森"] // shift头弹一个元素,改变数组长度,返回头弹出的元素。 let arr = [1, "花森", "hs"]; console.log(arr.shift()); console.log(arr); //["花森","hs"] // unshift头插一个元素,改变数组长度,返回值是元素的数量。 let arr = [1, "花森", "hs"]; console.log(arr.unshift("爱")); console.log(arr); //["花森","hs"] // fill填充数组,返回填充完成的数组。 console.log(new Array(4).fill("hs")); //["hs", "hs", "hs", "hs"] ``` ### 7.数组截取 #### <u>slice截取</u> 使用 `slice` 方法从数组中截取部分元素组合成新数组(并不会改变原数组),不传第二个参数时截取到数组的最后元素。不设置参数默认截取整个数组。 ```javascript let arr = [0, 1, 2, 3, 4, 5, 6]; console.log(arr.slice(1, 3)); // [1,2] [开始下标,结束下标] let str = "0123456"; console.log(str.slice(1, 3)); // 12 [开始下标,结束下标) ``` #### <u>splice截掉</u> 使用 `splice` 方法可以添加、删除、替换数组中的元素,会对原数组进行改变,返回值为删除的元素。 ```javascript // [开始下标,删除数量] let arr = [0, 1, 2, 3, 4, 5, 6]; console.log(arr.splice(1, 3)); //返回删除的元素 [1, 2, 3] console.log(arr); //删除数据后的原数组 [0, 4, 5, 6] // 通过修改length删除最后一个元素 let arr = ["1", "2","3","4","5"]; arr.length = arr.length - 1; console.log(arr); // [开始下标,删除参数,删除位置的新元素] let arr = [0, 1, 2, 3, 4, 5, 6]; console.log(arr.splice(1, 3, 8)); //返回删除的元素 [1, 2, 3] console.log(arr); //删除数据后的原数组 [0, 8, 4, 5, 6] // 两个交换位置小案例 function move(array, before, to) { if (before < 0 || to >= array.length) { console.error("指定位置错误"); return; } const newArray = [...array]; const elem = newArray.splice(before, 1); newArray.splice(to, 0, ...elem); return newArray; } const array = [1, 2, 3, 4]; console.table(move(array, 0, 3)); ``` ### 8.数组清空 ```javascript // 更改指针法 let user = [{ name: "hs" }, { name: "花森" }]; let cms = user; user = []; console.log(user); // [] console.log(cms); // [{...},{...}] // 修改长度法 let user = [{ name: "hs" }, { name: "花森" }]; user.length = 0; console.log(user); // splice方法删除所有数组元素 let user = [{ name: "hs" }, { name: "花森" }]; user.splice(0, user.length); console.log(user); ``` ### 9.元素查找 #### <u>indexOf</u> 使用 indexOf 从前向后查找元素出现的位置,如果找不到返回 -1,indexOf使用严格模式去匹配,找到则返回第一次出现的下标。 ```javascript let arr = [7, 3, 2, 8, 2, 6]; console.log(arr.indexOf(2)); // 2 从前往后查找第一个出现2的下标 // 指定索引向后进行查找 let arr = [7, 3, 2, 8, 2, 6]; console.log(arr.indexOf(2, 3)); //4 从第二个元素开始向后查找 ``` #### <u>lastIndexOf</u> 使用 `lastIndexOf` 从后向前查找元素出现的位置,具体使用参考indexOf方法使用。 ```javascript let arr = [7, 3, 2, 8, 2, 6]; console.log(arr.lastIndexOf(2)); // 4 从后查找2出现的位置 ``` #### <u>includes</u> 使用 `includes` 查找字符串返回值是布尔类型更方便判断 ```javascript let arr = [7, 3, 2, 6]; console.log(arr.includes(8)); //flase ``` #### <u>find</u> find 方法找到后会把值返回出来,找不到则返回值为undefined,返回第一次查找到的词就停止查找。 ```javascript let arr = [ { name: "猪琦", sex: "女" }, { name: "花森", sex: "男" }, { name: "皮卡丘", sex: "动物" }, ]; let find = arr.find(function (item) { return item.name == "花森"; }); console.log(find); // {name: "花森", sex: "男"} ``` ### 10.数组顺序 #### <u>reverse</u> 对数组进行反转输出 ```javascript let arr = [1, 4, 2, 9]; console.log(arr.reverse()); //[9, 2, 4, 1] ``` #### <u>sort</u> sort方法每次使用两个值进行比较,`Array.sort((a,b) => a-b)`,返回负数a排在b的前,从小到大排序;返回正数则b排在a的前面,返回0时不动。 ```javascript // 默认从小到大排序 let arr = [1, 4, 2, 9]; console.log(arr.sort()); //[1, 2, 4, 9] // 按某值进行排序 console.log(arr.sort(function (v1, v2) { return v2 - v1; })); //[9, 4, 2, 1] ``` ### 11.数组拓展函数 #### <u>every一假则假</u> `every` 用于递归的检测元素,要所有元素操作都要返回真结果才为真,遇到一个不满足条件就立即退出,不再继续遍历下去,最终返回布尔值。 ```javascript const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; const resust = user.every((item, index, arr) => { console.log(item); // 元素 console.log(index); // 下标 console.log(arr); // 原数组 return item.js >= 60; }); console.log(resust); ``` #### <u>some一真则真</u> 使用 `some` 函数可以递归的检测元素,如果有一个返回true。 ```javascript const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; const resust = user.some((item, index, arr) => { console.log(item); // 元素 console.log(index); // 下标 console.log(arr); // 原数组 return item.js >= 60; }); console.log(resust); ``` #### <u>filter</u> 按一定条件筛选元素并组成新数组返回 ```javascript const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; const resust = user.filter((item, index, arr) => { console.log(item); // 元素 console.log(index); // 下标 console.log(arr); // 原数组 return item.js > 60; // 过滤取到js成绩大于60的元素并返回一个新的数组 }); console.log(resust); ``` #### <u>map</u> 将遍历数组,遍历过程中执行代码,最后返回一个元素添加到新数组中并返回。 ```javascript const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; const resust = user.map((item, index, arr) => { console.log(item); // 元素 console.log(index); // 下标 console.log(arr); // 原数组 item.js > 60; return item.js + "分数"; }); console.log(resust); ``` #### <u>reduce</u> 使用 `reduce` 与 `reduceRight` 函数可以迭代数组的所有元素,`reduce` 从前开始 `reduceRight` 从后面开始,存在如下参数: | 参数 | 说明 | | ----- | -------------------------- | | prev | 上次调用回调函数返回的结果 | | cur | 当前的元素值 | | index | 当前的索引 | | array | 原数组 | ```javascript const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; const resust = user.reduce((pre, cur, index, arr) => { console.log(cur); // 当前元素 console.log(index); // 下标 console.log(pre); // 上一个遍历的元素 return "返回值"; // 返回值将作为pre参数 }, 0); // 0指定pre的初始值 //计算所有js成绩的和 const user = [ { name: "李四", js: 89 }, { name: "马六", js: 55 }, { name: "张三", js: 78 }, ]; let t = 0; const resust = user.reduce((pre, cur, index, arr) => { console.log(cur); // 当前元素 console.log(index); // 下标 console.log("pre", pre); // 上一个遍历的元素 return (pre += cur.js); // 返回值将作为pre参数 }, 89); ``` # 迭代器 数组中可以使用多种迭代器方法 ### keys 通过迭代对象获取索引,可通过next()一步一步向下拿到全部索引并执行操作。 ```javascript const hs = ["花森", "huasen","猪琦"]; const keys = hs.keys(); // 获得迭代器对象 console.log(keys.next()); // {value: 0, done: false} console.log(keys.next()); // {value: 1, done: false} // 需要把上面两行console.log去掉,因为上面两行迭代器就已经遍历到最后了,所以影响输出效果。 for (const key of keys) { console.log(key); } ``` ### values 通过迭代对象获取值,可通过next()一步一步向下拿到全部索引并执行操作。 ### entries 返回数组所有键值对的迭代器,可以使用解构加for-of循环获取数据。 ```javascript const arr = ["a", "b", "c", "花森"]; for (const [key, value] of arr.entries()) { console.log(key, value); } ``` # Symbol Symbol 的值是唯一的数据类型,防止使用属性名冲突而产生,比如向第三方对象中添加属性就有可能产生变量名冲突,而且Symbol不可以添加属性。 ### 1.变量声明 ```javascript let hs = Symbol(); let edu = Symbol("传入描述Symbol的字符串,有利于分辨Symbol!"); console.log(hs); // symbol console.log(edu); // Symbol(传入描述Symbol的字符串,有利于分辨Symbol!) console.log(edu.description) // 获取描述 console.log(hs == edu); //false ``` ### 2.查找Symbol.for 根据描述获取到唯一Symbol变量,如果不存则新建一个Symbol对象,使用`Symbol.for`会被系统登记,而使用Symbol不会被系统登记。 ```javascript let hs = Symbol.for("传入描述Symbol的字符串,有利于分辨Symbol!"); let edu = Symbol.for("传入描述Symbol的字符串,有利于分辨Symbol!"); console.log(hs == edu); // true ``` ### 3.登记记录Symbol.keyFor 返回Symbol登记的描述,如果没有找到则返回undefined ```javascript let hs = Symbol.for("花森"); console.log(Symbol.keyFor(hs)); //花森 ``` ### 4.操作要点 Symbol可以保证对象属性的唯一,Symbol的声明和访问使用[]的形式进行操作,不能使用`.`操作符,因为点是操作字符串属性的写法。 ```javascript let symbol = Symbol("花森"); let obj = { [symbol]: "huasenjio" }; console.log(obj[symbol]); //huasenjio ``` ### 5.实例操作 #### <u>覆盖耦合</u> 通过Symbol作为键值,每一个Symbol均是唯一,所以存入数据不会因为key值相同造成覆盖的可能。 ```javascript class Cache { static data = {}; static set(name, value) { this.data[name] = value; } static get(name) { return this.data[name]; } } let user = { name: "花森", key: Symbol("343434"), }; let cart = { name: "猪琦", key: Symbol("121212"), }; Cache.set(user.key, user); Cache.set(cart.key, cart); console.log(Cache.get(user.key)); console.log(Cache.data); ``` #### <u>遍历属性</u> Symbol 不能使用 `for/in和for/of` 遍历操作,遍历时Symbol属性默认被忽略,但是可以通过`Object.getOwnPropertySymbols`,遍历到对象中的所有Symbol属性。 ```javascript let symbol = Symbol("导航"); let user = { name: "花森", [symbol]: "huasenjio", }; for (const key of Object.getOwnPropertySymbols(user)) { console.log(key); } let symbol = Symbol("导航"); let user = { name: "花森", [symbol]: "huasenjio", }; for (const key of Object.getOwnPropertySymbols(user)) { console.log(key); } ``` #### <u>变量保护</u> 使用symbol作为属性名,起到变量保护的作用,无法被遍历访问到,可以通过对外开辟函数的方式去访问。 ```javascript const site = Symbol("网站名称"); class User { constructor(name) { this[site] = "森酱"; this[name] = name; } getName() { return `${this[site]}-${this.name}`; } } const hs = new User("hs"); console.log(hs.getName()); console.log(hs.site); // 无法访问到森酱 for (const key in hs) { console.log(key); } ``` # Set 无论是基本类型还是对象引用,都不能存入重复的元素,只可以保存值而没有键名;严格类型检测入字符串不等于数值型数字,即元素唯一。 ### 1.变量声明 使用数组作为初始化的数据,如果存在多个相同值则仅会保存第一个值,其余的全部忽略。 ```javascript let hs = new Set(["花森", "hs", "1", 1]); console.log(hs.values()); //{"花森", "hs", "1" ,1} ``` ### 2.常用操作 #### <u>add添加元素</u> ```javascript let hs = new Set(); hs.add('huasen'); ``` #### <u>size获取数量</u> ```javascript let hs = new Set(['huasen', '花森']); console.log(hs.size); //2 ``` #### <u>has检测元素</u> ```javascript let hs = new Set(['huasen', '花森']); console.log(hs.has("花森")); // true ``` #### <u>delete删除</u> ```javascript console.log(hs.delete("hdcms")); //true ``` #### <u>clear清空元素</u> ```javascript hs.clear(); ``` ### 3.数组转换 ```javascript const set = new Set(["hs", "花森"]); console.log([...set]) // 点语法 console.log(Array.from(set)); // array.from ``` ### 4.去重 ```javascript console.log([...new Set([1,2,3,3,3,4])];//[1,2,3,4] console.log([...new Set("hhssjjoo")].join(""));//hsjo ``` ### 5.遍历数据 ```javascript let arr = [7, 6, 2, 8, 2, 6]; let set = new Set(arr); //使用forEach遍历 set.forEach((item,key) => console.log(item,key)); // item 和 value一致 //使用for-of for (const iterator of set) { console.log(iterator); } ``` ### 6.交集 通过数组的过滤函数filter和has查询 ```javascript let hs = new Set(["1", "8"]); let zhuqi = new Set(["2", "8"]); let newSet = new Set([...hs].filter((item) => zhuqi.has(item))); // 返回zhuqi数据中也有的数 console.log(newSet); //{"8"} ``` ### 7.差集 ```javascript let hs = new Set(["1", "8"]); let zhuqi = new Set(["2", "8"]); let newSet = new Set([...hs].filter((item) => !zhuqi.has(item))); // 返回zhuqi数据中没有的数 console.log(newSet); ``` ### 8.交集 ```javascript let hs = new Set(["1", "8"]); let zhuqi = new Set(["2", "8"]); let newSet = new Set([...hs,...zhuqi]); // 返回zhuqi数据中没有的数 console.log(newSet); ``` ### 9.WeakSet WeakSet结构同样不会存储重复的值,储存的元素必须是对象类型,垃圾回收机制不考虑WeakSet,当一个变量被引用时,内存中会有一个引用计数器会进行加一,但对于weakset引用,计数器不会加一,所以weakset不管是否在使用变量都均会被删除,是属于弱引用的范畴,weakset没有keys(),values(),entries()和size等方法,且不能被遍历! #### <u>声明</u> ```javascript new WeakSet(["hs", "zhuqi"]); //Invalid value used in weak set new WeakSet("hdcms"); //Invalid value used in weak set new WeakSet([{ name: "huasen" }]); //正确 ``` #### <u>基本操作</u> ```javascript const hs = new WeakSet(); const arr = ["hdcms"]; //添加操作 hd.add(arr); console.log(hs.has(arr)); //删除操作 hd.delete(arr); //检索判断 console.log(hs.has(arr)); ``` # Map Map是一组键值对的结构,用于解决以往不能用对象做为键的问题,具有极快的查找速度,函数、对象、基本数据类型均可以作为键和值。其中键是对象则保存的是内存地址,如果值相同但内存地址不同也会被视为两个键值对。 ### 1.声明定义 可以接受数组作为参数,该数组的成员是一个表示键值对的数组。 ```javascript let m = new Map([ ["h", "花"], ["s", "森"], ]); console.log(m.get("h")); //花 ``` ### 2.基本操作 ```javascript let m = new Map([ ["h", "花"], ["s", "森"], ]); // 获取键值对的数量 console.log(m.size); // 读取元素 console.log(m.get("h")); //花 // 删除元素 console.log(m.delete("h")); //花 // 清空map console.log(map.clear()); ``` ### 3.遍历数据 可以使用`keys/values` 函数遍历键与值 ```javascript let hs = new Map([["h", "花"], ["s", "森"]]); for (const key of hs.keys()) { console.log(key); } for (const value of hs.values()) { console.log(value); } ``` ### 4.数组转换 1. [...map]; 2. Array.from(map); ### 5.WeabMap 详细请参考WeabSet的用法 # 函数进阶 函数是将复用的代码块封装起来的模块,js中的函数也是对象,可以通过构造函数Function创建实例,全域定义的函数是属于window对象中,容易造成覆盖的问题,使用let/const定义的参数时不会压入window对象中。标准声明的函数优先级更高,解析器会优先执行提取放在代码树的顶端,所以同一作用域下函数声明的位置顺序不限制。 ### 1.声明定义 ```javascript // 构造函数的方式 let hs = new Function("title", "console.log(title)"); hd('花森酱'); // 标准语法的方式 function hs(num) { return ++num; } console.log(hs(3)); // es6的箭头函数 let huasen = () => { } // 简写形式 user = { setName() { } } ``` ### 2.匿名函数 匿名函数需要赋值给某一个变量,而且一定需要以`;`结尾,匿名函数的执行者是window对象即内部的this是指向window对象,严格模式下this指向undefined而不是window对象。 ```javascript let hs = function () { console.log(this); // window对象 }; hs(); [].map(()=>{}) ``` ### 3.立即执行函数 立即执行函数指函数定义时立即执行,可以定义私有作用域防止污染全局作用域。 ```javascript "use strict"; (function () { var web = 'hs'; })(); console.log(web); //web is not defined ``` ### 4.函数参数 #### <u>形参实参</u> 调用函数时传入的变量叫做实参,定义函数参数是的变量叫做形参。当形参的数量大于实参时,没有被赋值的形参就被定义为undefined;实参的数量大于形参时,多余的实参将被忽略并且不会报错。 ```javascript // n1,n2 为形参 function sum(n1, n2) { return n1+n2; } // 参数 2,3 为实参 console.log(sum(2, 3)); //5 ``` #### <u>默认参数</u> 通常为形参设置默认值 ```javascript // 无序考虑兼容 function avg(total, year) { year = year || 1; // 赋值 return Math.round(total / year); } console.log(avg(2000, 3)); //es6写法需要考虑兼容性 function avg(total, year = 1) { return Math.round(total / year); } console.log(avg(2000, 3)); ``` #### <u>回调函数</u> 函数调用时可以传递函数参数,一般用于回调函数,执行调用的函数a时,默认执行传入的函数,这个场景叫做回调,传入的函数称为回调函数。 ```javascript // 箭头函数演示 [].filter(()=> {}) // 普通函数 [].filter(function(){}) ``` #### <u>argument</u> arguments 是函数获得到所有参数集合,获得传入参数的集合。 ```javascript // 普通参数 let hs = function () { console.log(arguments); // 函数参数 }; hs(1, 2, 3, 4); // 对参数进行求和 function sum() { return [...arguments].reduce((total, num) => { return (total += num); }, 0); } console.log(sum(2, 3, 4, 2, 6)); //17 ``` ### 5.递归调用 递归指函数内部调用自身的方式,主要用于数量不确定的循环操作,必须有要结束循环的条件否则会陷入死循环,一般慎用递归调用,容易造成内存泄露的风险。 ```javascript function factorial(sum) { if (sum <= 1) { return sum; } else { return sum * factorial(--sum); } } console.log(factorial(4)); ``` ### 6.标签函数 ```javascript function hd(str, ...values) { console.log(str); //["站点", "-", "", raw: Array(3)] console.log(values); //["花森", "huasenjio.top] } let name = "花森", url = "huasenjio.top"; hd`站点${name}-${url}`; ``` # 作用域 作用域链只向上查找,找到全局window即终止。当在每一个对象中引用了一个变量是,如果挡墙作用域不存在就往上级作用域查找,`[].toString()`,可以调用输出字符串,细心的小伙伴可能发现,数组的原型根本没有toString方法,但是Object上面存在toString方法。所以查找toString变量的顺序是先去找数组的原型,再去找在Object上的原型,直到window全局,如果还没有找到则报错。 ```javascript console.log(name); // 直接这样输出并不会报错因为window上默认存在name属性且为空 console.log(n); // n is not defined 因为window上面没有n属性 ``` # 对象 面向对象程序设计成为OOP,对象是属性和方法的集合体,内部的复杂逻辑代码被隐藏,仅仅暴露少量代码给外界,更改对象内部的复杂逻辑不会对外部造成影响和抽象,继承是通过代码复用减少冗余,根据不同形态的对象产生不同的结果称之为多态。 ### 1.声明定义 ```javascript let obj = { name: 'huasen', get:function() { return this.name; } } console.log(obj.get()); //huasen ``` ### 2.属性管理 #### <u>基本操作</u> ```javascript let user = { name: "huasen", ["my-title"]: "花森导航", }; console.log(user.name); // 使用点语法 console.log(user["name"]); // 使用数组形式 console.log(user["my-title"]); // 如果属性名不是合法变量就必须使用括号的形式 // 添加属性 user.a = "A" // 使用点语法 user["b"] = "B" // 使用数组形式 // 删除属性 delete user.name; ``` #### <u>检测属性</u> 1)`hasOwnProperty`检测对象自身是否包含指定的属性,不检测原型链上继承的属性。 ```javascript let obj = { name: '花森'}; console.log(obj.hasOwnProperty('name')); //true ``` 2)使用 `in` 可以在原型对象上检测。 ```javascript let obj = {name: "花森"}; let hs = { web: "huasenjio" }; //设置hs为obj的新原型 Object.setPrototypeOf(obj, hs); console.log(obj); console.log("web" in obj); //true 说明web属性在obj的原型链上 console.log(obj.hasOwnProperty("web")); //false ``` #### <u>assign属性合并</u> 使用Object.assign静态方法进行从一个或多个对象复制属性,并将属性合并,但是是浅拷贝。 ```javascript "use strict"; let hs = { a: 1, b: 2 }; hd = Object.assign(hs, { f: 1 }, { m: 9 }); console.log(hs); //{a: 1, b: 2, f: 1, m: 9} ``` #### <u>动态属性</u> 对象属性可以通过表达式计算定义,动态设置属性或者执行方法时很美妙。 ```javascript let id = 0; const user = { [`id-${id++}`]: id, [`id-${id++}`]: id, [`id-${id++}`]: id }; console.log(user); ``` ### 3.引用特性 对象和函数一样是引用数据类型,赋值操作相当于复制并赋予地址,其实还是引用同一块内存地址。 ```javascript let user = { name: "huasen", ["tit-le"]: "花森导航", }; let per = user; console.log(per); per.name = "猪琦"; console.log(user); // user中也被修改了 // 函数参数同样也是赋值地址 (function () { arguments[0]["tit-le"] = "笔录"; })(user); ``` ### 4.对象转换 对象直接参与计算时,系统根据计算的场景在`string、number、default`之间转换。如果场景需要字符串类型,则对象执行toString()后执行valueOf获取到字符串,如果需要字符串型,先执行valueOf获得数值后执行toString获得字符串。 ```javascript let hs = { name: "花森", num: 1, valueOf: function () { console.log("valueOf"); return this.num; }, toString: function () { console.log("toString"); return this.name; }, }; console.log(hs + 3); //valueOf 4 console.log(`${hs}导航`); //toString 花森导航 ``` ### 5.解构赋值 解构是一种更简洁的赋值特性,可以理解为分解一个数据的结构,用法与数组的解构相似,建议解构使用`var/let/const`声明,否则严格模式下会报错。 ```javascript // 对象的解构 let info = {name:'花森',url:'huasenjio'}; let {name:n,url:u} = info // 重新定义变量名为n u console.log(n); // 如果属性名与变量相同可以省略属性定义 let {name:n,url:u} = {name:'花森',url:'huasenjio'}; // 函数返回值解构到变量 function hs() { return { name: '花森', url: 'huasenjio' }; } let {name: n,url: u} = hs(); console.log(n); // 函数传参 "use strict"; function hd({ name, age }) { console.log(name, age); //花森 18 } hd({ name: "花森", age: 18 }); ``` ### 6.默认值 ```javascript let [name, site = '花森'] = ['hs']; console.log(site); //花森 ``` ### 7.遍历对象 #### <u>keys/values/entries</u> ```javascript const hs = { name: "花森", age: 10 }; console.log(Object.keys(hs)); //["name", "age"] 获取对象属性名组成的迭代器 console.log(Object.values(hs)); //["花森", 10] 获取对象属性组成的迭代器 console.table(Object.entries(hs)); //[["name","花森"],["age",10]] 两个迭代器 ``` #### <u>for/of遍历迭代器</u> For-of是不可以直接遍历对象,它是用于遍历迭代对象。 ```javascript const hd = { name: "后盾人", age: 10 }; for (const key of Object.keys(hd)) { console.log(key); } ``` ### 8.拷贝对象 #### <u>浅拷贝</u> ```javascript // for-in的方式遍历对象进行浅拷贝 let obj = {name: "花森"}; let hs = {}; for (const key in obj) { hs[key] = obj[key]; } hs.name = "huasen"; console.log(hs); console.log(obj); // Object.assign进行简单的浅拷贝但同名属性将会被覆盖 Object.assign(hs, obj); hs.name = "huasen"; console.log(hs); console.log(obj); // 展开语法 let hs = { ...obj }; ``` #### <u>深拷贝</u> 浅拷贝不会将深层的数据复制,深拷贝完全就是复制出一个新的对象,两个对象完全独立。 ```javascript let obj = { name: "花森", user: { name: "hs", }, data: [], }; function copy(object) { let obj = object instanceof Array ? [] : {}; // 判断是数组或者对象进行声明变量 // 解构获得键值对 for (const [k, v] of Object.entries(object)) { obj[k] = typeof v == "object" ? copy(v) : v; // 如果当前属性是引用类型则递归调用,基础数据类型则直接赋值。 } return obj; // 返还对象 } let huasen = copy(obj); huasen.data.push("zhuqi"); console.log(JSON.stringify(huasen, null, 2)); console.log(JSON.stringify(obj, null, 2)); ``` ### 9.构造函数 #### <u>工厂模式</u> 普通函数中返还一个相同结构的对象,修改工厂模式的方法会影响所有的同类对象,且声明不需要new关键词。 ```javascript function stu(name) { return { name, show() { console.log(this.name); // this代表函数调用者 } }; } const lisi = stu("李四"); lisi.show(); const hs = stu("huasen"); hs.show(); ``` #### <u>构造函数</u> 构造函数的函数名首字母需要大写的命名规范,this指向当前创建的对象,系统会自动返回this关键词,但也可以收到return返回,手动返回必须是对象,不然setter方法会屏蔽且需要new关键词生成对象。 ```javascript function Student(name) { this.name = name; this.show = function() { console.log(this.name); }; // return this; // 系统会自动返回 } const lisi = new Student("李四"); lisi.show(); const xj = new Student("王五"); wangwu.show(); ``` ### 10.属性特征 #### <u>查看特征</u> 使用 `Object.getOwnPropertyDescriptor`查看对象属性的描述 ```javascript let obj = { name: "花森", user: { name: "hs", }, data: [], }; console.log( JSON.stringify(Object.getOwnPropertyDescriptor(obj, "name"), null, 2) ); // { // "value": "花森", // "writable": true, // "enumerable": true, // "configurable": true ``` | 特性 | 说明 | 默认值 | | ------------ | ---------------------------------------------------- | --------- | | configurable | 能否使用delete 能否需改属性特性 或能否修改访问器属性 | true | | enumerable | 对象属性是否可通过for-in循环或Object.keys() 读取 | true | | writable | 对象属性是否可修改 | true | | value | 对象属性的默认值 | undefined | #### <u>设置属性</u> 使用`Object.defineProperty` 方法修改属性特性 ```javascript // 禁止遍历修改删除 "use strict"; const user = { name: "花森", age: 18 }; Object.defineProperty(user, "name", { value: "花森", writable: false, enumerable: false, configurable: false }); // 一次性修改多个属性 Object.defineProperty(user, { name: {value: "花森", writable: false}, age: {value: 18,enumerable: false} }); ``` #### <u>禁止添加</u> `Object.preventExtensions` 禁止向对象添加属性,`Object.isExtensible(user)`可以判断是否可以向属性中添加属性。 ```javascript "use strict"; const user = { name: "花森" }; Object.preventExtensions(user); user.age = 18; //Error ``` #### <u>封闭对象</u> Object.seal()`方法封闭一个对象,阻止添加新属性并将所有现有属性标记为 `configurable: false,可以通过`isSealed`检测是否发生封闭。 #### <u>冻结对象</u> `Object.free`za 冻结的对象不允许添加、删除、修改、writable、configurable都会被标记为`false`。 ```javascript "use strict"; const user = { name: "花森" }; Object.freeze(user); user.name = "花森"; //Error ``` ### 12.属性访问器 getter方法用于获得属性值,setter方法用于设置属性,这是JS提供的存取器特性即使用函数来管理属性。用于避免错误的赋值,需要动态检测值的改变。属性只能在访问器和普通属性选择一个,不能共同存在。 #### <u>getter/setter</u> ```javascript "use strict"; const user = { data: { name: "花森", age: null }, set age(value) { if (typeof value != "number" || value > 100 || value < 10) { throw new Error("年龄格式错误"); } this.data.age = value; }, get age() { return `年龄: ${this.data.age}`; }, }; user.age = 18; // 不能随便赋值 console.log(user.age); ``` #### <u>内部私有属性</u> ```javascript "use strict"; const user = { get name() { return this._name; }, set name(value) { if (value.length <= 3) { throw new Error("用户名不能小于三位"); } this._name = value; }, }; user.name = "花森酱酱"; console.log(user.name); ``` #### <u>访问器描述符</u> 使用 `defineProperty` 定义私有属性 ```javascript // 函数写法 function User(name, age) { let data = { name, age }; Object.defineProperties(this, { name: { get() { return data.name; }, set(value) { if (value.trim() == "") throw new Error("无效的用户名"); data.name = value; } }, age: { get() { return data.name; }, set(value) { if (value.trim() == "") throw new Error("无效的用户名"); data.name = value; } } }); } // class语法糖写法 "use strict"; const DATA = Symbol(); class User { constructor(name, age) { this[DATA] = { name, age }; } get name() { return this[DATA].name; } set name(value) { if (value.trim() == "") throw new Error("无效的用户名"); this[DATA].name = value; } get age() { return this[DATA].name; } set age(value) { if (value.trim() == "") throw new Error("无效的用户名"); this[DATA].name = value; } } // 测试代码 let hs = new User("花森", 18); console.log(hs.name); hs.name = "huasen"; console.log(hs.name); console.log(hs); ``` ### 13.代理拦截 代理(拦截器)是整个对象的访问控制,`setter/getter` 是对单个对象属性的控制,而代理是对整个对象的控制。 1. 读写属性时代码更简洁; 2. 对象的多个属性控制统一交给代理完成; 3. 严格模式下set必须返回布尔值; ```javascript let user = { data: { name: "花森", sex: 18 }, title: "函数代理", }; ("use strict"); // 为对象建立代理 const proxy = new Proxy(user, { get(obj, property) { console.log("getter方法执行了"); return obj[property]; }, set(obj, property, value) { console.log("setter方法执行了"); obj[property] = value; return true; }, }); // 使用代理修改属性 proxy.age = 10; // 使用代理获得属性 console.log(proxy.name); ``` ### 14.JSON json是广泛运用前后端数据交换的格式,具有轻量级且易于阅读和编写的特点。 1. json是数据格式替换xml的最佳方式; 2. 前后端交互数据的主要格式; 3. json标准中要求双引号包裹属性; ```json let lessons = [ { "title": '媒体查询', "category": 'css', "click": 199 }, { "title": 'FLEX', "category": 'css', "click": 12 }, { "title": 'MYSQL', "category": 'mysql', "click": 89 } ]; console.log(lessons[0].title); ``` #### <u>序列化</u> 序列化是将 `json` 转换为字符串,一般用来向其他语言传输使用。 ```json let lessons = [ { "title": '媒体查询', "category": 'css', "click": 199 }, { "title": 'FLEX', "category": 'css', "click": 12 }, { "title": 'MYSQL', "category": 'mysql', "click": 89 } ]; // 使用JSON字符串序列化 console.log(JSON.stringify(lessons)); // 值 属性 tab数 ``` #### <u>反序列化</u> 使用 `JSON.parse` 将字符串 `json` 解析成对象 ```javascript console.log(JSON.parse(jsonStr)); ``` # 事件 文档、浏览器、标签元素等元素在特定状态下触发的行为即为事件,JS为不同的事件定义的类型,事件目标是指产生事件的对象,事件具有冒泡捕获的特性,一个行为可能会造成多个事件的触发,处理事件的一段代码称为处理程序。 ### 1.事件绑定 #### <u>html中绑定</u> 可以在html元素上设置事件处理程序,浏览器解析后会绑定到DOM属性中。 ```html <button onclick="alert(`huasen`)">huasen</button> ``` #### <u>dom绑定</u> 可以将事件处理程序绑定到DOM属性中,使用setAttribute方法设置事件处理程序无效,属性名称区分大小写。 ```html <div id="app">huasen</div> <script> const app = document.querySelector('#app') app.onclick = function () { this.style.color = 'red' } </script> ``` #### <u>事件监听</u> 建议使用新的事件监听绑定方式,`transtionend / DOMContentLoaded` 等事件类型只能使用事件监听addEventListener 处理,同一个事件类型设置多个事件处理程序则会按顺序先后执行,同样可以给为添加元素添加事件。具有以下参数方法: 1)参数一,事件类型; 2)参数二,事件处理程序; 3)参数三,制定的选项,once仅执行一次,capture:true/false捕获阶段,passive:true永远不调用`preventDefault()`阻值默认行为; | 方法 | 说明 | | ------------------- | ---------------- | | addEventListener | 添加事件处理程序 | | removeEventListener | 移除事件处理程序 | ### 2.事件对象 执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对事。系统会自动做为参数传递给事件处理程序,事件对象常用属性如下: | 属性 | 说明 | | ------------------ | ------------------------------------------------------------ | | type | 事件类型 | | target | 事件目标对象 冒泡的父级通过该属性可以找到在哪个元素上执行了事件 | | currentTarget | 当前执行事件的对象 | | timeStamp | 事件发生时间 | | x | 相对窗口的X坐标 | | y | 相对窗口的Y坐标 | | clientX | 相对窗口的X坐标 | | clientY | 相对窗口的Y坐标 | | screenX | 相对计算机屏幕的X坐标 | | screenY | 相对计算机屏幕的Y坐标 | | pageX | 相对于文档的X坐标 | | pageY | 相对于文档的Y坐标 | | offsetX | 相对于事件对象的X坐标 | | offsetY | 相对于事件对象的Y坐标 | | layerX | 相对于父级定位的X坐标 | | layerY | 相对于父级定位的Y坐标 | | path | 冒泡的路径 | | altKey | 是否按了alt键 | | shiftKey | 是否按了shift键 | | metaKey | 是否按了媒体键 | | window.pageXOffset | 文档参考窗口水平滚动的距离 | | window.pageYOffset | 文档参考窗口垂直滚动的距离 | ### 3.冒泡捕获 #### <u>冒泡行为</u> 标签元素是嵌套的,在一个元素上触发的事件,同时也会向上执行父级元素的事件处理程序,一直到HTML标签元素。大部分事件都有冒泡特性,但像focus事件则不会发生冒泡。 ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>测试</title> <style> #app { background: #34495e; width: 300px; padding: 30px; } #app h2 { background-color: #f1c40f; margin-right: -100px; } </style> </head> <body> <div id="app"> <h2>花森酱</h2> </div> <script> const app = document.querySelector("#app"); const h2 = document.querySelector("h2"); app.addEventListener("click", (event) => { console.log(`执行事件的节点:${event.currentTarget.nodeName}`); console.log(`触发节点:${event.target.nodeName}`); }); h2.addEventListener("click", () => { console.log(`执行事件的节点:${event.currentTarget.nodeName}`); console.log(`触发节点:${event.target.nodeName}`); }); </script> </body> </html> ``` #### <u>阻止冒泡</u> 冒泡过程中的任何事件处理程序中,都可以执行 `event.stopPropagation()` 方法阻止继续进行冒泡传递,仅会阻止当前代码段的程序,但可以通过`event.stopImmediatePropagation()`阻止相同事件的冒泡行为。 #### <u>事件捕获</u> 事件执行顺序为 捕获 > 事件目标 > 冒泡阶段执行,在向下传递到目标对象的过程即为事件捕获,通过设置第三个参数为true或{ capture: true } 在捕获阶段执行事件处理程序。 ```html <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>测试</title> <style> #app { background: #34495e; width: 300px; padding: 30px; } #app h2 { background-color: #f1c40f; margin-right: -100px; } </style> </head> <body> <div id="app"> <h2> huasenjio <span>花森</span> </h2> </div> <script> const app = document.querySelector("#app"); const h2 = document.querySelector("h2"); const span = document.querySelector("span"); app.addEventListener( "click", (event) => { console.log("底部 事件"); }, { capture: true } ); h2.addEventListener("click", (event) => { console.log("中间 事件"); }); span.addEventListener("click", (event) => { console.log("上面 事件"); }); </script> </body> </html> ``` #### <u>事件委托</u> 借助冒泡思路,我们可以不为子元素设置事件,而将事件设置在父级。然后通过父级事件对象的event.target查找子元素,并对他做出处理,此过程叫做事件委托。 ```javascript <ul> <li data-action="hidden">花森导航</li> <li data-action="color" data-color="red">笔录</li> </ul> <script> class HS { constructor(el) { el.addEventListener("click", (e) => { const action = e.target.dataset.action; this[action](e); }); } hidden() { event.target.hidden = true; } color() { event.target.style.color = event.target.dataset.color; } } new HS(document.querySelector("ul")); </script> ``` ### 4.默认行为 JS会有些对象会设置默认事件处理程序,比如A链接在点击时会进行跳转。一般默认处理程序会在用户定义的处理程序后执行,所以我们可以在我们定义的事件处理程序员取消默认事件处理程序的执行。使用onclick绑定的时间处理程序,return false就可以阻止默认行为,推荐`event.preventDefault() `进行阻止默认行为。 ```html <a href="http://n.huasenjio.top">花森笔录</a> <script> document.querySelector('a').addEventListener('click', () => { event.preventDefault() alert(event.target.innerText) }) </script> ``` ### 5.窗口文档 | 事件名 | 说明 | | ------------------- | ------------------------------------------------------------ | | window.onload | 文档解析及外部资源加载后 | | DOMContentLoaded | 文档解析后不需要等待外部资源加载完毕就执行(只能使用addEventListener设置) | | window.beforeunload | 文档刷新或关闭时 | | window.unload | 文档卸载时 | | scroll | 页面滚动时 | ### 6.鼠标事件 针对鼠标操作的行为有多种事件类型,鼠标事件会触发在Z-INDEX最高的那个元素,仅能在顶层元素触发事件,被盖住的元素是无法触发点击事件。 | 事件名 | 说明 | | ----------- | ------------------------------------------------------- | | click | 鼠标单击事件 同时顺序触发 mousedown/mouseup/click | | dblclick | 鼠标双击事件 | | contextmenu | 点击右键后显示的所在环境的菜单 | | mousedown | 鼠标按下 | | mouseup | 鼠标抬起时 | | mousemove | 鼠标移动时 | | mouseover | 鼠标移动时 | | mouseout | 鼠标从元素上离开时 | | mouseup | 鼠标抬起时 | | mouseenter | 鼠标移入时触发 不产生冒泡行为 | | mosueleave | 鼠标移出时触发 不产生冒泡行为 | | oncopy | 复制内容时触发 | | scroll | 元素滚动时 可以为元素设置overflow:auto;产生滚动条来测试 | #### <u>事件属性</u> | 属性 | 说明 | | ------------- | ------------------------------------------------------------ | | which | 执行mousedown/mouseup时 显示所按的键 1左键 2中键 3右键 | | clientX | 相对窗口X坐标 | | clientY | 相对窗口Y坐标 | | pageX | 相对于文档的X坐标 | | pageY | 相对于文档的Y坐标 | | offsetX | 目标元素内部的X坐标 | | offsetY | 目标元素内部的Y坐标 | | altKey | 是否按了alt键 | | ctrlKey | 是否按了ctlr键 | | shiftKey | 是否按了shift键 | | metaKey | 是否按了媒体键 | | relatedTarget | mouseover事件时从哪个元素来的 mouseout事件时指要移动到的元素 当无来源(在自身上移动)或移动到窗口外时值为null | ### 7.键盘事件 针对键盘输入操作的行为有多种事件类型 | 事件名 | 说明 | | ------- | ------------------------------------------------ | | Keydown | 键盘按下时 一直按键不松开时keydown事件会重复触发 | | keyup | 按键抬起时 | #### <u>事件属性</u> | 属性 | 说明 | | -------- | ----------------------------- | | keyCode | 返回键盘的ASCII字符数字 | | code | 按键码 | | key | 按键的字符含义表示 大小写不同 | | altKey | 是否按了alt键 | | ctrlKey | 是否按了ctlr键 | | shiftKey | 是否按了shift键 | | metaKey | 是否按了媒体键 | ### 8.表单事件 下面是可以用在表单上的事件类型 | 事件类型 | 说明 | | --------------- | ------------------------------------------------------------ | | focus | 获取焦点事件 | | blur | 失去焦点事件 | | element.focus() | 让元素强制获取焦点 | | element.blur() | 让元素失去焦点 | | change | 文本框在内容发生改变并失去焦点时触发 select/checkbox/radio选项改变时触发事件 | | input | 内容改变时触发 包括粘贴内容或语音输入内容都会触发事件 | | submit | 提交表单 | # 原型和继承 每一个构造函数都存在prototype原型对象,通过new实例的对象会继承原型对象的方法属性,实例对象通过`__proto__`属性指向构造函数的prototype原型对象,所有的函数的原型默认是Object的实例对象,因为Object构造函数的原型上具有`toString/toValues/isPrototypeOf` 方法,所以每个对象都可以调用。当实例上不存在属性或者方法则将到原型上查找,原型对象包含constructor属性指向构造函数,对象包含`__proto__` 指向他的原型对象。  ### 1.Object.getPrototypeOf 用于获取一个对象的原型 ```javascript console.log(Object.getPrototypeOf(a)); ``` ### 2.Object.setPrototypeOf 可以使用 `__proto__` 或 `Object.setPrototypeOf` 设置对象的原型 ```javascript function Person() { this.getName = function() { return this.name; }; } function User(name, age) { this.name = name; this.age = age; } let lisi = new User("李四", 12); Object.setPrototypeOf(lisi, new Person()); //将Person的一个实例对象作为lisi的原型 console.log(lisi.getName()); //李四 ``` ### 3.原型检测 #### <u>instanceof</u> instanceof 检测构造函数的 `prototype` 属性是否出现在某个实例对象的原型链上 ```javascript function A() {} function B() {} function C() {} const c = new C(); B.prototype = c; const b = new B(); A.prototype = b; const a = new A(); console.dir(a instanceof A); //true console.dir(a instanceof B); //true console.dir(a instanceof C); //true console.dir(b instanceof C); //true console.dir(c instanceof B); //false ``` #### <u>isPrototypeOf</u> 使用`isPrototypeOf`检测一个对象是否是另一个对象的原型链中 ```javascript const a = {}; const b = {}; const c = {}; Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, c); console.log(b.isPrototypeOf(a)); //true console.log(c.isPrototypeOf(a)); //true console.log(c.isPrototypeOf(b)); //true ``` ### 4.属性遍历 #### <u>in</u> 使用`in` 检测原型链上是否存在属性,使用 `hasOwnProperty` 只检测当前对象是否存在属性。 ```javascript let a = { url: "huasen" }; let b = { name: "花森" }; Object.setPrototypeOf(a, b); // 将a设置为b的原型 console.log("name" in a); // true console.log(a.hasOwnProperty("name")); // false console.log(a.hasOwnProperty("url")); // false ``` #### <u>for-in</u> ```javascript let hs = { name: "花森" }; // 以hs为原型创建huasen对象 let huasen = Object.create(hs, { url: { value: "huasenjio", enumerable: true, }, }); console.log(huasen); for (const key in huasen) { console.log(key); } ``` ### 5.借用原型 使用 `call` 或 `apply` 可以借用其他原型方法完成功能 ```javascript let hs = { data: [1, 2, 3, 4, 5], }; // 借用Math上面的max方法 console.log(Math.max.apply(null, hs.data)); let zhuqi = { lessons: { js: 100, php: 78, node: 78, linux: 125 }, }; // Object.values()被返回可枚举属性值的对象 console.log(Math.max.apply(zhuqi, Object.values(zhuqi.lessons))); ``` ### 6.\__proto__ 实例化对象上存在一个`__proto__`记录原型的信息,可以通过对象访问到原型的属性和方法,严格意义上讲`__proto__` 不是对象属性,可以理解为protortype的一个getter/setter实现,是一个非标准的定义,内部使用`getter/setter` 控制输入值,所以只允许对象或者null的赋值,建议使用 `Object.setPrototypeOf `和`Object.getProttoeypOf `替代` __proto__` 的使用。  ```javascript lisi.__proto__ = new Person(); ``` ### 7.继承与多态 #### <u>实现继承</u> ```javascript <script> function Person() {} Person.prototype.getName = function () { console.log("parent method"); }; function User() {} // 方式一,全部实例对象实现继承,Object.create(Person.prototype),建立一个a空对象且空对象的原型对象是Person的原型对象,User.prototype = a.__proto__ = Person.prototype。 User.prototype = Object.create(Person.prototype); User.prototype.constructor = User; // 原型对象的方法设置放在建立原型链之后,避免造成覆盖。 User.prototype.showUser = function () { console.log("User的方法"); }; console.log(new User()); // 方式二,对单个对象实现继承,设置lisi对象原型,等同于lisi.__proto__ = Person.prototype,同样会造成原型对象的constructor构造函数丢失。 // let lisi = new User(); // Object.setPrototypeOf(lisi, Person.prototype); // console.dir(lisi); // console.dir(new User()); </script> ``` #### <u>继承</u> 对象能使用它上游原型链上存在的方法和状态 ```javascript // Array原型对象上不存在valueOf方法,但是因为Array继承Object对象,即[]的__proto__指向的是Array的原型对象,而Array的__proto__指向的是Object的原型对象,因为Object的原型对象上存在valueOf方法,所以[]可以使用。 console.log([]); console.log([1, 2, 3, 4].valueOf()); // (4) [1, 2, 3, 4] ``` #### <u>方法重写</u> 子类的定义与父类相同方法名的方法就可以重写父类的方法,思想就是不让子类顺着原型链网上找,因为它在自己的原型上查找到方法后就不会再向上查找,这一点与nodejs找依赖库的思想一致。 ```javascript function Person() {} Person.prototype.getName = function() { console.log("parent method"); }; function User(name) {} User.prototype = Object.create(Person.prototype); User.prototype.constructor = User; User.prototype.getName = function() { //调用父级同名方法 Person.prototype.getName.call(this); console.log("child method"); }; let hd = new User(); hd.getName(); ``` #### <u>多态</u> 根据多种不同的形态产生不同的结果,下而会根据不同形态的对象得到了不同的结果。例如动物是猫和狗的父类,动物有会叫的方法,猫和狗实现动物叫的方法时表现形式不同。这就是多态。 ```javascript function User() {} User.prototype.show = function() { console.log(this.description()); }; function Admin() {} Admin.prototype = Object.create(User.prototype); Admin.prototype.description = function() { return "管理员在此"; }; function Member() {} Member.prototype = Object.create(User.prototype); Member.prototype.description = function() { return "我是会员"; }; function Enterprise() {} Enterprise.prototype = Object.create(User.prototype); Enterprise.prototype.description = function() { return "企业帐户"; }; for (const obj of [new Admin(), new Member(), new Enterprise()]) { obj.show(); } ``` ### 8.继承进阶 #### <u>构造函数</u> ```javascript function User(name) { this.name = name; console.log(this); // Admin } User.prototype.getUserName = function () { return this.name; }; function Admin(name) { User.call(this, name); // 将User构造函数绑定给Admin } Admin.prototype = Object.create(User.prototype); let hs = new Admin("花森"); console.log(hs.getUserName()); //花森 ``` #### <u>Mixin多继承</u> `JS`不能实现多继承,如果要使用多个类的方法时可以使用`mixin`混合模式来完成。mixin类是一个包含许多其他类方法的对象,经过Object.assign对象合并的方式为原型添加方法。 ```javascript <script> function extend(sub, sup) { sub.prototype = Object.create(sup.prototype); sub.prototype.constructor = sub; } function User(name, age) { this.name = name; this.age = age; } User.prototype.show = function () { console.log(this.name, this.age); }; const Credit = { total() { console.log("统计积分"); }, }; const Request = { ajax() { console.log("请求后台"); }, }; function Admin(...args) { User.apply(this, args); } extend(Admin, User); Object.assign(Admin.prototype, Request, Credit); let hs = new Admin("花森", 19); console.dir(hs); hs.show(); hs.total(); //统计积分 hs.ajax(); //请求后台 </script> ``` # class 为了和其他语言的形态一致,JS提供了class关键词用于模拟传统的class,只是原型继承的语法糖形式,class为了让类的声明和继承更加简洁清晰,class默认使用严格模式执行。 ### 1.声明定义 ```javascript <script> // 构造函数构造对象 function User(name) { //实例对象属性 this.name = name; } //静态属性 User.type = "用户"; //静态方法 User.showTypea = function() { // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性 return this.type; }; // 原型添加函数(所有实例对象可以访问) User.prototype.showName = function() { return this.name; }; console.dir(User); console.dir(new User("森哥哥")); // 类的方式构建对象 (extends继承对于构造函数是 Admin.__proto__ == User, 对于实例对象是 实例对象.__proto__ == User.prototype.) class Admin extends User { //静态属性 static type = "管理员"; constructor(name) { super(); // 指调用父类的构造函数 //实例对象属性 this.name = name; } //静态方法 static showType() { // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性 return this.type; } // 原型添加函数(所有实例对象可以访问) showName() { return this.name; } } console.dir(Admin); console.dir(new Admin("猪琦琦")); </script> ``` ### 2.静态访问 #### <u>静态属性</u> 静态属性即为类设置属性,无需实例化即可调用,针对的是构造器设置。 ```javascript // es5构造函数构造对象 function User(name) {} //静态属性 User.type = "用户"; // class中使用static关键词 class Admin extends User { //静态属性 static type = "管理员"; } ``` #### <u>静态方法</u> ```javascript // es5形式 function User(name) {} User.showTypea = function() { // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性 return this.type; }; // class静态方法 static showType() { // 静态方法调用的对象是构造函数 所以静态方法中仅可以使用静态属性 return this.type; } ``` ### 3.访问器 访问器可以对对象的属性进行访问控制,访问器可以管控属性,有效的防止属性随意修改,加上get/set修饰,操作是不需要添加函数括号。 ```javascript class User { constructor(name) { this.data = { name }; } get name() { return this.data.name; } set name(value) { if (value.trim() == "") throw new Error("invalid params"); this.data.name = value; } } let hs = new User("花森"); hs.name = "huasen"; console.log(hs.name); ``` #### <u>public</u> `public` 指不受保护的属性,在类的内部与外部都可以访问到。 ```javascript class User { url = "huasenjio"; constructor(name) { this.name = name; } } let hs = new User("花森"); console.log(hs.name, hs.url); ``` #### <u>protected</u> protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问。 ```javascript // 属性定义为以 _ 开始,来告诉使用者这是一个私有属性,请不要在外部使用,自启提示作用。 class Article { _host = "http://huasenjio.top/"; set host(url) { if (!/^https:\/\//i.test(url)) { throw new Error("网址错误"); } this._host = url; } lists() { return `${this._host}/article`; } } ``` #### <u>private</u> `private` 指私有属性,只在当前类可以访问到,并且不允许继承使用,为属性或者方法名前家#则是声明私有属性,属性只能在声明的类中使用。 ```javascript class User { #host = "http://huasenjio.top/"; constructor(name) { this.name = name; this.#check(name); } #check = () => { if (this.name.length <= 5) { throw new Error("用户名长度不能小于五位"); } return true; }; } class Pig extends User { constructor(name) { super(name); this.name = name; } } let pig = new Pig("猪琦猪猪猪户"); // console.log(pig.#host); // '#host' must be declared in an enclosing class // console.log(new User().#host); // '#host' must be declared in an enclosing class ``` ### 4.super 所有继承中 `this` 始终为调用对象,`super` 是用来查找当前对象的原型。在constructor中super指调用父类引用,必须先调用super()后再进行this赋值。 ```javascript <script> class User { constructor(name) { this.name = name; } } class Pig extends User { constructor(name, type) { // super会调用父类的constructor构造器,因为父类的name属性是会被Pig继承,所以实例子类时需要将父类需要的参数通过super传递。 super(name); this.type = type; } } let pig = new Pig("猪琦", "猪"); console.log(pig); </script> // 实现原理 function Parent(name) { this.name = name; } function User(...args) { Parent.apply(this, args); } User.prototype = Object.create(User.prototype) User.prototype.constructor = User; const hs = new User("花森"); console.log(hs.name); ``` # 任务管理 JavaScript 语言的一大特点就是单线程,同一个时间只能处理一个任务。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,JavaScript 处理任务是在等待任务、执行任务 、休眠等待新任务中不断循环中,也称这种机制为事件循环。 ```javascript console.log("1.花森是同步代码"); setTimeout(function () { console.log("6.定时器是宏任务代码"); Promise.resolve().then(() => { console.log("7.下次循环才会执行当前微任务"); }); }, 0); // 激活微任务 Promise.resolve() .then(function () { console.log("3.promise1微任务"); }) .then(function () { console.log("4.promise2微任务"); }) .then(() => { console.log("5.单次循环内微任务执行完成后才可以执行宏任务"); }); console.log("2.huasenjio时同步代码"); ``` # 模块化开发 项目变大时需要把不同的业务分割成多个文件,即模块的思想,模块是比对象与函数更大的单元,使用模块组织程序便于维护与扩展且模块默认运行在严格模式下。使用模块化开发有以下优点: 1. 模块化是一个独立的文件,可以包含函数或者类库; 2. 使用模块化可以解决全局变量冲突; 3. 模块可以隐藏内部实现,只对外保留开发接口; 4. 模块化可以避免全局变量造成打代码不可控; 5. 模块可以服用提高编码的效率; ### 1.管理引擎 过去JS不支持模块时我们使用`AMD/CMD(浏览器端使用)、CommonJS(Node.js)、UMD(都支持)`等形式定义模块。AMD代表性的是 `require.js`,CMD 代表是淘宝的 `seaJS` 框架。 ### 2.基本使用 ```javascript // html文件中导入模块需要定义属性 type="module" <script type="module"> import { hs } from "./a.js"; // 浏览器中使用必须添加路径 </script> // 新建文件a.js内容如下 export let hs = { name: "花森" }; ``` ### 3.作用域 模块都有独立的顶级作用域,不同模块之间不可以访问。 ```javascript <script type="module"> let hs = "huasenjio"; </script> <script type="module"> alert(hs); // Error </script> ``` ### 4.预解析 模块在导入时只执行一次解析,之后的导入不会再执行模块代码,而使用第一次解析结果,并共享数据,可以在首次导入时完成一些初始化工作。 ### 5.导入导出 ES6使用基于文件的模块即一个文件一个模块: 1. 使用export导出方法属性; 2. 使用import导入模块接口; 3. 使用 * 导入全部模块接口; 4. 导出的状态和方法都是地址,模块内修改会影响导入的变量; #### <u>导出模块</u> 定义文件hd.js导出内容如下 ```javascript export const site = "花森导航"; export const func = function() { return "is a module function"; }; export class User { show() { console.log("user.show"); } } // 别名导出 export { site, func as action, User as user }; ``` #### <u>导入模块</u> ```javascript // 具名导入(不忽略大小写) <script type="module"> import { User, site, func } from "./hd.js"; console.log(site); console.log(User); </script> // 批量导入 <script type="module"> import * as api from "./hd.js"; console.log(api.site); console.log(api.User); </script> // 别名导入 <script type="module"> import { User as user, func as action, site as name } from "./hd.js"; let func = "houdunren"; console.log(name); console.log(user); console.log(action); </script> ``` ### 6.默认导出 当文件中导出的内容模块只有一个,也就是说仅需导入一个内容,这时可以使用默认导入,使用default定义默认导出的接口,导入时不需要使用{}且名字任意。 ```javascript // hd.js文件中暴露导出 default class { static show() { console.log("User.method"); } } // html中引入 <script type="module"> import User from "./hd.js"; User.show(); </script> ``` ### 7.动态导入 ```javascript <script> if (true) { let hd = import("./hd.js").then(module => { console.log(module.site); }); } </script> ``` ### 8.指令总结 | 表达式 | 说明 | | ------------------------------------------------ | :--------------- | | export function show(){} | 导出函数 | | export const name='花森' | 导出变量 | | export class User{} | 导出类 | | export default show | 默认导出 | | const name = '花森' export {name} | 导出已经存在变量 | | export {name as hd_name} | 别名导出 | | import defaultVar from 'houdunren.js' | 导入默认导出 | | import {name,show} from 'a.j' | 导入命名导出 | | Import {name as hdName,show} from 'houdunren.js' | 别名导入 | | Import * as api from 'houdunren.js' | 导入全部接口 | # Promise `JavaScript` 中存在很多异步操作,`Promise` 将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以通过链式调用多个 `Promise` 达到我们的目的,使用promise还可以避免回调地域的现象,当一个Promise建立时执行内部的同步代码,resolve或者reject后开启微任务(then内部代码),并载入微任务队列。 ### 1.基本理解 我在打游戏的时候(主进程),突然想吃可乐鸡腿,于是你让猪琦(Promise)开了一个做可乐鸡腿的任务(异步任务),因为不影响你打游戏,所以它叫异步任务。如果猪琦同意接受(resolve)则猪琦将去开始去烹饪,做好叫我吃可乐鸡腿(then中result回调函数);如果猪琦烹饪时可乐鸡腿发生意外(错误)或者拒绝任务(reject)并将我打了一顿,让我跪键盘(then中error回调函数),如果你没有跪键盘或者其他举措补偿(没设置error回调),则统一按分手(catch)处理。不管怎样最后都会和好(finally),开心的过日子。 ```javascript new Promise((resolve, reject) => { resolve("成功处理"); reject("拒绝处理"); console.log("同步代码"); // 优先执行 }) .then( (res) => { console.log("成功", res); // resolve则会执行此方法体 }, (err) => { console.log("拒绝", err); // 语法错误与reject则执行此方法体 } ) .catch((error) => { console.log("发生语法错误"); // 未设置reject回调函数时交给catch统一处理 }); ``` #### <u>状态说明</u> Promise包含`pending、fulfilled、rejected`三种状态,Promise是队列状态,状态可一直向后传递,每一个Promise都可以改变状态,Promise可以链式传递一个传一个。  1. `pending` 初始等待状态,可由`new Promise()`获得该状态; 2. `resolve` 已经解决, `promise` 状态设置为`fulfilled` ,可由`Promise.resolve()`获得该状态; 3. `reject` 拒绝处理, `promise` 状态设置为`rejected`,可由`Promise.reject()`获得该状态; 4. `promise` 是生产者,通过 `resolve` 与 `reject` 函数告知异步任务的状态,一旦状态改变将不可更改; ### 2.异步加载 ```javascript function load(file, resolve, reject) { const script = document.createElement("script"); script.src = file; script.onload = resolve; script.onerror = reject; document.body.appendChild(script); } load( "./js/hs.js", script => { console.log(`${script.path[0].src} 加载成功`); hs(); }, error => { console.log(`${error.srcElement.src} 加载失败`); } ); ``` ### 3.then promise 需要提供一个then方法访问promise 结果,`then` 用于定义当 `promise` 状态发生改变时的处理,即`promise`处理异步操作,`then` 用于结果处理输出。 1. then方法必须返回Promise,手动返回或者系统自动返回; 2. 执行resolve时跳入then方法中的第一个回调参数; 3. 执行reject时跳入then方法中的第二个回调参数; ```javascript <script> new Promise((resolve, reject) => { resolve("成功"); }) .then((res) => { console.log("1" + res); return new Promise((resolve, reject) => { resolve("成功"); }); }) .then((res) => { console.log("2" + res); // 如果返回的是未处理的Promise则阻塞等待处理 return new Promise((resolve, reject) => { reject("失败"); }); }) .then(null, (err) => { console.log("3" + err); return "then链式调用默认执行resolve回调方法并将return指赋值给res"; }) .then((res) => { console.log("4" + res); }) .catch((error) => { console.log(error); }); </script> ``` ### 4.catch catch用于失败状态的处理函数,等同于 `then(null,reject){}`,建议使用catch统一处理错误。如果不存在reject回调函数则会跳入catch方法,整一个Promise链上的错误都可以被catch捕获。 ```javascript new Promise((resolve, reject) => { resolve("成功"); }) .then((res) => { console.log("1" + res); abc; // 这个错误一直下沉,直到找到reject回调方法,如果then链上没有同意由catch捕获。 return new Promise((resolve, reject) => { resolve("成功"); }); }) .then((res) => { console.log("2" + res); // 如果返回的是未处理的Promise则阻塞等待处理 return new Promise((resolve, reject) => { reject("失败"); }); }) .then(null, (err) => { console.log("3" + err); return "then链式调用默认执行resolve回调方法并将return指赋值给res"; }) .then((res) => { console.log("4" + res); }) .catch((error) => { console.log(error); }); ``` ### 5.事件处理 unhandledrejection事件用于捕获到未处理的Promise错误,下面的 then 产生了错误,但没有`catch` 处理,这时就会触发事件。 ```javascript window.addEventListener("unhandledrejection", function(event) { console.log(event.promise); // 产生错误的promise对象 console.log(event.reason); // Promise的reason }); new Promise((resolve, reject) => { resolve("success"); }).then(msg => { throw new Error("fail"); }); ``` ### 6.finally 无论状态是`resolve` 或 `reject` 都会执行此动作 ```javascript const promise = new Promise((resolve, reject) => { reject("hs"); }) .then(msg => { console.log("resolve"); }) .catch(msg => { console.log("reject"); }) .finally(() => { console.log("resolve/reject状态都会执行"); }); ``` ### 7.拓展接口 #### <u>resolve</u> 使用 `Promise.resolve()` 方法可以快速的返回一个状态为fulfilled的promise对象 ```javascript Promise.resolve("花森").then(value => { console.log(value); // 花森 }); ``` #### <u>reject</u> 使用 `Promise.reject()` 方法可以快速的返回一个状态为rejected的promise对象 ```javascript Promise.reject("花森").then(null,err => { console.log(err); // 花森 }); ``` #### <u>all</u> 使用`Promise.all` 方法可以同时执行多个并行异步操作,等待多个promise完成任务后返回一个有序的数组,需要注意一下几点: 1. 任何一个Promise执行失败都会调用catch方法; 2. 一次发送多个异步操作; 3. 参数必须是可迭代对象例如Array和Set; ```javascript const hs = new Promise((resolve, reject) => { setTimeout(() => { resolve("第一个Promise"); }, 1000); }); const zhuqi = new Promise((resolve, reject) => { setTimeout(() => { resolve("第二个Promise"); }, 1000); }); const h = Promise.all([hs, zhuqi]) .then((results) => { console.log(results); }) .catch((msg) => { console.log(msg); }); setTimeout(() => { console.log(h); }, 1000); ``` #### <u>allSettled</u> `allSettled` 用于处理多个`promise` ,只关注执行完成,不关注是否全部执行成功,`allSettled` 状态只会是`fulfilled`。 ```javascript const p1 = new Promise((resolve, reject) => { resolve("resolved"); }); const p2 = new Promise((resolve, reject) => { reject("rejected"); }); Promise.allSettled([p1, p2]) .then(msg => { console.log(msg); }) ``` #### <u>race</u> 使用`Promise.race()` 处理容错异步,队列中Promise优先执行则优先返回,具有一下的几点特性: 1. 最快返回的promise为准; 2. 如果传入参数不是Promise则内部自动转为Promise; 3. 无论内部的Promise返回的转态是reject还是resolve,race都会返回一个`fulfilled`状态promise,如果传入的Promise都存在语法错误则会返回一个Pending状态的Promise; ```javascript const hs = new Promise((resolve, reject) => { setTimeout(() => { reject("第一个Promise"); }, 1000); }); const zhuqi = new Promise((resolve, reject) => { setTimeout(() => { reject("第二个Promise"); }, 3000); }); const h = Promise.race([hs, zhuqi]) .then((results) => { console.log("成功" + results); }) .catch((msg) => { console.log("错误" + msg); }); setTimeout(() => { console.log(h); // Promise {<fulfilled>: undefined} }, 4000); ``` ### 8.async/await 使用 `async/await` 是promise 的语法糖,可以让编写 promise 更清晰易懂。 #### <u>async</u> 函数前加上async关键词,函数将返回promise就可以像使用标准Promise一样使用了。 ```javascript async function hs() { return "huasenjio"; } console.log(hs()); hs().then((value) => { console.log(value); }); ``` #### <u>await</u> 使用`await`关键词后面的Promise执行完成则继续向下执行,否则阻塞等待,内部await一旦有一个出现reject或者语法错误,则直接跳入err的回调函数中,具有一下特性: 1. await后面跟Promise,若不是则直接返回后面值; 2. await必须放在async定义的函数中使用; 3. await用于代替then的链式调用; ```javascript const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("promise处理"); }, 1000); }); const huasen = new Promise((resolve, reject) => { setTimeout(() => { reject("p拒绝"); // 一旦出现拒绝又不进行处理,直接跳入err回调函数中,下面的代码将不再执行。 }, 1000); }); async function hs() { let result = await promise; console.log("需要等待await完成"); let res = await huasen; return "执行完成"; // 如果不存在语法错误且不拒绝,则返回值跳入resolve回调函数中的res参数中。 } hs().then( (res) => { console.log("成功", res); }, (err) => { console.log("失败", err); } ); ``` # DOM 浏览器解析HTML文件时会生成一个DOM对象,JS可以调用控制页面元素,但需要注意的是操控是浏览器以及渲染了页面内容,否则无法读取到节点对象。 ### 1.节点对象 html中的每一个标签对应着js中的一个DOM节点对象,包含有属性方法,可以调用,以下是对象的基本知识: 1. 包括有12种类型节点对象; 2. 常用节点对象为`document、标签元素节点、文本节点、注释节点`; 3. 节点均继承node对象,所以拥有相同的属性和方法; 4. document节点是DOM的根节点; | 类型 | 值 | | ------------ | ----------------------- | | 元素节点 | 1(body对象) | | 属性节点 | 2(标签属性) | | 文本节点 | 3(标签内文字) | | 注释节点 | 8(标签内注释) | | 文档节点 | 9(document对象) | | 文档类型节点 | 10(`<!DOCTYPE html>`) | ```javascript <body id="hs"> <!-- 花森 --> huasenjio </body> <script> console.log(document.nodeType) //9 document对象 console.log(document.childNodes.item(0).nodeType) //10 console.log(document.body.nodeType) //1 console.log(document.body.attributes[0].nodeType) //2 console.log(document.body.childNodes[1].nodeType) //8 </script> ``` ### 2.DOM原型链 浏览器渲染过程中会将文档内容生成为不同的对象,不同的节点类型有专门的构造参数创建对象,使用`console.dir`可以查看详细属性方法,节点也是对象所以觉有JS对象的特征,有以下节点类型: | 原型 | 说明 | | ------------------ | ------------------------------------------------------- | | Object | 根对象 | | EventTarget | 提供事件支持 | | Node | 提供parentNode等节点操作方法 | | Element | 提供getElementsByTagName、querySelector样式选择器等方法 | | HTMLElement | 所有元素的基础类 提供className、nodeName等方法 | | HTMLHeadingElement | Head标题元素类 | ````javascript <div id="hs">花森酱</div> <input type="text" name="title" id="title" /> <script> let hs = document.getElementById("id"); function prototype(el) { let proto = Object.getPrototypeOf(el); // 获取对象原型 console.log(proto); Object.getPrototypeOf(proto) ? prototype(proto) : ""; // 递归获取 } prototype(hs); </script> ```` #### <u>对象合并</u> ```javascript <div id="hs">huasenjio</div> <script> let hs = document.getElementById('hs') Object.assign(hs, { color: 'red', change() { this.innerHTML = '花森酱' this.style.color = this.color }, onclick() { this.change() }, }) </script> ``` #### <u>样式合并</u> ```javascript <div id="hs">huasenjio</div> <script> let hs = document.getElementById('hs') Object.assign(hs.style, { color: 'red', }) </script> ``` ### 3.页面文档 document是window对象的属性,是由HTMLDocument类实现的实例,继承node类则可以用node的相关方法,document文档包含着页面唯一的元素标签。 #### <u>html</u> 系统提供了简单的方式来获取html元素 ```javascript console.log(document.documentElement) ``` #### <u>文档信息</u> 使用title获取和设置文档标题 ```javascript //获取文档标题 console.log(document.title) //设置文档标签 document.title = '花森酱测试文件' //获取当前URL console.log(document.URL) //获取域名 document.domain //获取来源地址 console.log(document.referrer) ``` #### <u>body</u> 可通过`document.body`获取到页面 ### 4.节点属性 #### <u>nodeType</u> 不同类型的节点拥有不同的属性,nodeType指以数值返回节点类型,递归获取元素某节点下的全部标签元素,以下是类型展示: | nodeType | 说明 | | -------- | ------------ | | 1 | 元素节点 | | 2 | 属性节点 | | 3 | 文本节点 | | 8 | 注释节点 | | 9 | document对象 | ```javascript <div id="hs"> <ul> <li> <h2><strong>花森</strong></h2> </li> </ul> </div> <script> function all(el) { let items = []; [...el.childNodes].map((node) => { if (node.nodeType == 1) { items.push(node, ...all(node)); } }); return items; } console.log(all(document.body)); </script> ``` #### <u>nodeName</u> nodeName指定节点的名称,获取值为大写形式,注意空行也是一个文本节点。 | nodeType | nodeName | | -------- | ------------- | | 1 | 元素名称如DIV | | 2 | 属性名称 | | 3 | #text | | 8 | #comment | ### 5.节点集合 Nodelist与HTMLCollection都是包含多个节点标签的集合 1. getElementsBy...等方法返回的是HTMLCollection; 2. querySelectorAll样式选择器返回的是 NodeList; 3. NodeList和NodeList集合是动态的; #### <u>length</u> Nodelist与HTMLCollection包含length属性则记录节点元素的数量 #### <u>转换数组</u> 有时使用数组方法来操作节点集合,这就需要将节点集合转化为数组类型,有以下几种方式可以实现。 ```javascript // Array.from() console.log(Array.from(elements)) // 点语法 [...elements] ``` ### 6.常用元素 系统针对特定标签提供了快速选择的方式 | 方法 | 说明 | | ------------------------ | ---------------------- | | document.documentElement | 文档节点即html标签节点 | | document.body | body标签节点 | | document.head | head标签节点 | | document.links | 超链接集合 | | document.anchors | 所有锚点集合 | | document.forms | form表单集合 | | document.images | 图片集合 | ### 7.节点关系 节点是根据HTML内容产生的,所以也存在父子、兄弟、祖先、后代等节点关系,下例中的代码就会产生这种多重关系,目前文本节点也会匹配上关系。 1. h1与ul是兄弟关系; 2. span与li是父子关系; 3. ul与span是后代关系; 4. span与ul是祖先关系; ```html <h1>花森</h1> <ul> <li> <span>huasen</span> <strong>猪琦</strong> </li> </ul> ``` | 节点属性 | 说明 | | --------------- | ---------------- | | childNodes | 获取所有子节点 | | parentNode | 获取父节点 | | firstChild | 子节点中第一个 | | lastChild | 子节点中最后一个 | | nextSibling | 下一个兄弟节点 | | previousSibling | 上一个兄弟节点 | ### 8.元素关系 使用childNodes等获取的节点包括文本与注释 | 节点属性 | 说明 | | ---------------------- | :--------------- | | parentElement | 获取父元素 | | children | 获取所有子元素 | | childElementCount | 子标签元素的数量 | | firstElementChild | 第一个子标签 | | lastElementChild | 最后一个子标签 | | previousElementSibling | 上一个兄弟标签 | | nextElementSibling | 下一个兄弟标签 | ### 9.选择器 系统提供了丰富的选择节点(NODE)的操作方法 #### <u>getElementById</u> 使用ID选择是非常方便的选择具有ID值的节点元素,此方法仅存在与document对象上。 ```javascript <div id="hs">huasen</div> <div id="app"></div> <script> function getByElementIds(ids) { return ids.map((id) => document.getElementById(id)); } let nodes = getByElementIds(["hs", "app"]); console.dir(nodes); </script> ``` #### <u>getElementByClassName</u> getElementsByClassName用于按class 样式属性值获取元素集合 ```javascript const nodes = document.getElementsByClassName('hs') ``` #### <u>getElementByName</u> 使用getElementByName获取设置了name属性的元素,返回NodeList节点列表对象,顺序即元素在文档中的顺序。 ```javascript <div name="hs">花森</div> <script> const div = document.getElementsByName("hs"); console.dir(div); </script> ``` #### <u>getElementByTagName</u> 使用getElementsByTagName用于按标签名获取元素 ```javascript const divs = document.getElementsByTagName('div') ``` #### <u>通配符</u> 可以使用通配符`*`获取所有元素 ```javascript const nodes = document.getElementsByTagName('*') ``` #### <u>querySelectorAll</u> 在DOM操作中也可以使用这种方式查找元素,使用querySelectorAll根据CSS选择器获取Nodelist节点列表,获取的NodeList节点列表是静态的就是添加或者删除元素后list不会发生变化。 ```javascript <div id="app"> <div>花森/div> <div>猪琦</div> </div> <script> const app = document.body.querySelectorAll("#app")[0]; const nodes = app.querySelectorAll("div"); console.log(nodes); //2 </script> ``` #### <u>querySelector</u> querySelector使用CSS选择器获取一个元素 ```javascript const nodes = app.querySelectorAll("div"); ``` #### <u>matches</u> 用于检测元素是否是指定的样式选择器匹配 ```javascript <div id="app" class="app"> <div>花森/div> <div>猪琦</div> </div> <script> const app = document.body.querySelectorAll("#app")[0]; console.log(app.matches(".app")) // true </script> ``` #### <u>closest</u> 根据样式查找某元素最近的祖先元素 ```javascript // 这个例子是查找li元素最近的祖先并且符合`.comment`样式 <div class="comment"> <ul class="comment"> <li>huasenjio</li> </ul> </div> <script> const li = document.getElementsByTagName("li")[0]; const node = li.closest(`.comment`); console.log(node); </script> ``` ### 10.动态与静态 通过 getElementsByTagname 等getElementsByXXX函数获取的Nodelist与HTMLCollection集合是动态,即有元素添加或移动操作集合将实时反映最新状态。 1. 使用getElement...返回的都是动态的集合; 2. 使用querySelectorAll返回的是静态集合; ```javascript <h1>花森导航</h1> <h1>huasenjio</h1> <button id="add">添加元素</button> <script> let elements = document.getElementsByTagName("h1"); //let elements = document.querySelectorAll("h1"); console.log(elements); let button = document.querySelector("#add"); button.addEventListener("click", () => { document .querySelector("body") .insertAdjacentHTML("beforeend", "<h1>hs</h1>"); console.log(elements); }); </script> ``` ### 11.元素特征 标准的属性(src|className|herf)可以使用DOM属性的方式进行操作,但是对于非标准的属性则不可以,可以理解为元素的属性分两个地方保存,DOM属性记录标准属性,特征中记录标准和定制属性。简而言之就是对奇怪的属性进行操作,有以下几种方法: | 方法 | 说明 | | --------------- | -------- | | getAttribute | 获取属性 | | setAttribute | 设置属性 | | removeAttribute | 删除属性 | | hasAttribute | 属性检测 | #### <u>attributes</u> 元素提供了attributes 属性可以只读的获取元素的属性 ```javascript <div class="hs" data-content="花森">hs.com</div> <script> let hs = document.querySelector(".hs"); console.dir(hs.attributes["class"].nodeValue); //hs console.dir(hs.attributes["data-content"].nodeValue); //后盾人 </script> ``` #### <u>hasAttribute</u> 用于检测对象是否存在某个属性 ```javascript console.log(hdcms.hasAttribute('class')) //false ``` #### <u>自定义属性</u> 虽然可以随意定义特征并使用getAttribute等方法管理,建议使用以data-为前缀的自定义特征处理,针对这种定义方式JS也提供了接口方便操作。 1. 元素中以`data-`作为前缀的属性会添加按到属性map集合中; 2. 使用元素的dataset可以获取属性集合中的属性; 3. 改变dataset的值也会影响元素; ```javascript <div class="hs" data-title-color="red">花森酱</div> <script> let hs = document.querySelector(".hs"); console.log(hs.dataset); hs.innerHTML = ` <span style="color:${hs.dataset.titleColor}">${hs.innerHTML}</span>`; </script> ``` ### 12.创建节点 创建节点的就是构建出DOM对象 #### <u>createTextNode</u> 创建文本对象并添加到元素中 ```javascript <div id="app"></div> <script> let app = document.querySelector('#app') let text = document.createTextNode('花森') app.append(text) </script> ``` #### <u>createElement</u> 使用createElement方法可以标签节点对象 ```javascript <div id="app"></div> <script> let app = document.querySelector('#app') let span = document.createElement('span') span.innerHTML = '花森酱' app.append(span) </script> ``` #### <u>createDocumentFragment</u> 使用createDocumentFragment创建虚拟节点容器具有一下特点: 1. 创建的节点的parentNode为Null; 2. 使用createDocumentFragment创建的节点来暂存文档节点; 3. createDocumentFragment创建的节点添加到其他节点上时; 4. 不直接操作DOM所以性能更好; 5. 排序/移动等大量DOM操作时建议使用createDocumentFragment; #### <u>cloneNode&importNode</u> 使用cloneNode和document.importNode用于复制节点对象操作 1. cloneNode是节点方法; 2. cloneNode参数为true时递归赋值子节点即深拷贝; 3. importNode是document对象方法; ```javascript <div id="app">huasen</div> <script> let app = document.querySelector('#app') let newApp = app.cloneNode(true) document.body.appendChild(newApp) </script> ``` ### 13.节点内容 #### <u>innerHTML</u> inneHTML用于向标签中添加html内容,同时出发浏览器的解析器重绘DOM树。 ```javascript <div id="app"></div> <script> let app = document.querySelector("#app"); console.log(app.innerHTML); app.innerHTML = "<h1>花森</h1>"; </script> ``` #### <u>outerHTML</u> outerHTML与innerHTML的区别是包含父标签,不会将原来的内容删除掉依然在DOM树节点上。 ```javascript <div id="app"> <div class="hs" data="hs">花森</div> <div class="zhuqi">猪琦</div> </div> <script> let app = document.querySelector('#app') console.log(app.outerHTML) app.outerHTML = '<h1>酱</h1>' </script> ``` #### <u>textContent与innerText</u> textContent与innerText是访问或添加文本内容到元素中 ```javascript <div id="app"> <div class="hs" data="hs">花森</div> <div class="zhuqi">猪琦</div> </div> <script> let app = document.querySelector('#app') console.log(app.outerHTML) app.textContent = '<h1>酱</h1>' </script> ``` #### <u>insertAdjacentText</u> 将文本插入到元素指定位置,不会对文本中的标签进行解析,即不会将`<h1>皮卡丘</h1>`以HTML标签形式去渲染。 | 选项 | 说明 | | ----------- | ------------ | | beforebegin | 元素本身前面 | | afterend | 元素本身后面 | | afterbegin | 元素内部前面 | | beforeend | 元素内部后面 | ```html <div id="app"> <div class="hs">花森</div> <div class="zq">猪琦</div> </div> <script> let app = document.querySelector(".hs"); app.insertAdjacentText("afterend", "<h1>皮卡丘</h1>"); </script> ``` ### 14.节点管理 节点元素的管理,包括添加、删除、替换等操作,具体有以下参数位置: #### <u>推荐方法</u> | 方法 | 说明 | | ----------- | -------------------------- | | append | 节点尾部添加新节点或字符串 | | prepend | 节点开始添加新节点或字符串 | | before | 节点前面添加新节点或字符串 | | after | 节点后面添加新节点或字符串 | | replaceWith | 将节点替换为新节点或字符串 | ```javascript <div id="app">花森酱</div> <script> let app = document.querySelector("#app"); app.append("huasenjio.top"); </script> ``` #### <u>inserAdjacentHTML</u> html文本插入到元素指定位置,浏览器会对文本进行标签解析,具有包括以下参数位置: | 选项 | 说明 | | ----------- | ------------ | | beforebegin | 元素本身前面 | | afterend | 元素本身后面 | | afterbegin | 元素内部前面 | | beforeend | 元素内部后面 | ```html <div id="app">花森酱</div> <script> let app = document.querySelector("#app"); app.insertAdjacentHTML("afterbegin",'<h1>后盾人</h1>'); </script> ``` #### <u>inserAdjacentElement</u> nsertAdjacentElement() 方法将指定元素插入到元素的指定位置 1. 第一个参数是位置; 2. 第二个参数为新元素节点; ```html <div id="app">花森酱</div> <script> let app = document.querySelector("#app"); let span = document.createElement('span') app.insertAdjacentHTML("afterbegin",span); </script> ``` #### <u>古老管理手法</u> 下面列表过去使用的操作节点的方法 | 方法 | 说明 | | ------------ | ------------------------------ | | appendChild | 添加节点 | | insertBefore | 用于插入元素到另一个元素的前面 | | removeChild | 删除节点 | | replaceChild | 进行节点的替换操作 | ### 15.表单控制 #### <u>表单查找</u> JS为表单的操作提供了单独的集合控制 - 使用document.forms获取表单集合; - 使用form的name属性获取指定form元素; - 根据表单项的name属性使用form.elements.title获取表单项; - 直接写成form.name形式; - 针对radio/checkbox获取的表单项是一个集合; ```javascript <form action="" name="hs"> <input type="text" name="title" /> </form> <script> const form = document.forms.hs; console.log(form.title.form === form); //true </script> ``` ### 16.样式管理 通过DOM修改样式可以通过更改元素的class属性或通过style对象设置行样式来完成 #### <u>className</u> 使用JS的className可以批量设置样式,style中设置对应的类名,达到修改样式的目的。 ```javascript let app = document.getElementById('app') app.className = 'hs' ``` #### <u>classList</u> 通过使用 classList属性,添加移除操作操作,实现样式和dom的绑定。 | 方法 | 说明 | | ----------------------- | -------- | | node.classList.add | 添加类名 | | node.classList.remove | 删除类名 | | node.classList.toggle | 切换类名 | | node.classList.contains | 类名检测 | ```javascript let app = document.getElementById('app') app.classList.add('hs') console.log(app.classList.contains('hs')) //false ``` #### <u>单个样式属性设置</u> 使用节点的style对象来设置行样式,单词采用驼峰命名法。 ```javascript let app = document.getElementById('app') app.style.backgroundColor = 'red' app.style.color = 'yellow' ``` #### <u>批量设置样式</u> ```javascript // 方式一 let app = document.getElementById('app') app.style.cssText = `background-color:red;color:yellow` // 方式二 app.setAttribute('style', `background-color:red;color:yellow;`) ``` ### 17.样式获取 #### <u>style</u> 可以使用DOM对象的style属性读取行样式,需要注意的是style对象仅可以获取内联样式属性。 ```html <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>测试</title> <style> div { color: yellow; } </style> </head> <body> <div id="app" style="background-color: red; margin: 20px">后盾人</div> <script> let app = document.getElementById("app"); console.log(app.style.backgroundColor); console.log(app.style.margin); console.log(app.style.marginTop); console.log(app.style.color); </script> </body> ``` #### <u>getComputedStyle</u> 使用window.getComputedStyle可获取所有应用在元素上的样式属性,第一个参数为元素,第二个参数为伪类,此方式获得的是经过计算后的样式属性,可能与真实的设置值有所不同。 ```js let fontSize = window.getComputedStyle(app).fontSize ``` # 空间坐标 ### 1.基础理解 首先参考画布分为视口(窗口)与文档的含义 1. 文档尺寸一般大于视口尺寸; 2. F12打开控制台会造成尺寸相应变小; 3. 视口的尺寸不包括浏览器的菜单和状态栏等; 4. 视口坐标的操作需要考虑滚动条的位置;  ### 2.视口文档 获取浏览器视口宽度的集中方法 | 方法 | 说明 | 注意 | | ------------------------------------- | -------- | -------------------- | | window.innerWidth | 视口宽度 | 包括滚动条(不常用) | | window.innerHeight | 视口高度 | 包括滚动条(不常用) | | document.documentElement.clientWidth | 视口宽度 | | | document.documentElement.clientHeight | 视口高度 | | ### 3.定位方向 | | | | | ------------------------------- | --------------------------------------- | ------------------------------ | | 方法 | 说明 | 备注 | | element.getBoundingClientRect() | 返回元素相对于视口的坐标信息 | 窗口坐标 | | element.getClientRects | 行级元素每行尺寸位置组成的数组 | | | element.offsetParent | 拥有定位属性的父级 | 对于隐藏元素/body/html值为null | | element.offsetWidth | 元素宽度尺寸(内边距+边框+宽) | | | element.offsetHeight | 元素高度尺寸(内边距+边框+高) | | | element.offsetLeft | 相对于祖先元素的X轴坐标 | | | element.offsetTop | 相对于祖先元素的Y轴坐标 | | | element.clientWidth | 内容宽度(width+内边距)(行级元素为0) | | | element.clientHeight | 内容宽度(width+内边距)(行级元素为0) | | | element.clientLeft | 内容距离外部的距离 | | | element.clientTop | 内容距离顶部的距离 | | | element.scrollWidth | 元素宽度 内容+内边距+内容溢出的尺寸 | | | element.scrollHeight | 元素高度 内容+内边距+内容溢出的尺寸 | | | element.scrollLeft | 水平滚动条左侧已经滚动的宽度 | | | element.scrollTop | 垂直滚动条顶部已经滚动的高度 | | ### 4.坐标判断 获取相对于视口的xy坐标上的元素,如果坐标定在视口外则返回值为null。 | 方法 | 说明 | | ------------------------- | ---------------------------- | | element.elementsFromPoint | 返回指定坐标点所在的元素集合 | | element.elementFromPoint | 返回指定坐标点最顶级的元素 | ### 5.滚动控制 | 方法 | 说明 | 参数说明 | | -------------------------------------- | ------------------------------ | ------------------------------------------------------------ | | window.pageXOffset | 文档相对窗口水平滚动的像素距离 | | | window.pageYOffset | 文档相对窗口垂直滚动的像素距离 | | | element.scrollLeft() | 元素X轴滚动位置 | | | element.scrollTop() | 元素Y轴滚动位置 | | | element.scrollBy() | 按偏移量进行滚动内容 | 参数为对象{top:垂直偏移量,left:水平偏移量,behavior:'滚动方式'} | | element.scroll() 或 element.scrollTo() | 滚动到指定的具体位置 | 参数为对象{top:X轴文档位置,left:Y轴文档位置,behavior:'滚动方式'} | | element.scrollLeft | 获取和设置元素X轴滚动位置 | 设置X轴文档位置 | | element.scrollTop | 获取和设置元素Y轴滚动位置 | 设置Y轴文档位置 | | element.scrollIntoView(bool) | 定位到顶部或底部 | 参数为true元素定位到顶部 | ```javascript document.documentElement.scroll({ top: 30, behavior: 'smooth' }) ``` # 网络请求 浏览器天生具发送HTTP请求的能力 ### 1.XMLHttpRequest 使用XMLHttpRequest发送请求数据 ```javascript // 通过XMLHttpRequst封装Ajax网络请求 // 实例:HuaSenAjax.get(url,options,data),HuaSenAjax.post(url,options,data),new HuaSenAjax("GET",url,options,data),默认请求GET且异步请求,函数返回一个Promise对象。 class HuaAjaxTools { // 定义返回类型为JSON类型 options = { responseType: "json", }; // 构造函数,默认get请求,默认不传参数,参数列表options用于覆盖。 constructor(method = "GET", url, options, data = null, async = true) { this.method = method; this.url = url; this.data = this.formatData(data); this.async = async; Object.assign(this.options, options); // 合并覆盖参数 } // 格式化处理参数,处理将POST请求数据序列化。 formatData(data) { if (typeof data != "object" || data == null) data = {}; let form = new FormData(); // FormData类型其实是在XMLHttpRequest2级定义,它是为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。 for (const [name, value] of Object.entries(data)) { form.append(name, value); //添加至序列化列表中 } return form; } // 处理连接参数的静态方法 static handleUrl(url = null, data = null) { if (typeof url != "string" || url == null) url = ""; if (typeof data != "object" || data == null) data = {}; if (Object.keys(data).length !== 0) { url = url.replace(/\/$/, "") + "?"; let params = ""; for (const [name, value] of Object.entries(data)) { params += "&" + name + "=" + encodeURIComponent(value); } params = params.replace(/^&/, ""); // 利用正则去除参数中的第一个&符号 url = url + params; } return url; } // 声明get静态方法 static get(url, options, data) { try { url = this.handleUrl(url, data); return new this("GET", url, options).xhr(); } catch (e) { return Promise.reject("get方法内部出错" + e); } } // 声明post静态方法 static post(url, options, data) { try { return new this("POST", url, options, data).xhr(); } catch (e) { return Promise.reject("post方法内部出错" + e); } } static jsonp(url, data) { return new Promise((resolve, reject) => { try { // window对象绑定一个回调函数 const body = document.body; const script = document.createElement("script"); window[data.cb] = function (res) { resolve(res); // 处理promise激活微任务 }; url = this.handleUrl(url, data); // 处理URL script.src = url; body.appendChild(script); // body标签后添加script标签程序自动加载内容 body.removeChild(script); // 获得参数后移除添加的script标签 } catch (e) { body.removeChild(script); reject("jsonp中发生错误", e); } }); } // 异步发送 xhr() { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); // 声明网络请求对象 xhr.open(this.method, this.url, this.async); // 设置网络请求配置 xhr.responseType = this.options.responseType; // 请求数据类型 xhr.send(this.data); //发送参数 xhr.onload = function () { // 请求成功后的网络执行的函数 if (xhr.status == 200) { //状态码 resolve(xhr.response); } else { reject({ status: xhr.status, error: xhr.statusText }); } }; xhr.onerror = function (error) { reject(error); }; }); } } export { getByElementIds, HuaAjaxTools }; // 使用实例 import { HuaAjaxTools } from "../utils.js"; HuaAjaxTools.get( "http://api.tianapi.com/txapi/everyday/index", { responseType: "json" }, { key: "2cab6669e9d6766c2990eccfa3253ee5", } ).then( (result) => { console.log(result); }, (reject) => { console.log(reject); } ); HuaAjaxTools.post( "http://api.tianapi.com/txapi/everyday/index", { responseType: "json", headers: { "Content-Type": "application/json;charset=utf-8", }, }, { key: "2cab6669e9d6766c2990eccfa3253ee5", } ).then( (result) => { console.log(result); }, (reject) => { console.log(reject); } ); HuaAjaxTools.jsonp("https://www.baidu.com/su", { wd: "花森", // 参数关键值 cb: "handleSuggestion", // 回调函数 }).then( (success) => { console.log(success); }, (error) => { console.log(error); } ); ``` ### 2.fetch FETCH是JS升级后提供的更简便的网络请求的操作方法,内部使用Promise完成请求,使用response.json()接收JSON类型数据,使用response.text()接收text类型数据。 #### <u>get</u> ```javascript fetch( "http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA" ) .then((res) => { return res.json(); }) .then((data) => { console.log(data); }); ``` #### <u>post</u> 发送的JSON类型需要设置请求头为 `application/json;charset=utf-8` ```javascript fetch(`链接`, { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8', }, body: JSON.stringify({ name: '花森' }), }) .then((response) => { return response.json() }) .then((data) => { console.log(data) }) ``` # 正则表达式 正则表达式是用于匹配字符串中字符组合的模式,正则表达式是在宿主环境下运行,不是一门单独的语言,几乎主流语言(js/php/node.js)等都存在正则表达式语法。 ### 1.声明正则 ```javascript // 字面量 let hs = "huasenjio"; console.log(/u/.test(hs));//true "//"字面量形式写法但不可以在其中使用变量 console.log(/hs/.test(hs)); //false 变量形式被当做字符型去匹配 console.log(eval(`/${hs}/`).test(hs)); //true 通过eval函数实现解析变量 // 对象形式 let hs = "huasenjio.top"; let zhuqi = ".top"; let reg = new RegExp(zhuqi); // 匹配规则 console.log(reg.test(hs)); //true ``` ### 2.选择符 `|` 这个符号带表选择修释符,选择符左右两侧仅能匹配一个。 ```javascript let hs = "森酱生气了"; console.log(/(森酱|猪琦)生气了/.test(hs)); ``` ### 3.字符转义 转义用于改变字符的含义,例如`/`在是正则符号的边界,如果想要输入网址匹配,则需要通过`\`把`/`转移成字符串的含义。 ```javascript const url = "https://"; console.log(/https:\/\//.test(url)); //true ``` ### 4.字符边界 使用字符边界符用于控制匹配内容的开始与结束约定 | 边界符 | 说明 | | ------ | --------------------------- | | ^ | 匹配字符串的开始 | | $ | 匹配字符串的结束 忽略换行符 | ```javascript let hs = "n.huasenjio.top"; console.log(/^n\./.test(hs)); //n.打头的字符串会被匹配 ``` ### 5.元子字符 元字符是正则表达式中的最小元素,仅代表单一一个字符。 | 元字符 | 说明 | 示例 | | ------ | ------------------------------------------------- | ------------- | | \d | 匹配任意一个数字 | [0-9] | | \D | 与除了数字以外的任何一个字符匹配 | [^0-9] | | \w | 与任意一个英文字母数字或下划线匹配 | [a-zA-Z_] | | \W | 除了字母数字或下划线外与任何字符匹配 | [^a-zA-Z_] | | \s | 任意一个空白字符匹配 如空格 制表符`\t` 换行符`\n` | [\n\f\r\t\v] | | \S | 除了空白符外任意一个字符匹配 | [^\n\f\r\t\v] | | . | 匹配除换行符外的任意字符 | | ```javascript let hd = "huasenjio 2010"; console.log(hd.match(/\d/g)); // ["2", "0", "1", "0"] console.log(hd.match(/\d/)); // ["2"] console.log(hd.match(/\d+/)); // ["2010"] ``` ### 6.模式修饰 正则表达式在执行时会按他们的默认执行方式进行匹配 | 修饰符 | 说明 | | ------ | --------------------------------------------------- | | i | 不区分大小写字母的匹配 | | g | 全局搜索所有匹配内容 | | m | 视为多行 | | s | 视为单行忽略换行符 使用`.` 可以匹配所有字符 | | y | 从 `regexp.lastIndex` 开始匹配 匹配不成功就不再继续 | | u | 正确处理四个字符的 UTF-16 编码 | ```javascript // 全域g,忽略大小写i。 let hd = "huasenJIO 2010"; console.log(hd.match(/Jio \d+/gi)); // ["JIO 2010"] // u配合属性别名,L表示字母,P表示标点符号,\p{sc=Han}表示中国汉字。 console.log(hd.match(/\p{L}+ \d+/gu)); // ["huasenJIO 2010"] // RegExp对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置,必须配合g模式使用,匹配完成时lastIndex会被重置为0且对exec有效。 let hs = `花森导航网址`; let reg = /\p{sc=Han}/gu; reg.lastIndex = 2; // 正则遍历的脚步 while ((res = reg.exec(hs))) { console.log(reg.lastIndex); console.log(res[0]); } // y let hs = "udunren"; let reg = /u/y; console.log(reg.exec(hs)); console.log(reg.lastIndex); //1 console.log(reg.exec(hs)); //null console.log(reg.lastIndex); //0 ``` ### 7.原子表 在一组字符中匹配某个元字符就要将其加入`[]`中 | 原子表 | 说明 | | ------ | ----------------------------------- | | [] | 只匹配其中的一个原子 | | [^] | 只匹配"除了"其中字符的任意一个原子 | | [0-9] | 匹配0-9任何一个数字(需要升序) | | [a-z] | 匹配小写a-z任何一个字母(注意顺序) | | [A-Z] | 匹配大写A-Z任何一个字母(注意顺序) | ```javascript let hs= "huasenJIO 2010"; console.log(hs.match(/[hua]/g)); // ["h", "u", "a"] 匹配到h、u、a中任意的单个字符 ``` ### 8.原子组 多个元子当成一个整体匹配,可以通过元子组完成,用括号`()`进行包裹。 ```javascript const hs = `<h1>huasenjio</h1>`; console.log(/<(h1)>.+<\/\1>/.test(hs)); //true ``` #### <u>使用分组</u> `$n` 指在替换时使用匹配的分组数据,下面是匹配h标题标签替换成p段落标签,并引用第二个分组的数据放入。 ```javascript let hs = ` <h1>花森酱</h1> <span>huasen</span> <h2>Jio</h2> `; // 第一个分组(h[1-6]),第二个分组([\s\S]*)。 let reg = /<(h[1-6])>([\s\S]*)<\/\1>/gi; console.log(hs.replace(reg, `<p>$2</p>`)); ``` ### 9.重复匹配 #### <u>基本使用</u> 如果要重复匹配一些内容时我们要使用重复匹配修饰符 | 符号 | 说明 | | ----- | -------------------------- | | * | 重复取零次或更多次(0或n) | | + | 重复一次或更多次(1或n) | | ? | 重复零次或一次(0或1) | | {n} | 重复n次 | | {n,} | 重复n次或更多次 | | {n,m} | 重复n到m次 | ```javascript let hd = "hssss"; console.log(hd.match(/hs+/i)); //hssss ``` #### <u>禁止贪婪</u> 正则表达式在进行重复匹配时,默认是贪婪匹配模式,当后面还存在符合的字符就一直贪婪匹配下去,这时就可以使用禁止贪婪,可以通过`?`进行修饰。 | 使用 | 说明 | | ------ | ------------------------------ | | *? | 重复任意次 但尽可能少重复 | | +? | 重复1次或更多次 但尽可能少重复 | | ?? | 重复0次或1次 但尽可能少重复 | | {n,m}? | 重复n到m次 但尽可能少重复 | | {n,}? | 重复n次以上 但尽可能少重复 | ```javascript // 满足表达式即可不贪婪下去 let str = "1234"; console.log(str.match(/\d+/)); //1234 console.log(str.match(/\d+?/)); //1 console.log(str.match(/\d{2,3}?/)); //12 console.log(str.match(/\d{2,}?/)); //12 <h1>huasen</h1> <h2>花森</h2> <h3></h3> <h1></h1> <script> let body = document.body.innerHTML; let reg = /<(h[1-6])>[\s\S]*?<\/\1>/gi; // 禁止贪婪 ["<h1>huasen</h1>","<h2>花森</h2>","<h3></h3>","<h1></h1>"] let reg = /<(h[1-6])>[\s\S]*<\/\1>/gi; // 开启贪婪 ["<h1>huasen</h1>↵ <h2>花森</h2>↵ <h3></h3>↵ <h1></h1>"] console.table(body.match(reg)); </script> ``` ### 10.字符串方法 #### <u>search</u> search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索,返回值为索引位置。 ```javascript let str = "huasenjio.top"; console.log(str.search("top")); // 10下标匹配到 console.log(str.search(/\.top/i)); // 9 ``` #### <u>match</u> 直接使用字符串搜索 ```javascript let str = "huasenjio.top"; console.log(str.match("top")); // 返回匹配的参数 console.log(str.match(/\.top/i)); // 返回匹配的参数 ``` #### <u>split</u> 通过正则匹配到的符号进行字符串分割 ```javascript let str = "2023/02-12"; console.log(str.split(/-|\//)); ``` #### <u>replace</u> `replace` 方法不仅可以执行基本字符替换,也可以进行正则替换,下面替换日期连接符。 ```javascript let str = "2023/02/12"; console.log(str.replace(/\//g, "-")); //2023-02-12 ``` ### 11.正则方法 #### <u>test</u> 使用正则判定字符串是否存在特定符号,检测输入的邮箱是否合法。 ```html <body> <input type="text" name="email" /> </body> <script> let email = document.querySelector(`[name="email"]`); email.addEventListener("keyup", e => { console.log(/^\w+@\w+\.\w+$/.test(e.target.value)); }); </script> ``` #### <u>exec</u> 不使用 `g` 修饰符时与 `match` 方法使用相似,使用 `g` 修饰符后可以循环调用直到全部匹配完。 ```html <div class="content">花森导航中的作者花森不断分享种花技巧</div> <script> let content = document.querySelector(".content"); let reg = /(?<tag>花)/g; let num = 0; while ((result = reg.exec(content.innerHTML))) { num++; } console.log(`花共出现${num}次`); </script> ``` ### 11.断言匹配 断言虽然写在扩号中但它不是组,所以不会在匹配结果中保存,可以将断言理解为正则中的条件。 #### (?=exp) **零宽先行断言** `?=exp` 匹配后面为 `exp` 的内容 #### (?<=exp) **零宽后行断言** `?<=exp` 匹配前面为 `exp` 的内容 #### (?!exp) **零宽负向先行断言** 后面不能出现 `exp` 指定的内容 #### (?<!exp) **零宽负向后行断言** 前面不能出现exp指定的内容 # window对象 window 是客户端浏览器对象模型的基类,window对象也称BOM是JavaScript的全局对象,每一个标签页就是一个独立的窗口独立的BOM对象,通过BOM对象我们可以操作当前窗口,进行进一步操作! ### 1.对象分类 1. Window,客户端js的顶层对象,当body和frameset标签出现时window对象就会自动创建; 2. navigator,包含客户端浏览器的信息; 3. screen,客户端屏幕信息; 4. history,浏览器窗口访问过的URL信息; 5. location,包含当前网页文档的URL信息; 6. document,整个页面文档标签信息; ### 2.系统交互 window定义了3个人机交互的方法 ```javascript alert("你好"); console.log(confirm("你的电脑将被攻击")); // true console.log(prompt("花森导航的网址为:")); // 输入值 ``` ### 3.打开窗口 使用 window 对象的 open() 方法可以打开一个新窗口并返回一个window对象 ```javascript //window.open (URL, name, features, replace) let a = window.open("http://n.huasenjio.top/", "zhuzhu"); console.log(a); // 返回window对象 ``` 1. URL跳转网页的链接; 2. name新窗口的名称,注意不是网站标题; 3. feature声明了新窗口要显示的标准浏览器的特征; 4. replace确定打开的网页在浏览器记录中添加一条访问记录还是替换掉原来网页中的条目; | 特征 | 说明 | | -------------------------------- | ------------------------------------ | | fullscreen = yes \| no \| 1 \| 0 | 是否使用全屏模式显示浏览器(默认no) | | height = pixels | 窗口文档显示区的高度 | | left = pixels | 窗口的 x 坐标 | | location = yes \| no \| 1 \| 0 | 是否显示地址字段(默认是 yes) | | menubar = yes \| no \| 1 \| 0 | 是否显示菜单栏(默认是 yes) | | resizable = yes \| no \| 1 \| 0 | 窗口是否可调节尺寸(默认是 yes) | | scrollbars = yes \| no \| 1 \| 0 | 是否显示滚动条(默认是 yes) | | status = yes \| no \| 1 \| 0 | 是否添加状态栏(默认是 yes) | | toolbar = yes \| no \| 1 \| 0 | 是否显示浏览器的工具栏(默认是 yes) | | top = pixels | 窗口的 y 坐标 | | width = pixels | 窗口的文档显示区的宽度 | 新创建的window对象有一个opener属性指向原始的网页对象 ```javascript win = window.open(); //打开新的空白窗口 win.document.write("<h1>这是新打开的窗口</h1>"); //在新窗口中输出提示信息 win.focus(); //让原窗口获取焦点 win.opener.document.write("<h1>这是原来窗口</h1>"); //在原窗口中输出提示信息 console.log(win.opener == window); //检测window.opener属性值 ``` ### 4.关闭窗口 关闭当前窗口,使用 window.closed 属性可以检测当前窗口是否关闭。 ```javascript window.close(); ``` ### 5.定时器 | 方法 | 说明 | | --------------- | ------------------------------------------------ | | setInterval() | 按照执行的周期(单位为毫秒)调用函数或计算表达式 | | setTimeout() | 在指定的毫秒数后调用函数或计算表达式 | | clearInterval() | 取消由 setInterval() 方法生成的定时器 | | clearTimeout() | 取消由 setTimeout() 方法生成的定时器 | ### 6.框架集合 每一个frame标签都是一个window对象,使用frame可以访问每一个window对象,frames是一个数据集合,存储者所有的window对象,下标从0开始,访问顺序从左到右从上到下,通过`parent.frames[0]`访问对应的frame框架的window对象。 ```javascript <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <frameset rows="50%,50%" cols="*" frameborder="yes" border="1" framespacing="0"> <frameset rows="*" cols="50%,*,*" framespacing="0" frameborder="yes" border="1"> <frame src="http://n.huasenjio.top/" name="left" id="left" /> <frame src="middle.html" name="middle" id="middle"> <frame src="right.html"name="right" id="right"> </frameset> <frame src="http://huasenjio.top/" name="bottom" id="bottom"> </frameset> <body> </body> </html> ``` ### 7.窗口大小位置 方法 moveTo() 可以将窗口的左上角移动到指定的坐标,方法 moveBy() 可以将窗口上移、下移、左移、右移指定数量的像素,方法 resizeTo() 和 resizeBy() 可以按照绝对数量和相对数量调整窗口的大小。 #### <u>调整窗口位置</u> moveTo() 和 moveBy() #### <u>窗体大小</u> resizeTo() 和 resizeBy() #### <u>滚动条</u> scrollTo() 和 scrollBy() # navigator对象 navigator 对象存储了与浏览器相关的基本信息,例如名称、版本、系统信息,通过window.navigator 可以引用该对象,读取客户端基本信息。 ### 1.浏览器检测方法 #### <u>特征检测法</u> 特征检测法就是根据浏览器是否支持特定的功能来决定相应操作的非精确判断方式,但却是最安全的检测方法,仅仅在意浏览器的执行能力,那么使用特征检测法就完全可以满足需要。 ```javascript // 检测当前浏览器是否支持 document.getElementsByName 特性,不支持则使用document.getElementsByTagName 特性,进行兼容处理。 if (document.getElementsByName) { //如果存在则使用该方法获取a元素 var a = document.getElementsByName ("a"); } else if (document.getElementsByTagName) { //如果存在则使用该方法获取a元素 var a = document.getElementsByTagName ("a"); } ``` #### <u>字符串检测法</u> 客户端浏览器每次发送 HTTP 请求时,请求头中有一个user-agent(用户代理)属性,使用用户代理字符串检测浏览器类型,可以通过`navigator.userAgent`获取客户端信息。 ```javascript var s = window.navigator.userAgent; //简写方法 var s = navigator.userAgent; console.log(s); //返回类似信息:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36 ``` ### 2.检测版本号 获得客户端信息后通过正则表达式筛选对应信息即可 # location对象 location 对象存储了当前文档位置(URL)相关的信息,例如网页的地址,访问历史信息,可以通过`window.location`进行访问,location中定义有一下8个属性: ### 1.对象属性 | 属性 | 说明 | | -------- | ------------------------------------------------------------ | | href | 声明了当前显示文档的完整 URL(设置可以利用网页跳转) | | protocol | 声明了 URL 的协议部分(例如:“http:”) | | host | 声明了当前 URL 中的主机名和端口部分(例如:“huasenjio.top:80”) | | hostname | 声明了当前 URL 中的主机名 | | port | 声明了当前 URL 的端口部分 | | pathname | 声明了当前 URL的路径部分 | | search | 声明了当前 URL 的查询部分(例如:“?id=123&name=location”) | | hash | 声明了当前 URL 中锚部分(“#top”指定在文档中锚记的名称) | ### 2.对象方法 #### <u>reload</u> 可以重新装载当前文档 #### <u>replace</u> 可以装载一个新文档而无须为它创建一个新的历史记录,即不能通过单击“返回”按钮返回当前的文档,`window.location`和`document.location`不同,前者是location对象,后者是一个只读字符串,当服务器发生重定向,document.location 包含的是已经装载的 URL,而 location.href 包含的则是原始请求文档的 URL。 # history对象 history 对象存储了库互动浏览器的浏览历史,通过`window.history`可以访问对象,取到最近访问且有限的条目URL信息。HTML5 之前,为了保护客户端浏览信息的安全和隐私,history 对象禁止`javascript`脚本直接操作这些访问信息。HTML5 新增了一个 History API,该 API 允许用户通过 JavaScript 管理浏览器的历史记录,实现无刷新更改浏览器地址栏的地址。 ### 1.基本操作 ```javascript window.history.back();1// 历史记录中后退,等效于在浏览器的工具栏上单击“返回”按钮。 window.history.forward();1 // 历史记录中前进,等效于浏览器中单击“前进”按钮。 window.history.go(1); // 移动到指定的历史记录点 window.history.length; // 访问记录长度 window.history.state; // 当前标签的访问记录 ``` ### 2.添加修改 HTML5 新增 history.pushState() 和 history.replaceState() 方法,允许用户逐条添加和修改历史记录条目。 #### <u>pushState</u> pushState可以修改referer值,调用该方法后创建的 XMLHttpRequest 对象会在 HTTP 请求头中使用referer值,referrer 的值则是创建 XMLHttpRequest 对象时所处的窗口的 URL。 pushState() 方法类似于设置 window.location='#foo',它们都会在当前文档内创建和激活新的历史记录条目,pushState() 方法永远不会被触发 hashchange 事件,因为没有记录被删除或者被添加,push进行修改当前的历史条目且不刷新网页。 ```javascript var stateObj = {foo : "bar"}; history.pushState (stateObj, "标题", "bar.html"); // 当前标签URL变为XXX/bar.html ``` #### <u>replaceState</u> history.replaceState() 与 history.pushState() 用法相同,pushState() 是在 history 栈中添加一个新的条目,replaceState() 是替换当前的记录值。 ### 3.popstate事件 每当激活的历史记录发生变化时,都会触发 popstate 事件。如果被激活的历史记录条目是由 pushState() 创建,或者是被 replaceState() 方法替换的,popstate 事件的状态属性将包含历史记录的状态对象。 # screen对象 screen 对象存储了客户端屏幕信息,可以用来探测客户端硬件配置,实现根据显示器屏幕大小选择使用图像的大小,根据颜色深度选择使用 16 色图像或 8 色图像。 ```javascript function center(url) { //窗口居中处理函数 var w = screen.availWidth / 2; //获取客户端屏幕宽度的一半 var h = screen.availHeight / 2; //获取客户端屏幕高度的一半 var t = (screen.availHeight - h) / 2; //计算居中显示时顶部坐标 var l = (screen.availWidth - w) / 2; //计算居中显示时左侧坐标 var p = "top=" + t + ",left=" + l + ",width=" + w + ",height=" + h; //设计坐标参数字符串 var win = window.open(url, "url", p); //打开指定的窗口,并传递参数 win.focus(); //获取窗口焦点 } console.log(screen); center("http://huasenjio.top"); //调用该函数 ``` # document对象 浏览器加载文档会自动构建文档对象模型(DOM),文档中每个元素都映射到一个数据集合。  ### 1.动态生成文档内容 使用 document 对象的 write() 和 writeln() 方法可以动态生成文档内容 #### <u>write()</u> 生成内容追加在文档树的末尾 ```javascript document.write ('Hello,World'); ``` #### <u>writeln()</u> writeln() 方法与 write() 方法完全相同,只不过在输出参数之后附加一个换行符。 # XMLHttpRequest `XMLHttpRequest`(XHR)对象用于与服务器交互,通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,更新页面的局部内容。 ### 1.定义声明 构造函数用于初始化一个 `XMLHttpRequest` 实例对象 ```javascript let xhr = new XMLHttpRequest(); ``` ### 2.属性类型 | XMLHttpRequest对象属性 | 说明 | | ---------------------- | -------------------------------------------------- | | onreadystatechange | 根据readyState属性发生变化调用方法 | | readyState | 发送请求进程状态分为(0\|1\|2\|3\|4完成) | | response | 服务器返回的数据类型 | | responseText | 返回文本数据 | | responseType | 定义服务器响应类型 | | responseURL | 返回经过序列化(serialized)的响应 URL | | responseXML | 返回XML格式数据 | | status | 代表服务器响应的状态码 | | statesText | 包含完整的响应状态文本(例如"`200 OK`") | | unload | 上传进度 | | timeout | 请求的最大请求时间(毫秒)超出自动终止 | | withCreadtials布尔值 | 指定跨域 `Access-Control` 请求是否应当带有授权信息 | ### 3.常用方法 | XMLHttpRequest.方法 | 说明 | | ----------------------- | ------------------------------------------------------------ | | abort() | 立即终止请求 | | getAllResponseHeaders() | 字符串的形式返回所有用 [CRLF](https://developer.mozilla.org/zh-CN/docs/Glossary/CRLF) 分隔的响应头 | | getResponseHeader() | 返回包含指定响应头的字符串 | | open() | 初始化一个请求 | | overrideMimeType | 覆写由服务器返回的 MIME 类型 | | send() | 发送请求 | | setRequestHeader() | 设置 HTTP 请求头的值(open之后且send()之前设置) | ### 4.事件监听 | addEventListener监听 | 说明 | | -------------------- | ------------------------------ | | abort | 当 request 被停止时触发 | | error | 当 request 遭遇错误时触发 | | load | 请求成功完成时触发 | | timeout | 预设时间内没有接收到响应时触发 | ### 5.简单示例 ```javascript let xhr = new XMLHttpRequest(); // 开启请求任务 readyState=0 xhr.open( "GET", "http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA" ); xhr.send(); //发送请求 readyState=1 // 监听readyState变化来等待服务器响应 xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log("获取数据:" + xhr.responseText); // readyState=4 } else { console.log("readyState:" + xhr.readyState); // readyState=2 readyState=3 } }; // 请求成功回调 xhr.addEventListener("load", () => { console.log("请求成功会触发事件!"); }); ``` ### 6.XMLHttpRequest2新特性 旧版本只支持文本格式传输,无法用于读取上传二进制文件,接收和传送时无法查看进度信息,不能跨域请求,第二版本优化功能具有一下特点: 1. 设置HTTP请求的时限; 2. 可以使用FormData对象管理表单数据; 3. 上传文件; 4. 可以请求不同域名下的数据(跨域请求); 5. 获取服务器端的二进制数据; 6. 可以获得数据传输的进度信息; #### <u>formData</u> ajax操作往往用来传递表单数据,所以HTML5新增了一个FormData对象,可以模拟表单数据。 ```javascript // 实例化对象 var formData = new FormData(); // 添加表单项 formData.append('username', '张三'); formData.append('id', 123456); // 发送格式 xhr.send(formData); ``` #### <u>上传文件</u> 新版XMLHttpRequest对象可以上传文件,假定files是一个"选择文件"的表单元素`(input[type="file"])`,我们同样使用formData格式进行传输。 ```javascript var formData = new FormData(); for (var i = 0; i < files.length;i++) { formData.append('files[]', files[i]); } xhr.send(formData); ``` #### <u>接收二进制数据</u> 服务器取回二进制数据是使用新增的responseType属性,如果服务器返回文本数据则属性值为”text“,不要用IE6这种古董浏览器还可以支持其他格式数据,例如设置传回属性是json字符串格式。 ```javascript var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://wthrcdn.etouch.cn/weather_mini?city=%E9%87%91%E5%B7%9E%E5%8C%BA'); xhr.responseType: "json", xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.response); // readyState=4 } else { console.log("readyState:" + xhr.readyState); // readyState=2 readyState=3 } }; ``` #### <u>进度信息</u> 新版本的XMLHttpRequest对象在传输数据时可以通过监听progress事件用来返回进行信息,它分成上传下载情况,下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。 ```javascript xhr.onprogress = 方法; xhr.upload.onprogress = 方法; // 实例 xhr.onprogress = function (event) { console.log(event); if (event.lengthComputable) { var percentComplete = event.loaded / event.total; } }; ``` # HTTP请求头 HTTP协议报文头部由方法、URL、HTTP版本、HTTP首部字段等部分构成,携带报文主题大小,使用语言,认证信息详细信息。 ### 1.首部字段类型 1. 通用首部字段; 2. 请求首部字段; 3. 响应首部字段; 4. 实体首部字段; ### 2.通用首部字段 | 通用首部字段 | 说明 | | ---------------- | ------------------------------------------------------------ | | Cache-Control | 控制缓存行为(no-cache\|max-age\|public) | | Connection | 连接状态管理(close\|Keep-Alive\|timeout max=500) | | Date | HTTP/1.1协议使用RFC1123中规定的日期时间的格式 | | Pragma | 客户端要求中间服务器不返回缓存资源(Pragma:no-cache) | | Trailer | 简要记录主体报文记录首部字段() | | Trailer-Encoding | 规定了传输报文主体时使用的编码方式 | | Upgrade | 用于检测HTTP协议及其他协议是否可以使用更高的版本进行通信(TLS/1.0) | | Via | 追踪客户端与服务端之间的请求和响应报文的传输路径 | | Warning | 错误通知(110响应过期\|112断开连接\|) | ### 3.请求首部字段 | 首部字段名 | 说明 | | ------------------- | ------------------------------------------------------------ | | Accept* | 用于代理可处理的媒体类型 | | Accept-Charset | 通知服务器用户代理支持的字符集及字符集的相对优先级顺序 | | Accept-Language | 告知服务器用户代理能够处理的自然语言集以及自然语言集的相对优先级 | | Accept-Encoding* | 通知服务器用户代理支持的内容编码及内容编码的优先级顺序 | | Authorization | 告知服务器用户代理的认证信息 | | Expect | 期待服务器的特定行为 | | From | 告知服务器使用代理的用户的电子邮件地址 | | Host | 请求的资源所处的互联网主机名和端口号 | | If-Mach | 条件请求(比较实体标记(ETAG)相同才可以请求) | | If-Modified-Since | 用于确认代理或客户端拥有的本地资源的有效性(304缓存) | | If-None-Match | 相反比较实体标记 | | Max-Forwads | 字段以十进制整数形式指定可经过的服务器最大数目 | | Proxy-Authorization | 客户端接收到从代理服务器发送过来的认证质询(返回时提交必要信息) | | Range | 客户端发送带有该首部字段的请求可以指定服务器资源的范围 | | User-Agent | 创建请求的浏览器和用户代理名称等信息传达给服务器 | | If-Range | 资源未更新是发送实体Byte的范围请求 | | If-UNmodified-Since | 相反的比较更新时间 | | Referer | 告知服务器请求的原始资源的URI | | TE | 首部字段会告知服务器客户端能够处理响应的传输编码方式以及相对优先级 | ### 4.响应首部字段 | 响应首部字段 | 说明 | | ---------------- | ------------------------------------------------------------ | | Accept-Ranges | 是否接受字节范围请求 | | Age | 推算资源创建经过时间 | | Etag | 告知客户端实体标识(可将资源以字符串形式做唯一性标识) | | Location | 命令客户端重定向至指定URL | | Retry-After | 首部字段告知客户端应该在多久之后再次发送请求 | | Server | 首部字段告知客户端当前服务器上安装的 HTTP 服务器应用程序的信息 | | Vary | 源服务器会向代理服务器传达关于本地缓存使用方法的命令 | | WWW-Authenticate | 首部字段用于 HTTP 访问认证 | ### 5.实体首部字段 | 实体首部字段 | 说明 | | ---------------- | -------------------------------------------------------- | | Allow | 首部字段用于通知客户端能够支持指定资源的所有 HTTP 方法 | | Content-Encoding | 告知客户端服务器对实体的主体部分选用的内容编码方式 | | Content-Language | 告知客户端实体主体使用的自然语言 | | Content-Length | Content-Length 表明了实体主体部分的大小 | | Content-Location | 首部字段给出与报文主体部分相对应的 URI | | Content-MD5 | MD5 算法生成的值目的是检查报文主体在传输过程中是否保持完 | | Content-Range | 告知客户端作为响应返回的实体 | | Content-Type | 首部字段说明了实体主体内对象的媒体类型 | | Expires | 资源失效的日期告知客户端 | | Last-Modified | 首部字段指明资源最终修改的时间 | ### 6.服务Cookie的首部字段 属于响应首部字段,用于服务端开始管理客户端状态的交互的载体。 | 属性 | 说明 | | ------------ | ------------------------------------------ | | NAME=VALUE | 赋予Cookie的名称和值 | | expires=DATE | Cookie有效期(不指明则默认为浏览器关闭时) | | path=PATH | 服务器上的文件目录作为Cookie的适用对象 | | domain=域名 | 作为Cookie适用对象的域名 | | Secure | 仅在HTTPS安全通信时才会发生Cookie | | HttpOnly | 限制Cookie不能被JavaScript脚本访问 |
## 互联网 计算机网络是由通信介质将地理位置不同的且相互独立的计算机连接起来,实现数据通信与资源共享。场景化分析,假设有A,B两个蜘蛛网彼此独立,同在一个网上的蜘蛛是可以相互通信,蜘蛛网和蜘蛛网之间需要通过某种规定(协议)通讯交换资源。 ## 因特网构成和网络分类 ### 1.组成概念 因特网工作设备有主机,服务器,路由器,交换机,信号发射器,边缘部分。通过交换机连接电脑形成一个个子网,进而路由器连接零散的子网,分组转发消息,计算机之间交换数据,主机对交换数据进行信息处理呈现的。  ### 2.网络连接方式 1. 主机直接通过电路相连,但是传输效率不高,合适传输要求延时抖动小的实时数据; 2. 采用以交换机为中心连接,无需建立连接即可通讯; 3. 新型网络连接,组建子网,通过路由器转发资源,共享连接在网络上的数据资源; ### 3.网络分类 1. 局域网,小型网络,一个公司,一个学校,一个家庭的网络设备组建的网络成为局域网,一般公司共用的wifi,学校校园网wifi,家庭wifi都是一个个局域网,同属于一个局域网内可以相互通信; 2. 城域网,中型网络,一个地区,一个城市中的各个局域网连接起来组建成为城域网,一般一个市区的网络就是一个城域网; 3. 广域网,大型网络,一个国家,一个国家中各个城域网连接组建称为的网络称为广域网,一般一个国家就是一个大型的广域网,我们正常不能直接访问别人国家的网络,因为伟大的祖国限制,如果需要访问外国网站借助第三方技术,俗称翻墙; ### 4.网络常用拓扑 1. 总线型,常用于局域网入网; 2. 星型和树型,常用于局域网,操作相对简单; 3. 环型,常用于城域网,无中心依赖,有较强的灵活性和自愈能力,出现故障后能够快速定位并进行保护倒换,拓展困难; 4. 格型,主要用于骨干网、广域网,灵活性和自愈能力较好,设计复杂;  ## 性能指标和协议设计 ### 1.协议 简而言之,协议就是一种约定,我们国家有很多民族,各个民族的语言不同,彼此间不可交流,所以引入普通话,各个民族可以通过普通话进行交流,每个网络传输的方式语言可能不同,所以需要通过协议来规约协调通讯。 ### 2.要素 1. 时序,实现顺序和速度匹配的详细说明; 2. 语法,数据与控制信息的结构和编码等; 3. 语义,发出控制信息,完成动作并做出特定的响应; ### 3.数据传输格式 报文格式是PPP帧格式,带有标志字段,地址字段,控制字段,协议字段,信息字段等数据信息。  ### 4.协议模型设计 互联网协议分布设计有多种,osi七层模型,tcp/ip五层模型,tcp/ip四层模型,现在用途最广泛的模型就是五层协议模型,所以我们理解它就可以明白计算机底层通讯的原理,每一层都运行着一个特定的协议,共同组成互联网协议。  ## 物理层 物理层主要通过双绞线,光纤,电缆,无线电波传输,信息传递的本质就是高电压(1),低电压(0)的电信号,通过调制解调器进行转化成为信号,最后通过传媒媒介进行传播。  ### 1.常用术语 1. 码元,表示数字信号时代表不同离散值的基本波形; 2. 信号,数据表现转化为电信号或者电磁形态; 3. 数据,运送的信息实体; ### 2.信道 1. 单工通信,仅可以单方向传输; 2. 半双工通信,双方交替通信,但是不可以同时发送; 3. 全双工通信,双方可以同时发送接收; ### 3.传播介质 #### <u>导向性</u> 1. 双绞线; 2. 光纤和光缆; 3. 同轴电缆; #### <u>非导向性</u> 1. 地面基站微波通讯; 2. 卫星微波通讯; ### 4.信道传输速率 1. 香农定理,有噪声信道信息传递速率公式; 2. 奈式准则,无噪声信道数据传输速率公式; ## 数据链路层 物理层发送接收0和1是无意义的行为,例如:QQ中想给女朋友发送一句:“你是头猪!”随之转变成为0和1的比特流交给网卡,网卡直接懵圈。因为无目的地址和接收人信息,消息想要发送成功则需要通过以太网协议,需要解决介质访问控制和寻址的问题。 ### 1.以太网协议(CSMA/CD) 以太网协议通过交换机或者集线器将电脑连接,使用广播信道发送MAC帧,其中帧分为:单播(一对一),广播(一对多),多播(多对多),帧数据格式如下:  每一个MAC帧由报头(head)和和数据(data)两部分组成,其中报头固定18个字节,发送者地址个接受者地址均为6个字节,数据域最短46字节,最长1500字节。MAC帧中的目的地址和原地址所指的是MAC地址,MAC地址是网卡唯一的物理地址,网卡被生产时就烧录一串12位16进制码在卡里。通过MAC地址可以寻找到对应的电脑,发送数据帧到交换机,交换机默认会发送广播类型的数据帧,广播就相当于给所有连接的电脑发送数据帧,每一个电脑都将响应一个单播传回交换机,所以交换机记住连接电脑网卡的MAC地址。  #### <u>网络适配器(网卡)</u> 串行/并行在计算机内部,具有简单的数据缓存,实现以太网协议,网卡是操作系统设备驱动程序,为系统高级协议栈提供服务。 #### <u>交换机</u> 按道理说电脑跟电脑直接连接网线(集线器)就可以相互通讯,但是大多数电脑仅有一个网卡,那么一台电脑仅能连接一台电脑,所以引入交换机,交换机就是负责将局域网的电脑相互连接,通过以太网协议中的MAC地址,交换机就能确定一台电脑的网口,电脑信息资源通讯均需要通过网口,传输数据。  显而易见,广播方式十分原始,基本通讯就是类似于靠拿着大喇叭喊话,连接交换机在同一台交换机的电脑懂能够听到,但是这些电脑默认屏蔽不关于自己的话。场景化分析一下,电脑a想给电脑b发送一个报文,报文中写有电脑b的mac地址,此时通过交换机发送给每一个连接在交换机上的电脑,电脑b发现该报文是关于自己则接收,其他电脑默认屏蔽。如果把全世界的电脑都连在一个局域网下,那么一个人发消息,这个消息就会发送给64亿台电脑,网络会瞬间瘫痪,这个现象叫做广播风暴。所以需要引入城域网,广域网的概念!  ### 2.面向连接(PPP协议) 点对点面向连接协议,使用最广泛的数据链路层协议,主机拨号接入因特网一般都是使用PPP协议,PPP协议由链路控制协议LCP和网络控制协议NCP构成。用户拨号接入ISP,路由器调制解调器确认拨号报文并建立物理连接,用户主机向路由器发送LCP分组的PPP帧,路由接收处理,网络层配置,NCP为主机分配一个临时的IP地址,使用户主机成为因特网上的主机。家庭的路由器一般内置PPP协议,安装人员进行拨号入网,验证通过后你才可以使用WIFI并联网使用!  ## 网络层 相互独立的局域网之间是不可以直接通信,需要通过路由器进行连接转发,路由器通过IP地址查找到对应的子网下的计算机,网络层IP协议需要几个辅助协议,几个协议的功能和名称如下: 1. 地址解析协议ARP,负责通过IP获取到网卡的MAC地址; 2. 逆地址解析协议RARP,负责通过MAC获取IP地址; 3. 网际控制报文协议ICMP,检测网络故障报文; 4. 网际组管理协议IGMP; ### 1.路由器 交换机组建局域网,研究MAC地址,路由器负责组建广域网,研究IP地址。路由器可以根据IP网段查找对应的子网,路由器通过路由表寻址,路由器与相邻连接的路由器连接时会告诉对方掩码信息。  ### 2.IP地址 目前普遍使用IPV4协议,其中规定,一个网络地址由32位二进制组成,32位平均分成四份,一个份8位二进制,一个部分所能表示0-255的数值,一个IP地址分为两个部分:网络号和主机号,网络号决定地址,主机号决定计算机。 ### 3.子网掩码 掩码和IP地址相似,32位二进制表示,子网掩码也分为网络部分和主机部分,网络部分由`1`构成,主机部分由`0`构成,子网掩码用于判断IP是否属于一个局域网(网关或者网段),如果存在于一个局域网内则直接通讯,如果不在用一个局域网则会经过路由器转发到对应的网段。根据子网掩码和IP地址可以通过`与`运算得到网关地址。 ```text IP1: 172.13.4.58 IP2: 172.13.4.90 子网掩码均为:255.255.255.0(24位掩码) IP1:172.13.4.58 10101100 00001101 00000100 00111010 子网掩码: 11111111 11111111 11111111 00000000 &运算结果: 10101100 00001101 00000100 00000000(172.13.4.0) IP1:172.13.4.90 10101100 00001101 00000100 01011010 子网掩码: 11111111 11111111 11111111 00000000 &运算结果: 10101100 00001101 00000100 00000000(172.13.4.0) ``` 计算结果得到相同的局域网(网段)内,可以直接进行通讯,不需要出网关经过路由器跳入其他子网。因为相互通讯的计算机属于同一局域网! ### 4.ARP协议 存在于数据链路层和网络层之间的协议,ARP基于广播网络,解决IP地址到MAC地址之间映射关系(局域网上主机或者路由器中IP地址和MAC地址映射关系),每个使用IP的主机都存在一个ARP高速缓存协议,如果主机A想要向局域网内主机b通信,如果主机A中未存在主机b的MAC地址,则主机a进行广播ARP报文给连接的所有主机,主机b发现a发送的ARP报文中带有自己的IP,所以回应a主机一个单播并携带MAC地址,a主机将存入b主机的MAC主机,a主机和b主机开始通讯! ### 5.ICMP协议 ICMP作为IP协议栈的一部分且报文封存在IP分组中,每一个系统均要实现ICMP,可能在传输过程中经过很多个物理子网才可以到达最终目的地,ICMP报文通常用于检测网络是否存在故障,PING一台主机就是发送ICMP报文。 ### 6.场景化分析 以太网的数据帧包含有**自己MAC地址**和**目标MAC地址**,局域网(子网)内,通过ARP协议广播获取到所在局域网的电脑的MAC地址,假如a电脑`172.13.4.58`需要发消息给b电脑`172.13.4.90`,首先需要判断目的IP是否属于同一子网,直接广播发送数据报文,报文情况如下: ```text 自己MAC:1a:2f:6c:ee:39:5d 目标MAC:ff:ff:ff:ff:ff:ff // 未知对方MAC 自己IP:172.13.4.58 目标IP:172.13.4.90 ``` 子网内交换机ARP报文获取得到目的IP对应的MAC地址,随之将消息发送给目的电脑,目标电脑响应一个报文,其中带有自己的MAC地址返回给a电脑,这时a电脑下次发送报文则带有b电脑的MAC地址。如果a,b电脑不在同一个网络则a电脑的报文发送给网关,网关交给路由器,转发到b电脑对应的子网。 ## 传输层 前三层已经基本实现计算机通讯,但是我们计算机通常开启很多程序,例如QQ,微信,LOL程序,软件都需要进行网络传输,我们无法进行区分数据是具体送达哪一个程序,这时引入传输层,传输层定义端口的概念,每一个程序占用一个端口。传输层有两大协议TCP和UDP,根据IP实现数据传输,通过端口确定发送给某个进程。  ### TCP协议 面向连接的可靠传输,提供全双工信道,面向字节流,TCP数据报文没有长度限制,但是为了保证网络效率,通常TCP报文不会太长,TCP报文头部主要是源端口和目标端口,TCP协议将客户端和服务端的socket套接字连接,连接过程需要进行三次握手,进行传输数据,断开连接需要进行四次挥手!  #### <u>三次握手</u>   #### <u>四次挥手</u>  ### UDP协议 无连接的不可靠传输,报文头部一共只有8个字节,总长度不超过65535字节,不会出现重传的现象,传输只管发送,不在乎目的地址是否收到。  ## 应用层 用户软件均工作与应用层,应用层规定数据的格式,TCP协议可以传递多种格式数据,HTTP协议规定网页数据格式,FTP协议规定文件传输格式,多种应用协议构成应用层,连接应用层和传输层的技术API叫套接字socket,应用通过调用socket接口实现对网络层和传输层的数据封装,为应用进程之间建立通信连接。  ## 总结 数据传输就是`发送方封装数据报文`和`接收方拆开数据报文`的过程   ## 场景化分析 我们在谷歌浏览器地址栏输入huasenjio.top域名时回车时,浏览器向服务器请求资源到展示页面,大体上分为如下几个步骤: 1. 浏览器判断/校验/补齐,判断用户输入地址是否合法规范,补齐未输入的http协议,参数编码等操作; 2. 通过DNS解析IP地址,浏览器优先查询缓存中是否存在huasenjio对应的IP地址,存在则将IP赋值给请求头中Remote Address参数,假设不存在,则查询HOST文件是否预设huasenjio.top对应的IP地址,如果仍未找到则请求电脑设置的DNS服务器,国外较为出名谷歌DNS服务器`8.8.8.8`,国内比较出名南京国风`114.114.114.114`,浏览器发起网络请求,首先在应用层对报文进行封装DNS协议信息,其次传输层UDP协议对报文进行封装相关信息,网络层IP协议封装相关信息,根据子网掩码和IP进行`与运算`发现不等于网关则说明不属于一个子网,直接发送至a网关路由器,数据链路层中路由器根据ARP协议进行地址解析,广播报文,收到播回应,形成路由表(IP对应MAC地址),进行将报文封装成为MAC帧,根据CSMA/CD报文对应转化为物理层的曼切斯特编码,比特流通过传输介质传输到下一个路由器处理,如此重复,最后进入`8.8.8.8`所在的子网的b网关路由器中,b网关路由器获取到报文,通过ARP协议根据IP获取到对应的MAC地址,之后进入对应网卡中,再进行逆序解析,由物理层网上至应用层一点一点拆开封装分数据,最后请求到对应的程序,获取得到huasenjio所对应的IP地址; 3. 浏览器获取到huasenjio对应的47.98.129.109(IP地址),首先将IP赋值给HTTP请求头中Remote Address参数,进入传输层TCP报文封装信息,进而进入网络层封装IP协议信息,根据子网掩码和47.98.129.109进行`与运算`发现不等于网关则说明不属于一个子网,直接发送至a网关路由器,进入数据链路层中路由器根据ARP协议进行地址解析,广播报文,收到单播回应,形成路由表(IP对应MAC地址),进行将报文封装成为MAC帧,根据CSMA/CD报文对应转化为物理层的曼切斯特编码,比特流通过传输介质传输到下一个路由器处理,如此重复,进入到47.98.129.109对应子网的c网关路由器,通过ARP协议根据IP获取到对应的MAC地址,之后进入对应网卡中,再进行逆序解析,由物理层网上至应用层一点一点拆开封装分数据。往返进行相互发送报文,俗称三次握手,建立TCP连接,开始传输数据; 4. 浏览器解析HTMLl源码标签,根据标签嵌套创建DOM树,并行加载静态资源文件,每一个HTML标签都是文档树中的一个节点,构成了由documentElement节点为根节点的DOM树; 5. 浏览器解析CSS代码,计算得出样式数据,非法的语法被直接忽略掉,解析CSS时按照优先级`浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < !important`的规则解析,构建CSSOM树; 6. js引擎进行注册事件操作,并将DOM和CSSOM树合成渲染树,最后通过渲染引擎渲染; 7. 如果渲染树中的节点被移除、位置改变、元素的显示隐藏等属性改变都会重新执行上面的流程,这一个行为称为回流;与重绘不同,重绘是渲染树上的某一个属性需要更新且仅影响外观、风格,不影响布局,例如:修改background-color就属于重绘只是重新绘制到屏幕中上,回流必定造成重绘;
# 操作系统 ### 1.概念 操作系统(Operation System OS) 管理和控制计算机硬件与软件资源的计算机程序,直接运行在物理设备上的系统软件,任何其他的引用程序均是在操作系统平台上运行,操作系统是计算机资源的管理者,主要协调如下几个方面: 1. 处理机管理; 2. 存储器管理; 3. 设备管理; 4. 文件管理; 同时操作系统也是用户和硬件设备交互的工具,用户可以通过系统开放的API接口进行与硬件接口交互,具有如下几个场景: 1. 命令行接口,调用CMD命令行查询当前网卡的MAC地址; 2. 程序接口,程序内调用print接口实现显示输出内容在屏幕; ### 2.功能 1. 操作系统管理硬件和软件及数据资源; 2. 控制应用程序运行; 3. 增加用户使用体验(可视化界面); 4. 作为其他应用软件的平台; ### 3.特征 1. 并发,两个或者多个任务在同一个时间间隔内发生; 2. 共享,系统中的资源可供内存中多个并发执行的进程共同使用; 3. 虚拟,物理设备变为若干个逻辑对应物; 4. 异步,躲到程序环境下,允许多个程序并发执行,程序不是同步执行而是延后执行是进程异步性的体现; # 基本概念 1. 互斥,进程之间访问临界资源时相互排次的现象; 2. 临界资源,一次仅允许一个进程使用,例如:打印机,IO输入输出设备; 3. 临界区,包含临界资源的代码段; 4. 并发,同一个时间段,多个进程同时从启动到运行完毕,并发的两种关系是同步和互斥; 5. 并行,单核处理器中,进程之间不允许同时执行,多个经常交替执行也有可能重叠执行,进程不需要同一个时刻发生; 6. 同步,进程之间存在依赖关系,一个进程结束结果是另一个进程的输入; 7. 异步,进程之间彼此独立,开启运行时刻不确定不统一,不需要等待其他进程完成或者开始; 8. 多线程,进程并发执行一段代码,实现进程之间切换执行,异步不同等于多线程,多线程是实现异步的一个手段; # 发展历程 - 手工阶段 - 单道批处理系统 - 多道批处理系统 - 分时操作系统 - 实时操作系统 - 网络操作系统和分布式操作系统 网络操作系统和分布式操作系统的不同,分布式操作系统中多太计算机相互协作完成同一个任务,网络操作系统中每台计算机都是相互独立。 # CPU工作状态 大多数计算机系统将CPU的执行状态分为目态和管态,管态又称特权态,CPU管态状态下可以执行系统的全部指令;目态称为用户态,目态状态下程序仅能执行非特权指令,不能直接使用系统资源,不可以改变CPU的工作状态并且仅能访问属于自己的存储空间。如下几个方法可以将目态转化成为管态: 1. 系统调用,用户态进程通过系统调用申请使用操作系统习惯的服务程序完成工作; 2. 异常,CPU执行用户态程序发送位置异常,此时触发处理异常处理的内核程序,完成目态到管态的切换,例如缺页异常; 3. I/O设备中断,用户态申请IO设备操作并完成后,CPU发出相应的中断信号,此时CPU暂停用户程序代码,将优先执行中断信号对应的处理程序; # 处理机管理 ### 1.进程控制 传统多道程序环境中,一个作业任务运行,必须创建一个或者多个进程并为之分配必要的资源,进程结束后立即销毁进程,及时收回进程占用的各类资源; ### 2.进程同步 通过多个进程协调运行 ### 3.进程关系 ##### <u>互斥关系</u> 进程(线程)在对临界资源进行访问则属于互斥关系 ##### <u>同步关系</u> 相互协作合作完成共同任务的进程,一个进程结束结果是另一个进程的输入。 ### 4.进程通讯 多道程序环境下为了加速应用进程的运行时间,系统中建立多个进程并且为一个进程建立若干个线程,进程(线程)相互合作完成一个共同的任务,进程线程中需要交互通讯。 ### 5.调度 后备队列中等待的作业或者进程,经过CPU调度后才可以执行。 # 存储器管理 ### 1.内存分配 采用静态和动态两种方式实现内存分配,记录内存使用情况,按照一定的算法进行对不在被需要的内存进行收回合并。 ### 2.内存保护 确保用户程序仅在自己内存空间运行彼此相互独立 ### 3.地址映射 编译后的程序地址分为逻辑地址和物理地址,每个程序不可能均能占据连续的物理内存,所以必须采用地址映射的方式将逻辑地址转换成内存空间的物理地址。 ### 4.动态重定位 需要访问的程序数据的逻辑地址动态转换成实际的物理地址,通过重定位寄存器,记录安装程序时所在内存中的首地址,程序执行时通过将相对地址和重定位寄存器中的地址计算,实现动态重定位。 ### 5.内存扩充 逻辑上扩充内存容量,实现原理是划取实际内存中的一部分内存,作为映射表,将本需要存在内存中的数据通过映射表存入实际硬盘中,通过映射表中的相对地址去寻找实际的物理地址。 # 设备管理 ### 1.缓冲管理 CPU速度非常快,但是IO读写相对缓慢,所以引入缓冲区机制能够有效缓解CPU高速性和IO低速性之间的矛盾。 ### 2.设备管理 设置设备控制表,控制器控制表等数据结构,目的获取指定设备当前状态,是否可用,忙绿,进而解决协调进程之间调用冲突。 ### 3.设备处理程序 处理CPU和设备管理器之间的通信,通过CPU向设备控制器发送IO命令,设备控制器调用IO设备进行指定的读写操作。 # 文件管理 ### 1.文件存储空间管理 文件系统对资源的存储空间实施统一的管理,每个资源文件分配必要的外存空间,提高外存的利用率和文件系统的执行速度。 ### 2.目录管理 文件资源的索引,建立目录以及文件对应的路径关系,方便用户查阅资源文件。 ### 3.文件读写管理保护 防止未经批准的用户存取文件,不正当行为修改使用文件的行为。 # 进程与线程 对于操作系统而言,一个任务就是一个进程(process),打开电脑记事本就是启动一个记事本进程,进程中的子任务成为线程,事本中的拼写检查是进程启动后建立出来的线程,线程辅助进程运行。 ### 1.进程 进程是程序执行的一个实例,进程是操作系统进行资源调度分配的单位,每一个进程拥有独立的地址空间,`进程=程序+数据+PCB`,其中PCB是**进程控制块**保存进程运行时的上下文,PCB是进程存在的唯一标志,进程之间无法直接访问,需要通过管道,文件,套接字等技术才可以实现通讯。 ### 2.进程状态 进程的基本五种状态改变  1. 创建状态,进程正在被创建,尚未转到就绪状态,创建过程中需要申请一个空白的PCB并写入一个控制和管理进程的信息,等待系统分配资源,进程进入就绪状态; 2. 就绪状态,进程准备执行的状态,已经获取CPU之外的所有资源,处于进程队列中准备被CPU调度; 3. 执行状态,进程正在被CPU调度执行,如果是单处理机环境下,同一个时刻仅可以执行同一个进程; 4. 阻塞状态,进程中由于调用IO操作导致等待某一个事件而暂停运行,此时进入阻塞状态,IO操作完成后进入就绪状态队列等待被CPU调度; 5. 结束状态,进程开始被销毁消失,正常执行完成和异常中断均可以导致进程被销毁,进程准备销毁时必须处于结束状态,随后操作系统进行处理资源释放和回收等操作; 值得注意的是后备队里存在于外存中,而就绪状态的进程存在于内存中,方便高效地被调度。 ### 3.进程同步与互斥 PV操作是实现PV操作是一种实现,PV操作与信号量的处理相关,P代表通过,V代表释放,PV操作操作信号量S,信号量S大于或者等于零,表示可以被进行调用的资源实体数,信号量S小于零时,表示正在等待资源的进程数,所以信号量S是代表资源数。 ##### <u>P操作</u> ```javascript S减1(占用一个资源数) if(S减1 >= 0){ // 程序继续执行 }else { // 该进程进入阻塞队列中等待被调度 } ``` ##### <u>V操作</u> ```javascript S加1(释放一个资源数) if(S加1 > 0){ // 代表存在可以使用的资源,该进程继续执行。 }else { // 从阻塞等待队列中调用一个进程使用,再继续执行该进行。 } ``` 值得注意的是PV操作对于每一个进行而言仅能执行一次且必须成对使用 ### 4.进程通信 根据交换信息量的多少和效率的高低,将进程通信分为低级通讯和高级通讯模式。 #### <u>低级通信</u> 仅能传递状态和整数(控制信息),具有传输信息量小,效率低,容量固定的特点,实现通讯难度较大。 #### <u>高级通信</u> 提高信号通信的效率,传递大量数据并减轻编程的复杂度,高级通信分为如下三种方式: 1. 共享内存模式,通信进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作,实现进程之间的信息交换。进程对共享空间进行写/读操作时,需要同步互斥工具( PV操作);  2. 消息传递模式,消息传递模式中,进程间的数据交换是以格式化的消息(Message)为单位,进程通过系统提供的**发送消息**和**接收消息**两个原语进行数据交换,可以分为直接和间接通信方式; ```javascript 类比: 甲给乙写信 直接:甲直接把信交给乙 间接:甲通过邮差把信交给乙 ``` 3. 共享文件模式,共享缓冲区(pipe管道)连接一个发送进程和一个接收进程,进程向管道内输入发送信息,信息以字节流的形式通过管道传入其他进程没实现进程通信;  ### 5.线程 CPU的执行单元,执行流的最小单元,由线程ID,程序计数器,寄存器和堆栈组成,线程是CPU调度的基本单元。线程拥有**线程控制块TCB**保存运行期间线程的数据,TCB是线程存在的唯一标志,线程有以下几个场景解释: 1. 线程属于进程中的一个实体,线程是CPU独立分配的基本单位; 2. 线程不拥有系统资源,但是可以共享进程中所拥有的其他资源; 3. 进程可以创建和撤销一个线程,同一个进程之间可以存在多个进程并发; ### 6.总结 - 进程是资源分配和调度的一个独立单元,线程是CPU调度的基本单元; - 一个进程中可以包括多个线程,并且线程共享整个进程的资源,一个进程至少包括一个线程; - 进程的创建调用fork或者vfork,但是线程的创建调用pthread_create,进程结束将拥有线程都销毁,线程的结束不会影响进程其他线程; - 线程是轻量级的进程,线程创建和销毁所需要的时间比进程小很多; - 线程中执行都要进行同步和互斥,线程共享同一进程的所有资源; - 线程和线程之间的资源不会被共享; # CPU调度 处理机调度线程有三种方法: 1. 高级调度(作业调度),调度处于外存后备队列的进程进入内存执行; 2. 中级调度(内存调度),展示不能运行的进程移入外存后备队列等待,进程状态改为就绪且驻外状态或者挂机状态; 3. 低级调度(进程调度),根据调度算法将内存中的就绪队列中的进程进行处理; ### 1.调度算法 调度算法是系统根据资源分配策略的规则,有的算法用于作业调度,有的适用于进程调度,同样有的算法都将适用。调度算法的考量`周转时间=等待时间+执行时间`,通过周转时间取决于适用算法。 #### <u>先来先服务(FCFS)</u> 先来先服务调度(First Come First Service),按照作业/进程进入系统的先后次序进行调度,先进入系统者先调度,适用于作业调度,进程调度,算法简单,对长进程有利,短进程吃亏(骂骂咧咧:我这么短也要等这么久!),不利于IO频繁的进程。 #### <u>短作业优先调度(SJF)</u> 短作业优先调度(Shortest Job First),算法每次从队列中挑选估计执行时间最短的进程交给CPU处理。适用于作业调度和进程调度,具有平均等待时间和平均周转时间最少的优点,对于长作业不利(骂骂咧咧:什么时候可以轮到我!)不能保证紧迫性作业/进程会被及时处理。 #### <u>优先级调度</u> 优先级调度,算法每次从后备队列/就绪队列中选择优先级最高的一个作业/进程,根据新的更高优先级进程能否抢占正在执行的进程,可以将调度分为: 1. 非抢占式优先级调度算法,当队列中进入优先级更高的进程时,不会中断正在执行的进程,而是等待CPU空闲后再调度优先级较高的进程; 2. 抢占式优先权调度算法,当队列中进入优先级更高的进程时,中断正在执行的进程,抢夺CPU资源; #### <u>高响应比优先调度(HRRN)</u> 高响应比优先调度(Highest Response Ratio Next),算法是对FCFS调度算法和SJF调度算法的一种综合平衡,CPU对队列中每个进程计算响应比,响应比高的作业优先执行`响应比=作业周转时间/作业执行时间=(等待时间+要求服务时间)/要求服务时间`,但是调度前要进行计算响应比,增加系统开销。 #### <u>时间片轮转调度</u> 算法将所有就绪进程按到达的先后次序排成一个队列,CPU按队列顺序传递时间片,进程享有同样的调度时间,时间片用完则计时器发出中断请求。 #### <u>多级反馈队列调度</u> 将进程分入不同的就绪队列并设置队列调度优先级,通过不同的队列优先级设置不同的时间片,同一队列中采用先来先服务的调度策略,多级反馈队列中后来的进程不一定最后完成! # 死锁 多个进程竞争临界资源而造成相互等待的僵局,如果没有外部调节,进程无法继续执行,产生死锁的本质是系统提供的资源个数小于进程需求个数,资源使用造成环路必定造成死锁。 ```javascript A同学有纸 B同学有笔 需要完成写字操作 A同学说:“你不给我笔,我就不能写字!” B同学说:“你不给我纸,我就不能写字!” 相互不肯让出造成僵局 ``` 产生死锁的必要条件: 1. 互斥条件,涉及的资源是非共享,一次仅能被一个进程使用; 2. 不剥夺条件,进程不会剥夺其他进程正在使用的非共享资源; 3. 占有等待,进程等待其他资源同时,同时占据已分配的资源; 4. 环路条件,存在一种进程循环链,链中每一个进程已获得的资源同时被链中下一个资源所请求; ### 1.预防死锁 设置进程的限制条件,破坏死锁的一些必要条件,使其不能产生死锁。 ### 2.避免死锁 动态分配资源的过程,使用银行家算法防止系统进入不安全状态,避免死锁的发生。新进程进入系统,必须说明各类型资源的的最大需求量,数量不能超过系统各个类的资源总数,如果有一个类型系统不能够提供则进程进行等待状态,简而言之,进程申请者可以在一定时间内无条件归还它申请的全部资源,系统才会把资源分配它。 ### 3.死锁的检测和解除 如果发生死锁,可以采取资源剥夺法,撤销法,进程回退法进行解除死锁。 # 主存管理 ### 1.分区存储管理 分区存储管理中程序的地址空间时一堆线性的连续地址,分区管理即对存储介质的地址进行分区块进行存储文件资源,分区储存管理中的分配策略有: 1. 首次适应法,程序按地址从小到大排序,分配给第一个符合条件的分区,具有保留高地址部分的大空闲区,有利于大型程序作业分配,但是低地址不断被划分,留下许多难以利用的碎片; 2. 循环首次适应法,从上次查找结束为止开始查找第一个合适的分区进行存储,具有空闲空间分区相对均匀和减少查找时间的优点,但是可能导致缺乏大空间区域; 3. 最佳适应算法,空间从小到大排序进程查找,分配第一个符合条件的分区,具有每次分配都是最合适的空间,但是可能导致难以利用的小空闲区域; 4. 最坏适应算法,地址从大到小排序,匹配第一个符合条件的分区; ### 2.页式存储管理 页式存储管理只用给出一个逻辑地址,页面大小固定,`逻辑地址÷页的大小=页号`且余数等于偏移量,所以地址空间用一个逻辑地址表示,地址映射过程中,若发现所要访问的页面不在内存则产生缺页中断,发送中断时操作系统内存没有空闲空间,通过算法决定淘汰页面,`缺页中断率=成功访问次数/总访问次数`,抖动(颠簸)指页面置换过程中,刚进入页面马上要被淘汰,刚淘汰跳出的页面又进行调入。页时存储管理的调度算法有以下几种: 1. 最佳置换算法,最长时间内不会被访问的页面调出; 2. 先进先出置换算法,最早进入内存的页面调出; 3. 最近最久未使用算法等算法,最近最长时间未访问的页面调出,算法为每个页面设置一个访问字段,记录页面距离上次被访问经历的时间,调出页面时选择时间最长的页面; 4. 最不经常使用置换算法,最近应用次数最少的页淘汰; 5. 时钟置换算法(CLOCK),算法是为每个页面设置一个使用位(1),页面被调用时使用为设置为0,其他页面调用使用位自加,需要页面替换时,淘汰使用位最高的页面; ### 3.段式存储管理 段式存储管理的用户地址是二维的按段进行划分,段式存储管理必须表明(段号和段内偏移量)。 ### 4.段页式存储管理 段式存储管理结构中分别划分成为大小相等的页,段页式存储管理也必须给出(段号和偏移量),虽然段大小不固定,但是段内页号和页内偏移可以根据偏移量算出来。 ### 5.虚拟存储器 指具有请求调入功能和置换功能并能从逻辑上对内存容量加以扩充的一种存储器系统,实现原理是**局部性原理**CPU访问存储器,无论存取指令还是存取数据,访问的存储单元都趋于聚集在一个较小的连续区域中,表现形式为如下两大特性: 1. 时间局部性,一个数据被访问,近期很有可能再次被访问; 2. 空间局限性,正在访问一个数据的地址,很有可能与将要访问数据的地址相邻; 通过虚拟存储器技术,系统好像为用户提供一个比实际内存大得多的存储器,称为虚拟存储器,具有一下几个特征: 1. 离散性,指内存分配时采用离散分配的方式; 2. 多次性,进程运行时不是一次性加载全部页面,可分为多次载入; 3. 对换性,作业运行时无序常驻内存,可以调入调出; 4. 虚拟性,逻辑上扩充了内存的容量; # 文件系统 文件分配对应于文件的物理结构规定为文件分配磁盘块,常用的磁盘空间分配方法有三种: 1. 连续分配,文件在磁盘中占有一块连续的外存地址; 2. 链接分配,采取离散分配的方式; 3. 索引分配,每个文件盘块号集中在一起构成索引块(表); ### 1.磁盘调度算法 #### <u>先来先服务算法</u> 根据进程请求访问磁盘的先后顺序进行调度 #### <u>最短寻找时间优先算法</u> 选择调用请求文件所处磁道距离当前磁道所在位置最近进程 #### <u>扫描算法(电梯算法SCAN)</u> 磁头当前移动方向上选择与当前磁头所在距离最近的请求作为下一次服务的对象 #### <u>循环扫描算法</u> SCAN算法的基础上规定磁头单向移动来提供服务,磁头到达磁盘端点返回时,直接快速返回起始端。 # IO设备管理 ### 1.程序IO方式 计算机从外部设备读取数据到存储器,每次读取一个字数据,CPU需要对外设状态进行循环检查,直到确定该字已经在I/O控制器的数据寄存器中。 ### 2.中断驱动I/O控制方式 允许I/O设备主动打断CPU的运行并请求服务,从而使CPU在对I/O控制器发送命令后可以做其他工作,由于数据中每个字在存储器与I/O控制器之间的传输都必须经过CPU发送指令,消耗CPU较多的时间。 ### 3.DMA I/O控制方式 I/O设备和内存之间开辟直接的数据交换通路,数据的基本单位是数据块,传送的数据是从设备直接送入内存。 ### 4.IO通道控制方法 只在一组数据的传输开始和结束时需要CPU干预,可以实现CPU,通道,I/O设备三者的并行操作,适用于设备与主机进行数据交换是一组数据块的情况。 ### 5.缓冲区 缓冲区(buffer)是内存空间的一部分,缓冲区存储空间用来缓冲输入或输出的数据,解除CPU和IO设备两者的制约关系,IO输入数据进入缓冲区时,CPU可以进行其他操作不必进行等待,引入缓冲区的目的有以下几点: - 缓和CPU与I/O设备间速度不匹配的矛盾; - 减少对CPU的中断频率,放宽对CPU中断响应时间的限制; - 解决基本数据单元大小(即 数据粒度)不匹配的问题; - 提高CPU和I/O设备之间的并行性; ### 6.缓存(cache)和buffer的区别 CPU的Cache即高速缓冲存储器,读写速度极快,几乎接近CPU,缓和内存和CPU之间速度不匹配的问题,buffer用于缓冲写入数据时CPU和IO设备速度不匹配。
# 策略模式在数据校验中的应用 设计模式就是在**面向对象**软件设计过程中针对**特定问题**的简洁而优雅的解决方案,包含`发布者订阅者模式`、`策略模式`、`单例模式`等模式,分别作用于不同场景,本文便涉及到策略模式在数据校验中的应用。 ## 名词规约 1. 策略模式:设计模式中的策略模式; 2. 开放封闭原则:简称OCP,是所有面向对象原则的核心; 3. 知识最少原则:迪米特法则,简称LOD,如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,其目的是降低类之间的耦合度,提高模块的相对独立性; ## 介绍和理解 关于策略模式,即定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。光从字面上理解,算是比较晦涩难懂,接下来我们将用小篇幅的例子介绍理解一下策略模式。 ### 问题场景 假设某公司的年终奖金由个人工资基数+个人评级组成,关系如下所示: 1. S级,个人工资基数*4; 2. A级,个人工资基数*3; 3. B级,个人工资基数*2; ### 实现代码 我们需要编写一段程序计算员工的年终奖金,于是有如下代码段: ```javascript // 方法封装 let calculateBonus = function(level, salary) { if ( level === 'S' ){ return salary * 4; } if ( level === 'A' ){ return salary * 3; } if ( level === 'B' ){ return salary * 2; } } // 使用实例 calculateBonus("B", 1000); // 2000 calculateBonus("S", 1000); // 4000 ``` 我们不难发现代码逻辑很简单,但是缺陷也很明显,具体由如下几点: - 当条件过多时,出现较多if-else,并且要覆盖所有的逻辑分支; - 封装的方法缺乏弹性,若增加一个等级,则需要深入方法内部改动,违反开发封闭原则; - 复用性较差; ### 重构代码 尝试使用策略模式,重构本场景中的方法,本着定义一定的算法,一个个封装起来,把**不变**的部分和**变动**的部分隔离开的思想。基于策略模式的程序至少有**策略类**和**环境类**组成,环境类不参与业务逻辑,而是将请求委托给某个策略类完成。Javascript不同于其他传统的面向对象语言,所以实现更为简便,具体重构代码如下所示: ```javascript // 策略组 let strategies = { "S": function(salary) { reture salary * 4; }, "A": function(salary) { reture salary * 3; }, "B": function(salary) { reture salary * 2; } }; // 环境组 let calculateBonus = function(level, salary) { reture strategies[level](salary); } ``` 由上面的例子,我们不难发现,环境组不直接执行业务相关,而是借助一定的算法,委托给策略类进行处理返回。如此一来可以轻易地将不变的部分抽离成库,方便复用拓展,如下图所示:  ## 表单校验中的运用 在日常开发中,我们会遇到注册、登陆、信息填写等功能,需要对大量的表单输入进行验证,而且重复性较大,所以我们照葫芦画瓢,尝试使用策略模式优化重构代码。 ### 核心代码 环境类定义使用规则,通过算法转发校验请求,策略类处理环境类转发的**校验规则**和**数据**,属于动态变化的部分,属于团队间不断拓展完善的部分,建议抽离成单独文件。 ```javascript // 策略类 let strategies = { isNotEmpty: function (value, errorMsg) { if (value === '') { return errorMsg || '非空' } }, minLength: function (value, length, errorMsg) { if (value && value.length < length) { return errorMsg || `长度不小于${length}` } }, maxLength: function (value, length, errorMsg) { if (value && value.length > length) { return errorMsg || `长度不大于${length}` } }, isMobile: function (value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg || '输入正确的手机号' } }, isChinese: function (value, errorMsg) { if (!/^[\u4E00-\u9FA5\uf900-\ufa2d0-9a-zA-Z]+$/.test(value)) { return errorMsg || '输入字母/汉字/数字' } }, isIDCard: function (value, errorMsg) { if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(value)) { return errorMsg || '输入正确的身份证' } }, } // 环境类 class Validator { constructor(name) { this.caches = []; } // 添加校验规则的方法 add(value, rules) { rules.map(rule => { // 处理策略标识,支持minLength:5规则写法,可以根据需要自定义使用规则 let strategyArr = rule.strategy.split(/:|:/) // 1.获取策略标识 let strategy = strategyArr.shift() // 2.压入校验的值 strategyArr.unshift(value) // 头插入校验值 // 3.压入错误提示文字 strategyArr.push(rule.errMsg) // 压入待校验的规则集合 this.caches.push(() => { return strategies[strategy].apply(this, strategyArr) }) }) } // 开始执行校验 start() { for (let validatorFun of this.caches) { let errText = validatorFun() if (errText) { return errText } } } } ``` ### 直接单独使用 默认已经导入`Validator`类,通过`new`关键词实例,存在`add`添加表单项和校验规则,执行`start`方法,若校验不通过,则返回提示信息,否则返回`undefined`,接下来查看演示代码段: ```javascript let validA = new Validator(); let validB = new Validator(); let aValue = ''; // 表单项a let bValue = '1234'; // 表单项b validA.add(aValue, [ { strategy: 'isNotEmpty', errMsg: '不能为空' }, { strategy: 'minLength:5', errMsg: '长度不能小于5' } ]) validB.add(bValue, [ { strategy: 'isNotEmpty', errMsg: '不能为空' }, { strategy: 'minLength:5', errMsg: '长度不能小于5' } ]) console.log(validA.start()) // 不能为空 console.log(validB.start()) // 长度不能小于5 ``` ### 一次校验多个表单项 回顾`直接单独使用`演示的实例,代码量不少,何来优雅之谈。不可以一次校验多个表单项,返回校验不通过的信息,所以我们可以基于之前的**策略类**和**环境类**,简单封装一个使用方法,主要代码如下: ```javascript // 策略类 ... // 环境类 ... // 使用辅助方法,返回最先校验不通过的提示 function checkParamsByRules(arr) { for (let item of arr) { let v = new Validator(); v.add(item.value, item.rules); let errText = v.start(); if (errText) { return errText; } } } // 使用示例 let errText = checkParamsByRules([ { value: '123567', rules: [ { strategy: 'isNotEmpty', errMsg: '请输入信息' }, { strategy: 'minLength:5', errMsg: '长度不能小于5' }, ] }, { value: '0123456', rules: [ { strategy: 'isIDCard', errMsg: '请输入身份证' }, ] }, ]) console.log(errText) // 请输入身份证 ``` ### HUI/ElementUI中使用 当然可以用于结合`el-form`组件使用,我们可以自定义使用规则,常规使用如下所示: ```vue // template模版的内容 <template> <el-form :rules="siteRules" :model="siteForm" > <el-form-item label="名称" prop="name"> <el-input v-model="siteForm.name" placeholder="请输入网站名"></el-input> </el-form-item> </el-form> </template> // script模版的内容 <script> export default { name: 'Demo' data(){ // 定义校验规则 const checkEmpty = (rule, value, cb) => { let vali = new Validator(); vali.add(value, [ { strategy: 'isNotEmpty', errMsg: '必填项', }, ]); let errText = vali.start(); if (errText) cb(new Error(errText)); // 存在报错则输出报错 cb(); // 正常放行 }; reture { // el-form校验规则集 siteRules: { name: [{ validator: checkEmpty, trigger: 'blur' }], }, } } } </script> ``` ## 策略模式的利弊 ### 优点 1. 策略模式利于组合、委托、多态等技术思想,可以有效避免过多重复的条件选择语句; 2. 完美地符合开放封闭原则,将算法封装在独立的策略类中,便于理解、替换、拓展; 3. 自定义环境类中的定义属于团队自己的使用规则; ### 缺点 1. 违反知识最少原则,我们必须知道策略类内的各种策略算法的含义,才能上手使用; 2. 一定程度上策略类会堆积很多算法;
# Git ## 配置SSH ```shell # 生成非对称密钥 ssh-keygen -t rsa # 查看公钥设置于 Github cat ~/.ssh/id_rsa.pub # 查看私钥设置于 Jenkins cat ~/.ssh/id_rsa # 连接仓库 git ls-remote -h git@github.com:huasenjio/huasen-compose.git HEAD ``` ## 解决.gitignore规则不生效 ```shell # 删除缓存 git rm -r --cached . # 添加所有变动至缓存区 git add . # 提交至本地仓库 git commit -m 'update .gitignore' # 推送至远程仓库 git push -u origin <分支名> ``` ## 删除所有提交记录 ```shell # 新建并切换到xxx新分支 git checkout --orphan xxx # 添加所有文件 git add -A # 提交所有记录 git commit -am "." # 强制删除原分支名 git branch -D <原分支名> # 重命名当前分支为原分支 git branch -m <原分支名> # 强制推送到远程仓库 git push -f origin 旧分支 ``` # CentOS ## tar命令解压缩文件夹 ```shell # 压缩 tar -czvf huasen-mongo.tar.gz huasen-mongo # 解压 tar -xvf huasen-mongo.tar.gz ``` ## 定时分割Nginx日志任务 根目录创建data文件夹,`mkdir /data`,进入文件夹`cd /data`,创建并编辑定时脚本`vim runlog.sh`,写入如下内容: ```sh #!/bin/bash # 建立日志对应的变量 LOGPATH=/usr/local/nginx/logs/access.log # 建立日志存放路径变量 BASEPATH=/data # 日志文件名称 bak=$BASEPATH/$(date -d yesterday +%Y%m%d%H%M).access.log echo $bak # 将新日志移动到nginx的logs目录下 mv $LOGPATH $bak # 修改文件信息(不存在则生成) touch $LOGPATH # 切割日志指令: kill -USR1 `cat /usr/local/nginx/logs/nginx.pid` # 执行一次,查看效果 sh runlog.sh # 进入日常任务管理 crontab -e # 建立任务,每天6点18分执行脚本 */18 6 * * * sh /data/runlog.sh ``` # 宝塔面板 ## 命令操作Docker ### 创建docker-compose软连接 ```shell ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose ln -sf /usr/libexec/docker/cli-plugins/docker-compose /usr/bin/docker-compose ``` ### 卸载 > 通过命令行形式卸载通过宝塔默认安装的docker程序 ```shell yum remove docker-ce docker-ce-cli containerd.io -y rm -rf /usr/local/bin/docker-compose rm -rf /usr/bin/docker-compose ``` # Jenkins ### 批量删除构建日志 进入“系统管理”,找到“脚本命令行”,输入代码,点击运行。 ```python # 流水线的名字 def jobName = "huasen-compose" # 保留的最小编号,意味着小于等于该编号的构建都将被删除。 def maxNumber = 258 Jenkins.instance.getItemByFullName(jobName).builds.findAll { it.number <= maxNumber }.each { it.delete() } ```
# 寄语 表情比文字传达的情感更为直接,每次写文章无法快速准确的找到适合表情,所以本文记录常见的 emoji 表情! # 表情 😀 😃 😄 😁 😆 😅 🤣 😂 🙂 🙃 😉 😊 😇 😍 🤩 😘 😗 😚 😋 😛 😝 😜 🤪 🤔 🤨 🧐 🤓 😎 🥳 🤯 😱 😨 😰 😥 😢 😭 😓 🤗 🤭 🤫 🤥 😶 😐 😑 😬 🙄 😔 😞 😒 😌 😔 🤤 😴 😷 🤒 🤕 🥺 🤢 🤮 🥴 🥵 🥶 😇 🥰 🥳 🥴 🥺 🤪 🤩 🤯 # 食品和饮料 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🥭 🍎 🍏 🍐 🍑 🍒 🍓 🥝 🍅 🥑 🍆 🥔 🥕 🌽 🌶️ 🥒 🥬 🥦 🧄 🧅 🍄 🥜 🌰 🍞 🥐 🥖 🥨 🥞 🧇 🍳 🥓 🥩 🍗 🍖 🌭 🍔 🍟 🍕 🥪 🥙 🧆 🌮 🌯 🥗 🥘 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🦪 🍤 🍙 🍚 🍘 🍥 🥠 🥮 🍢 🍡 🍧 🍨 🍦 🥧 🧁 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🧂 🥤 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🧉 🍾 🥄 🍴 🍽️ # 动物和自然 🐶 🐱 🐭 🐹 🐰 🦊 🦝 🐻 🐨 🐼 🦘 🦡 🐾 🦃 🐔 🐓 🐣 🐤 🐥 🦆 🦢 🦜 🦚 🦩 🐸 🐊 🐢 🦎 🐍 🐲 🐉 🦕 🦖 🦔 🌵 🎄 🌲 🌳 🌴 🌱 🌿 ☘️ 🍀 🎍 🎋 🍃 🍂 🍁 🍄 🐚 🌾 🌺 🌻 🌹 🥀 🌷 🌼 🌸 💐 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🥭 🍎 🍏 🍐 🍑 🍒 🍓 🥝 # 旅游和地点 🚗 🚕 🚙 🚌 🚎 🏎️ 🚓 🚑 🚒 🚐 🛻 🚚 🚛 🚜 🛵 🛴 🚲 🛹 🏍️ 🛺 🚂 🚆 🚇 🚊 🚉 🚁 🛩️ 🛫 🛬 🛥️ 🚤 🛳️ ⛵ 🚢 ⚓ 🚧 ⛽ 🚏 🚦 🚥 🗺️ 🗽 🗼 🏰 🏯 🏟️ 🎡 🎢 🎠 ⛲ ⛱️ 🏖️ 🏝️ 🏜️ 🌋 🏕️ ⛺ 🏠 🏡 🏘️ 🏚️ 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛️ ⛪ 🕌 🕍 🛕 🕋 # 物体和符号 🚽 🚰 🚿 🛁 🛀 🧼 🧽 🧴 🛎️ 🔑 🗝️ 🚪 🛋️ 🛏️ 🛌 🖼️ 🛍️ 🛒 🎁 🎈 🎏 🎀 🎊 🎉 🎎 🎐 🎓 🧑🎓 🎒 🧳 🌂 ☂️ 🧵 🧶 👓 🕶️ 🥽 🥼 🦺 🥾 🥿 👔 👕 👖 🧣 🧤 🧥 👗 👘 👙 👚 👛 👜 👝 🎒 🩰 🎩 🎓 🧢 👑 💍 💄 💋 👄 👅 👂 👃 👣 👁️ 👀 🫀 💔 ❤️ 🧡 💛 💚 💙 💜 🤎 🖤 🤍 💯 💢 💥 💫 💦 💨 🕳️ 💣 💬 🗯️ 💭 💤 👋 🤚 🖐️ ✋ 🖖 👌 🤏 ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 👐 🤲 🙏 ✍️ 💅 🤳 💪 🦵 🦶 🍆 🍑 🍌 🍒 🍓 🍔 🍟 🍕 🍖 🥓 🥩 🍗 🍤 🍣 🍱 🥟 🍜 🍝 🍛 🍲 🥘 🍚 🍘 🍙 🍚 🍛 🍜 🍝 🍠 🍤 🍢 🍡 🍦 🍧 🍨 🍩 🍪 🎂 🍰 🧁 🥧 🍫 🍬 🍭 🍮 🍯 🥛 🍼 🥤 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🧉 🍾 🍶 🍵 🍼 🥛 🥤 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🧉 🍾 🍶 🍵 🥄 🍴 🍽️
## 🍭 介绍 本篇文章中,我们约定前端和后端沟通的桥梁称为网络模块。日常学习工作中,经常因为网络模块混乱难拓展而头疼,时常因为后端接口无法提供而停滞不前。为解决开发时的痛点问题,经过学习探究,封装了一个轻量化、自由拓展、低耦合的网络模块。无需经过后端服务返回字符串、数字、布尔值、数组、图片等随机数据,支持取消请求、配置覆盖、请求状态提示、文件上传、下载等功能,极大地提升前端的开发体验。 ## 💊 解决痛点问题 1. 后端资源不足,无法提供接口,需要写又长又臭的假数据测试; 2. 开发用于演示的静态网页,暂时不需要后端接口; 3. 后端接口不稳定时常崩溃无数据,影响前端开发体验; 4. 产品上线后,需要维护定制,普通的网络模块隔离性、拓展性、自由度不高。害怕修改后影响原先功能,导致复制一份同样文件,增加代码冗余,写法各异,难以形成团队规范; 5. 某些接口展示请求状态提示信息,例如:错误提示,成功提示; 6. 开发环境下配置不好代理服务器; 7. 特定场景下需要取消向后端请求; ## 💻 测试环境 1. Vue.js 2.6.11; 2. Mockjs2; 3. Axios 0.21.1; ## 📙 规约原则 1. 配置生效原则遵循,后配置权重最高; 2. 数据模拟,根据接口约定的数据格式规则,返回随机生成数据,请求不真实发出,浏览器无法查询到接口调用,无需经过后端服务; ## 🧩 目录结构 ```javascript network ├─api.js // 请求接口 ├─config.js // 配置 ├─http.js // Axios实例 ├─intercept.js // 拦截器 ├─request.js // 请求方法实例 └utils.js // 辅助方法 ``` ## 🚀 工作流程 简要的功能流程描述图  ## 📌 请求方法说明 基于axios实例,封装了get、post、upload等请求方法,根据url自动注册Mock拦截,支持在请求方法中设置默认的配置。灵感来源于切面编程的思想,提高接口的灵活度,具体逻辑处理移步查阅`request.js`文件。 ```javascript // request.js /** * get请求方法 * @param {String} url 接口地址 必填 * @param {Object} mockData 随机数据(硬编码对象|Mock数据格式|函数)选填 * @param {Boolean} FWS 数据模拟功能开关 选填 */ get(url, mockData, FWS) /** * post请求方法 * @param {String} url 接口地址 必填 * @param {Object} mockData 随机数据(硬编码对象|Mock数据格式|函数)选填 * @param {Boolean} FWS 数据模拟功能开关 选填 */ post(url, mockData, FWS) /** * 上传请求方法 * @param {String} url 接口地址 必填 */ upload(url) /** * 通过URL链接下载文件 * @param {String} url 文件地址 * @param {String} filename 文件名称 */ downloadFileByUrl(url, filename) /** * 通过二进制流下载文件 * @param {Blob} content 二进制流 * @param {String} filename 文件名称(不带后缀) * @param {String} MIME MINE类型 * @param {Function} callback 回调函数 */ downloadFileByBlob(url, fileName, MIME, callback) ``` ## 💡 请求接口约定示例 ### 无数据 无数据模拟功能的正常接口,只有一个参数。 ```javascript // api.js const login = get('/user/login') const register = post('/user/register') ``` ### 有数据无逻辑 有数据模拟功能,但是不能根据请求参数进行逻辑处理后返回。当模拟功能开启时,调用请求接口并不会真实的发出,而是通过第二个参数控制返回随机数据。如果第二个参数为**普通的对象**,则原样返回,若第二个参数为**mock数据格式**,则由数据格式生成数据后返回。数据模拟功能在**开发环境**下默认开启,**生产环境**下默认关闭,支持通过传入第三个**布尔值**参数,精准控制接口数据模拟功能的开关状态。 ```javascript // api.js const mockTest = get("/mock/test", { // 生成含5-10个元素的数组 "persons|5-10": [ { "name|1-6": "花", // 字符串 "status|1": true, // 布尔值 "type|1": ["在线", "离线"], // 数组中其一 "headIcon|": Mock.Random.image("50x50", '#ec7168', "花森"), // 特定尺寸图片 'key|+1': 1, // 递增整型 "longitude|119.8": 1.36, // 经度 "latitude|26.8": 1.03, // 纬度 } ] }, true) ``` ### 有数据有逻辑 既有数据模拟功能,又能根据请求参数进行逻辑处理后返回。当模拟功能开启时,调用请求接口并不会真实的发出,而是通过第二个参数传入**函数**经过逻辑控制后返回数据,其中函数的形参包含有请求携带的参数。该功能同样在**开发环境**下默认开启,**生产环境**下默认关闭,支持通过传入第三个**布尔值**参数,精准控制接口数据模拟功能的开关状态。 ```javascript // api.js const mockTest = get("/mock/test", function (param) { console.log("请求参数", param) // 逻辑判断代码块 return { name: 'huasen', age: 18 } }, true) ``` ## 🤖 请求接口调用说明 灵感来源于切面编程的思想,我们在多个阶段可以设置请求配置,它们逐层覆盖,提高接口的灵活度。具体优先级规则如下所示: 1. 生成Axios示例时候的配置优先级最低; 2. 封装get、post请求方法时候的配置优先级较高; 3. 请求接口调用时候配置的优先级最高; ```javascript // 约定的请求接口 const login = get('user/login') /** * 请求接口调用说明 * @param {Object} param 请求携带的参数 * @param {Object} config 请求配置 */ login(param, config) // 调用示例 login({}, {baseURL: '/demo-dev'}) ``` ## 🐛 快速入门 入门整合教程选取Vue-cli搭建的工程,并且默认已经安装`Axios`、`Mockjs2`。若项目内未有相关的依赖,可以执行`npm install axios mockjs2 --save`进行安装。 ### 模块引入 引入工作十分简单,复制`network`到前端工程的`src`目录下,可以任何位置引用`api.js`文件,调用请求接口发起请求。 ```javascript src ├─... // 其他文件 └network // 网络模块目录 ``` ### 约定请求接口 通常在`api.js`文件中约定请求接口,当然不同团队可以建立专属的请求接口文件,最后汇总到`api.js`统一导出。 ```javascript // 引入相关资源 import Mock from 'mockjs' import { get, post } from './request.js' // 约定的请求接口 const login = get('/user/login') const register = post('/user/register') const mockTest = get("/mock/test", { "persons|5-10": [ { "name|1-3": "花", "status|1": true, "type|1": ["在线", "离线"], "headIcon|": Mock.Random.image("50x50", '#ec7168', "花森"), 'key|+1': 1, "longitude|119.8": 1.36, "latitude|26.8": 1.03, } ] }) // 统一导出 export default { login, register, mockTest, } ``` ### 绑定 Vue 原型 进入main.js入口文件,把请求api绑定到Vue的原型中,免去后期频繁引入,`.vue`单文件组件中直接可以通过`this`进行访问,快速调用请求接口,进行发起请求。 ```javascript import Vue from 'vue' import api from '@/network/api.js' Vue.prototype.API = api; ``` ### 发起请求 #### 单文件组件 由于`.vue`结尾的单文件组件直接继承于Vue原型,所以直接通过`this.API`可以访问到约定的请求接口,快速调用发起请求。 ```javascript // Home.vue文件中 ... mounted() { // mock功能请求 this.API.mockTest().then(res => { console.log('mock随机数据', res.data); }); // 正常请求 this.API.login(); }, ``` #### js文件 普通javascript的文件需要引入Vue,通过`Vue.prototype.API`可以访问到约定的请求接口。 ```javascript import Vue from 'vue' Vue.prototype.API.login() ``` ### 取消请求 某些场景下,需要通过取消已经发送但未响应的请求来减少性能的消耗。网络模块默认封装的get、post请求方法预留取消令牌的配置,可以调用请求接口时在**请求配置**中传入`_cancelable`字段控制请求是否可被取消。经过调用时配置后,我们就可以从`config.js`文件中导出`source`对象,调用`cancel`方法实现取消已经发出但为响应的请求。 ```javascript import Config from '@/network/config.js' // 调用请求接口 this.API.login( { name: '花森', age: 18, }, { _cancelable: true, }, ); // 取消请求 Config.source.cancel('手动取消请求'); ``` ## 🥳 联系我们 由于涉及知识面较广,文字讲解篇幅过大,可以关注我的 Bilibili 账号,后续更新视频教程,感兴趣的小伙伴可以添加站长微信 ,进入前端技术交流群! 🐧企鹅QQ:184820911 😸微信 :huasencc(站长邀请进入前端交流群) 📮邮箱 :[184820911@qq.com](https://gitee.com/HuaSenJioJio/huasenjio-compose/blob/master/184820911@qq.com) 📺哔哩哔哩:[花森酱 JioJio](https://gitee.com/link?target=https%3A%2F%2Fspace.bilibili.com%2F241546158) 🦑代码示例地址:https://github.com/huasenjio/huasen-portal/tree/main/src/network
# Docker 手册 一个开源的应用容器引擎 ## 文档资料 1. 官方文档:https://docs.docker.com/get-started/overview/; 1. 菜鸟教程:https://www.runoob.com/docker/docker-tutorial.html; ## 核心架构图    ## 概念解释 ### 仓库 类似 github,存储可以上传拉取镜像,资源在国外,因此需要配置镜像源。 ### 镜像 类似于软件模版,可以通过镜像创建容器,例如:redis 镜像 --> run --> redis01 容器,一个镜像可以创建多个容器,容器之间相互隔离,互不影响。 ### 容器 通过镜像创建,并且可以独立运行一个或一组应用,可以进行启动、重启、删除、停止等操作。 ## Docker 命令 ### 查看容器 ```sh # 查看正在运行的容器 docker ps # 查看全部容器 docker ps -a ``` ### 删除容器 ```sh # 强制删除容器 docker rm -f <容器ID> # 删除已停止容器 docker rm <容器ID> ``` ### 启动容器 ```sh # 启动容器 docker start <容器ID> # 重启 docker restart <容器ID> ``` ### 停止容器 ```sh # 停止容器 docker stop <容器ID> # 杀死容器 docker kill <容器ID> ``` ### 显示容器信息 ```sh docker inspact <容器ID> ``` ### 进入容器 ```sh docker exec -it <容器名> bash ``` ## 工具安装 ```bash apt-get update apt-get install vim ``` ## Docker-compose 命令 ### 启动服务 ```sh docker-compose up -d ``` ### 停止服务 ```sh docker-compose stop ``` ### 删除服务 ```sh docker-compose down ``` ### 服务日志 ```sh docker-compose logs <容器名> ``` ### 进入服务 ```sh docker-compose exec <容器名> sh ``` ### 强制删除容器 ```sh docker-compose rm -f <容器名> ``` ### 重启某个容器 ```sh docker-compose restart <容器名> ``` ### 重新构建容器 ```sh docker-compose build --no-cache ``` ### 仅未启动的容器 ```sh docker-compose up -d --no-recreate --remove-orphans ``` ### 启动并清理孤儿容器 ```sh docker-compose up -d --remove-orphans ```
# 公告栏 > 如果我们的产品让您愉悦,立即 `Ctrl + D` 收藏起来吧! 🧨🧨🧨喜讯!我们的站点服务器已完成升级,**性能大幅提升**!为了节省成本,我们之前使用**1核2G2M**腾讯云(大品牌),老是被吐槽**加载太慢**。如今升级为**4核8G100M**破碎工坊云(小品牌),显著改善使用体验,并且价格相对便宜,缺点就是**不够稳定**。开源项目,为了省钱,没有办法,**建议大家加入社群实时了解最新动态**,避免因服务器问题而**失联**,感谢您的理解与支持!🙌 ## 💡 快速访问 1. [网站使用说明书](http://n.huasenjio.top/#/read/63666be7964404694b299522) 2. [源代码仓库](https://github.com/huasenjio/huasenjio-compose) ## 🔨 推出计划 ### 最近更新 1. 数据非对称加密传输,提升安全性; 2. 支持通过ohttps配置SSL,申请证书及自动续签; 3. 前/后台支持选择常见邮箱进行登录; 4. 系统无作者权限用户时,后台管理登录界面支持初始化管理员; 5. 前台搜索引擎支持动态配置; 6. 新增查看在线用户功能,支持强制用户下线; 7. 优化界面功能,解决已知问题; ### 即将推出 1. 提交链接功能; 2. 优化网站本地数据备份恢复功能;✅ 3. 对接ChatGPT; 4. 后台管理系统,支持Excel文件导入网链;✅ 5. 网站链接图标自动爬取补全;✅ ### 站点大事件 #### 2024/11 1. 网站硬件升级,告别1核2G2M低配服务器,网站访问速度大幅提升,缺点是小厂商服务器不够稳定,建议大家加入社群实时了解最新动态,避免失联; 2. 网站支持使用第三方MongoDB、Redis服务; #### 2024/04 1. **huasen.cc**域名已弃用,请认准官方域名:**huasenjio.top**; 2. 和风天气插件已停用,重新对接魅族天气信息; ### 更早以前 网站已持续更新**3年**有余,详细功能实现及界面快照,请移步**github仓库**查看。 ## ❌ 免责声明 1. 本站严格遵守中华人民共和国相关法律,收录数据均来源于免费论坛社区,不存在破解、串改、售卖等违法行为,违反者造成损失及法律责任与本人无关; 2. 全站代码开源免费,仅供大家学习参考,未经授权,请勿用于商业用途; 3. 如有疏忽大意,不幸侵犯到您的合法权益,请与我们联系,花森团队一定会积极配合,5个工作日内完成整改; ## 🥳 联系我们 企鹅 🐧:184820911 邮箱 📮:184820911@qq.com WeChat:huasencc ### 媒体矩阵 哔哩哔哩:[花森酱的空间](https://space.bilibili.com/241546158) ### 打赏|捐助|赞助 > 行好事要留名,烦请联系我们上捐赠榜,感谢您的爱心和认可! 如果网站帮助到大家,可以为我的 bilibili 视频,送上免费的点赞和硬币。另外,阔绰的小伙伴可以请我喝一杯蜜雪冰城🥤~ 
大家在平时的开发过程中,肯定有过二次封装组件的经历。一个优雅的组件封装,不仅可以提升可复用性,还能保持代码的可维护性和一致性。正因为它比较重要,所以我向大家分享在工作中实践得出的方法和思路。 ## 定义 二次封装是指在**原有的组件基础上**,通过**拓展额外的功能、优化参数、统一风格**等方式,对组件进行**再包装**,以提升其**适用性**和**易用性**。 ## 代码环境 我通过**遇到问题、讨论问题、提出结论**的方式向大家分享,便于大家思考总结。编程语言选择Vue.js 2.x和HUI,以下是简要的代码结构。 ```vue <!-- 二次封装组件 --> <template> <el-input></el-input> </template> <script> export default { // 定义接收父组件传递参数 props: { attr1: { type: String, default: '' } ... } } </script> ``` ## 传递属性 我们封装组件时,需要在原有的组件基础上拓展,最好不要破坏使用方式及参数传递。可是el-input,支持传递type、placeholder、disabled等参数,总不能挨个定义props属性接收,挨个传递到el-input吧?这样的代码它不优雅,不可取。所以$attrs就是我们的大救星,它是Vue组件实例的属性,通过this.$attrs可以访问到,我们只需要通过v-bind整个绑定透传到el-input。这样我们使用组件时,只需要传递我们拓展的参数即可,代码如下所示: ```vue <!-- 二次封装组件 --> <template> <el-input v-bind=“$attrs”></el-input> </template> ``` 这时候又出现一个问题,我想拓展el-input原生的属性,我该怎么办?很简单,我们只需要在props定义对应的参数,这样该参数就不会往下透传,代码如下所示: ```vue <!-- 二次封装组件 --> <template> <el-input v-bind=“$attrs” :type="type"></el-input> </template> <script> export default { props: { // 只要定义type参数,那么父组件传递的type将不会透传,需要我们手动传递。 type: { type: String, default: 'new type' } } } </script> ``` ## 传递插槽 el-input支持传入插槽,例如:prefix、suffix,我们二次封装的时候,也是不能写死的,所以借助Vue组件实例的$slots,能够拿到父组件传递的插槽,所以我们遍历它,定义插槽,然后传递作用域,代码如下所示: ```vue <!-- 二次封装组件 --> <template> <el-input v-bind=“$attrs”> <template v-for="(value, name) in $slots" #[name]="slotData"> <slot :name="name" v-bind="slotData || {}"></slot> </template> </el-input> </template> ``` ## 传递事件 el-input支持监听事件,例如:select、change,我们二次封装的组件,也要保持监听回调方式一致,那么有请$listeners,我们只需要透传即可,这样我们封装的组件也能愉快的使用@change监听回调,非常方便,代码如下所示: ```vue <!-- 二次封装组件 --> <template> <el-input v-bind=“$attrs” v-on="$listeners"> <template v-for="(value, name) in $slots" :key="name" #[name]="slotData"> <slot :name="name" v-bind="slotData || {}"></slot> </template> </el-input> </template> ``` ## ref实例方法 因为我们二次封装el-input,所以通过ref拿到组件实例,调用暴露的方法就非常困难,很难写出适用性高的代码。在Vue.js领域中,没有太优雅的解决方案,我认为有两个解决方案:(1)约定使用,告知大家通过rootRef属性,获取原生组件的ref;(2)方法合并,把原生组件的方法合并到二次封装组件的实例上。这里我给大家两个示例,大家按照喜好定夺,代码如下所示: ```vue <!-- 二次封装组件 --> <template> <el-input ref='root' v-bind=“$attrs” v-on="$listeners"> <template v-for="(value, name) in $slots" #[name]="slotData"> <slot :name="name" v-bind="slotData || {}"></slot> </template> </el-input> </template> <script> export default { mounted() { // 1.规约使用方案 // this.rootRef = this.$refs.root // 2.合并方法(推荐) const entries = Object.entries(this.$refs.root) for(const [key, value] of entries) { this[key] = value } } } </script> ``` ## 结尾 经过上面的分享,相信大家能够有效地封装好Vue.js组件,使其更好地服务于项目需求。 希望这篇经验案例对大家有所帮助!