HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 1. HTML5 结构语义化: 用正确的标签做正确的事情。p段落、h标题、aside边栏、main主要内容等... 对开发者:便于维护、较好的呈现内容结构和代码结构、易于阅读; 对浏览器:有利于SEO,搜索引擎爬虫依赖于标签、方便其他设备解析、提高可访问性 2. HTML5 新特性: 新增:主要是关于图像、位置、存储、多任务等功能的增加 媒体播放的 video/audio 元素 本地存储 localStorage长期(浏览器关闭不消失) / sessionStorage会话(数据浏览器关闭删除) 语义化更好的内容元素(article,footer,header,nav,section) 表单控件(calendar、date、time、email、url、search) 新的技术(webworker,websocket,geolocation) 移除:纯表现的元素(basefont, big, center, font, s, strike, tt, u)、可用性有负面影响的元素(frame, frameset, noframes) 3. cookies, sessionStorage, localStorage 区别: cookies(最大4k): 标识用户身份,存储在用户本地终端(通常经过加密),始终自动同源http请求中携带 sessionStorage(5M或更大): 数据在当前浏览器窗口关闭后自动删除 localStorage(5M或更大): 持久化存储数据,浏览器关闭不会丢失除非主动删除 4. 浏览器的渲染机制的步骤: 处理HTML并构建DOM树 → 处理CSS构建CSSOM树 → 将DOM与CSSOM合并为一个渲染树 → 布局并计算每个节点的位置 → 调用GPU绘制合成图层并显示在屏幕上 注意:CSSOM树会阻塞渲染,因此CSS选择器要尽量层级扁平,减少过度层叠;JS文件要按需加载,不在首页去全部加载 5. 重绘(Repaint) 和 重排或回流(Reflow) 重绘: 节点需要更改 外观(不影响布局),如改变color就是重绘 回流: 布局或者几何属性(影响布局) 改变就是回流/重排 减少重绘和回流: 1)批量操作DOM: DocumentFragment 2)绝对定位: 复杂动画效果,绝对定位使其脱离文档流 3)CSS3硬件加速:(GPU加速) transform/opacity/filters 这些动画不会引起回流重绘 6. data:属性的用法,有何优势? data-* 的值的获取和设置,2种方法: 1)[无兼容性问题,不优雅] getAttribute() 获取 data-属性值; setAttribute() 设置 data-属性值 2)[有兼容性问题,更优雅] HTML5新方法:如 data-jerry, dataset.jerry 获取属性值; dataset.jerry="张三" 设置属性值 优势:自定义的数据可以让页面拥有更好的交互体验
|
CSS(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| 1. display:none; 与 visibility:hidden; 的区别 相同:都可以让元素不可见 区别: 1)display:none 元素完全从渲染树中消失,渲染时不占空间;visibility:hidden 不会让元素从渲染树消失,渲染时占空间,内容不可见 2)修改常规常规流中的元素 display 会造成 重排(★) 、修改 visibility 属性只会造成本元素的 重绘(★) 2. 外边距折叠 相邻的两个或多个 margin 会合并成一个 margin,叫做外边距折叠。规则如下: 1)两个或多个相邻的普通流中的【块元素垂直方向】上 margin 会折叠 2)【浮动】元素或【inline-block】元素或【绝对定位】元素 margin 不会和垂直方向上的其他元素 margin 折叠 3)创建了 块级格式上下文(BFC) 的元素,不会和它的子元素发生margin折叠 3. z-index是什么?position的值什么时候可以触发? z-index 设置元素的堆叠顺序。 注意:z-index 仅能在定位元素上有效,position = relative / absolute / fixed / sticky 时才会触发层级设置。 4. box-sizing 的有效值以及对应的盒模型规则: 内边距和边框(★) 1)box-sizing:content-box; 默认值,宽度和高度分别应用到元素的内容框;在宽度和高度 之外(★) 绘制元素的内边距和边框。 2)box-sizing:border-box; 内边距和边框都在已设定宽度和高度 之内(★) 进行绘制。内部元素宽高会被挤压,使我们更容易控制元素宽高。 3)box-sizing:inherit; 继承父元素的 box-sizing 属性的值。 5. 移动端适配怎么做? 1) meta标签viewport(视口适配) 移动端初始视口大小默认是 980 px,为了解决页面缩放体验问题,代码头部会加入一行 viewport 原标签,告诉浏览器视口的宽度为设备的宽度。 属性含义: initial-scale: 第一次进入页面的初始比例 minimum-scale: 允许缩小最小比例 maximum-scale: 允许放大最大比例 user-scalable:允许使用者缩放,1 or 0(yes or no) 2) 图片适配(★) img { max-width: 100%; } 此时图片会自动缩放,同时图片最大值为自身的100% 为什么不用 img { width: 100%; } ? 当容器大于图片宽度时,图片会无情的拉伸变形。 3) 媒体查询(★) 自动检测屏幕宽度,然后加载相应的CSS文件或样式,语法 @media screen and (min-width:1200px) { 样式代码 } 4) 动态rem方案(★) rem 和媒体查询配合实现响应式布局。 - px:像素,屏幕上显示数据的最基本的点,HTML中默认单位; - em:相对于【父元素的 font-size】 的百分比大小; - rem:相对于【根元素的 font-size】的大小,如 css 中 html{ font-size: 100px; } js 中动态设置 fontSize,以 iphone6的750px为标准宽度等比缩放设计稿,使用375px进行计算,计算方式 document.documentElement.style.fontSize = document.documentElement.clientWidth / 375 * 100 + 'px'; 6. CSS3中的 transform? transition? animation? 区别是什么? transform: 静态样式,对元素进行移动translate、缩放scale、旋转rotate、扭曲skew、矩阵变形matrix transition: 样式过度,从一种效果改变为另一种效果,复合属性,分别可以设置 CSS属性、过度效果时间、速度曲线、过渡开始的延迟时间 animation: 动画属性,由 `@keyframes` 来描述每一帧的样式 区别: 1) transform 仅描述元素静态样式,尝尝配合 transition 和 animation 使用 2) transition 通常和 hover 等事件配合使用,animation 是自发的,立即播放 3) transition 可与 js 配合使用,js设定要变化的样式,transition 负责动画效果 4) animation 可设置循环次数 5) animation 可设置每一帧的样式和时间,transition 只能设置头尾 7. 父元素和子元素的宽高不固定,如何实现水平垂直居中? 方式1: 定位和位移(★) 父元素相对定位,子元素绝对定位 + left:50% + top: 50% + transform: translate(-50%, -50%) 位移 方式2: 弹性布局(★) 主轴和侧轴居中,即 display:flex; justify-content:center; align-item:center; 8. 假设高度默认100px,写出三栏布局、其中左栏、右栏各位300px,中间自适应 方式1: 浮动(盒子1 left左浮动、盒子2 right右浮动、盒子3 center自动填满中间)--必须是这个顺序! 方式2: 绝对定位(盒子1定位left0、盒子3定位right0) 方式3: 弹性布局(盒子1宽度300,盒子3宽度300,盒子2设置flex:1) 9. BFC, 块级格式上下文 BFC(Block Formatting Context,块级格式上下文)是 CSS 中一种独立的渲染环境 / 布局规则。 BFC 的元素===“封闭的独立盒子”,盒子内部元素布局都不会影响到外部元素,外部元素也不会干扰盒子内部布局,且盒子内遵循独立的布局规则。 只要元素满足以下任意一个条件,就会创建 BFC: 1)根元素(<html>) 2)浮动元素(float 不为 none,比如 float: left/right) 3)绝对定位、固定定位元素(position: absolute/fixed) 4)行内块元素(display: inline-block) 5)表格单元格、表格标题(display: table-cell/table-caption) 6)溢出元素(overflow 不为 visible,比如 overflow: hidden/auto/scroll【最常用】) 7)弹性、网格容器(display: flex/grid) 应用场景:(触发 BFC 最常用方法,简单无副作用,添加 'overflow: hidden') 1) 解决父元素高度塌陷(★) 父元素里子元素设置浮动后,父元素会失去高度(塌陷),无法包裹子元素。【解决】给父元素触发BFC(最常用 overflow: hidden) 2) 解决垂直方向margin重叠问题(★) 两个相邻的块级元素,垂直方向的 margin 会 “合并”(取最大值,而非相加)。【解决】给其中1个元素包裹一层div触发 BFC 的父元素 3) 阻止浮动元素覆盖普通元素(★) 浮动元素会覆盖旁边的普通块级元素,导致布局错乱。【解决】给被覆盖的元素(如浮动的div盖住了 p 标签)p标签触发 BFC 10. Flex 布局和 Grid 布局 各自核心场景是什么?列举实际案例 Flex: 弹性布局,一维布局,单方向布局样式; 场景: 布局方向单一、内容动态伸缩、快速实现对齐(如移动端组件-'导航栏'、'卡片流'等) Grid: 网格布局,二维布局,行与列布局样式; 场景: 需精确控制行列关系、构建复杂响应式框架、实现跨维度对齐(如桌面端整页布局-'圣杯布局'、'数据看板'等) 混合使用: 场景: Grid 搭建宏观框架,Flex 处理微观组件,兼顾效率与灵活性 11. 同一页面混合使用 Flex 和 Grid 做适配的案例?比如 PC 端用 Grid 做数据看板的二维布局,移动端适配时怎么快速切换成 Flex 的一维布局? 看板布局: PC端Grid(二维看板)、移动端媒体查询切换Flex(一维流式),看板子项设置 flex:1 在 Grid/Flex下均生效自适应 12. 有没有用到 CSS 媒体查询之外的适配技巧? 1) 容器查询(Container Queries): @container (max-width: 768px){...} 2) 动态尺寸 clamp() :grid-template-columns: repeat(auto-fit, minmax(clamp(280px, 100%, 350px), 1fr)),小屏自动合并列
|
圣杯布局

1 2 3 4 5
| <div class="container"> <div class="sidebar">Sidebar</div> <div class="main">Main Content</div> <div class="sidebar">Sidebar</div> </div>
|
① flex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <style> .container { display: flex; justify-content: center; } .main { width: 100%; background-color: #f0f0f0; } .sidebar { width: 200px; background-color: #ccc; } </style>
|
② grid
1 2 3 4 5 6 7 8 9 10 11 12 13
| <style> .container { display: grid; grid-template-columns: 200px 1fr 200px; gap: 10px; } .main { background-color: #f0f0f0; } .sidebar { background-color: #ccc; } </style>
|
双飞翼布局

1 2 3 4 5 6 7 8
| <div id="main"> <div id="content"> <p>hello world</p> <p>hello world</p> </div> </div> <div id="left">left</div> <div id="right">right</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <style> div { height: 100px; } #main { width: 100%; background-color: red; float: left; } #content { margin-left: 200px; margin-right: 200px; } #left { width: 200px; background-color: blue; float: left; margin-left: -100%; } #right { width: 200px; background-color: yellow; float: left; margin-left: -200px; } </style>
|
JavaScript(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| 1. JS内置对象: 数据封装类对象: Object, Array, Boolean, Number, String 其他对象: Function, Arguments, Math, Date, RegExp, Error ES6新增对象: Symbol(标识唯一性的ID), Map, Set, Promises, Proxy, Reflect 2. 如何最小化或不触发 重绘(repaint) 和 重排或回流(reflow)? 1)对元素复杂操作时,先隐藏(display:none),操作完再显示 2)创建多个DOM节点时,使用 DocumentFragment(轻量级虚拟DOM容器,独立于文档流之外,批量操作DOM) 创建完后一次性加入 document 3)缓存 Layout 属性值,如 `var left=elem.offsetLeft;` 这样多次left只产生一次回流 4)尽量避免用 table 布局 3. JS作用域链? 全局函数无法查看局部函数的内部细节,局部函数可以查看其上层的函数细节,直至全局细节; 当前作用域函数没有找到属性或方法,会向上层作用域[[Scoped]]查找,直至全局函数,这就是作用域链。 4. 数据请求?XHR, fetch, jsonp XHR: XMLHttpRequest(), xhr.abort(); fetch: w3c新的标准 fetch().then(res => res.json()).then(res => console.log(res)) jsonp: 解决跨域,且只有get请求;原理是动态创建 script 标签,请求数据没有限制;返回值通过 callback 函数的入参方式给到。 5. 跨域和同源策略 同源:同协议、同域名、同端口 才能进行数据请求和响应交互。非同源就会触发CORS跨域。 1) JSONP(★):利用动态创建 script 标签,请求数据没有限制,解决跨域。 2) CORS(★):依赖服务端对前端的请求头进行放行,不做限制(Access-Control-Allow-Origin:*) 3) 反向代理(★):前端访问无跨域问题的代理服务器,代理服务器再去访问目标服务器 6. 面向对象 1)构造函数: 通过函数方法创建一个对象,可以 new 一个该对象 2)原型对象 prototype: Test.prototype.getName = function() {} 缺点是:原型很容易被覆盖而丢失原有的功能 3)继承: Test.call(this, ...) 或 Test.apply(this, ...) 然后原型继承 Test2.prototype = Test.prototype 4)原型链: 任何一个实例,通过原型链找到它上面的原型,该原型对象中的方法和属性,可以被所有的原型实例共享。 7. 闭包:实际开发中使用到的闭包 函数内部返回一个函数(★),而这个函数又被外界使用,导致该函数和返回的函数都不会被垃圾回收,此用法称之为`闭包`。 使用多了或者使用不当可能会导致【内存泄露】。 场景1: 函数防抖(搜索查询),即 input 绑定一个延迟500ms定时器去延时请求搜索查询来实现防抖,而不是所有输入都请求数据 场景2: 函数节流(图表自适应),即 resize 用于 echart 重新渲染图标大小时,拖动窗口超过如1s时,才重新设置 echarts 的 option渲染图标 场景3: 循环添加事件(ul li),即 ul li 标签遍历 li 添加事件时,遍历会瞬间执行完,使用闭包可以让每个点击对应每个事件,而不是直接全部执行完毕 8. 数组去重: Array.from(new Set([1, 2, 2, 3, 3, 4])) 9. 数组合并: 1)concat 2)[...a, ...b]展开运算符 3)[a, b].flat()扁平化 10. cookie 和 session - 【登陆鉴权如何实现的?】 cookie: 客户端向服务端登陆的时候,服务端通过响应头去set-cookie,在浏览器注入cookie(可能携带隐私敏感信息 -- 可能会被模拟cookie即模拟登陆 session: 服务端建立用户信息表和生命周期机制,服务端在客户端登陆时将此信息返回为set-cookie,客户端存储到cookie或localStorage均可 每次请求携带该验证信息,服务端去验证是否有效或过期 一般是 cookie 中存 sessionId --或-- localStorage 中存储 token 11. 协商缓存和强缓存的区别 相同:都是浏览器对静态资源文件的缓存机制 不同: - 强缓存:客户端直接查看本地的缓存文件是否过期,如果没有过去就直接取用。【不涉及服务端交互】 依靠响应头上的 expires(绝对时间) 和 cache-control(相对时间) 两个值来对比 - 协商缓存:客户端去询问服务器对应的文件是否有更新,如果有更新才会重新请求。 依靠 last-modified(最后更新时间) 和 etag(内容变更标识) 来确认文件是否有更新 【一般做法】在静态文件的 url 如 xxx.js 后面追加了 '?t=时间戳' 参数,可以阻止浏览器缓存,每次都可以获取到最新的信息 12. HTTP与HTTPS HTTP:明文传输,TCP传输交换3个包,默认端口号 80 HTTPS:加密传输,TCP传出交换3+9=12个包,默认端口号 443,TLS加密即通过证书对双方进行身份验证 13. 跨域问题解决: 1)一般线上环境不会有跨域问题,因为前后端都部署在同一台服务器。 2)本地开发可能会有跨域,比如本地访问测试或开发服务器的接口,基本都是后端设置允许跨域为 * ,有时前端也需要配置脚手架的 devServer 中配置 Proxy 3)极少的情况下,项目中可能会访问一些第三方api,如定位、天气这些接口,可能会根据接口的情况使用 jsonp 进行跨域处理。 14. 事件循环机制: 1)所有同步任务在主线程上执行,形成一个【执行栈】 2)主线程之外,还存在一个【任务队列】,只要一步任务(setInterval, setTimeout, i/o...)有了结果,就在【任务队列】中放置一个事件 3)一旦执行栈中的所有同步任务执行完毕,系统就会读取【任务队列】,拿到队列的第一个任务,进入执行栈,开始执行 4)主线程不断重复步骤3) 15. 宏任务 与 微任务 宏任务:script 全部代码、setTimeout、setInterval、I/O、UI Rendering... 微任务:Promise、Process.nextTick(Node独有)... 16. HTTP状态码: 200 OK 正常返回信息 || memory cache '强缓存' 201 Created 请求成功并且服务器创建了新资源 202 Accepted 服务器已接收请求,但尚未处理 301 Moved Permanently 重定向-请求的网页已永久移动到新位置 302 Found 重定向-临时性重定向 304 Not Modified 自动上次请求后,请求的网页未修改过 '协商缓存' 307 Internal Redirect 重定向-内部重定向 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次请求 401 Unauthorized 请求未授权 403 Forbidden 禁止访问 404 Not Found 找不到任何与 URI 相匹配的资源 500 Internal Server Error 服务端错误 503 Service Unavailable 服务端无法处理的请求,服务不可用 17. 一个页面从输入URL到页面加载显示完成,这个过程都发生了什么? 1)【域名解析IP】:浏览器查找域名对应的IP地址(DNS查询:浏览器缓存->系统缓存->路由器缓存->ISP DNS缓存->根域名服务器) 2)【TCP三次握手】:浏览器向web服务器发送一个HTTP请求(TCP三次握手) 3)【重定向】:服务器301重定向(从 xxx.com 重定向到 www.xxx.com) 4)【请求】:浏览器跟踪重定向地址,请求另一个带www的网址 5)【处理请求】:服务器处理请求(通过路由读取资源) 6)【响应】:服务器返回一个HTTP响应(抱头中把Content-type 设置为 text/html) 7)【DOM构建】:浏览器进DOM树构建 8)【请求资源】:浏览器发送请求获取嵌在HTML中的资源(如图片、音频、视频、CSS、JS等) 9)【显示页面】:浏览器显示完成页面 10)【异步请求】:浏览器发送异步请求 18. CSRF跨站请求伪造 & XSS脚本注入攻击 CSRF:1)登陆受信任网站A,并在本地生成cookie; 2)在不登出A的情况下,访问危险网站B(利用了A网站的漏洞)【因为浏览器会自动携带cookie】 解决方案: 1)Token验证(最常用):前端携带token后端验证token 2)隐藏token在head请求头中 3)Referer验证:只接受本站请求,其他拦截掉 XSS:不需要做任何登陆认证,通过合法的操作,向你的页面注入脚本(如js代码发送你的明文cookie外部),即盗用cookie/破坏页面插入广告/doss攻击 解决方案: 1)编码:对用户输入的数据进行 HTML Entity 编码 2)过滤:移除用户输入的和事件相关的属性、移除用户输入的style、script、iframe节点 区别:CSRF是利用网站A本身的漏洞,去请求网站A的api; XSS是向网站注入js代码,然后执行js代码,篡改网站的内容
|
闭包应用
①函数防抖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <input type="text" id="mytext"> <script> var oInput = document.getElementById('mytext') function vibration(fn, delay) { let timer = null; return function () { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, arguments); }, delay); } } oInput.oninput = vibration(function () { console.log(this.value); }, 500) </script>
|
②函数节流
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> window.onresize = throttle(1000) function throttle(delay) { var date = Date.now(); return function () { console.log("resize") if (Date.now() - date >= delay) { date = Date.now(); console.log("1秒内只能触发一次") } } } </script>
|
③循环添加事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <ul> <li>111</li> <li>222</li> <li>333</li> </ul> <script> var oLis = document.getElementsByTagName('li'); for (var i = 0; i < oLis.length; i++) { oLis[i].onclick = (function (index) { return function () { console.log(index); } })(i) } </script>
|
宏任务与微任务
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> console.log(111); setTimeout(() => { console.log(555); }, 0); Promise.resolve().then(() => { console.log(333); }).then(() => { console.log(444); }) console.log(222); </script>
|

ES6 - ES13(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 1. ES6新增方法: let&const、解构赋值、箭头函数、模版字符串 Symbol、Map、Set常用数据类型 Proxy 重新定义了数据劫持的能力 Reflect 定义了一套标准化的数据操作方式 Promise解决异步嵌套回调地狱问题,定义异步逻辑3种状态 pending/fullfilled/rejected, 搭配then/catch/all/race和async+await语法糖 Generator函数,可以将异步逻辑划片执行 (大厂面试):同步遍历器,会返回一个遍历器对象,即可以一次遍历 Generator 函数内部的每一个状态 class类 modules模块化 2. var & let & const 区别: var: 存在声明提前,不存在作用域限制 let: 不会声明提前, 存在暂时性死区,存在块级作用域限制 const: 常量定义,无法修改 3. 箭头函数: `()=>{}` ES6推出的,低版本浏览器有兼容性问题。箭头函数没有自己的 this 指向(默认指向window) 4. 解构赋值: let { type, payload } = data 5. ...展开合并: [...arr1, ...arr2] {...obj1, ...obj2} 6. 异步处理方案: 1)回调函数 2)Promise - 解决回调地狱(默认pending状态,resolve()后进入fullfilled状态进入.then(),如果rejected则进入.catch()) - Promise.all() 所有异步都结束(fullfilled)进入.then - Promise.race() 所有异步任一返回结果(fullfilled)就进入.then() 或 任一被rejected()则就进入.catch() - Promise.any() 所有异步都变成rejected()才会结束 3)generator 生成器 yield 4)async await 7. class 类:与Java的类写法基本一致,支持箭头函数、构造函数、babel-loader 8. modules 模块化规范: 1)导出 export default aaa 导入 import obj from './a' 2)导出 export {bbb} 或 export var bbb = function() {} 导入 import {bbb} from './b' AMD - 前端异步加载 - 提前下载,提前加载 require.js CMD - 异步加载 - 提前下载,按需加载 - sea.js CommonJS - 同步加载(webpack) --> require('./b') =>module.exports =>exports ES6可以导入某几个方法;CommonJS是默认导入一个文件 9. Generator 异步遍历器 场景:一个数组中均为异步任务,在 for 循环中进行挨个按顺序执行
|
Generator 异步遍历器(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <script> function timer(t) { return new Promise((resolve, reject) => { setTimeout(() => { resolve("data-" + t); }, t); }) } async function *gen() { yield timer(1000) yield timer(2000) yield timer(3000) } async function test() { let g = gen() let arr = [g.next(), g.next(), g.next()]; for await (let item of arr) { console.log("start-", Date.now()); console.log(item); console.log("end-", Date.now()); } } test(); </script>
|
Vue(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
| 1. 单项数据流 & 双向数据绑定 单向数据流:修改一边,另一边不会同步改变 双向数据绑定:数据或视图任何一个发生改变,另一个都会实时改变 2. Object.defineProperty() 有什么缺点 1)无法监听 es6 的 Set、Map 变化 2)无法监听 class 类型的数据 3)属性的新加或删除也无法监听 4)数组元素的增加和删除也无法监听 3. MVC/MVP/MVVM MVC: Model-View-Controller (观察者模式通过View,强依赖Model,无法组件化无法复用) MVP: Model-View-Presenter (View不依赖Model,View可以组件化,View-Model需要手动同步,维护困难) MVVM: Model-View-ViewModel (双向绑定机制,提高代码可维护性和复用性) 4. 生命周期 vue2: beforeCreate/created, beforeMount/mounted(操作DOM等), beforeUpdate/updated, beforeDestroy/destroyed vue3: setup/setup, onBeforeMount/onMounted(操作DOM等), onBeforeUpdate/onUpdated, onBeforeUnmount/onUnmounted 5. Vue响应式数据原理? 响应式原理-vue2(如果是 Vue3 则是通过 Proxy 代理对象的方式拦截,拦截面更广): 1)利用 Object.defineProperty() 的方法里的 【setter 和 getter】 方法的 观察者模式(拦截)。 2)组件初始化会【给每个data属性注册 setter和getter】,然后再 new 一个自己的 【watcher】 对象,它会立即调用【render函数生成虚拟DOM】 3)render渲染时就会需要data的属性值,就会遍历所有的 watcher 对象,对虚拟DOM会进行新旧对比diff算法,以最小patch补丁的方式更新DOM Object.defineProperty-vue2 与 Proxy-vue3 优劣对比? - Object.defineProperty: 遍历对象属性直接修改,兼容性好,支持IE9,无法监听数组改变、class改变、Map、Set结构改变。 - Proxy: 可以直接监听对象而非属性,监听数组的变化,超过13种拦截方法,返回的是新对象,只操作新的对象达到目的,有浏览器兼容性问题 6. Composition API组合式API有哪些新的体验,为什么需要这个? 1)可读性和可维护性高,类似 react 的 hook 写法 2)更好的重用逻辑代码,在 Option API中只能通过 Mixins重用逻辑代码,容易发生命名冲突且关系不清晰 3)解决生命周期函数包含不相关的逻辑,又不得不把逻辑分离到了不同方法中的问题 7. Vue 对比 jQuery jQuery: 专注视图层,通过直接操作 DOM 去实现页面的逻辑渲染 Vue: 专注于数据层,数据双向绑定,最终表现在DOM层面,减少了DOM操作;组件化思想,使项目子集职责清晰,提高开发效率,方便重用和协同开发 8. Vue 单文件组件中定义全局CSS: <style>标签中不添加 'scoped' 属性,默认为全局 CSS 样式 9. $root, $parent, $refs $root: 访问父组件属性和方法,根父组件实例 $parent: 访问父组件属性和方法,最近一级的父组件实例 $refs: 子组件标签定义 ref 属性,在父组件中可以使用 $refs 访问子组件实例 10. 自定义指令(用的少):标签上可以添加一个 v-xxx 指令,就能够在插入到DOM后,对该节点进行相应的操作;分全局指令和局部指令 11. 过滤器(vue3已废弃):全局/局部过滤器,对数据进行处理,可以传递参数,使用方式参考 {{ message | capitalize }} 12. Vue单页面应用的优缺点: 优点: 1)内容改变不需要加载整个页面,响应性极高 2)没有页面之间的切换,不会出现白屏现象 3)服务器压力小,吞吐能力提高 4)良好的前后端分离 缺点: 1)首次加载耗时较多 - 需要路由懒加载处理 2)SEO问题,不利于搜索引擎收录 - 需要SSR服务端渲染处理 3)容易造成 CSS 命名冲突 4)前进、后退、地址栏、书签等需要程序管理,页面复杂度高,开发成本较高 13. Vue-router 使用 params 和 query 传参有什么区别? params: 需要'name'来引入,接参都是 this.$route.query 和 this.$route.params; 路由的一部分,必须要有 query: 需要'path'来引入,接参都是 this.$route.query 和 this.$route.params; 拼接在 url 后面的参数 14. keep-alive 的作用 vue的内置组件,可以使被包含的组件保留状态,避免重新渲染。即mounted/created等钩子函数只会在第一次进入组件时调用,再次切换回来将不会调用; 切换时做其他事情,需要使用【actived和deactived】这两个钩子函数(只有被 keep-alive 包裹时才有这两个生命周期钩子函数) 15. vue如何实现单页面应用:利用 hash 和 history 以及监听其路由变化 hash: 监听浏览器 onhashchange() 事件变化,查找对应的路由规则 history: 监听浏览器的 pushState 和 replaceState 两个API方法,使用 onpopstate() 监听URL变化 16. 列举vue中指令和用法: v-if 判断是否隐藏,用来判断元素是否创建 v-show 元素的显示和隐藏,类似css中的 display的block和none v-for 遍历数据,必须添加key,提高性能(虚拟DOM的diff算法打补丁更新DOM-性能核心) v-bind 绑定属性 v-model 双向绑定属性 17. 如何实现一个路径渲染多个组件? 通过命名视图<router-view>允许同一界面拥有多个【单独命名】的视图。没有取名字则为 default 比如: <router-view class="view left-sidebar" name="LeftSidebar"></router-view> <router-view class="view main-content"></router-view> <router-view class="view right-sidebar" name="RightSidebar"></router-view> 18. 如何实现多个路径共享一个组件? 只需要将多个路径的 component 字段的值设置为同一个组件即可 比如: const routes = [ { path: '/', component: Home }, { path: '/home', component: Home } ] 19. 如何监测动态路由的变化? 1)通过 watch 方法来对 $route 进行监听; 2)通过导航守卫的钩子函数 beforeRouteUpdate 来监听它的变化。 20. vue-router 中的 router-link 上的 v-slot 属性怎么用? 高阶API,通过作用域插槽暴露底层的定制能力。多数情况用在 NavLink 这样的自定义组件里。 比如: <router-link custom to="/films" v-slot="{isActive, navigate}"> <li :class="isActive ? 'jerry-active' : ''" @click="navigate">电影</li> </router-link> 21. Vue中如何去除 url 中的 # 将路由模式改为 'history' 模式。 问题: 页面路径访问时有些 url 会认为是资源请求,可能会出现 404 的情况。 解决: 服务端的 nginx 中配置为 默认没有找到的页面都重定向到 首页 index.html 22. $route 和 $router 的区别 $route: 获取路由信息,包含 name,meta,path,hash,query,params,fullPath,matched,redirectedFrom等 $router: 操作路由跳转,它是VueRouter实例,包含了路由跳转方法 push, go, replace, 钩子函数等 23. Vue路由守卫(路由拦截器) router.beforeEach(...) router.afterEach(...) 参数: to: 即将要进入的目标路由对象 from: 当前导航即将要离开的路由对象 next: 调用该方法后,才能进入下一个钩子函数 afterEach 24. Vue路由底层原理: Vue中利用数据劫持defineProperty在原型 prototype 上初始化了一些 getter,分别是router代表的当前Router实例、route代表的Router信息 在install中也全局注册了router-view、router-link,其中 Vue.util.defineReactive 是vue里观察者劫持数据的方法: - 劫持 _route 当出发 setter方法时,会通知到依赖的组件 接下来的init中,会挂载判断是路由的模式,是history或者是hash,点击行为按钮,调用 hashchange 或者 popstate 监听事件方法 同时更新_route,再触发 route-view 的重新渲染。 25. 路由懒加载: Vue Router 支持开箱即用的 `动态导入`,首屏加载时只关联需要的静态资源,如js、css等 26. 插槽用过吗?具名插槽还是匿名插槽? 都使用过。插槽就是预留一个组件的位置,将写在组件内的内容放入替换掉。写一个插槽就替换一次,多次就替换多次。 为了自定义插槽的位置可以给插槽取名,根据插槽名字进行内容插入替换。一一对应 27. Vue-loader 解释一下 解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,分别交给 Loader 去处理。 28. Vue和React的 diff 算法区别: 相同: 都是忽略跨级,即`同级比较`。 不同: 1)vue diff时调动patch函数,参数是 vnode 和 oldVnode 分别代表新旧节点 2)vue 对比节点: 当节点元素相同,但是classname不同,认为是不同类型的元素,删除重建;react 则认为是同类型节点,只是修改节点属性 3)vue 对比列表: 采用两端到中间的对比方式;react 则是从左到右依次对比 ——整体来说 vue 的执行效率更高效! 29. Vue 生命周期中 create 和 mount 的区别: create: 组件初始化阶段,主要完成数据观测data observer、属性和方法的运算、watch/event事件回调,还未生成真实DOM,无法获取DOM元素 mount: 从虚拟DOM到真实DOM挂载完成,此时html已经渲染出来了,可以直接操作 DOM 节点了 30. axios 的使用,实现登陆功能的流程? axios 是请求 api 资源的模块。大多数情况下需要封装请求和响应拦截器,来进行 登陆拦截(路由跳转、提示等) 31. computed 和 watch 的区别?watch 实现原理?watch 的写法有几种? 都是基于 data 中声明过 或 父组件传递的 props 数据 computed: 计算属性 1)支持缓存 2)不支持异步 3)默认走缓存 4)一个属性依赖其他属性计算时常用 5)属性值是函数默认走get方法,数据变化走set方法 watch: 属性监听 1)不支持缓存 2)支持异步 3)监听函数有2个参数,一个是新值,一个是旧值 4)属性变化时则会执行监听逻辑,一对多 5)额外两个常用参数: - immediate: 组件加载时,立即出发回调函数执行 - deep: 深度监听,对象内部值发生变化、复杂类型的数据时使用 32. Vue `$forceUpdate` 原理(用的少) 作用: 迫使 vue 实例重新渲染那。仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件 原理: 依赖发生变化的时候会通知 watcher,然后通知 watcher来调用 update 方法。 33. v-for key(关键字) :key="唯一标识" 是为 vue 中的 vnode 标记唯一id,通过这个key 在 diff算法中可以更准确和快速 diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行对比,然后将差异打 patch 补丁方式渲染 DOM 34. 为什么要设置key值,可以使用 index 值吗?为什么不能? 使用 key 时为了标记遍历节点的唯一性,更好的利用 diff 算法的性能;如果使用 index 时,在列表中间进行 '新增/删除' 时会增加计算复杂度和性能问题。 核心原因: index 索引在中间新增/删除时,对应节点后面的索引值全部会发生变化,diff时也会重新计算,复杂度上升。`只在没有唯一标识时才会选择使用` 35. diff 算法的原理 diff: 通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n) 特点: 1)只会同'层级比较' 2)比较过程中循环从'两边向中间'收拢 36. vue 组件中的 data 为什么是函数?根组件却是对象呢? data 是函数,内部的私有数据空间对于每个不同的组件更具有复用性,不会在复用的组件之间造成一改全改的影响。 这是 js 的特性带来的,与 vue 的设计无关。 37. vue 的组件通信: 1)props和$emit 2)$attrs和$listeners 3)中央事件总线 bus 4)provide和inject 5)v-model 6)$parent和$children 7)boradcast和dispatch 8)vuex|pinia公共状态管理(处理业务逻辑复杂的状态管理) 38. vuex|pinia 什么情况下使用? 如果应用简单,最好不去使用 vuex或pinia,一个简单的 store 或 父子通信即可。 vuex工作流: vue components commit() -> mutations 只支持同步 -> state render components vue components dispatch() -> actions 同步和异步 -> state render components 39. vuex可以直接修改state的值吗? 可以修改,但是极不推荐,因为无法被 devtool 监控,无法检测数据、快照、漫游/回滚都不被支持了。 40. mutations 为什么不支持异步? 为了支持 devtools 的数据检测、快照、漫游/回滚,所以需要有同步的操作在 mutations,异步操作可以到 actions 中进行。 41. 访问和修改 vuex 的状态 访问: this.$store.state.属性 修改: this.$store.commit('mutation中的方法') 42. vuex 缺点: 如果不是开发大型单页应用,使用vuex是繁琐和冗余的,并且 state 中的值会伴随着浏览器的刷新而初始化,无缓存。 43. Vue.nextTick() 是什么? nextTick() 是 Vue3 中的一个核心函数,它的作用是延迟执行某些操作,直到下一次 DOM 更新循环结束之后再执行。 常用在修改数据之后立即使用这个方法,可以获取更新后的DOM。即 等DOM加载完以后再去调用 nextTick() 获取DOM里面的数据内容。 44. Vue.nextTick()知道吗?实现原理是什么?是宏任务还是微任务? nextTick 是'微任务',使用了宏任务与微任务,定义了一个异步方法,多次调用nextTick会存入一个队列中,通过异步方法清空队列。 用于下次DOM更新循环结束之后执行延迟回调。 45. 虚拟DOM为什么能提高性能? 其实就是一个JavaScript对象,通过这个对象描述真实DOM,利用 DOM diff 对比算法避免了没有必要的 dom 操作,最小代价更新视图操作。 46. Vue性能优化做过哪些? 1)首屏加载优化 2)路由懒加载 3)开发服务器Gzip 4)启动CDN加速 5)代码层面优化: 5.1)computed和watch区分使用场景:computed-计算属性,依赖缓存;watch-监听值改变,触发回调操作 5.2)v-if和v-show区分使用场景:v-if在不需要频繁切换场景使用;v-show会在页面加载时就创建 5.3)v-for遍历为item添加key:唯一id会提高性能 5.4)webpack配置:对图片进行压缩 5.5)避免内存泄露:尽量避免使用闭包 5.6)减少ES6转为ES5的冗余代码 47. Vue常用修饰符: v-model: .lazy 输入框可以实现光标离开才会更新数据 .trim 输入框过滤收尾的空格 .number 输入框输入值转为数字 事件修饰符: .stop 阻止事件冒泡 .prevent 阻止默认行为 .self 只有元素本身触发时才触发方法 .once 事件只执行一次 .capture 捕获阶段触发 .sync 对prop进行双向绑定 .keyCode 监听按键指令 48. Vue中template的编译过程: 经过 parse() 生成 ast(抽象语法树),optimize 对静态节点优化,generate() 生成 render 字符串,之后调用 new Watcher() 函数,用来监听数据的变化。 数据更新 Vnode 会与数据改变之前的 Vnode 做 diff计算,对内容做改动之后,就会更新到真正的 DOM 49. Vue3.0 的了解 vue3亮点: 1)性能更快1.2-2倍 2)按需编译,体积小 3)组合式API 4)支持TS 5)先进的组件 1)性能更快1.2-2倍:diff算法更快、静态提升、事件侦听缓存 2)按需编译,体积小:利用ES6的import进行按需加载 3)组合式API: VCA(Vue Composition API) 更专注自己的 data method computed watch等 50. ref和reactive的理解: ref: 对vue3监听数据的方法,本质都是proxy;ref支持对基本类型和复杂类型,一般是监听基本类型;ref底层还是reactive,封装多了一层value reactive: 对vue3监听数据的方法,本质都是proxy;reactive只能监听对象、数组、json; 51. Vuex和redux区别 和 共同思想: vuex 改进了 redux 中的 action 和 reducer 函数,以 mutations 变化函数取代 reducer 无需 switch 只需在对应mutation函数里改变state; vuex 自动重新渲染的特性,无需订阅,只要生成新的 state; vuex 数据流的顺序:view调用 store.commit 提交对应请求到 store 中对应 mutation --> store 改变(vue检测数据变化则渲染) 共同思想: 单一数据源、变化可预测、MVVM思想、vuex借鉴了redux将store作为全局数据中心进行管理 52. 微信小程序 和 vue 的区别: 生命周期: 小程序的钩子函数更简单,跳转方式不一样钩子函数对应就也不一样 数据绑定: vue使用:冒号绑定动态数据、小程序绑定是使用{{}}双大括号 列表循环: wx-for 与 v-for 显示与隐藏: vue中 v-if和v-show,小程序中 wx-if 和 hidden 事件处理: vue中 v-on:event或@event绑定事件,小程序中 bindtap或catchtap绑定事件 数据双向绑定: vue中可以通过 v-model绑定表单组件 绑定事件传参: vue中形参传入、小程序中需要绑定到 data-属性 上,在事件方法中通过 e.currentTarget.dataset.* 的方式获取 父子组件通信: 父传子-子组件通过v-bind传入值,子组件内部通过props接收;小程序没有v-bind,通过直接将值复制给一个变量,子组件properties中接收值 53. 管理系统项目 vue与react 如何选择? 项目成员水平: 如果js基础较好、编码能力较强则选择 React 否则 vue 系统的大小: 构建生态系统选择 react;如果要求快、简单、能用就行、选择 vue 系统运行环境: 如果适用于web端和原生APP框架,选择 React(RN),生态更好
|
Vue2&Vue3&案例综合(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| 1. Vue2 和 Vue3 的响应式原理核心区别是什么?Vue3 的响应式方案解决了 Vue2 哪些痛点? Vue2响应式原理: Vue2 使用 'Object.defineProperty' 实现响应式,通过为对象的每个属性定义 getter 和 setter 来'拦截'数据访问和修改。 优点: 兼容性好,支持IE9+;API稳定,经过长期验证 缺点: 无法监听数组改变、class改变、Map、Set结构改变。 Vue3响应式原理: Vue3 使用 'Proxy' 实现响应式,代理整个对象,提供更全面的拦截能力。 优点: 支持动态添加和删除属性;性能更优,尤其是处理大型对象时;支持 Map、Set 等新数据类型;无需特殊处理数组操作。 缺点: 不支持IE 2. Vue3响应式失效排查:解决数据更新但视图不刷新 参考实现: https: 可能原因: 脱离了 Proxy 代理、依赖收集失败、引用替换、异步时序问题等 理解层面: 只有通过 reactive()、ref() 等API创建的才是响应式 - 响应式是通过Proxy实现的,需要通过代理对象访问 - 深层响应式需要正确的嵌套处理 排查思路: 主要通过打印看对象是否为 Proxy 实例 解决方案: 1)排查并正确使用响应式api - 缺点: 需要人工去排查和肉眼识别 2)通过vue自带的 isReactive() / isRef() 写工具类检测和修复响应式失效属性 - 缺点: 运行时增加了开销 3)对于大型应用建立完整的响应式数据流管理体系(验证/转换/错误处理/重试/数据追踪) - 缺点: 内存占用大,复杂度高适合大型项目 3. Vue3 的生命周期和小程序生命周期冲突/衔接” 的问题?比如页面 onLoad 和 Vue 的 mounted 执行顺序导致的接口请求时机错误? vue3+uniapp 如何规范多端的生命周期使用的? 场景: 小程序onLoad(页面加载)→ Vue3 beforeCreate/created → 小程序 onShow → Vue3 mounted 若在 mounted 发请求,会晚于 onLoad,导致数据加载时机滞后。 规范: 1) 接口请求/数据初始化: 统一在 onLoad 中进行,通过props或全局状态传递给vue组件 2) DOM操作/视图渲染: 统一在 vue mounted 中进行 3) 页面显示与隐藏: 复用小程序 onShow/onHide,Vue 侧监听状态变化 4. 前端性能优化案例 案例1-网校课程播放页:'路由&组件懒加载 + 分包加载' 优化前:首屏加载 8s-10s,资源体积 2MB+,课程页因加载全量路由 / 组件卡顿; 操作: 1)路由用import()懒加载,按「首页 / 课程 / 题库」分包; 2)Element Plus/Vant 组件按需引入,剔除未用组件; 优化后:首屏加载 3s,资源体积 600KB+,课程页秒开无卡顿。 案例2-网校课件和海报图:'静态资源OSS+CDN + 图片懒加载' 优化前:首屏图片加载占 60% 耗时,总加载 7s多,服务器带宽压力大; 操作: 1)课件图片 / 海报上传至 OSS+CDN 分发,开启 gzip / 缓存;2)列表图片用 IntersectionObserver() 懒加载,首屏仅加载可视区图片; 优化后:首屏加载 3s,服务器带宽消耗也降低了,图片加载耗时也提高了。 5. 前后端对接优化课程播放页高并发场景的优化 场景: 高峰期看课数大约上万,因为服务器资源有限,需要对这个页面和接口响应做优化,单页8个接口,整体加载时间超过3s 前端: 1)接口缓存:localStorage-有效期5分钟-重复进入仅首次请求接口 2)防抖节流:点赞/收藏(500ms内单用户仅触发1次请求)lodash.debounce封装 后端: 1)接口聚合:单页8接口聚合为1个接口,后端使用线程池的方式多线程并行查询,减少请求数 2)数据裁剪:仅返回必要的所需字段,剔除无用字段 优化后: 单页整体请求和加载时间优化到了 3s -> 0.8ms内 6. 课程播放页偶发白屏、接口超时问题?如何排查?如何解决? 工具: 前端 Sentry 捕获报错-预警、浏览器的控制台和请求日志、后端nginx/服务日志 原因: 高并发下接口超时未兜底、CDN缓存失效静态资源加载失败、播放器初始化依赖接口超时未兜底 数据资源: 1)接口超时5s配置+重试2次 2)页面上接口超时降级使用本地缓存的数据 3)CDN开启永久缓存和回源策略+播放器核心JS预加载 代码优化: 1)播放器初始化空值判断,白屏显示加载占位符 2)全局捕获接口超时异常,触发局部刷新而非整页白屏 7. 前端通用逻辑抽离?分包加载?是否遇到过 跨包跳转/组件引用 问题? 工具类: 这个是都会做抽离的,比如时间格式化、数据加密、请求拦截等都在 util 中 Hooks: 通用hooks抽离,比如useCoursePlay(如播放进度/倍速)、useUserAuth(如权限校验/登录状态)、useRequestCache(接口缓存)多端直接引入 小程序包体积优化: 1)配置分包加载 2)静态资源CDN化 3)无用代码剔除(如webpack中没用使用到的插件) 跨包跳转问题: 封装通用跨包跳转工具类,因为跳转时需要先拼接分包的目录 跨包组件引用: 小程序禁止跨分包直接引用组件,因此通用组件如播放器、弹窗、表单等统一放在 components 目录下,所有分包页面通过'绝对路径'引用主包组件 跨包数据通信: 全局状态管理 wx.setStorageSync() 存储全局数据,跨包页面通过全局状态通信,成功率就可以100%
8. 登陆鉴权如何做的? 我从认证、鉴权、安全三层做完整登录体系: - 认证流程:前端提交'账号密码',后端用BCrypt'不可逆加密'方式比对库中密文,验证通过后,'签发'JWT短时效AccessToken+长时效RefreshToken双令牌; - 前端维护:封装'请求拦截器'统一带鉴权头,'响应拦截器'自动处理 401、无感刷新令牌;刷新 Token 需通过后端接口完成(如/auth/refresh); - 后端鉴权:Token存在HttpOnly'Cookie'防XSS(后端),通过'网关/拦截器'统一校验 Token 合法性与过期时间,解析用户信息; - 安全兜底:密码不可逆加密、接口'限流'防暴力破解,登出/强制下线时将 'Token 加入黑名单失效',杜绝越权访问。 RefreshToken时效和机制设计: -- 令牌设计:AccessToken(2小时,短时效)用于接口鉴权,RefreshToken(7天,长时效)仅用于刷新令牌,存储在后端 Redis 并绑定用户ID/设备信息; -- 触发时机:前端请求拦截器捕获 401(Token 过期),自动携带 RefreshToken 调用/auth/refresh接口; -- 后端校验:校验RefreshToken合法性(是否存在Redis、未过期、设备匹配),验证通过后签发新AccessToken(旧的立即失效); -- 安全兜底:RefreshToken 仅允许使用 1 次(刷新后旧的立即失效),若校验失败直接返回 403,前端引导重新登录;前端封装无感刷新逻辑,避免用户感知。
|
React
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| 1. react 类组件和函数组件的区别 1)函数组件是个函数,返回一个jsx元素;类组件是 ES6语法糖class定义,继承 component 这个类 2)类组件可以通过 state 进行状态管理,函数组件不能使用 setState(),在 react16.8+ 以上函数组件可以通过hooks的 useState()来模拟状态管理 3)类组件有一系列生命周期钩子函数;函数组件需要借助hooks来使用生命周期函数 4)类组件捕获最新的值(永远保持一致);函数组件捕获渲染所需的值,因为有闭包的特性,无法从外部进行更改 2. react 事件绑定原理 基于Virtual DOM的基础上实现了合成事件,小驼峰命名: 1)事件注册 2)事件合成 3)批处理 3. react 中的 setState() 缺点是什么 1)默认是异步的,但在原生事件和 setTimeout 中都是同步的 2)无法立即拿到更新后的值,需要在第二个参数callback中拿到更新后的结果 3)批量更新也是异步,需要多次调用该方法实现批量 4. react 中 props 和 state 区别 props: 外部组件传入的参数、组件通信使用、不可以在组件内修改-只能父组件修改 state: 组件内部的状态变量、内部通过 setState 进行修改 5. 虚拟DOM优劣?实现原理? 虚拟DOM是 js 模拟的一颗 dom 树,相当于加了一层 dom 缓存,利用 dom diff算法避免了没有必要的 dom 操作,从而提高性能。 优点: 有效降低大面积的重绘和回流 缺点: 首次渲染大量DOM时,会比 innerHTML 慢 6. diff 与 key 之间的联系? 两个虚拟DOM在对比的时候,为了降低算法复杂度,利用 key 值的不同可以只对比不同的元素节点内容、以及批处理的合并操作等,实现最小补丁更新和渲染。 7. react 组件通信: 1)props - 父传子 2)实例方法 - 父组件用 refs 引用子组件,调用子组件的实例方法 3)回调函数 - 子传父,子组件调用props传递过来的方法 4)状态提升 - 两个子组件通过父组件定义的参数进行传参 5)Context上下文 - 一般用于全局主题 6)公共状态管理 - mobx/redux/dva 通过在 view 中出发 action,改变 state 进而改变其他组件 state 8. react 中 refs 作用和原理: ref 是 react 提供用来操作组件实例或 DOM 元素的接口,主要用来做文本框的聚焦、触发强制动画等... 9. react 生命周期函数: - 老 1)第一阶段:装载阶段 constructor(), render(), componentDidMount() 2)第二阶段:更新阶段 [shouldComponentUpdate()], render(), componentDidUpdate() 3)第三阶段:卸载阶段 componentWillUnmount() 10. A组件嵌套B组件,生命周期执行顺序: 父组件-constructor 父组件-render 子组件-constructor 子组件-render 子组件-componentDidMount 父组件-componentDidMount 11. 新出的声明周期钩子函数? react16 废弃了三个钩子函数: componentWillMount/componentWillReceiveProps/componentWillUpdate react16.8 以后新增的方法: getDerivedStateFromProps() 静态方法 / getSnapshotBeforeUpdate() 12. react hooks 用过吗?为什么要用? useState() 创建状态,返回一个数组,第一个值为状态、第二个值为改变状态的函数 useEffect() 副作用,数据获取、dom操作影响页面,在渲染结束之后执行;第一个参数为函数,第二个参数为依赖列表;如果不传第二个参数则渲染完就执行1次 useRef() 返回一个可变ref对象,整个生命周期不变;用来获取元素的实例,比如用于输入框聚焦或动画的触发 useMemo() 优化函数组件中的功能函数,在渲染期间执行 - 类似与 vue的 computed 计算属性 useContext() 获取上下文注入的值,接受一个context对象,返回该对象<MyContext.Provider>元素的value值:const value = useContext(MyContext) useLayoutEffect() 有DOM操作的副作用,时机不同,在dom更新后马上'同步'调用的代码,会阻塞页面渲染-防抖场景用;而 useEffect 是整个页面渲染完才调用 useCallback() 与 useMemo() 类似,将函数缓存 react-router useHistory() 跳转路由 useLocation() 得到url对象 useParams() 得到url上的参数 react-redux useSelector() 共享状态,从redux的store中提取数据 useDispatch() 共享状态,返回redux的store中对dispatch的引用 13. hooks 的使用注意事项 1)只能在函数式组件 或 自定义hook 中使用hooks 2)不要在循环、条件或嵌套函数中调用 hooks,必须使用react函数的顶层hooks。原理是react的闭包会导致调用顺序的不一致性,从而产生难以预料的后果。 14. hooks 会取代 render props 和 高阶组件HOC 吗? 可以取代,但没必要。都在处理同一种问题:逻辑复用。 15. 列举重新渲染 render 的情况 this.setState() / this.forceUpdate() / 接收新的props / 通过状态管理mobx,redux等 / 改变上下文 16. 如何避免组件的重新渲染 当 props/state 改变时,组件会执行 render 函数重新渲染: 1) class组件中使用 shouldComponentUpdate() 钩子函数 2) PureComponent() 默认有避免重新渲染的功能 3) 函数组件使用高阶组件 memo 处理 17. 渲染一个 react 组件的过程 1) babel 编译 2) 生成 element 3) 生成真实节点(初次渲染) 4) 生命周期:挂载、更新、卸载 18. 类/函数组件怎么做性能优化? 类组件性能优化: 1)使用 shouldComponentUpdate() 2)使用 React.PureComponent() 3)使用 immutable持久化 4)bind函数 函数组件性能优化: 1)使用 useCallback() 缓存函数 2)使用 useMemo() 进行缓存 两者都可用: 1)React.memo 2)使用key 3)不要滥用props 19. react 按需加载 1)懒加载 React.lazy() 但是不支持服务端渲染 2)使用 Loadable Components 这个库 20. 纯函数 与 副作用函数特点: 纯函数: 与外界交互只有参数和返回值,无状态、线程安全、结果可缓存-性能高 副作用函数: 除参数和返回值外,还有附加影响,如调接口、修改全局变量、抛异常/错误终止、打印到终端/读取用户输入、读写文件等 21. react 的 StrictMode 严格模式是什么? 仅开发模式下运行,不会影响生产构建(关掉即可),作用: 1) 验证内部组件是否遵循某些推荐做法,否则给出警告 2) 验证是否使用已废弃的方法,否则给出警告 3) 识别潜在风险预防的作用 22. react 的 props 上使用属性校验 使用 PropTypes 进行类型检查,在 react15.5+ 起,使用库 prop-typs `import PropTypes from 'prop-types';` 23. 高阶组件、受控组件、非受控组件 高阶组件HOC: 不是 react api的一部分,是基于react的设计模式;参数为组件、返回值为新组建的函数-无副作用 受控组件: 被 react 以 state 作为唯一数据源的方式控制取值的表单输入元素 非受控组件: 表单数据由 dom 节点来处理,而不是 state 来管理数据,一般可以用 ref 来从DOM节点中获取表单数据 【区别】 1)受控和非受控是表单中的组件、高阶组件是对某个组件注入一些属性和方法 2)高阶组件是 解决代码复用性 而产生的 3)受控组件必须要有一个 value;非受控组件相当于稻草 DOM,一般有个 defaultValue 24. react 的路由:react-router来配置 Router: 对应路由两种模式 <BrowserRouter> 和 <HashRouter> matchRoutes组件: 控制路径对应显示组件,同步和异步加载 <Route> navigation组件: 用作路由切换和跳转 <Link> 25. react 路由懒加载 1) React.lazy 包裹组件,实现动态按需加载 2) react-loadable 库 3) webpack 配置 lazyload-loader 4) import webpack v2+ 5) require.ensure webpack v1/v2 26. react-router-dom 内部实现,怎么做路由守卫? 利用 ContextAPI 通过上下文对象将路由信息注入到 Router 组件中,Router 渲染的内容就是 ContextAPI 的 Provider 组件,然后接收 Router 组件中的当前路由信息对象。这样在Router的所有组件下都能通过 '上下文' 拿到当前路由信息对象 路由守卫: 路由里设置 meta 元字符实现路由拦截 27. react 性能优化手段 1)使用纯组件 2)使用 React.memo 进行组件记忆 3)路由懒加载 4)列表渲染的时候加 key 5)使用 React Fragments 避免额外标记 6)不要使用内联函数定义 7)避免使用内联样式属性 8)优化 react 中的条件渲染 9)不要在 render 方法中导出数据 10)避免在 Willxxx系列生命周期中进行异步请求、操作dom等 11)【函数组件】中 useCallback 和 useMemo进行组件优化 12)【类组件】使用 shouldComponentUpdate 决定什么时候渲染组件 13)【类组件】使用 immutable 对象 14)【类组件】事件函数在 Constructor 中 绑定 bind 改变 this 指向 28. 描述 Flux 与 MVC 模式 MVC模式:model-view-controller 数据流不清晰、缺乏数据完整性 Flux模式:数据和逻辑永远单向流动、复杂用户界面不再收到级联更新、通过限制对共享数据的直接访问来加强数据完整性 29. redux 的三个原则: 1)单一数据源 2)state是只读的 3)使用纯函数来执行修改 30. setState 不能获取值怎么办? 1) addeventListener 添加的事件或者 dom事件中出发 2) 接收的餐食可以是一个函数 3) async/await 异步调用处理 31. redux 和 vuex 的设计思想 - 都是基于 Flux模式 redux: 1)单一数据源 2)状态只读 3)状态修改由纯函数完成 vuex: 1)全局只有一个store实例 2)mutations是同步事务 3)actions处理异步事务 4)模块化通过 module 方式来处理 32. redux 是同步的,为什么可以执行异步逻辑? 通过 redux-thunk 中间件的作用,可以异步执行 redux。检查 action 以及通过 next() 进行放行。 33. redux 的 saga 和 thunk 中间件的区别,优缺点 区别: redux-thunk 异步采用 async/await; redux-saga 采取generate函数 优缺: redux-thunk 库小、代码就几行; redux-saga 异步区分、更加优雅、适合大量api请求 34. redux 和 mobx 的区别 1)redux 是函数式的,mobx 是面向对象的 2)redux 理想是 immutable 的,每次都返回一个新的数据;mobx 从始至终都是一份引用 3)redux 利用 dispatch 进行广播,通过 Provider 和 connect 来对比前后差别和更新粒度;mobx 组件可以做到更精确更新,基于 observer 可观察对象 4)redux 采用 Provider 和 connect 方式; mobx 采用 Provider 和 inject、observer 35. 什么是 immutable?为什么要用它? immutable 是持久化数据,一旦创建就不会被修改。修改时返回新的 immutable,但是原数据不会改变。 redux中因为深拷贝对性能消耗极大,immutable `只拷贝改变的节点,从而节省性能`。
|
微信小程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| 1. 简述微信小程序原理 采用 js、wxml、wxss 三种技术进行开发,本质是一个单页面应用,所有页面渲染和事件处理都在一个页面内进行。 数据驱动的架构模式,可以通过微信客户端调用原生的各种接口。 UI和数据分离,webview 和 appservice 2. 微信小程序的相关文件类型 WXML: 结合基础组件、事件系统、构建页面结构、微信自定义的一套组件 WXSS: 样式语言,描述WXML的组件样式 JS: 逻辑处理、网络请求 JSON: 小程序设置、如页面注册、页面标题、tabBar等 app.json 全局配置文件 app.js 全局入口文件 app.wxss 全局样式(可选) 3. 小程序的双向绑定 与 vue 有哪些不一样 1)小程序通过 this.setData({}) 修改值,才能同步到视图中 2)vue 上表单元素加 v-model 再绑定一个 data值,实现同步到视图 3)取值: 小程序中 this.data.xxx, vue中 this.xxx 4. 自定义小程序的 navigationBar 思路: 隐藏原生样式、获取胶囊按钮、状态栏相关数据供后续计算、根据不同机型计算出该机型的导航栏高度 进行适配、编写新的导航栏引用到页面 5. 小程序中的 wx:if 和 hidden 的区别 wx:if 条件为true显示、显示时创建元素、隐藏时销毁元素 - 适用于不频繁切换的场景 hidden 提前创建元素,相当于display、显示时渲染、隐藏时保留 - 适用于频繁切换的场景 6. 小程序的 wxss 和 css 有哪些不一样的地方 1)尺寸单位 rpx: 以 iphone6 为基准,固定宽度 750rpx,对应设计稿为 750px,即可量取多少设置多少 2)使用 @import 标识符导入外联样式,后面跟相对路径 7. 小程序页面间传递数据的方法 1)全局变量,放在 app.js 2)使用 wx.navigateTo(不关闭当前页面) 和 wx.redirectTo(关闭当前页面跳转) 3)使用小程序本地缓存 Storage 8. 小程序的生命周期函数 onLoad() 页面加载时触发,只会调用一次,建议在此 请求数据 onShow() 页面显示/切换前台时触发,不建议在此 请求数据 onReady() 页面初次渲染完成时触发,只会调用一次,可以进行视图层交互 onHide() 页面隐藏/切入后台时触发,如 navigateTo 或底部 tab 切换到其他页面、小程序切入后台等 onUnload() 页面卸载时触发,如 redirectTo 或 navigateBack 到其他页面时 9. 哪些方法可以提高微信小程序的应用速度? 1)提高页面加载速度 2)用户行为预测 3)减少默认 data 的大小 4)组件化方案 10. 微信小程序的优劣势 优点: 即用即走、无需安装、省流量、省安装、不占桌面、依托微信、开发成本比APP低 缺点: 用户留存相对低、入口对传统APP要深、限制较多,页面大小不能超过2M,不能打开超过10个层级的页面 11. 怎么解决小程序的异步请求问题 1)在返回成功的回调里面处理逻辑 2)Promise异步 12. 小程序关联微信公众号如何确定用户的唯一性? 通过 `unionid` 来区分用户的唯一性,因为微信开放平台下的不同应用,相同用户的 unionid 都是相同的。 13. 如何实现下拉刷新? 方案1: 全局 config 中的 window 配置 enablePullDownRefresh 方案2: 在 Page 中定义 onPullDownRefresh 钩子函数,到达下拉刷新条件后,该钩子函数执行 注意: 请求返回后,调用 wx.stopPullDownRefresh 停止下拉刷新 14. bindtap 和 catchtap 的区别 bindtap: 点击事件,不会阻止冒泡事件 catchtap: 点击事件,会阻止冒泡事件 15. 简述微信支付业务流程:必须为企业用户,且经过企业认证 wx.requestPayment({}) 1)用户在商户APP中选择商品,提交订单,选择微信支付 2)商户后台收到用户支付单,调用微信支付统一下单接口 3)统一下单接口返回正常的 prepay_id 预支付id,再按签名规范重新生成签名后,将数据传输给APP。 参与签名字段: appid, partnerid, prepayid, noncestr, timestamp, package 注意: package的值格式为 Sign=WXPay 4)商户APP调起微信支付 5)商户后台接收支付结果 6)商户后台查询支付结果 16. 小程序自定义组件样式隔离,有哪几种隔离模式? 指定特殊的样式隔离选项 styleIsolation Component({ options: { styleIsolation: 'isolated' } }) 17. 小程序让图片保持宽高比例不变 使用 mode='widthFix' 宽度不变、高度自适应; mode='heightFix' 高度不变,宽度自适应 18. 小程序组件传参 父传子: 自定义属性,子组件通过 props 接收 子传父: 自定义事件,父组件通过事件接收子组件传过来的值 19. 小程序【组件】生命周期 created - attached - ready - moved - detached - error 20. 小程序【页面】生命周期 onLoad - onShow - onReady - onHide - onShow - noUnload 21. 小程序路由传参 wx.navigateTo({ url: 'xxx?id=2' }) - 保留当前页面跳转 wx.redirectTo({ url: 'xxx?id=2' }) - 关闭当前页面跳转 22. 小程序路由跳转 switchTab/navigateTo/redirectTo 区别 wx.navigateTo() - 保留当前页面跳转 wx.redirectTo() - 关闭当前页面跳转 wx.switchTab() - 专门用于跳转 tabBar 页面 wx.navigateBack() - 关闭当前页面,返回上一页面或多级页面 wx.reLaunch() - 关闭所有页面,打开应用内某个指定页面 23. 小程序 tabbar 的实现原理 tabBar: { "list": [ {"pagePath": xxx, "text": xxx, "iconPath": xxx, "selectedIconPath": xxx} ] } 24. 小程序性能为什么高? 1)轻量级、代码包体积2M内,超过2M还可以分包 2)运行在微信端,原生API调用 3)基于微信宿主,可以让小程序快速渲染
|
NodeJS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 1. nodejs创建静态服务器:要使用HTTP服务器和客户端,则必须 require('http') 2. CommonJS规范 与 ES6模块化规范 CommonJS规范: require 导入,module.exports 导出 ES6模块化规范: import 导入,export default 导出 3. socket通信/聊天 websocket 利用了 HTTP 协议来建立连接,且必须由浏览器发起。 该请求与HTTP普通请求的区别: 1)GET请求地址不再是路径,而是 'ws://' 或 'wss://' 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket。 2)请求头是 Upgrade: websocker 和 Connection: Upgrade 表示连接要被转换为 websocket 连接 3)Sec-WebSocket-Key 用于标识这个连接,并非用于加密数据 4)Sec-WebSocket-Version 指定了 WebSocket 的协议版本 4. MongoDB 非关系型数据库: 关系型数据库:sql语句表之间关系可以连表增删改查、事务一致性/事务回滚等,如 mysql、oracle、sqlserver... 非关系型数据库:sql语句没有表关系,轻量、高效、自由,如 mongoDB、redis... MongoDB: collection(集合/表)、document(文档/行数据)、field(域/字段) MongoDB数据库连接通过 'mongoose' 库的引入实现。 5. token(jwt-json) 与 session(express-session) 配合 cookie(登陆鉴权) 1)浏览器-POST账号密码-服务端-校验账号密码-数据库 - 校验成功-用户信息存session-返回sessionId 2)浏览器-存储sessionId-请求接口自动携带Cookie:sessionId-数据库查session - 校验成功-接口返回 以CSRF攻击为例: cookie: 用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是放行进行操作 token: 用户点击了链接,由于浏览器不会自动带上token,所以即使发了请求,后端的token验证不会通过,所以不会进行扣款操作 6. token 存在 localStorage 里,当过期时,过期的 token 怎么处理? token过期:后端会返回一个 401 鉴权失败的状态码给前端,前端接收后重定向到登录页,引导用户登录。【`axios`拦截器中进行判断和重定向】 7. 使用原生Node.js操作 cookie? 获取cookie: req.headers.cookie 设置cookie: res.writeHead(200, {'Set-Cookie': 'myCookie=test', 'Content-Type': 'text/plain'}) 8. nextTick 和 setImmediate 的区别: nextTick: 延迟加载,放在当前队列的最后一个执行 setImmediate: 延迟加载,在下一个队列的队首执行 9. koa 与 express 区别: 相同:都是 nodejs 的快速开发框架 不同: 1)语法:最大区别 express 的异步采用的是回调函数的形式 koa 支持generator+yield,支持async+await,更加优雅 2)中间件: koa 采用洋葱模型,进去顺序执行,出去反向执行,支持context传递数据。 express 需要引入插件,不支持 await 中间件异步函数 3)集成度: express 内置了很多中间件,集成度高、省心 koa 轻量简洁、容易定制 10. koa 中间件的实现原理 1)每个中间件接收两个参数:'Context', 'next' 只要调用next函数就可以把执行权交给下一个中间件 2)如果没有调用 next,执行权就不会传递下去 3)多个中间件会形成一个 栈 解构,以先进后出的顺序执行。 11. 图片上传到服务器的过程 'Multer' 是nodejs的一个中间件,只会处理 multipart/form-data 类型的表单数据,主要用于上传文件。 12. 服务端渲染 页面渲染在服务端完成,最终的HTML字符串直接通过请求发送给客户端。 服务端渲染的优势: 利于SEO优化、首屏加载快(客户端接收到的是完整HTML页面) 13. nodejs 优缺点 和 适用场景 优点: 事件驱动、异步变成、简单易学、非阻塞IO、性能较高、轻量高效 缺点: 单线程、可靠性低、开源组件库参差不齐-兼容性差、不适合做企业级应用开发-特别是复杂业务 场景: 1)大量 ajax 请求的应用 2)实时应用:在线聊天、实时通知推送等 3)工具类应用:海量工具,前端压缩部署、桌面图形界面等 3)高并发、I/O密集、少量业务逻辑和不依赖可靠性的场景,nodejs 还有其一席之地
|
git | webpack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 1. git命令相关 git init 初始化仓库 git status 查看各个区域的代码状态 git log 查看commit记录 git reflog 查看完整记录(★) git add 添加工作区代码到暂存区 git commit 暂存区代码的提交 git reset 代码的版本回退(★) git stash 将暂存处代码收起来(★) git stash pop 将收起来的暂存区的代码释放出来(★) git tag 可以打标签(★) git branch 基于当前分支创建一个分支 git checkout 切换分支 git merge 合并分支 git remote add origin 添加远端仓库地址 git clone 克隆仓库 git pull 下拉对应分支代码 git push 上传对应分支代码 【规范】gitlab 和 公司 git 规范 4个环境:开发环境DEV-dev分支、测试环境TEST-test分支、预发布环境UAT-uat分支、生产环境PROD-master分支 2. sass 和 scss sass是 css 的辅助工具,在CSS语法上增加了变量、嵌套、混合、导入等高级功能。 scss是 sass 的一个语言版本,.scss 文件的特点是层级靠 {} 来区分,.sass文件的特点是层级靠【缩进】来区分。 3. 前端工程化:gulp & webpack gulp - src=>pipe(scss翻译).pipe(css合并).pipe(css压缩)=>dist gulp - src=>pipe(模块化编译).pipe(js压缩)=>dist 流程化 webpack 模块化,默认支持的 commonjs 规范。 所有js模块打包生成一个js文件,编译解析浏览器不能识别的语言(如scss/vue/jsx/ts/es6等) 配置:入口、出口、devServer启动(自动刷新/热更新/反向代理)、sourcemap-调试代码.map地图 loaders: sass-loader/css-loader/file-loader/babel-loader/vue-loader/postcss-loader plugin: 压缩、提取公共库 4. webpack配置、代码分割流程及操作 webpack包含:mode模式、entry入口、output出口、plugins插件、loader加载器、resolve、devServer开发服务器相关配置 组件模块化导入的时候可以采用懒加载的形式,就会单独打包对应的代码。(路由匹配到就会加载,提高首屏加载速度) 5. webpack 4大核心理念、编译原理: entry入口、output出口、plugins插件、loader加载器(一切皆为模块) 6. webpack构建优化(打包时间过长,可以优化时间短点 20s): 1)提取公共库,避免重复引用。(使用DllPlugin把第三方库文件分离出来单独编译,并且缓存,极大减少业务页面编译时间) 2)happypack,多线程解析文件,如babel-loader等耗时较长的 3)缓存 cache-loader 4)loaders 尽可能配置解析路径include参数,排除路径exclude参数,减少解析时查询范围 5)dev 阶段 devtool 设置成 cheap-module-eval-source-map,已经能满足调试需求,编译更快 6)prod 阶段 去掉 source-map
|
浏览器与网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 1. url从输入到渲染页面的全过程 1)浏览器构建 HTTP request 请求,DNS 解析 url 地址,生成 HTTP 请求保温,构建TCP连接,使用 IP 协议选择传输路线 2)将请求通过网络传输到服务端,从客户机到服务器需要通过许多网络设备,一般包括集线器、交换器、路由器等 3)服务器构建HTTP response 响应,响应客户端的请求 4)将响应体的数据通过网络传输返回给客户端 5)浏览器渲染页面,解析HTML、CSS、JS,生成 RenderTree渲染页面 2. TCP 三次握手、四次挥手、可靠传输原理 三次握手: 客户端sync(发送seq为x序列号) -> 服务端sync+ack(发送seq为y的序列号并将x+1) -> 客户端ack(发送seq为z的序列号并将y+1) 四次挥手: 客户端fin -> 服务端ack(fin+seq) -> 服务端fin -> 客户端ack(fin+seq) 3. http 200 与 302 200: 成功 或 强缓存 302: 临时重定向 4. HTTP 头部字段 Cache-Control 控制缓存行为 Connection 逐跳首部、连接的管理 Date 创建报文的日期时间 Program 报文指令 Trailer 报文末端的首部一览 Transfer-Encoding 指定报文主体的传输编码方式 Upgrade 升级为其他协议 Via 代理服务器的相关信息 Warning 错误通知 Authorization web认证信息 5. 为什么浏览器要限制TCP的最大个数 内存资源、CPU资源 一个 tcp 连接最小占用内存为 8k,对于8G内存的机器,不考虑其他限制,最多支持的并发量为 100万,实际情况下,8G是达不到100万,因为系统本身需要占用资源 6. HTTP2 的特点 二进制分帧、多路复用、头部压缩、流量控制、请求优先级、服务器推送 7. HTTP2 多路复用的原理 HTTP1.1 是管道串行化的,HTTP2 是管道并行的,因此是多路复用,主要体现在 I/O 流数据的传输上。 8. https原理 端口: 443 证书: 需要申请证书,用于验证服务器身份和加密使用 加密: 在TCP的基础上,客户端与服务器会进行SSL加密,确定对话密钥,以密文进行传输数据 9. CDN是什么?原理? CDN 内容分发网络,核心是以地理位置最近的节点去获取浏览器请求的服务器数据,以更快的速度访问网站。 CDN原理:内容存储和分发技术。 10. form表单提交没有跨域,为什么 ajax 有跨域? 浏览器的`同源`策略,没有经过允许不能获取另一个域名的内容。 form表单是不需要返回的,因此没有跨域限制。ajax请求需要返回数据,并读取响应数据内容,但浏览器对其有安全策略限制。 浏览器的安全策略主要针对 js脚本、并不限制 src、form表单提交的请求。
|
HTTP缓存策略

高频算法(js)
https://visualgo.net/zh/sorting
冒泡排序
比较相邻的两个项,如果第一个比第二个大,则交换它们。
1 2 3 4 5 6 7 8 9 10 11
| function bubbleSort(arr) { const { length } = arr for(let i = 0; i < length; i++) { for(let j = 0; j < length - 1 - i; j++) { if (arr[j] > arr[j+1]) { [arr[j], arr[j+1]] = [arr[j+1], arr[j]] } } } return arr }
|
选择排序
原址比较排序算法,找到数据中的最小值并将其放在第一位,接着找到第二小的值放在第二位,依次类推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function selectionSort(arr) { const { length } = arr let minIndex for(let i = 0; i < length - 1; i++) { minIndex = i for(let j = i; j < length; j++) { if (arr[minIndex] > arr[j]) { minIndex = j } } if (i !== minIndex) { [arr[minIndex], arr[i]] = [arr[i], arr[minIndex]] } } return arr }
|
插入排序
每次从无序区间中取出一个数据,插入到有序区间的合适位置,直到无序区间没有数据为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function insertSort(arr) { const { length } = arr let temp for(let i = 1; i < length; i++) { temp = arr[i] let j = i while(j > 0 && arr[j-1] > temp) { arr[j] = arr[j-1] j-- } arr[j] = temp } return arr }
|
归并排序
将原始数组分成较小的数组,知道每个小数组只有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
function mergeSort(arr) { if (arr.length <= 1) { return arr; }
const mid = Math.floor(arr.length / 2); const left = arr.slice(0, mid); const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right)); }
function merge(left, right) { const result = []; let i = 0; let j = 0;
while (i < left.length && j < right.length) { if (left[i] < right[j]) { result.push(left[i]); i++; } else { result.push(right[j]); j++; } }
return [...result, ...left.slice(i), ...right.slice(j)]; }
const unsortedArr = [5, 2, 9, 3, 7, 6, 1, 8, 4]; const sortedArr = mergeSort(unsortedArr); console.log(sortedArr);
|
快速排序
通过选定一个数字作为比较值,将要排序其他数字,分为 >比较值 和 <比较值,两个部分。并不断重复这个步骤,直到只剩要排序的数字只有本身,则排序完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
function quickSort(arr) { if (arr.length <= 1) { return arr; }
const pivot = arr[0]; const left = []; const right = [];
for (let i = 1; i < arr.length; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } }
return [...quickSort(left), pivot, ...quickSort(right)]; }
const unsortedArr = [5, 2, 9, 3, 7, 6, 1, 8, 4]; const sortedArr = quickSort(unsortedArr); console.log(sortedArr);
|
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
function binarySearch(sortedArr, target) { let left = 0; let right = sortedArr.length - 1;
while (left <= right) { const mid = left + Math.floor((right - left) / 2); if (sortedArr[mid] === target) { return mid; } else if (sortedArr[mid] < target) { left = mid + 1; } else { right = mid - 1; } }
return -1; }
const sortedArr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(binarySearch(sortedArr, 5)); console.log(binarySearch(sortedArr, 10)); console.log(binarySearch(sortedArr, 2));
|
随机算法
迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位置比当前位置小。这个算法可以保证随机过的位置不会再被随机一次。
1 2 3 4 5 6
| function shuffle(arr) { for(let i = arr.length - 1; i > 0; i--) { const randomIndex = Math.floor(Math.random() * (i + 1)) [arr[randomIndex], arr[i]] = [arr[i], arr[randomIndex]] } }
|
代码
Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> var p = new JerryPromise((resolve, reject) => { resolve('111') }) p.then((res) => { console.log("success1", res) return "jerry-" + res }).then((res) => { console.log("success2", res) }).catch((err) => { console.log("error", err) }) </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function JerryPromise(executor) { this.status = 'pending' this.result = null this.cb = [] var _this = this function resolve(res) { if (_this.status !== 'pending') return _this.status = 'fulfilled' _this.result = res _this.cb.forEach(item => { item.onResolved && item.onResolved(_this.result) }) } function reject(res) { if (_this.status !== 'pending') return _this.status = 'rejected' _this.result = res _this.cb.forEach(item => { item.onRejected && item.onRejected(_this.result) }) } executor(resolve, reject) }
JerryPromise.prototype.then = function (onResolved, onRejected) { return new JerryPromise((resolve, reject) => { if (this.status === 'fulfilled') { let result = onResolved && onResolved(this.result) resolve(result) } if (this.status === 'rejected') { let result = onRejected && onRejected(this.result) reject(result) } if (this.status === 'pending') { this.cb.push({ onResolved, onRejected }) } }) }
JerryPromise.prototype.catch = function (onRejected) { return new JerryPromise((resolve, reject) => { if (this.status === 'rejected') { let result = onRejected && onRejected(this.result) reject(result) } if (this.status === 'pending') { this.cb.push({ onRejected }) } }) }
|
观察者模式
一个对象状态改变,所有依赖它的对象都的到通知并自动更新。
- 场景:面包屑导航
- 优点:解耦
- 缺点:不能对事件进行细分管控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Subject { constructor() { this.observers = [] } add(observer) { this.observers.push(observer) } notify() { this.observers.forEach(observer => { observer.update() }) } remove(observer) { this.observers = this.observers.filter(item => item !== observer) } } class Observer { constructor(name) { this.name = name } update() { console.log(`${this.name} is updated`) } }
const subject = new Subject() const observer1 = new Observer('tom') const observer2 = new Observer('jerry') subject.add(observer1) subject.add(observer2) subject.observers.forEach(observer => { console.log(observer.name) }) subject.remove(observer1)
subject.notify()
|
订阅发布模式
订阅者与发布者不用互相知道,通过第三方实现调度,属于经过解耦合的观察者模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const PubSub = { message: {}, publish(type, data) { if (this.message[type]) { this.message[type].forEach(cb => cb(data)) } }, subscribe(type, cb) { if (!this.message[type]) { this.message[type] = [cb] } else { this.message[type].push(cb) } }, unsubscribe(type, cb) { if (this.message[type]) { this.message[type] = this.message[type].filter(item => item !== cb) } } }
function testA(data) { console.log('testA', data) } function testB(data) { console.log('testB', data) } PubSub.subscribe("A", testA) PubSub.subscribe("B", testB)
PubSub.publish("A", "hello1") PubSub.publish("B", "world1") PubSub.unsubscribe("A", testA) PubSub.publish("A", "hello2") PubSub.publish("B", "hello2")
|
监测数组变化-返回数组长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
function watchArrayLength(arr, callback) { const arrayProto = Array.prototype; const methods = ['push', 'pop', 'unshift', 'shift', 'splice', 'sort', 'reverse'];
methods.forEach(method => { arr[method] = function(...args) { const result = arrayProto[method].apply(this, args); callback(this.length); return result; }; });
callback(arr.length); return arr; }
const testArr = [1, 2, 3];
const watchedArr = watchArrayLength(testArr, (length) => { console.log('数组最新长度:', length); });
console.log('===== 初始状态 =====');
console.log('===== push 一个元素 ====='); watchedArr.push(4);
console.log('===== pop 一个元素 ====='); watchedArr.pop();
console.log('===== splice 删除/添加元素 ====='); watchedArr.splice(1, 2);
console.log('===== sort 排序(长度不变)====='); watchedArr.sort();
|
深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
function deepClone(target, cache = new WeakMap()) { if (target === null || typeof target !== 'object') { return target; } if (cache.has(target)) { return cache.get(target); } let cloneObj; if (target instanceof Array) { cloneObj = []; } else { cloneObj = {}; } cache.set(target, cloneObj); for (const key in target) { if (target.hasOwnProperty(key)) { cloneObj[key] = deepClone(target[key], cache); } } return cloneObj; }
|
项目
网校项目:经过两个月的选课系统的开发、测试、上线。选课系统是服务于学校教学的第一站,参与选课的学生上午8点集中开测。
反馈如下:
- 打开网站出现 502
- 登陆不进去系统
- 提交数据一直反复
- 有时出现白屏现象
排查解决:
- 检查进程是否挂掉,nginx负载均衡配置,手动启动服务
- 检查服务器内存、CPU占比,看是否是资源打满
- 排查接口情况,是否有数据库sql慢的情况、加redis缓存或修正sql查询
- 网络延迟或内存不足等,登陆或表单提交可以进行 loading 框的添加
- 白屏优化上 SSR、路由懒加载、骨架屏等方案的尝试,每个方案都有自己的优劣势
综合
工作中的角色
后端开发 - 前端开发 - 技术经理
印象深刻的问题
百度搜索不到官网,在页面添加了 meta 标签三要素 title/keywords/description
后期通过服务端渲染 SSR 去实现,需要单独排期,相当于重写项目了,人力物力耗费。
所以先考虑如下优化点,实现SEO的收录和排名:
- 首页和所有栏目页面加入 meta 标签三要素
- 具有代表性的 title、以及使用语义化标签,h标签等
- 生成对搜索引擎友好的 sitemap
- 使用合理的 html 结构内容,比如标题、内容、页脚 这样的顺序
项目中的挑战
项目规模、背景、具体情况下遇到的具体问题。
- 遇到问题:如何思考、如何执行、处理结果如何
- 反思复盘:学到了什么、看到的不足、后续动作等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 1) 缓存处理 使用vue移动端页面时,开发时没注意,直接在 mounted()前面试用品async,里面使用await调用接口,又通过watch监听了分类的变化,调了一遍接口。 每次验证时,发现数据都会闪烁一下,排查发现每次都会重复调用接口。 就使用 vuex 做缓存解决了这个问题。 vuex中mutation中数据更新时,做了一下深复制,就做好了缓存处理。以及清缓存要在生命周期结束时进行清理,做到缓存使用的有始有终。
2) 关键词高亮问题(原理和字符串敏感词替换一样,但是用在关键词高亮上属于亮点) 一开始词不多,我用的遍历方式,后来用户会配置几十万量级的词,遍历会让页面卡死崩溃。 解决办法:就是优化性能,分为三步: 1)生成字典树 2)遍历页面文字,取出进行匹配 3)使用字典树代替遍历 最终效果:百万级的词量都可以 1s 内处理完
3) 封装自定义组件 原生的组件库的 tabs 标签页组件不能实现丝滑的专场,我就单独二次封装了 swiper 组件,组件内定义插槽,配合原生swiper实现。 小程序的 swiper 是有默认高度的,必须手动设置其高度,以及动态获取屏幕尺寸,最终实现丝滑切换的效果。
4) 遇到了难用的轮子 对音频和视频播放组件的小程序原生文档比较难以查看和使用,提炼自己的问题为一个小demo,借助技术博客、ai等工具,最终得到解决。
|
等等 按实际项目和技术点去说…