4年没找工作了,最近找工作发现自己8年工作经验也很难有面试机会(因为本科学历非统招还是跨专业转行),但这不是重点哈!面试时发现郑州一多半公司都在招聘Vue3,uni-app,Typescript,小程序甚至还有NodeJS,mysql,UI设计等全栈技术要求的,但是薪资只是中高级前端;而另一部分大厂招聘要求是React、Vue,antd技术栈(这和自己工作经历匹配度高,在一线城市工作时也是走的这条技术路线);大厂强调专精,小公司强调全栈开发,一个人可以搞定UI设计、前端开发、原生开发、工众号/小程序开发甚至后端开发…,不能全怪市场毕竟经济环境不好,但是给的薪资不及大厂要求,干的活多杂而且可能无视劳动法,这就是个永恒的矛盾,谁不希望多会点、多干点,但是工资给不到,时间也压榨。这样就是前端想转全栈但是薪资不及预期,也没有业余时间去提升技术和改善生活质量;而大厂薪资可能合适但是技术专精但是无法接触更多全栈技术;这个永恒的悖论实在困扰有一定工作经验的前端开发😕
一、什么是 HTML?HTML,全称是 HyperText Markup Language,即超文本标记语言,它不是编程语言,而是一种用来告知浏览器如何组织页面的标记语言,用来描述网页的表现,展示效果或功能及行为“超文本”(hybertext) 是指连接单个网站或多个网站网页的链接HTML 使用“标记”(markup) 来注明文本、图片和其它内容HTML 通过“标签”(tag)标记元素,标签由在<和>中包裹的元素名组成HTML 标签里的元素名不区分大小写。可以用大写、小写或混合形式书写二、常用的浏览器引擎是什么 ?浏览器是一种从 Web 获取和显示页面的程序,让用户通超链接访问更多页面排版引擎(Layout Engine),也称为浏览器引擎(Browser Engine)、页面渲染引擎(Rendering Engine)或样板引擎,它是软件组件,负责获取标记式内容(如 HTML、XML 及图像文件等)和整理信息(如 CSS 及 XSL 等),并将排版后内容输出至显示器或打印机常见的浏览器排版引擎分别是:Mozilla Firefox 使用 Gecko 引擎Apple Safari 和 早期 Google Chrome 使用 KDE 引擎,后发展成为 WebKit 引擎Internet Explorer 使用 Trident 引擎Microsoft Edge 早期使用 EdgeHTML 引擎Opera 早期使用 Presto 引擎目前,Google Chrome 及基于 Chromium 浏览器,如 Microsoft Edge,Opera 使用基于 WebKit 分支自行构建的 Blink 引擎三、请列举常用的 HTML 实体字符 ?字符 < > " ' 和 & 等本身是 HTML 语法自身的特殊字符表示其本身需要使用字符引用,即表示字符的特殊编码,每个字符引用以 & 开始,分号 ; 结束四、HTML 注释如何写 ?HTML 注释使用特殊标记<!--和-->包裹HTML 注释不会被渲染会被传输解析时,早期 IE 浏览器使用 HTML 注释区分版本通常使用 UglifyJS 和 Terser 或正则匹配的方式,在生产环境删除注释HTML 注释用来描述代码是如何工作的不同部分代码做了什么五、什么是 HTML 语义化,有什么好处,一定要 HTML 语义化吗 ?语义是语言的含义,语义化是前端开发的专用术语,语义类标签是对内容的补充,表达标题摘要,文章结构、强调重点、丰富含义,避免歧义HTML 语义化的好处包括增强可读性,便于开发和维护增强可访问性,便于屏幕阅读器定位和朗读增强结构清晰度,利于 SEOHTML 语义化不是一定要执行的标准利用无语义标签,如<div>和<span>可以满足几乎所有开发需求可读性,可访问性和 SEO,使用语义化标签不是必须的部分语义化标签存在兼容性问题,如 <button> 的默认 type不总为 submit 等滥用列表标签,会增加不必要的嵌套,增加额外的 CSS Reset 的样式HTML 语义化以外,良好的命名,简明扁平的结构,良好的无障碍设计,清晰的导航和分区,一定程度上,也能弥补语义的欠缺,提升代码的机器阅读体验,降低抓取难度,提高索引权重在明确知晓语义化标签的含义和组合搭配后,探索其使用的最佳实践和场景,而不是盲目地滥用、错用语义化标签,才能让 HTML 语义化标签体现更好的价值六、连续空格如何渲染,意义是什么 ?为了代码的可读性,开发者通常会在 HTML 元素嵌套中使用空白空白可以使用空格或 TAB 缩进实现HTML 解释器会将连续出现的空白字符减少为一个单独的空格符如果一定要使用连续空格,可以使用全角空格或者实体字符 七、如何声明文档类型 ?<!DOCTYPE html> 是最简单有效的文档类型声明,目的是防止浏览器在渲染文档时,切换到“怪异模式(兼容模式)”。确保浏览器按照最佳相关规范进行渲染,而不是使用一个不符合规范的渲染模式。八、哪些字符集编码支持简体中文,如何解决 HTML 乱码问题 ?(1)支持简体中文的字符集编码GB 2312共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个,同时收录拉丁字母、希腊字母、日文平假名和片假名字母、俄语西里尔字母在内 682 个字符使用区位码“分区”,每区含有 94 个汉字 / 符号 01 - 09 区为特殊符号 16 - 55 区为一级汉字,按拼音排序 56 - 87 区为二级汉字,按部署 / 笔画排序无法处理人名、古汉语中的罕用字和繁体字GBK汉字内码扩展规范拓展 GB 2312 - 80,拥有 23940 个码位,包括 21003 个汉字,883 个图形符号兼容 BG 2312 - 80,支持 希腊字母、俄语字母,不支持韩国字GB 18030国家标准 GB 18030 - 2005多字节编码,编码空间可定义 161 万个字元,包括 70244 个汉字完全兼容 GB 2312,基本兼容 GBK,支持少数民族文字、繁体汉字和日韩汉字BIG5大五码、五大码支持 13060 个中文文字Unicode万国码,国际码,统一码或单一码采用 ISO 10646 通用字符集,应用 UCS-2 使用 16 位编码空间,支持 65536 个字符Unicode 转换格式即 UTF,UTF-8、UTF-16、UTF-32 是将数字转换到程序数据的编码方案UTF-8多字节编码,针对 Unicode 的可变长度字符编码使用 1 到 6 字节为每字符编码,实际最多 4 字节 1 字节编码:ASCII 字符 2 字节编码:带附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母 3 字节编码:其他基本多文种平面(BMP)中字符(包含大部分常用字,汉字) 4 字节编码:其他极少使用 Unicode 辅助平面的字符,如 Emoji 字符UTF-16介于 UTF-8 和 UTF-32 间,使用 2 字节或 4 字节存储,长度既固定又可变UTF-32固定长度的编码方案,不管字符编号大小,始终使用 4 字节存储(2)如何解决 HTML 汉字乱码问题HTML 汉字乱码的原因:客户端不支持 HTML 编码的字符集实际存储的字符集与使用 meta 标签声明的字符集不一致部分现代浏览器会自动纠正,根据实际使用的字符集编码渲染 HTML解决方法:建议使用 utf-8 存储并在页面添加 <meta charset="utf-8"> 声明编码类型9.如何验证 HTML 是否正确 ?难度:★ ☆ ☆ ☆ ☆验证 HTML 的最好方法使用 W3C 创立并维护的标记验证服务,网址如下:https://validator.w3.org/提交一个线上 URL,HTML 文件或者代码,网页会返回相应的错误报告10.什么是 HTML5,HTML5 有哪些新特性 ?难度:★ ★ ★ ☆ ☆(1)什么是 HTML5?HTML5 是定义 HTML 标准的组新版本,具有两个不同的概念:HTML5 是一个新版本的 HTML 语言,具有新的元素,属性和行为HTML5 有更大的技术集,允许构建多样化和更强大的网站和应用程序(2)HTML5 有哪些新特性 ?根据功能,HTML5 新特性可以分为:语义:能够更恰当地描述内容是什么新的区块和段落元素举例: <section> 表示一个包含在 HTML 文档的独立部分 <article> 表示文档、页面、应用或网站中的独立结构 <nav> 表示页面的一部分,其目的是在当前文档或其他文档提供导航链接 <header> 用于展示介绍性内容辅助导航。包含标题,Logo,搜索框和作者名称 <footer> 表示最近一个章节或根节点元素的页脚,包含作者,版权,相关链接 <aside> 表示一个和其余页面内容几乎无关的部分,通常是侧边栏或标注框 <hgroup>代表文档章节所属的多级别目录嵌入和允许操作新的多媒体内容举例: <audio> 用于在文档中嵌入音频内容 <video> 用于再文档嵌入媒体播放器,支持视频及音频播放表单的改进强制性校验 API举例: required 必填属性 pattern 声明正则校验规则属性 minlength 和 maxlength 限制输入的长度 constraint validation API 检测和自定义表单元素的状态新 <input> 元素的 type 属性值举例: color 取色器 date 日期控件 detetime-local 不包括时区的日期控件 month 输入年和月的控件,没有时区 range 输入不需要精确地数字空间 search 搜索字符串的单行文字区域 tel 输入电话号码的控件 time 输入时间的控件 url 输入并校验 URL 的控件其它新的语义元素举例: <mark> 为表示引用或符号目的而标记或突出显示的文本 <figure> 常与 <figcaption> 配合使用,表示独立的说明内容 <data> 将一个指定内容和机器可读的翻译联系在一起 <time> 表示机器可读的 24 小时制的时间或者公历日期 <progress> 显示一项任务的完成进度 <meter> 用来显示已知范围的标量值或者分数值 <main> 呈现了文档的 <body> 或应用的主体部分 output 表示计算或用户操作的结果<iframe> 的改进精确控制 <iframe> 元素的安全级别和期望的渲染举例: sandbox 对呈现在 iframe 框架中的内容启用一些额外的限制条件 srcdoc 支持的浏览器优先使用 srcdoc 代替 src MathML用于描述数学公式、符号的一种标记语言,允许直接嵌入数学公式连通性:能够通过创新的新技术方法进行通信Web Sockets允许在页面和服务器之间建立持久连接,并通过这种方法来交换非 HTML 数据Server-sent events允许服务器向客户端推送事件WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议离线 & 存储:能够让网页再客户端本地存储数据并且更高效地离线运行离线资源:应用程序缓存缓存 .manifest 上的资源,离线或资源没有更新时,浏览器会加载缓存的离线资源在线和离线事件 navigator.onLine 返回在线 true 或离线 false online 和 offline 事件 window document document.body 使用 addEventListenerdocument document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象<body> 标签上指定 ononline="..." 或 onoffline="..." 属性WHATWG 客户端会话和持久化存储(又名 DOM 存储)StorageDOM 存储被设计为用户提供一个更大存储量,更安全,更便捷的存储方法代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法构造函数 Storage 及其实例seesionStorage 全局对象,维护着页面会话期间有效的存储空间,重新载入或从崩溃中恢复不会丢失localStorage 全局对象,本次持久化存储,隐身模式下关闭浏览器会丢弃IndexedDB用于在客户端存储大量的结构化数据,包括文件 / 二进制大型对象(blobs)使用索引实现对数据的高性能搜索在 Web 应用程序中使用文件File API:可以访问 FileList,包含表示用户所选择的 File 对象name 文件名称,只读字符串,只包含文件名,不包含任何路径信息size 以字节数为单位的文件大小,只读的 64 位整数type 文件的 MIME 类型,只读字符串,当类型不能确定为 ""通过 change 事件访问被选择的文件this.files通过 drogenter dragover drag 的 dataTransfer 的 files 中获取文件列表对象 URL window.URL.createObjectURL() 和 window.URL.revokeObjectURL()多媒体:加快普及 video 和 audio 应用,丰富 web 表现力HTML5 音视频<video> 和 <audio> 标签以及 JavaScript 和 APIs 用于对其进行控制WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议Camera API使用手机的摄像头拍照,然后把拍到的照片发送给当前网页Track 和 WebVTT<track> 元素怒被当作媒体元素 <audio> 和 <video> 的子元素WebVTT(Web 视频文本跟踪格式)使用 <track> 元素现实定时文本轨道(如字幕或标题)的格式化,支持 VTTCue 和 VTTRegion 接口2D/3D 绘图 & 效果:提供定制图形、动画界面的新选择Canvas<canvas> 元素被用来通过 JavaScript (Canvas API 或 WebGL API)绘制图形及图形动画HTML5 文本 API 由 <canvas> 支持fillText(text, x, y, [, maxWidth]) 在指定的 (x, y) 位置填充指定的文本strokeText(text, x, y, [, maxWidth] 在指定的 (x, y) 位置绘制文本边框WebGLWebGL (Web 图形库) 是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,无需使用插件WebGL 引入 OpenGL ES 2.0,通过 canvas.getContext('webgl') 使用WebGL 2 引入 OpenGL ES 3.0,通过 canvas.getContext('webgl2') 使用SVGSVG (可缩放矢量图形)是一种描述二维的矢量图形,基于 XML 的标记语言优雅而简洁地渲染不同大小的图形,并和 CSS,DOM,JavaScript 和 SMIL 等其他网络标准无缝衔接可以搜索、索引、编写脚本和压缩,也可以使用任何文本编辑器和绘图软件来创建和编辑 SVG性能 & 集成:提供作用显著的性能优化方案,更有效地使用设备硬件Web Workers为 Web 内容在后台线程中运行脚本提供一种简单方法线程可以执行任务而不干扰用户界面专用 workernew Worker() 构建通过 postMessage() 和 onmessage 事件函数发送和接收消息共享 workernew SharedWorker() 构建通过 port.postMessage() 和 port.onmessage 事件函数发送和接收消息worker 中需先使用 onconnect 获取 portXMLHttpRequest Level 2可以设置 HTTP 请求的时限可以使用 FormData 对象管理表单数据可以上传文件可以请求不同域名下的数据(跨域请求)可以获取服务器端的二进制数据可以获得数据传输的进度信息即时编译的 JavaScript 引擎新一代的 JavaScript 引擎更强大,性能更杰出History APIHistory 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录属性 History.length 返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页 History.scrollRestoration 允许 Web 应用程序在历史导航上显式地设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual) History.state 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态的方式方法 History.back() 在浏览器历史记录里前往上一页,用户可以点击浏览器左上角的返回按钮模拟此方法,等价于 history.go(-1) History.forward() 在浏览器历史记录中前往下一页,用户可以点击浏览器左上角的前进按钮模拟此方法,等价于 history.go(1) History.go() 通过当前页面的相对位置从浏览器历史记录(会话记录)加载页面 History.pushState() 按指定的名称和 URL(如果提供该参数)将数据 push 进会话历史栈,数据被 DOM 进行不透明处理,你可以指定任何可以被序列化的 JavaScript 对象 History.replaceState() 按指定的数据,名称和 URL(如果提供该参数)更新历史栈上最新的入口。这个数据被 DOM 进行了不透明处理。您可以指定任何可以被序列化的 JavaScript 对象Content EditableHTML 中任何元素都可以被编辑,设置 contenteditable 属性为 true 即可HTML5 将此属性标准化HTML 拖放 APIHTML 拖放(Drag and Drop)接口使应用程序能够在浏览器中私用拖放功能引入拖放功能的基本步骤确定可拖拽元素给元素添加 draggable 属性,添加全局事件处理函数 ondragstart定义拖拽数据通过 drag event 的 dataTransfer 属性访问事件数据通过 dataTransfer 的 setData() 方法为拖拽数据添加一个项通过 dataTransfer 的 setDrageImage 方法定义拖拽图像通过 dataTransfer 的 dropEffect 属性定义拖拽效果 copy 表明拖拽的数据将从它原本的位置拷贝到目标的位置 move 表明被拖拽的数据将被移动 link 表明拖拽源位置和目标之间将会创建一些关系表格或是连接确定放置区域给元素添加 ondragover 和 ondrop 事件处理程序属性定义放置效果通过 dataTransfer 的 dropEffect 属性定义拖拽效果拖拽结束拖拽操作结束时,在源元素(开始拖拽时的目标元素)上触发 dragend 事件不管拖拽是完成还是取消,这个事件都会被触发HTML 焦点管理DOM 属性 activeElement 与方法 hasFocus() 为程序按提供了更好的控制页面交互的能力,特别是丢与用户行为引发的交互activeElement 只读属性,用来返回当前在 DOM 或者 shadow DOM 树中处于聚焦状态的 ElementDocument.hasFocus() 方法返回一个 Boolean,表明当前文档或者文档内的节点是否获得了焦点。该方法可以用来判断当前文档中的活动元素是否获得了焦点两者关系获得焦点的元素一定是当前文档的活动元素一个文档中的活动元素不一定获得了焦点基于 Web 的协议处理程序使用 navigator.registerProtoolHandler(scheme, url, title) 方法把 web 应用程序注册成一个协议处理程序requestAnimationFrame传入一个回调函数,该回调函数会在浏览器下一次重绘之前执行全屏 API全屏 API 为使用用户的整个屏幕展现网络内容提供了一种简单的方式,不需要时退出全屏模式方法Document.exitFullscreen() 用于请求从全屏模式切换到窗口模式,会返回一个 Promise,会在全屏模式完全关闭的时候,被重置为 resolved 状态Element.requestFullscreen() 请求浏览器将特定元素置为全屏模式,隐去屏幕上的浏览器所有 UI 元素,以及其它应用属性DocumentOrShadowRoot.fullscreenElement fullscreenElement 属性提供了当前在 DOM(或者 shadow DOM)里被展示为全屏模式的 Element,如果这个值为 null,文档不处于全屏模式Document.fullscreenEnabled fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用事件处理程序Document 事件处理程序 onfullscreenchange 和 onfullscreenerrorElement 事件处理程序 onfullscreenchange 和 onfullscreenerror指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法在线和离线事件navigator.onLine 返回在线 true 或离线 falseonline 和 offline 事件window document document.body 使用 addEventListenerdocument document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象<body> 标签上指定 ononline="..." 或 onoffline="..." 属性设备访问 :能够处理各种输入和输出设备Camera API 使用手机的摄像头拍照,然后把拍到的照片发送给当前网页触摸事件 触摸事件提供了在触摸屏或触控板商解释手指(或触控笔)活动的能力 触摸事件接口可为程序提供多点触控交互的支持,分为开始、移动、结束三个阶段 接口TouchEvent 接口将当前所有活动的触摸点封装起来Touch 接口表示单独一个触摸点,其中包括浏览器视角的相对坐标TouchList 表示一组 Touch,用于多点触控的情况使用地理位置定位地理位置 API 允许用户向 Web 应用程序提供他们的位置出于隐私考虑,报告地理位置和前会先请求用户许可方法,通过 navigator.geolocation 提供getCurrentPosition(success[, error[, options]]) 用来获取设备当前位置watchPosition(success[, error, options]]) 用于注册监听器,在设备的地理位置发生改变的时候自动被调用,返回一个 idclearWatch(id) 清除注册的位置及错误监听器检测设备方向DeviceOrientationEvent 它会在加速度传感器检测到设备在方向上产生变化时触发DeviceMotionEvent 它会在加速度发生改变时触发指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法 样式设计:支持创作更复杂的主题什么是 CSS3?自 CSS2.1 后,CSS 标准被拆解成多个模块,每个模块有自己的版本并独立更新CSS3 泛指这些模块的总和,作为 CSS 的第 3 版本的 CSS3 事实上已不存在* 什么是 CSS3 新特性?CSS 标准的各个模块都在快速更新,其中已经进入到候选推荐、建议推荐和推荐 的模块被称为稳定模块稳定模块中新增的特性大多已获得浏览器广泛支持,使用不需要加私有前缀这里的新特性通常指这些模块中新增的标准* CSS3 新特性有哪些,举例说明? * 颜色模块 * 新增 `opacity` 属性 * 新增 `hsl()` `hsla()` `rgb()` `rgba()` 方法 * 新增 颜色关键字 currentColor * 定义 `transparent` 为 `rgb(0, 0, 0, 0.0)` * 选择器模块 * 新增 属性选择器:`[attribute^="value"]` `[attribute$="value"]` `[attribute*="value"]` * 新增 伪类:`:target` `:enabled` `:disabled` `:checked` `:indeterminate` `:root` `:nth-child` `:nth-last-child` `:nth-of-type` `:nth-last-of-type` `:last-child` `:first-of-type` `:last-of-type` `:only-child` `:only-of-type` `:empty` `:not` * 新增 普通兄弟选择器:`~` * 规范 伪元素表示为两个冒号:`::after` `::before` `::first-letter` `::first-line` * 命名空间模块 新增 @规则:`@namespac` * 媒体查询模块 * 支持更多媒体查询条件:`tv` `color` `resolution`等 * 支持 `<link>` 标签的媒体查询 * 背景和边框模块 * 支持渐变 `linear-gradient`背景 * 支持多背景图片 * 新增 `background-origin` `background-size` `background-clip` * 新增 圆角边框:`border-radius` * 新增 边框图片:`border-image` * 新增 边框阴影:`box-shadow` * 多列布局模块 * 支持多列布局:`columns` `column-count` `column-width` 等 * 值和单位模块 * 新增 想对长度单位:`rem` `ch` `vw` `vh` `vmax` `vmin` * 新增方法:`calc()` * 弹性盒布局模块 * 支持弹性布局:`dispaly:flex` `flex-direction` `flex-wrap` 等 * 文本装饰模块 * 新增 着重符号:`text-emphasis` * 新增 文本阴影:`text-shadow` * 过渡和动画模块(草案) * 新增 过渡效果:`transition` `transition-delay` `transition-duration` `transition-propery` 和 `transition-timing-function` 属性来支持定义两个属性值间的 transitions effects(过渡效果) * 新增 动画效果:`animation` `animation-delay` `animation-direction` `animation-duration` `animation-fill-mode` `animation-iteration-count` `animation-name` `animation-play-state` 和 `animation-timing-function` 属性,以及 `@keyframes` @规则
一、如何插入音频?IE 浏览器,早期用非标准属性 <bgsound> 设置网页背景音乐,只支持 .wav .au 和 .midFlash 支持.mp3 .flv .f4v rtmp 和 m3u8,用于 Web 播放音视频HTML5 使用<audio> 元素用于在文档中嵌入音频内容可以使用内嵌 <source> 提供不同播放源设置 type属性,避免消耗大量时间和资源让浏览器尝试加载浏览器会使用第一个支持的格式二、如何插入视频 ?Flash 支持.mp3 .flv .f4v rtmp 和 m3u8,用于 Web 播放音视频HTML5 使用 <video> 元素用于在文档中嵌入视频内容可以使用内嵌 <source> 提供不同播放源设置 type属性,避免消耗大量时间和资源让浏览器尝试加载浏览器会使用第一个支持的格式<video> 标签支持 width / height 属性<video> 标签支持 poster 属性设置缩略图三、有哪些标签可以嵌入外部内容 ?可以嵌入外部内容的标签包括:<link> 外部 CSS,Favicon.ico<script> 外部 JavaScript<img> 外部图像<audio> <bgsound> 外部音频<video> 外部视频<iframe> 嵌入外部网页<embed> 嵌入插件<object> 嵌入插件可以嵌入外部内容的标签,通常被用来解决跨域问题四、兼容性较好的视频、音频格式分别是 ?视频MPEG-4 即 MP4容器格式 MP4 支持流媒体 MP4支持 MPEG-2、MPEG-4、HEVC、H.265、H.264 和 H.263 视频编码 MP4 支持 AAC、MPEG-1、Layers Ⅰ、Ⅱ、Ⅲ 和 AC-3 等音频编码 所有现代浏览器、移动端浏览器和 Internet Explorer 都支持WebM WebM 支持流媒体 WebM 支持 VP8 和 VP9 视频编码 WebM 支持开源的 Vorbis 和 Opus 音频 所有现代浏览器都支持音频MPEG Audio Layer 3 即 MP3 MP3 利用 MPEG Audio Layer 3 技术,将音乐以 1:10 至 1:12 压缩率压缩成小文件 绝大多数浏览器,包括 IE9 +,除老版本的 Firefox、Opera 外都支持五、如何使媒体文件支持不同平台,不同设备的浏览器 ?使用 <source> 标签用于为图片 <picture> 音频 <audio> 和视频 <video> 指定多个媒体资源设置 type 属性声明资源的 MIME 类型,增加资源的备选类型图像,优先现代图片格式,如 webp 或 avif 使用 jpg 或 png托底视频,优先 webm 使用 mp4 托底音频,优先 mp3 使用 ogg 兼容老版本 Firefox,使用 wav 兼容老版本 Opera使用 替换内容,如图片的 <img> 标签, <audio> 和 <video> 嵌入 Flash 兼容老浏览器六、如何为视频插入字幕 ?使用 <track> 标签设置 src 属性,声明视频字幕的地址引用 WebVTT 格式 或者 TTML 时序文本标记语言格式的字幕地址设置 kind 属性为 subtitles。subtitles 是默认值,这步可省略设置 srclang 属性,从合法 BCP 47 语言标签中选择一种声明字幕语言设置 label 属性,用户可读可选择语种七、如何为视频设置缩略图 ?设置 <video> 的 poster 属性提供视频的缩略图地址缩略图将在视频播放或跳帧前显示未指定缩略图,在第一帧可用前,什么都不显示八、为什么设置 `autoplay` 的视频无法自动播放 ?移动端普遍按流量付费,所以移动端浏览器在用户首次打开视频时忽略 autoplay的自动播放属性弹出确认框,询问用户是否使用流量播放视频自动播放策略限制始终允许静音视频自动播放以下情况允许自动播放声音 用户与域进行了交互(单击、点击等) 桌面上,用户的媒体参与指数阈值已被超过,即用户之前曾播放过有声视频 用户已将站点添加到其移动设备主屏幕或在桌面设备上安装了 PWA上层框架可以将自动播放权限委托给它们的 iframe,允许自动播放声音策略影响版本音视频元素从 Chrome 66 起受自动播放策略限制网络音频从 Chrome 71 起受自动播放策略限制 如果 AudioContext 在文档接收到用户手势之前创建,AudioContext.state 为 suspended,需在用户手势之后调用 resume() 开始播放 用户主动点击节点,调用 start() 开始播放
十六、HTML 下拉框有哪两种实现方式 ?使用 <select> 标签包裹 <option> 标签,每一行是一项代码<select> <option value="选项 1">选项 1</option> <option value="选项 2">选项 2</option> <option value="选项 3">选项 3</option> </select>效果使用 <input> 标签使用 <datalist> 标签包裹 <option> 标签,每一行是一项设置 list 属性,关联 <datalist>代码<input type="text" list="select" /> <datalist id="select"> <option value="选项 1">选项 1</option> <option value="选项 2">选项 2</option> <option value="选项 3">选项 3</option> </datalist>效果十七、如何构建一个兼顾老版本浏览器的自动补全输入框 ?可以在 <datalist> 标签中嵌入 <select> 和 <option> 标签,当 <datalist> 不被支持时,展示下拉选择框,提供用户手动输入内容的第二选择<label for="colorInput">What is your favorite color ?</label> <input type="text" id="colorInput" list="colorList"> <dataList id="colorList"> <label for="colorSuggestion">or pcik a color</label> <select> <option>Blue</option> <option>Red</option> <option>Orange</option> <option>Green</option> <option>Yellow</option> <option>Pink</option> <option>Purple</option> <option>White</option> </select> </dataList>支持浏览器预览:不支持浏览器预览:十八、如何构建单选框,如何构建复选框 ?单选框设置 type=radio相同 name 的单选框同时只有一个能被选中选中项有 checked 属性,没有一个选中项,不会发送 name 的值默认 value 值为 on复选框5. 设置 type=checkbox6. 相同 name 的复选框表单被提交后,可以获得提交的键名和键值对字符串Python 可以使用 self.request.get('name', allow_multiple = True) 或 self.request.getAll('name') 获取PHP 可以设置 name 值的格式为 name[] ,使用名称 name 获取数组7. 选中项有 checked 属性,没有一个选中项,不会发送 name 的值8. 默认 value 值为 on十九、如何在表单中发送图片被点击时的坐标 ?1. 使用图像按钮元素设置 type=image支持与 <img> 元素的相同属性2. 支持其它表单按钮的支持属性使用图像按钮提交表单不会提交自身值会提交单击处相对于图像左上角的 X 和 Y 坐标以查询字符串的格式跟在 URL 后https://www.leetcode-cn . com/?pos.x=100&pos.y=200二十、支持 `min` 和 `max` 的表单组件有哪些 ?1. 数字选择器 min 和 max 分别设置最小值和最大值type=number 的 <input>2. 滑块选择器 min 和 max 分别设置最小值和最大值type=range 的 <input>3. 日期时间选择器 min 和 max 设置开始时间和结束时间type=datetime-local 的 <input>type=month 的 <input>type=time 的 <input>type=week 的 <input>4. 进度条选择器 <progress>max 指定随时间变化而变化到最大的值5. 仪表选择器 <meter>min 值域的最小边界值,默认为 0max 值域的上限边界值,默认为 1二十一、哪种输入表单适合显示密码强度 ?1. low 和 high 将范围划分为三个部分较低部分:min 和 low中间部分:low 和 high较高部分:high 和 max2. optimum 定义 <meter> 元素的最优值optimum 值在较低范围内,较低范围最优,中等范围一般,较高范围最坏optimum 值在中等范围内,较低范围一般,中等范围最优,较高范围最坏optimum 值在较高范围内,较低范围最坏,中等范围一般,较高范围最优3. <meter> 颜色最优显示为绿色平均显示为黄色最坏显示为红色实现一个密码强度显示器,分别对应 弱、中等、强的密码<p>密码强度指示器</p> <p>弱:<meter min="0" low="33" high="66" max="100" value="30" optimum="100"></meter></p> <p>中等:<meter min="0" low="33" high="66" max="100" value="55" optimum="100"></meter></p> <p>强:<meter min="0" low="33" high="66" max="100" value="88" optimum="100"></meter></p>效果如图所示:二十二、使用 GET 和 POST 发送表单数据,有什么不同 ?根据 rfc-2616 规范,HTTP 协议中的 GET 和 POST 主要是语义上的区别在浏览器的实现及应用中,存在 GET 和 POST 的最佳实践GET 发送表单数据设置 <form> 的 method 属性为 get数据以查询字符串的形式被追加到 URL,参数上限受早期浏览器和 Web 服务器的限制问号 ? 后跟查询字符串符号 & 分隔开的键名键值对默认缓存,受缓存策略控制可回退可收藏参数随 URL 保存在浏览器历史中适用于无副作用,幂等的请求POST 发送表单数据设置 <form> 的 method 属性为 post数据以查询字符串的形式附加到请求体中,参数上限可能受后端脚本限制,如 PHP 通过 max_input_vars 限制最大输入参数上限包含请求行Content-Type: application/x-ww-form-urlencodedContent-Length: {请求体的数据长度}默认不缓存,受缓存策略控制,可声明缓存通常回退会触发重新提交警告,避免回放攻击通常不可收藏参数不随 URL 保存适用于有副作用,非幂等的请求二十三、Form 的 enctype 属性都有哪些值,各自适合什么场景 ?enctype 属性通常用来提交表单的内容类型application/x-www-form-urlencoded 默认值,数据转换为键值对,用于不含文件的表单提交multipart/form-data 使用 <input> 标签上传文件时,必须设置此类型text/plain 表示纯文本形式,HTML5 新增,通常用于调试二十四、什么是表单校验,为什么要使用表单校验 ?(1)什么是表单校验向 Web 应用输入或提交数据时,验证数据的过程就是表单校验正确则允许数据继续提交后端或后台服务失败则提示错误类型、原因、位置或更改建议(2)为什么要进行表单校验 ?确保数据格式正确:引导用户:引导用户输入符合预期的数据保护系统:符合设定类型、位数或具体规则,避免不正确格式数据影响程序运行信息安全保护用户:确保用户设定密码、二次密码、安全问答够复杂,不易被暴力破解,无泄漏减少攻击:经常与后端或后台服务的校验一起应用,减少恶意或伪造的数据提交二十五、表单校验都有哪些类型 ?表单校验可以分为客户端校验和服务器端校验客户端校验校验时机:发生在应用端或浏览器端,表单数据被提交到服务器之前实时性:即时反馈用户输入的校验结果作用:确保数据格式正确,保护信息安全方式: JavaScript:监听输入框输入、失去焦点和提交事件,验证非空(必填),使用 length 校验长度,正则校验格式等,校验通过或失败分别设置不同 class 或 style,失败时,通过禁用提交按钮,阻止 onsubmit 事件,阻止表单继续提交 HTML5 内置校验:通过设置不同的 type 类型,声明 required 必填属性,minlength 和 maxlength 限制长度,min 和 max 限制数字或日期范围,设置 pattern 正则表达式校验输入内容,更改伪类类名的属性自定义校验或失败的提示,性能更好,代码更少,老版本 IE 不兼容 HTML5 内置校验 + JavaScript(constraint validaton API):检测、自定义表单元素状态和错误信息服务器端校验校验时机:应用端或浏览器提交数据并被后端或后台服务接收之后滞后性:需要等待后端或后台服务响应作用:提供验重等需要查询数据库的校验,校验、过滤和清洗数据方式: 通常采用 Ajax 方式,将用户输入数据提交给后端或后台服务,再次校验规则或查询数据库,将校验结果作为响应返回。应用端或浏览器端根据响应结果,更改 class 或设置 style 展示不同的提示二十六、如何使用 HTML5 的内置表单数据校验 ?HTML5 内置表单数据校验通常包括校验属性、约束校验 API 及对应的 CSS 伪类1.校验属性类型校验:设置 type 类型email url 类型限制输入文字必须为邮箱或网址其它非 text 类型,通过控件,触屏端唤起数字键盘等,来限制用户输入类型非空必填校验:声明 required 属性即可长度校验:设置 minlength 和 maxlength 属性限制长度数字 / 日期 / 范围:设置 min 和 max 属性设置数值范围、起止日期、range 范围步长:step 与 min 或 max 其中至少一个一起设置时,限制值的有效性2. Constraint Validation API 约束验证 API属性 validationMessage 本地化消息描述元素不满足校验条件时的文本信息无需校验或满足校验条件时,为空 validity ValidityState 对象,每一个子项都返回布尔值customError 是否设置了自定义错误patternMismatch 是否匹配正则表达式rangeOverflow 元素的值是否高于所设置最大值rangeUnderflow 元素的值是否低于所设置最小值stepMismatch 元素的值是否符合 step 属性规则tooLong 元素的值是否超过最大长度typeMismatch 元素的值是否出现语法错误valid 元素的值是否有效valueMissing 元素的值是否 required 且为空willValidate 元素的值是否是在表单提交时被校验方法 checkValidity() 校验元素的值是否有效 reportValidity() 元素报告其校验状态,重新展示校验失败提示给用户 setCustomValidity(message) 为元素添加自定义的错误消息3.伪类:valid 有效:invalid 无效:in-range 在范围内:out-of-range 超出范围二十七、使用 JavaScript 发送表单数据都有哪些方法 ?使用 JavaScript 发送表单数据,需要两步骤首先,构造请求参数构建查询字符串使用 ES3const buildParam = data => { const dataPairs = [] for (const key in data) dataPairs.push(encodeURIComponent(key) + encodeURIComponent(data[key])) return dataPairs.join('&').replace(/%20/g, '+') }* 使用 jQueryconst buidParam = form => $(form).serialize()使用 FormData 对象将对象转为 FormData 对象const buildParam = data => { const formData = new FormData() for (const key in data) formData.append(key, data[key]) return formData }* 将表单元素构建 `FormData` 对象const buildParam = form => { const formData = new FormData(form) return formData }然后,发送数据使用 XMLHttpRequest 对象const send = (url, param, cb) => { const request = new XMLHttpRequest() request.addEventListener('load', e => cb(null, e => cb(e.target.responseText))) request.addEventListener('error', e => cb(e.message)) request.open('POST', url) request.send(param) }使用 jQueryconst send = (url, param, cb) => { $.ajax({ url, type: 'post', data: param }).done(data => cb(null, data)).fail((_. textStatus) => cb(textStatus)) }使用 fetch 方法const send = (url, param, cb) => { fetch(url, { method: 'POST', body: param }).then(response => cb(null, response)).catch(error => cb(error)) f }二十八、如何自定义表单元素的样式 ?首先,由于表单元素先于 CSS 被添加到 HTML,早期的渲染依靠底层系统实现,至今,表单元素在不同浏览器,同一浏览器的不同系统版本中,存在不同的默认外观。自定义表单样式的方法:使用 JavaScript + HTML + CSS 重建表单元素,模拟其交互行为。最终通过 UI 组件库的方式供设计团队参考和业务开发调用。保持跨浏览器,跨操作系统的一致性可以自定义所有样式交互、校验、提交等都需要自己完成,代码多,复杂度高JavaScript 出错或禁用,CSS 加载失败,都可能导致表单失效。在稳定性要求高的场景,需要能降级到原生表单元素自定义原生表单元素的样式 原生表单元素按应用 CSS 的难度 ,分为三类容易应用,跨平台不易出问题:<form> <fieldset> <label> <output>难应用,不同平台写不同属性:<legend> checkbox radio placeholder不推荐应用:<select> <option> <optgroup> <datelist> <progress> <meter> 保持跨平台一致性字体和文本大小:继承父级元素的 CSS,而不使用系统默认样式button, input, select, textarea { font-family: inherit; font-size: 100%; }盒子模型:保持相同的宽度和高度button, input, select, textarea { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }定位<legend> 元素定位是 <fieledset> 父元素的上边框的最顶端<textarea> 元素的垂直对齐由基线改为顶端对齐 vertical-align: top清除默认样式-moz-appearance 清除 Firefox 表单元素的默认样式-webkit-appearance 清除 Safari 和 Chrome 表单元素的默认样式使用 CSS 伪类定义表单组件的细节CSS 2.1 支持 3 伪类::active :focus :hoverCSS Selector Level 3 新增 4 伪类::enabled :disabled :checked :indeterminateCSS Basic UI Level 3 新增 9 伪类::default :valid :invalid :in-range :out-of-range :required :optional :read-only :read-writeCSS Selector Level 4 新增: :user-error
1. HTML5 使用 元素用于在文档中嵌入音频内容,如何提供提供不同播放源?<audio>元素。可以使用内嵌 <source> 提供不同播放源。2. 兼容性较好的视频格式有?MP4 格式、 WebM格式。3. 兼容性较好的音频格式有?MP3 格式。4. HTML 表格有哪三种实现方式?基于 table HTML 的 table 元素表示表格数据,即通过二维数据表表示的信息基于 div 或其它 DOM 元素 + CSS 实现 • 基于 canvas 实现5. HTML 表单有哪三个基本组成部分?表单标签、表单域、表单按钮。6. 如何禁用一组表单元素?将一组表单元素放入 <fieldset> 标签中给 <fieldset> 添加 disabled 属性7. 如何让表单元素自动对焦 ?给表单元素添加 autofocus 属性8. 哪些类型的输入框默认会对用户输入的内容进行前端校验 ?日期时间类 date month week datetime-local time;电话号码类 tel;文件选择类 file;网址输入类 url9. 多行文本框和单行文本框的区别有哪几点 ?可替换元素、自闭合标签、值、属性。10. textarea 和 input 哪个是自闭合标签?input。11. HTML 下拉框有哪两种实现方式 ?使用 <select > 标签;使用 <input> 标签。12. 支持 `min` 和 `max` 的表单组件有哪些 ?数字选择器、滑块选择器、日期和时间选择器、进度条选择器、仪表选择器。13. 使用 GET 或 POST 发送表单数据时,数据以查询字符串的形式附加到请求体中的是?POST。14. 客户端校验的作用是?确保数据格式正确,保护信息安全。15. 服务器端校验的作用是?提供验重等需要查询数据库的校验,校验、过滤和清洗数据。16. 客户端校验时机是?发生在应用端或浏览器端,表单数据被提交到服务器之前。17. 服务器端校验时机是?应用端或浏览器提交数据并被后端或后台服务接收之后。18. HTML5 内置表单数据校验通常包括哪些?校验属性、约束校验 API 及对应的 CSS 伪类。19. 使用 JavaScript 发送表单数据,需要哪两步?首先,构造请求参数,然后发送数据。20. 构造请求参数有哪些方法?构造请求参数,使用ES3;将对象转为 FormData 对象。21. 发送数据有哪些方法?使用 XMLHttpRequest 对象 使用 jQuery 使用 fetch 方法22. 自定义表单样式的方法有哪两种?使用 JavaScript + HTML + CSS 重建表单元素,模拟其交互行为。最终通过 UI 组件库的方式供设计团队参考和业务开发调用。自定义原生表单元素的样式
九、如何验证 HTML 是否正确 ?验证 HTML 的最好方法使用 W3C 创立并维护的标记验证服务,网址如下:https://validator.w3.org/提交一个线上 URL,HTML 文件或者代码,网页会返回相应的错误报告十、什么是 HTML5,HTML5 有哪些新特性 ?(1)什么是 HTML5?HTML5 是定义 HTML 标准的组新版本,具有两个不同的概念:HTML5 是一个新版本的 HTML 语言,具有新的元素,属性和行为HTML5 有更大的技术集,允许构建多样化和更强大的网站和应用程序(2)HTML5 有哪些新特性 ?根据功能,HTML5 新特性可以分为:语义:能够更恰当地描述内容是什么新的区块和段落元素举例: <section> 表示一个包含在 HTML 文档的独立部分 <article> 表示文档、页面、应用或网站中的独立结构 <nav> 表示页面的一部分,其目的是在当前文档或其他文档提供导航链接 <header> 用于展示介绍性内容辅助导航。包含标题,Logo,搜索框和作者名称 <footer> 表示最近一个章节或根节点元素的页脚,包含作者,版权,相关链接 <aside> 表示一个和其余页面内容几乎无关的部分,通常是侧边栏或标注框 <hgroup>代表文档章节所属的多级别目录嵌入和允许操作新的多媒体内容举例: <audio> 用于在文档中嵌入音频内容 <video> 用于再文档嵌入媒体播放器,支持视频及音频播放表单的改进强制性校验 API举例: required 必填属性pattern 声明正则校验规则属性 minlength 和 maxlength 限制输入的长度 constraint validation API 检测和自定义表单元素的状态新 <input> 元素的 type 属性值举例: color 取色器 date 日期控件 detetime-local 不包括时区的日期控件 month 输入年和月的控件,没有时区 range 输入不需要精确地数字空间 search 搜索字符串的单行文字区域 tel 输入电话号码的控件 time 输入时间的控件 url 输入并校验 URL 的控件其它新的语义元素举例: <mark> 为表示引用或符号目的而标记或突出显示的文本 <figure> 常与 <figcaption> 配合使用,表示独立的说明内容 <data> 将一个指定内容和机器可读的翻译联系在一起 <time> 表示机器可读的 24 小时制的时间或者公历日期 <progress> 显示一项任务的完成进度<meter> 用来显示已知范围的标量值或者分数值 <main> 呈现了文档的 <body> 或应用的主体部分 output 表示计算或用户操作的结果<iframe> 的改进精确控制 <iframe> 元素的安全级别和期望的渲染举例:sandbox 对呈现在 iframe 框架中的内容启用一些额外的限制条件srcdoc 支持的浏览器优先使用 srcdoc 代替 srcMathML用于描述数学公式、符号的一种标记语言,允许直接嵌入数学公式连通性:能够通过创新的新技术方法进行通信Web Sockets允许在页面和服务器之间建立持久连接,并通过这种方法来交换非 HTML 数据Server-sent events允许服务器向客户端推送事件WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议离线 & 存储:能够让网页再客户端本地存储数据并且更高效地离线运行离线资源:应用程序缓存缓存 .manifest 上的资源,离线或资源没有更新时,浏览器会加载缓存的离线资源在线和离线事件navigator.onLine 返回在线 true 或离线 falseonline 和 offline 事件window document document.body 使用 addEventListenerdocument document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象<body> 标签上指定 ononline="..." 或 onoffline="..." 属性WHATWG 客户端会话和持久化存储(又名 DOM 存储)StorageDOM 存储被设计为用户提供一个更大存储量,更安全,更便捷的存储方法代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法构造函数 Storage 及其实例seesionStorage 全局对象,维护着页面会话期间有效的存储空间,重新载入或从崩溃中恢复不会丢失localStorage 全局对象,本次持久化存储,隐身模式下关闭浏览器会丢弃IndexedDB用于在客户端存储大量的结构化数据,包括文件 / 二进制大型对象(blobs)使用索引实现对数据的高性能搜索在 Web 应用程序中使用文件File API:可以访问 FileList,包含表示用户所选择的 File 对象name 文件名称,只读字符串,只包含文件名,不包含任何路径信息size 以字节数为单位的文件大小,只读的 64 位整数type 文件的 MIME 类型,只读字符串,当类型不能确定为 ""通过 change 事件访问被选择的文件this.files通过 drogenter dragover drag 的 dataTransfer 的 files 中获取文件列表对象 URL window.URL.createObjectURL() 和 window.URL.revokeObjectURL()多媒体:加快普及 video 和 audio 应用,丰富 web 表现力HTML5 音视频<video> 和 <audio> 标签以及 JavaScript 和 APIs 用于对其进行控制WebRTC支持在浏览器客户端之间语音 / 视频交流和数据分享的技术浏览器原生支持点对点的分享应用数据和进行电话会议Camera API使用手机的摄像头拍照,然后把拍到的照片发送给当前网页Track 和 WebVTT<track> 元素怒被当作媒体元素 <audio> 和 <video> 的子元素WebVTT(Web 视频文本跟踪格式)使用 <track> 元素现实定时文本轨道(如字幕或标题)的格式化,支持 VTTCue 和 VTTRegion 接口2D/3D 绘图 & 效果:提供定制图形、动画界面的新选择Canvas<canvas> 元素被用来通过 JavaScript (Canvas API 或 WebGL API)绘制图形及图形动画HTML5 文本 API 由 <canvas> 支持fillText(text, x, y, [, maxWidth]) 在指定的 (x, y) 位置填充指定的文本strokeText(text, x, y, [, maxWidth] 在指定的 (x, y) 位置绘制文本边框WebGLWebGL (Web 图形库) 是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,无需使用插件WebGL 引入 OpenGL ES 2.0,通过 canvas.getContext('webgl') 使用WebGL 2 引入 OpenGL ES 3.0,通过 canvas.getContext('webgl2') 使用SVGSVG (可缩放矢量图形)是一种描述二维的矢量图形,基于 XML 的标记语言优雅而简洁地渲染不同大小的图形,并和 CSS,DOM,JavaScript 和 SMIL 等其他网络标准无缝衔接可以搜索、索引、编写脚本和压缩,也可以使用任何文本编辑器和绘图软件来创建和编辑 SVG性能 & 集成:提供作用显著的性能优化方案,更有效地使用设备硬件Web Workers为 Web 内容在后台线程中运行脚本提供一种简单方法线程可以执行任务而不干扰用户界面专用 workernew Worker() 构建通过 postMessage() 和 onmessage 事件函数发送和接收消息共享 workernew SharedWorker() 构建通过 po``r``t``.``postMessage() 和 po``r``t.onmessage 事件函数发送和接收消息worker 中需先使用 onconnect 获取 portXMLHttpRequest Level 2 可以设置 HTTP 请求的时限 可以使用 FormData 对象管理表单数据 可以上传文件 可以请求不同域名下的数据(跨域请求) 可以获取服务器端的二进制数据 可以获得数据传输的进度信息即时编译的 JavaScript 引擎 新一代的 JavaScript 引擎更强大,性能更杰出History APIHistory 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录属性History.length 返回一个整数,该整数表示会话历史中元素的数目,包括当前加载的页History.scrollRestoration 允许 Web 应用程序在历史导航上显式地设置默认滚动恢复行为。此属性可以是自动的(auto)或者手动的(manual)History.state 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态的方式方法History.back() 在浏览器历史记录里前往上一页,用户可以点击浏览器左上角的返回按钮模拟此方法,等价于 history.go(-1)History.forward() 在浏览器历史记录中前往下一页,用户可以点击浏览器左上角的前进按钮模拟此方法,等价于 history.go(1)History.go() 通过当前页面的相对位置从浏览器历史记录(会话记录)加载页面History.pushState() 按指定的名称和 URL(如果提供该参数)将数据 push 进会话历史栈,数据被 DOM 进行不透明处理,你可以指定任何可以被序列化的 JavaScript 对象History.replaceState() 按指定的数据,名称和 URL(如果提供该参数)更新历史栈上最新的入口。这个数据被 DOM 进行了不透明处理。您可以指定任何可以被序列化的 JavaScript 对象Content EditableHTML 中任何元素都可以被编辑,设置 contenteditable 属性为 true 即可HTML5 将此属性标准化HTML 拖放 APIHTML 拖放(Drag and Drop)接口使应用程序能够在浏览器中私用拖放功能引入拖放功能的基本步骤确定可拖拽元素给元素添加 draggable 属性,添加全局事件处理函数 ondragstart定义拖拽数据通过 drag event 的 dataTransfer 属性访问事件数据通过 dataTransfer 的 setData() 方法为拖拽数据添加一个项通过 dataTransfer 的 setDrageImage 方法定义拖拽图像通过 dataTransfer 的 dropEffect 属性定义拖拽效果copy 表明拖拽的数据将从它原本的位置拷贝到目标的位置move 表明被拖拽的数据将被移动link 表明拖拽源位置和目标之间将会创建一些关系表格或是连接确定放置区域给元素添加 ondragover 和 ondrop 事件处理程序属性定义放置效果通过 dataTransfer 的 dropEffect 属性定义拖拽效果拖拽结束拖拽操作结束时,在源元素(开始拖拽时的目标元素)上触发 dragend 事件不管拖拽是完成还是取消,这个事件都会被触发HTML 焦点管理DOM 属性 activeElement 与方法 hasFocus() 为程序按提供了更好的控制页面交互的能力,特别是丢与用户行为引发的交互activeElement 只读属性,用来返回当前在 DOM 或者 shadow DOM 树中处于聚焦状态的 ElementDo``cumen``t.hasFocus() 方法返回一个 Boolean,表明当前文档或者文档内的节点是否获得了焦点。该方法可以用来判断当前文档中的活动元素是否获得了焦点两者关系获得焦点的元素一定是当前文档的活动元素一个文档中的活动元素不一定获得了焦点基于 Web 的协议处理程序使用 navigator.registerProtoolHandler(sch``em``e,``ur``l``, tit``le) 方法把 web 应用程序注册成一个协议处理程序requestAnimationFrame传入一个回调函数,该回调函数会在浏览器下一次重绘之前执行全屏 API全屏 API 为使用用户的整个屏幕展现网络内容提供了一种简单的方式,不需要时退出全屏模式方法Document.exitFullscreen() 用于请求从全屏模式切换到窗口模式,会返回一个 Promise,会在全屏模式完全关闭的时候,被重置为 resolved 状态Element.requestFullscreen() 请求浏览器将特定元素置为全屏模式,隐去屏幕上的浏览器所有 UI 元素,以及其它应用属性DocumentOrShadowRoot.fullscreenElement fullscreenElement 属性提供了当前在 DOM(或者 shadow DOM)里被展示为全屏模式的 Element,如果这个值为 null,文档不处于全屏模式Document.fullscreenEnabled fullscreenEnabled 属性提供了启用全屏模式的可能性。当它的值是 false 的时候,表示全屏模式不可用事件处理程序Document 事件处理程序 onfullscreenchange 和 onfullscreenerrorElement 事件处理程序 onfullscreenchange 和 onfullscreenerror指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法在线和离线事件 navigator.onLine 返回在线 true 或离线 false online 和 offline 事件 window document document.body 使用 addEventListener document document.body 的 .ononline 或 .onoffline 属性设为一个 JavaScript Function 对象 <body> 标签上指定 ononline="..." 或 onoffline="..." 属性设备访问 :能够处理各种输入和输出设备Camera API 使用手机的摄像头拍照,然后把拍到的照片发送给当前网页触摸事件触摸事件提供了在触摸屏或触控板商解释手指(或触控笔)活动的能力触摸事件接口可为程序提供多点触控交互的支持,分为开始、移动、结束三个阶段接口TouchEvent 接口将当前所有活动的触摸点封装起来Touch 接口表示单独一个触摸点,其中包括浏览器视角的相对坐标TouchList 表示一组 Touch,用于多点触控的情况使用地理位置定位地理位置 API 允许用户向 Web 应用程序提供他们的位置出于隐私考虑,报告地理位置和前会先请求用户许可方法,通过 navigator.geolocation 提供getCurrentPosition(success[, error[, options]]) 用来获取设备当前位置watchPosition(success[, error, options]]) 用于注册监听器,在设备的地理位置发生改变的时候自动被调用,返回一个 idclearWatch(id) 清除注册的位置及错误监听器检测设备方向DeviceOrientationEvent 它会在加速度传感器检测到设备在方向上产生变化时触发DeviceMotionEvent 它会在加速度发生改变时触发指针锁定 API 光标移到浏览器或者屏幕区域之外,指针锁定也能够让你访问鼠标事件 指针锁定是持久性的。指针锁定不释放鼠标,直到作出一个显式的 API 调用或者用户使用一个专门的释放手势 指针锁定不局限于浏览器或者屏幕边界 指针锁定持续发送事件,而不管鼠标按钮状态如何 指针锁定隐藏光标 指针锁定目前需要先进入全屏模式 requestFullscreen() 然后执行 requestPointerLock() 方法 样式设计:支持创作更复杂的主题十一、什么是 MIME types,常见的 MIME types 有哪些 ?MIMEtype(现在称为“媒体类型(media type)”,但有时也是“内容类型”(content type))是指示文件类型的字符串,与文件一起发送(例如,一个声音文件可能被标记为 audio/ogg 一个图像文件可能是 image/png )。它与传统 Windows 上的文件扩展名有相同目的NavigatorPlugins.mimeTypes 返回一个 MimeTypeArray 对象,其中包含可被当前浏览器识别的 MimeType 对象列表两种主要的 MIME 类型text/plain 表示文本文件的默认值,一个文本文件应当是人类可读的,并且不包含二进制数据application/octet-stream 表示所有其他情况的默认值。一种未知的文件类型应当使用此类型。浏览器在处理这些文件时会特别小心,试图防止、避免用户的危险行为Web 常见的 MIME 类型十二、什么是 ARIA?ARIA(Accessible Rich Internet Applications)是能够让残障人士更加便利地访问 Web 内容和使用 Web 应用的一套机制,来自 W3C 的网络无障碍计划(Web Accessibility Initiative)ARIA 是对超文本标记语言(HTML)的补充,以便在没有其他机制的情况下,使得应用程序中常用的交互和小部件可以传递给辅助交互技术ARIA 是一组特殊的易用性属性,可以添加到任意标签上,尤其适用于 HTML。role 属性定义了对象的通用类型(例如文章、警告、或幻灯片)。额外的 ARIA 属性提供了其他有用的特性,例如表单的描述或进度条的当前值ARIA 在大多数流行的浏览器和屏幕阅读器中得到了实现开发人员应该更倾向使用对应的语义化 HTML 元素,而不是使用 ARIA
问题 1:HTML5 新特性有哪些语义化标签音视频处理API(audio,video)canvas / webGL拖拽释放(Drag and drop) APIhistory APIrequestAnimationFrame地理位置(Geolocation)APIwebSocketweb存储 localStorage、SessionStorage表单控件,calendar、date、time、email、url、search问题 2:HTTP 状态码1xx:指示信息类,表示请求已接受,继续处理(临时响应)2xx:指示成功类,表示请求已成功接受3xx:指示重定向,表示要完成请求必须进行更近一步的操作4xx:指示客户端错误,请求有语法错误或请求无法实现5xx:指示服务器错误,服务器未能实现合法的请求常见状态码【403】表示【服务器拒绝执行客户端的请求】【404】表示【服务器找不到客户端所请求的资源(网页)】【304】表示【所请求的资源并未修改(命中协商缓存)问题 3:正则表达式的用法?问题 4:HTML5 语义化的优点:在没CSS样式的情况下,页面整体也会呈现很好的结构效果代码结构清晰,易于阅读,利于开发和维护 方便其他设备解析(如屏幕阅读器)根据语义渲染网页。有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重问题 5:介绍下 304 过程缓存过期后向服务器发起请求验证缓存是否有效,有效的话则返回304。304多出现在对于静态资源的请求上面。对于静态资源来说:当浏览器第一次发起请求时(请求头中没有If-Modified-Since),server会在响应中告诉浏览器这个资源最后修改的时间(响应头中的Last-Modified)。当你再次请求这个资源时,浏览器会询问server这个资源有没有被修改(请求头中If-Modified-Since)。如果资源没有被修改,server返回304状态码,浏览器使用本地的缓存文件。问题 6:数组的常用方法有哪些?join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符push():将参数添加到原数组末尾,并返回数组的长度(修改原数组)pop():删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined(修改原数组)shift():删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefinedunshift(): 将参数添加到原数组开头,并返回数组的长度(修改原数组)slice(start,end):可以截取出数组某部份的元素为一个新的数组,有两个必填的参数,第一个是起始位置,第二个是结束位置( 操作时数字减1 ) 原数组不改变splice(start,deleteCount,val1,val2,…):从start位置开始删除deleteCount项,并从该位置起插入。(修改原数组)fill():使用特定值填充数组中的一个或多个元素(修改原数组)filter():过滤,数组中的每一项运行给定函数,返回满足过滤条件组成的数组concat():可以将两个数组合并在一起,如果是使用ES6语法也可以用扩展运算符…来代替indexOf():返回当前值在数组中第一次出现位置的索引lastIndexOf():返回查找的字符串最后出现的位置,如果没有找到匹配字符串则返回 -1。every():判断数组中每一项是否都符合条件some():判断数组中是否存在满足的项includes():判断一个数组是否包含指定的值sort(orderfunction):按指定的参数对数组进行排序(修改原数组)reverse():将数组反序(修改原数组)forEach():循环遍历数组每一项(没有返回值)map():循环遍历数组的每一项(有返回值)copyWithin(): 从数组的指定位置拷贝元素到数组的另一个指定位置中(修改原数组)find(): 返回第一个匹配的值,并停止查找findIndex(): 返回第一个匹配值的索引,并停止查找toLocaleString()、toString():将数组转换为字符串flat()、flatMap():扁平化数组entries() 、keys() 、values():遍历数组问题 7:CSS 选择器及优先级选择器id选择器(#myid)类选择器(.myclass)属性选择器(a[rel="external"])伪类选择器(a:hover, li:nth-child)标签选择器(div, h1,p)相邻选择器(h1 + p)子选择器(ul > li)后代选择器(li a)通配符选择器(*)优先级:!important内联样式(1000)ID选择器(0100)类选择器/属性选择器/伪类选择器(0010)元素选择器/伪元素选择器(0001)关系选择器/通配符选择器(0000)带!important 标记的样式属性优先级最高; 样式表的来源相同时: !important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性问题 8:强缓存和协商缓存浏览器缓存的特点:浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。协商缓存协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。协商缓存主要有以下两种情况:协商缓存生效,返回304协商缓存失效,返回200和请求结果结果问题 9:== 和 === 区别1.== 和 === 区别== 表示相等 (值相等)===表示恒等(类型和值都要相等)js在比较的时候如果是 == 会先做类型转换,再判断值得大小,如果是===类型和值必须都相等。2.== === 和 Object.is() 区别== 相等,如果两边类型不一致,进行隐式转换后,再比较。+0 和 -0 相等, NaN 不等于任何数=== 严格相等,如果类型不一致,就不相等。 +0 和 -0 相等, NaN 不等于任何数Object.is() 严格相等,+0 和 -0 不相等, NaN 等于自身问题 10:script 标签放在 header 里和放在 body 底部里有什么区别?放在 header 中你能看到 html 第一时间被加载进来,但页面 body 内容迟迟没有渲染出来。因为在等待 header 标签中 script 脚本的加载,3 秒后,整个页面渲染完成。放在 body 底部这次 html 内容第一时间渲染完成,随后等待 js 的加载。总结:脚本会阻塞页面的渲染,所以推荐将其放在 body 底部,因为当解析到 script 标签时,通常页面的大部分内容都已经渲染完成,让用户马上能看到一个非空白页面。另外你能看到多个脚本之间都是异步向服务器请求,他们之间不互相依赖,最终只等待 3 秒,而非 3+3+3 秒。
如何发布模块如何发布一个Deno的模块?操作其实很简单。前提是你有一个GitHub账号,新建一个对外开放的工程。发布前,在你的工程里,最好先配置好标签与描述。标签用来在deno.land中搜索,描述便于其他开发者了解你的用图。回到deno.land/x页面。点击**Publish a module,**出现以下弹窗: 点击下一步填写一个唯一的模块名称,比如xx_test。如果没有重复的,就可以点击下一步。填写要发布的工程中文件夹路径,一般情况下我们都是根目录,不填即可。下一步是提示页面按上一步的提示一步步就可以完成了。这里我再详细点,到我们的github工程,找到设置-Webhooks,点击增加webhook按钮。填写Payload URL为提示页面的地址;修改Content-type为application/json。选择Which events would you like to trigger this webhook?的最后一项。只勾选第一个。点击Add webhook按钮页面会自动回到这里,看到有了一条记录。点击编辑,会有2个tab页,第一个就是你刚才配置的页面,第二个是你以后所有hooks的状态。如果失败了,可以在这里看详细信息。你的工程使用git打标签,如git tag -a v0.0.1 -m "feat: xxx",推送到github,就会触发hooks。回到deno.land/x,在下面就能看到你新推送的模块了(官网改版后已经没有了,需要你自己搜索)。点击你的带版本号的工程,可以看到弹出的地址是deno.land/x/xx_test@v…需要注意的一点是,Deno对个人发布模块的数量限制为15个,超过需要向管理员发邮件(modules@deno.com)申请,所以没事儿不要发垃圾的包上去,撤销也是需要向管理员申请的,而且也不一定会通过。快速发布一般情况下,我们需要在根目录下创建一个README.md,说明你的模块功能与使用方法。以我的工程oak_nest为例,通常有段使用示例:import { Body, Context, UseGuards, } from "https://deno.land/x/oak_nest@v1.2.1/mod.ts"; 示例里有你的模块地址,这个地址的版本号如果每次发布都要手动修改,那就太麻烦了。全局安装一个命令:deno install --allow-read --allow-write --allow-run --unstable -n tag -f https://deno.land/x/jw_cli@v0.5.1/cli/tag/mod.ts 在根目录下创建一个deno.json文件,内容大致如下:{ "name": "xx_test", "version": "0.0.1", } 之后就可以使用tag来更新版本,并推送到服务器。详细说明具体见文档。版本更新版本号更新的命令使用patch/minor/major,逻辑与Node.js的npm version一致,它会更新deno.json[c]文件中的版本号。tag tag patch # 与上面等价 tag minor tag major 它会同时更新根目录下的deno.json[c]文件和README.md,如果后者有使用deno.json[c]中配置的name,将会对应替换。比如工程的名称为oak_nest,deno.json[c]中version为1.2.1,README.md中oak_nest@v1.2.1会对应替换:执行tag或tag patch后,替换为oak_nest@v1.2.2;执行tag minor后,替换为oak_nest@v1.3.0;执行tag major后,替换为oak_nest@v2.0.0。版本号不以v开头假设你推送的tag版本号不想以v开头,那么可以添加一个参数-L或者--local:tag patch -L 添加自定义信息打标签时默认提交信息是版本号,如果想自定义信息,可以使用-M或者--msg:tag minor -M "feat: change some" 更新所有目录的README.md文件如果想要更新所有目录的README.md文件,可以使用-D或者--deep:tag -D 本地调试如果你的另一个项目中使用这个模块,那可能会遇到调试问题,如果每次发布都推送一次,那太不合理了(虽然你可以这么干,github的hooks也是秒级的)。你可以在本地启动一个服务器。全局安装file_serverdeno install --allow-net --allow-read https://deno.land/std@0.125.0/http/file_server.ts 在你的模块目录下,执行命令:$ file_server . Downloading https://deno.land/std@0.125.0/http/file_server.ts... [...] HTTP server listening on http://0.0.0.0:4507/ 这样打开http://0.0.0.0:4507/就可以看到当前目录的文件了。你在另一个工程里使用这个地址就可以进行调试了。需要注意的一点是,你原来是这样运行代码的:deno run -A aa.ts 现在需要换成:deno run -A --reload=http://localhost:4507/ aa.ts 不然永远用的是旧的代码。当然,你也可以用vscode安装一个插件live server,怎么使用就不赘述了。总结本文介绍了如何利用GitHub的hooks来发布一个Deno模块到deno.land上,如何快速更新版本,如何本地调试。
API版本管理方案通常来说,API的版本管理有2种方案:1、URI以v1或v2这种版本号为前缀:请求:http://localhost:8080/v1/student 响应:{"name":"aa bb"} 请求:http://localhost:8080/v2/student 响应:{"name":{"firstName":"aa","lastName":"bb"}}或者将版本以参数形式拼接到后面:请求:http://localhost:8080/student/param?version=v1 响应:{"name":"aa bb"} 请求:http://localhost:8080/student/param?version=v2 响应:{"name":{"firstName":"aa","lastName":"bb"}}优点:不破坏现有浏览器缓存机制,服务端、中间层也不需要额外考虑headers的缓存控制。GET请求可以直接在浏览器执行。如果您有非技术消费者,那么基于URI的版本将更容易使用。缺点:URI污染。API文档。如何让文档生成理解两个不同的url是同一服务的版本?2、自定义Header例:请求:http://localhost:8080/student/header header:X-API-VERSION = 1 请求:http://localhost:8080/student/header header:X-API-VERSION = 2优点:如果是已经上线的服务,不会影响现有的客户端(默认没有版本)。没有URI污染。缺点:滥用请求头。请求头并不是为版本控制而设计的。缓存。不能仅仅基于URL缓存,需要考虑特定的请求头。即使是GET请求,也不能在浏览器直接执行。API版本控制原则一旦发布,API应被视为契约,如果没有新版本,则不能被替换。服务端上线后,客户端新版本才能上线。必须所有API使用者迁移后,旧版本的API才能废除。API的输入参数或输出的数据结构如果有变化,或者JSON数据中有调用者使用的字段修改、删除,即视为破坏性变更,需要增加版本。开发实践通常来说,URI的方式更符合我们的直觉。Node.js以nestjs工程为例:文件路径中带版本号。比如user.controller.v1.ts、user.controller.v2.ts,或者v1/user.controller.ts。推荐使用装饰器统一处理版本的技术细节。比如@ApiVersion('v1'),这样在ApiVersion这个装饰器中不管是变更URI还是自定义Header,都与每个Controller的方法的业务代码无关。如果想要同一个函数支持多个路由,可以使用alias,使用方法参见github.com/nestjs/nest…或github.com/nestjs/nest…。Deno如果是Deno的oak_nest工程,请更新到v1.10.2版本,Controller、Get/Post等装饰器都添加了可选参数alias和isAbsolute,比如:@Controller("user", { alias: "/v1/user/", }) export class UserController { @Get("/info") info(context: Context) { context.response.body = "info"; } } 或者:@Controller("/user") export class UserController { @Get("/info", { alias: "/v1/user/info", }) info() {} }
为什么要切换Git用户呢?通常来说,我们在GitLab上使用工作邮箱,而GitHub、Gitee等网站使用我们的个人邮箱。我们的电脑上,Git账号一般都是全局设置个人邮箱,在公司的项目里,再设置工作邮箱。而有时候,有的电脑上是配置的这个,有的配置是那个,在提交记录里就有些混乱了,个人项目还好,团队协作中就显得很不专业了。怎么切换Git用户呢?切换Git用户的几种方法方法1:Git命令我们知道,全局设置用户是这样的:git config --global user.name XXX git config --global user.email XXX@xxx.com 那么某个工程设置用户只需要去掉--global即可:git config user.name XXX git config user.email XXX@xxx.com 但每次都执行这两句命令也挺麻烦的,有没有更便捷的方法呢?方法2:sh脚本最简单是建个sh的脚本文件,每次都执行一次。#!/bin/sh git config user.name "XXX" git config user.email "XXX@xxx.com" 缺点是每次都得把这个文件复制一遍,那样跟把两个命令合并成一行有什么区别?方法3:Deno脚本另一种就是用Deno写个脚本,在子任务里执行这两句:export async function runTask(str: string): Promise<string> { const [cmd, ...args] = str.split(" "); const command = new Deno.Command(cmd, { args, }); const { code, stdout, stderr } = await command.output(); const te = new TextDecoder(); if (code !== 0) { throw new Error(te.decode(stderr)); } return te.decode(stdout); } export async function runTasks(arr: string[]) { for (const str of arr) { await runTask(str); console.log(`运行任务success:${str}`); } } if (import.meta.main) { await runTasks([ `git config user.name "XXX"`, `git config user.email "XXX@xxx.com"`, ]); } 我们使用deno install把它安装为全局的命令,然后就可以到处使用了。缺点是这个文件在本地还好,如果把它发布到CDN上,里面再包含我们的用户名,有种暴露隐私的感觉。如果Git全局设置的是工作邮箱,用它来切换个人邮箱可能比较合适。方法4:Git includeifGit配置文件支持include和includeif关键字来包含其它配置文件,对我们这种需求,可以使用includeif。首先,打开当前用户的根目录下,找到.gitconfig文件:$ cat ~/.gitconfig # 下面是我的文件内容,之前git config --global user.name修改的就是这里 [user] name = jw397 email = jw397@126.com [init] defaultBranch = main 在下面加入以下代码:[includeIf "gitdir:~/wk/gitlab/"] path = .gitconfig-gitlab 在当前目录下,新建一个.gitconfig-gitlab文件:$ vim .gitconfig-gitlab [user] name = 测试 email = test@test.com 这样之后在~/wk/gitlab/这个目录下,所有的项目默认就是新配置的用户名和邮箱了,除非你单独使用git config user.name设置过,在当前目录下使用这个命令,本质上修改的是.git/config文件。手写一个CLI进行切换本来上面的方法4已经很完美了,为什么还要折腾一个新的CLI呢?因为我是按代码类型分的文件夹,这样的话需要重新整理一遍(虽然也不是不可以):上面Deno脚本的方案,用户名、邮箱如果修改为可以动态配置的,就比较合理了。对于这个简单的功能,我们可以选择将数据存储到JSON文件里,也可以存储到LocalStorage里,当然,也可以使用目前Deno正在主推的本地数据库Deno.kv。刚好我想试用一个Deno.kv,所以决定模仿nrm实现一个简单的切换Git脚本管理器,就叫gum(Git User Manager)吧。命令无非是增(add)、删(del)、改(改就算了,直接用add替换)、查(list)、切换(use)。数据存储存储3个字段,使用alias作为key值:interface GitUser { username: string; email: string; alias: string; // 用作key值 } 先打开Deno.kv:import os from "node:os"; import { join } from "https://deno.land/std@0.202.0/path/mod.ts"; import { ensureFile } from "https://deno.land/std@0.202.0/fs/ensure_file.ts"; const PREFIX = "git_user"; const kvPath = join(os.homedir(), ".deno", "kv", PREFIX); await ensureFile(kvPath); // 如果不存在则创建 const kv = await Deno.openKv(kvPath); // 指定路径后,版本升级数据也不会丢失 之所以这么复杂,是因为需要指定kv文件的存储路径,如果不指定的话,默认位置在Deno的缓存目录的location_data/${hash}/kv.sqlite3,后续版本升级hash值变化,数据就丢失了。再封装几个函数:async function getUserList(): Promise<GitUser[]> { const entries = kv.list<GitUser>({ prefix: [PREFIX] }); const users: GitUser[] = []; for await (const entry of entries) { users.push(entry.value); } return users; } async function addUser(user: GitUser) { await kv.set([PREFIX, user.alias], user); } async function getUser(alias: string): Promise<GitUser | null> { const entry = await kv.get<GitUser>([PREFIX, alias]); if (entry) { return entry.value; } return null; } async function removeUser(alias: string) { await kv.delete([PREFIX, alias]); } add我们先实现add命令,把它制作为可交互的,也就是让用户自己输入用户名、邮箱和alias。为了方便,我们选用cliffy来进行交互。import { Input } from "https://deno.land/x/cliffy@v1.0.0-rc.3/prompt/mod.ts"; import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts"; const add = new Command() .description("Add one git user.") .action(async () => { const name: string = await Input.prompt({ message: `Set a username`, }); const email: string = await Input.prompt({ message: `Set a email`, }); const alias: string = await Input.prompt({ message: `Set an alias`, default: name, }); console.log(`${name} ${email} ${alias}`); }); await new Command() .name("gum") .description("Change the git user in current project") .command("add", add) .parse(Deno.args); 执行命令:$ deno run mod.ts add ? Set a username › aa ? Set a email › bb ? Set an alias (aa) › cc aa bb cc 上面已经写好了添加的接口,所以直接调用即可:const user: GitUser = { username: name, email, alias, }; await addUser(user); list增加之后,我们需要显示所有用户信息与当前用户。先调用Git命令获取当前用户名称,再匹配出来记录的用户信息:async function getCurrentUser(): Promise<GitUser | undefined> { const username = await runTask("git config user.name", false); const allUsers = await getUserList(); return allUsers.find((user) => user.username === username.trim()); } 将用户信息绘制成一个表格:import { Row, Table, } from "https://deno.land/x/cliffy@v1.0.0-rc.3/table/mod.ts"; async function showGitList() { const users = await getUserList(); const currentUser = await getCurrentUser(); const rows = users.map((user) => { const isCurrent = currentUser?.alias === user.alias; return new Row( user.alias, user.username, user.email, isCurrent ? "✓" : "", ).border(); }); new Table() .header(Row.from(["Alias", "UserName", "Email", "Current"]).border()) .body(rows) .render(); } 这样list命令就有了:const list = new Command() .description("List all git users.") .action(showGitList); await new Command() .name("gum") .default("list") .command("list ls", list) .parse(Deno.args); 效果是这样的:$ gum # 等同于 $ gum list ┌───────┬──────────┬───────────────┬─────────┐ │ Alias │ UserName │ Email │ Current │ ├───────┼──────────┼───────────────┼─────────┤ │ jw │ jw397 │ jw397@126.com │ ✓ │ ├───────┼──────────┼───────────────┼─────────┤ │ test │ test │ test@test.cn │ │ └───────┴──────────┴───────────────┴─────────┘ use下来就该切换用户信息了,直接调用这俩Git命令即可:async function changeGitUser(username: string, email: string) { await runTasks([ `git config user.name ${username}`, `git config user.email ${email}`, ]); } 添加use命令:const use = new Command() .description("Change current git user.") .arguments("<alias:string>") .action(async (_options: unknown, alias: string) => { const user = await getUser(alias); if (!user) { console.log(error(`Not find user by alias: ${alias}`)); return; } console.log(`${user.alias} ------ ${user.username} ---- ${user.email}`); await changeGitUser(user.username, user.email); console.log(info("Git user changed")); await showGitList(); }); await new Command() .name("gum") .default("list") .command("list ls", list) .command("add", add) .command("use", use) .parse(Deno.args); del删除时需要注意下如果是当前用户,不允许删除。const del = new Command() .description("Remove one git user.") .arguments("<alias:string>") .action(async (_options: unknown, alias: string) => { const user = await getUser(alias); if (!user) { console.log(error(`Not find user by alias: ${alias}`)); return; } const currentUser = await getCurrentUser(); if (currentUser?.alias === alias) { console.log(error(`Can't remove current git user`)); return; } console.log(`${user.alias} ------ ${user.username} ---- ${user.email}`); await removeUser(alias); console.log(info("Git user has been removed")); await showGitList(); }); await new Command() .name("gum") .default("list") .command("list ls", list) .command("add", add) .command("delete del remove", del) .command("use", use) .parse(Deno.args); upgrade本来上面就算结束了,但看cliffy还提供了一个更新的处理,就把它加进来了:import { DenoLandProvider, UpgradeCommand, } from "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts"; import pkg from "../../deno.json" with { type: "json" }; const upgrade = new UpgradeCommand({ main: "cli/git/user_change.ts", args: [ "--allow-net", "--allow-run", "--allow-env", "--allow-write", "--allow-read", "--unstable", ], provider: new DenoLandProvider({ name: "jw_cli" }), }); await new Command() .name("gum") .version("v" + pkg.version) .command("upgrade", upgrade) .parse(Deno.args); 这里有几处需要注意:version是发布到deno.land的版本号,我的发布都带了v,记录在deno.json里,所以需要读取这个文件upgrade的main参数是这个脚本的相对路径,args是脚本需要的权限,DenoLandProvider的参数是发布到deno.land中的包名。底层是调用的这个接口http://cdn.deno.land/jw_cli/meta/versions.json,会得到所有的包的版本信息:{"latest":"v0.9.2","versions":["v0.9.2","v0.9.1","v0.9.0","v0.8.0","v0.7.0","v0.6.0","v0.5.2","v0.5.1","v0.5.0","v0.4.0","v0.3.1","v0.3.0","v0.2.7","v0.2.6","v0.2.5","v0.2.4","v0.2.3","v0.2.2","v0.2.1","v0.2.1","v0.2.0","v0.1.6","v0.1.5","v0.1.4","v0.1.3","v0.1.2","v0.1.1","v0.1.0","v0.0.10","v0.0.9","v0.0.8","v0.0.7","v0.0.6","v0.0.5","v0.0.4","v0.0.3","v0.0.2","0.0.2","v0.0.1"]} 这样gum upgrade -l就会展示所有版本:可以使用gum upgrade --version v0.9.2来降级到指定版本。gum --help每次都会请求这个版本,如果有新版本,会提示你更新:Usage: gum Version: v0.9.2 (New version available: v0.9.3. Run 'gum upgrade' to upgrade to the latest version!) deno: 1.37.0 v8: 11.8.172.3 typescript: 5.2.2 Description: Change the git user in current project Options: -h, --help - Show this help. -V, --version - Show the version number for this program. Commands: list, ls - List all git users. add - Add one git user. delete, del, remove <alias> - Remove one git user. use <alias> - Change current git user. upgrade - Upgrade gum executable to latest or given version. 不过,如果新的版本有权限申请,这样升级后不会成功,每次命令行都会提示让你添加允许:$ gum ┌ ⚠️ Deno requests env access to "HOME". ├ Run again with --allow-env to bypass this prompt. └ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) 这时就需要手动重新安装下了。安装我们的CLI就算完成了。我本地开发时,使用命令安装:deno install --allow-run --allow-net --allow-read --allow-write --allow-env --unstable -n gum -f ./cli/git/user_change.ts 而现在可以使用deno.land上的文件:deno install --allow-run --allow-net --allow-read --allow-write --allow-env --unstable -n gum -f https://deno.land/x/jw_cli@v0.9.3/cli/git/user_change.ts 有兴趣的同学可以试试。总结本文介绍了几种切换Git用户的方法,核心说起来只有2种:在工程下调用git config user.xxx修改当前工程的配置项(.git/config文件)。使用includeif,读取全局配置时,某路径下读取另一个配置文件。我们使用Deno封装了一个CLI,可以便捷地在某工程下切换Git用户,更多目的是为了熟悉Deno.kv与CLI交互。Deno开发CLI的优势还是很大的,不像Node.js一样需要做额外的配置,TypeScript也是开箱即用,又有完善的权限管理,对于第三方的代码也能直接查看源码不用担心投毒,推荐大家平时寻找合适的场景玩一玩。