目录
- HTML & CSS
- 1. XML、HTML、XHTML 有什么区别?⭐
- 2. XML和JSON的区别?
- 3. 是否了解W3C的规范?⭐
- 4. 什么是语义化标签?⭐⭐
- 5. 行内元素和块级元素的区别?⭐
- 6. 行内元素和块级元素的转换?⭐
- 7. 常用的块级元素和行内元素有哪一些? ⭐
- 8. css盒子模型有几种类型?它们区别是什么 ?⭐
- 9. 标签上title与alt属性有什么区别?
- 10. H5新特性有哪些?⭐⭐
- 11. Canvas和SVG 的区别?
- 12. LocalStorage、SessionStorage、Cookie的对比?⭐⭐⭐⭐
- 13. CSS3的新特性有哪些?⭐⭐
- 14. css的引用有哪些?⭐
- 15. link和@import的区别?⭐
- 16. href和src的区别?⭐
- 17. CSS常用尺寸单位有哪些?应用场景?⭐⭐
- 18. 移动端适配方案有哪些?⭐
- 19. 什么是浮动?⭐
- 20. 清除浮动有哪些方法? ⭐
- 21. css选择器有哪些?⭐
- 22. CSS 样式覆盖规则?⭐
- 23. CSS 样式的优先级?⭐⭐
- 24. display: none 和 visibily: hidden 区别? ⭐⭐
- 25. 相对定位,绝对定位,固定定位的区别?⭐⭐⭐
- 26. 说几个未知宽高的元素进行水平垂直居中的方法? ⭐⭐⭐
- 27. calc的注意点?
- 28. CSS的布局类型?
- 29. CSS画三角形?
- 30. CSS实现三列布局(左右固定宽度,中间自适应)?⭐
- 31. 如果要做优化,CSS提高性能的方法有哪些?⭐⭐
- JS
- 1. JS数据类型有哪些?区别?⭐⭐⭐
- 2. JS中检测数据类型的方法有哪些?⭐⭐
- 3. JS中的栈和堆是什么?优缺点?⭐⭐⭐
- 4. 深克隆和浅克隆?⭐⭐⭐
- 5. JS垃圾回收机制?⭐⭐
- 6. JS垃圾回收机制存在问题,该如何优化?⭐⭐
- 7. JS哪些操作会造成内存泄露?⭐⭐
- 8. 什么是原型链?⭐⭐⭐
- 9. 闭包?⭐⭐⭐
- 10. JS继承的方法有哪些?优缺点?⭐⭐
- 11. new操作符具体都干了什么?⭐⭐
- 12. JS的几种具体异常类型(报错)?
- 13. 什么是事件冒泡?
- 14. 什么是事件委托?
- 15. undefined 和 null 区别?⭐
- 16. 伪数组和数组的区别?
- 17. 数组去重都有哪些方法?⭐⭐
- 18. 数组的基本操作方法?⭐⭐
- 19. forEach、for...of、for...in的区别?⭐⭐⭐
- 20. for循环和forEach有什么区别?⭐⭐
- 21. indexOf方法与includes方法的区别?⭐
- 22. JS中call、apply、bind有什么区别?⭐⭐
- 23. 箭头函数和普通函数有什么区别?⭐⭐
- 24. 说一下this指向?⭐⭐
- 25. JQuery对象和DOM元素之间如何转换?
- 26. 如何操作DOM元素?⭐
- 27. 防抖与节流的区别?并分别用代码表示? ⭐⭐
- 28. 防抖和节流应用场景?⭐
- 29. JS模块化有哪些?
- 30. 使用JQuery和vue的区别?
- 31. 加密方式?⭐
- 32. 前端加密方式?⭐
- 33. webpack?⭐⭐
- TS
- 1. 什么是 TypeScript ?
- 2. TypeScript 的特点是什么?
- 3. 使用 TypeScript 有什么好处?
- 4. TypeScript 的内置数据类型有哪些?
- 5. TypeScript 中的接口是什么?
- ES6
- 1. ES6新增的内容有哪些?⭐⭐⭐
- 2. ES6中,Set和Map的区别 ?⭐⭐⭐
- 3. map()和forEach的区别?⭐
- 4. ES6 中的箭头函数?⭐⭐⭐
- 5. 什么是扩展运算符,用于什么场景?⭐⭐
- 6. JS变量提升?⭐⭐⭐
- 7. 怎么实现Module模块化?⭐
- 8. 微任务和宏任务的区别?⭐⭐
- 9. JS事件循环机制?
- 10. 异步函数 ⭐⭐⭐⭐⭐
- VUE
- 1. vue的常用指令 ⭐⭐
- 2. v-model 实现原理?⭐
- 3. v-if 和 v-show 的区别?⭐⭐
- 4. v-if 和 v-for 那个优先级更高?⭐⭐
- 5. 常用的事件修饰符有哪一些(v-on 指令的修饰符)? ⭐
- 6. 虚拟 DOM?⭐⭐⭐
- 7. v-for 为什么要加 key?不加 key 会怎么样?⭐⭐
- 8. v-for 写在 template 上,不能加 key 怎么办?
- 9. vue2 生命周期⭐⭐⭐
- 10. VUE3 生命周期 ⭐⭐⭐
- 11. VUE中的 el 是什么?
- 12. 小程序生命周期? ⭐⭐
- 13. uni-app 生命周期 ⭐⭐
- 14. VUE 里边如何让 CSS 样式只在当前组件中生效? ⭐
- 15. VUE 的 watch 和 computed 有什么区别?应用场景?⭐⭐⭐⭐⭐
- 16. $nextTick 事件轮询?⭐⭐
- 17. 哪个阶段无法获取 DOM 节点?怎么处理?⭐
- 18. VUE 中使用 refs 获取组件的 DOM 元素报 undefined 如何解决?⭐⭐⭐
- 19. 数据修改后页面不刷新案例?怎么解决?⭐⭐⭐
- 20. Vue.extend 和 Vue.component 的区别?
- 21. VUE2 中为什么组件 date 是一个函数而不是一个对象?⭐⭐
- 22. VUE 是什么?说一下对 VUE 的理解?⭐⭐⭐⭐
- 23. 什么是渐进式?⭐⭐⭐
- 24. VUE 是如何实现双向数据绑定? (原理与缺陷)⭐⭐⭐
- 25. 什么是订阅发布模式?⭐
- 26. VUE2 和 VUE3 有什么区别?⭐⭐⭐
- 27. 选项式API和组合式API的优缺点?⭐
- 28. VUE2 数据劫持和 VUE3 数据代理的优缺点?⭐
- 29. MVC、MVP、MVVM 架构模式?⭐⭐⭐
- 30. Vuex 状态管理?⭐⭐⭐⭐
- 31. Vue Router 路由?⭐⭐⭐⭐
- 32. VUE 组件?⭐⭐⭐⭐
- 33. VUE 的优缺点?⭐
- 34. React 的优缺点?⭐
- 35. Anguar 的优缺点?⭐
- 36. 说一下 VUE 的服务端渲染 SSR?⭐
- 37. VUE 的插槽(slot)有哪几类?
- 38. VUE 自定义指令?
- 39. VUE有了数据响应式,为何还需要diff?
- 40. 什么是时间分片?
- 41. VUE3 为什么不需要时间分片?
- 42. VUE3为什么要引入Composition API?
- uni-app
- 1. uniapp 打包过程(安卓 Android)⭐
- 2. uniapp 打包过程(苹果 ISO)⭐
- 3. uniapp 小程序打包过程 ⭐
- 4. uniapp 的基本配置?
- 5. uniapp 上传文件时用到的 API 是什么?格式是什么?
- 6. uniapp 获取地理位置的API是什么?
- 7. px、rpx、em、rem、%、vh、vw 的区别是什么?⭐
- 8. uniapp 如何监听页面滚动?
- 9. 如何让图片宽度不变,高度自动变化,保持原图宽高比不变?
- 10. uni-app 的优缺点?
- 11. 分别写出 jQuery、VUE、小程序、uni-app 中的本地存储?
- 12. JQuery、VUE、uni-app、小程序的页面传参方式? ⭐
- 13. VUE、微信小程序、uni-app 绑定变量属性区别?
- 14. 小程序组件传参有哪些方式?
- HTTP请求
- 1. 浏览器输入url后都经历了什么?⭐⭐⭐
- 2. 浏览器地址栏的完整URL都包含哪些内容都各代表什么?
- 3. cookie、sessionStorage、localStorage的区别?⭐⭐⭐
- 4. 如何实现可过期的 localstorage 数据?
- 5. 请求行、请求头、请求体、响应行、响应头、响应体?
- 6. http 和 https 有何区别?
- 7. get 和 post 的区别?
- 8. 常见的HTTP状态码?
- 9. Token 能放在 cookie 中吗?
- 10. Token 认证流程?
- 11. 什么是同源策略?为什么要有同源策略?⭐⭐
- 12. XSS攻击是什么?
- 13. CSRF攻击是什么?
- 14. 什么是跨域?为什么有跨域问题?⭐⭐⭐
- 15. 跨域的解决方案有哪几种?⭐⭐⭐
- 16. 什么是 options 请求?
- 17. 什么是浏览器内核?有什么用?有哪一些?
- 18. 什么是浏览器兼容问题?
- 19. 优雅降级和渐进增强(浏览器兼容问题)
- 20. 说一说前端性能优化手段?⭐⭐⭐
- 21. 网站性能优化的好处?怎么优化?
- 22. axios的拦截器原理及应用?
- 23. 创建 ajax 过程?
- 24. Fetch 请求方式?
- 25. 浏览器是如何渲染页面的?
- 26. 有什么方法可以保持前后端实时通信?
- Git
- 1. Git 是什么?⭐
- 2. git 常用的命令?⭐
- 3. Git 和 SVN 的区别?⭐⭐
- 4. Git项目如何配置?如何上传至GitHub?
- 5. 你们公司Git分支是怎么管理的?⭐⭐
- 6. Git 工作流⭐
- 7. Git版本冲突是什么?⭐
- 8. 如何解决Git版本冲突?⭐
- 微服务
- 1. 微服务是什么?
- 2. 微服务的特征?
- 3. 什么是单体应用?
- 4. 微服务架构需要的功能或使用场景?
- 手撕代码
- 项目中遇到过哪些性能优化的问题?
- 1. 表格数据量大导致卡顿问题。
- 2. 表格树(有展开功能)数据量大导致渲染慢问题
- 3. 系统上线后,首屏加载慢问题。
- 面试过程
HTML & CSS
1. XML、HTML、XHTML 有什么区别?⭐
- XML:XML 是
可扩展标记语言
。主要是用来存储和传输数据,而非显示数据,可以用来标记数据,定义数据类型,允许用户对自己的标记语言进行定义。XML强调数据的结构化表示,要求所有标签必须正确闭合,并且区分大小写。 - HTML:HTML 是
超文本标记语言
。主要是用来描述网页的一种标记语言,通过标记标签来描述网页。 - XHTML:XHTML 是
可扩展超文本标记语言
。XHTML 基于 XML 和 HTML 而来,也是用来描述网页的标记语言,是更严格的 HTML 版本。例如:XHTML 元素必须被正确地嵌套,标签名必须用小写字母,文档必须拥有根元素,对于图片需添加 alt 属性等。XHTML 和 HTML 4.01 几乎是相同的,XHTML 是 W3C 标准。 - XML和HTML区别:
XML 相比 HTML 语法要求严格
。HTML 是预定义的,XML 标签是免费的、自定义的、可扩展的。HTML 的设计目的是显示数据并集中于数据外观,XML 的设计目的是描述数据、存放数据并集中于数据的内容。XML 是一种跨平台的,数据处理和传输的工具。总的来说,XML用来存储和传输数据,而 HTML 和 XHTML 用来描述网页,XHTML 比 HTML 更为严格
。
2. XML和JSON的区别?
- JSON:JSON 是一种
轻量级的数据交换格式
,它基于 JavaScript 的一个子集,简单的说 JSON 就是一串字符串用于不同平台的数据交换。 - XML:XML 是 可扩展标记语言 ,是标准通用标记语言 (SGML) 的子集,XML 主要是用来存储和传输数据,而非显示数据,可以用来标记数据、定义数据类型,允许用户对自己的标记语言进行定义。
- JSON和XML区别:JSON 数据的体积小,传递速度更快,与 JavaScript 数据交互更加方便,更容易解析处理。XML 对数据的描述性比较好。JSON 支持数组,XML 不支持数组。JSON 不支持命名空间,XML 支持命名空间。JSON 容易阅读,XML 难以阅读和解释。JSON 不使用结束标记,XML 有开始和结束标签。JSON 的安全性较低,不支持注释,仅支持 UTF-8 编码。XML 比 JSON 更安全,支持注释,支持各种编码。
3. 是否了解W3C的规范?⭐
- W3C 标准指的是
万维网联盟标准
。万维网联盟标准指的不是一个标准,而是一系列标准的集合。旨在确保互联网上的信息和服务能够在不同的平台和设备上无障碍地访问和交互。web 可以简单分为结构、表现、行为
三部分,三部分独立开来使其模块化,W3C 是对 web 做出规范,使代码更加严谨,做出来的网页更加容易使用和维护
。 - 结构标准:主要包括
XHTML 和 XML
。比如像标签闭合、标签元素和属性名字小写、标签不乱嵌套、属性必须有属性值、属性值必须用引号括起来、特殊符号用编码表示、定义语言编码等。标签规范可以提高搜索引擎对页面的抓取效率,对 搜索引擎优化很有帮助,越规范的网站搜索排名越靠前。 - 表现标准:主要包括
CSS
。 - 行为标准:主要包括
对象模型
(如 W3C DOM),ECMAScript等。比如说尽量使用外链的 CSS 和 JS 脚本,提高页面的渲染效率;尽量少使用行内样式;类名要做到见名知意。遵循W3C标准可以让我们的页面,我们的程序能够支持所有浏览器,能够满足尽可能多的用户。
4. 什么是语义化标签?⭐⭐
-
语义化标签是 H5 新特性。语义化标签有:
<header>
定义页面头部,<footer>
定义页面尾部,<nav>
定义导航链接,<article>
定义内容,<section>
定义块级标签,<aside>
定义侧边栏等。 -
语义化标签就是标签语义化,让标签有自己的含义,使浏览器和搜索引擎能直观的认识标签的用途和内容。
-
虽然可以采用 DIV + CSS 的方式布局页面,但 DIV 标签本身没有特殊含义,文档结构不够清晰,不利于浏览器对页面的读取,在分离 CSS 样式后,体验不友好。
-
使用语义化标签可以
使代码结构清晰,可读性高,便于团队开放和维护
。在页面没有加载 CSS 的情况下也能呈现良好的结构,易于阅读。有利于搜索引擎优化。
5. 行内元素和块级元素的区别?⭐
-
块级元素(block):
默认换行,独占一行,可以设置宽度(默认宽度是父容器的100%)、高度以及边距等样式属性。块级元素可以嵌套任意元素。
-
行内元素(inline):
默认不换行,在同一行显示,设置宽高无效 (宽高由其内容决定),设置内外边距只有左右边距生效。行内元素不能包含块级元素,只能包含文本或者其它行内元素。
-
行内块元素(inline-block):
综合块级元素与行内元素的特性,可设宽高(默认是内容宽高),也可以设置内外边距。不独占一行,不影响布局。
6. 行内元素和块级元素的转换?⭐
- 定义元素为块级元素:
display:block
- 定义元素为行内元素:
display:inline
- 定义元素为行内块级元素:
display:inline-block
7. 常用的块级元素和行内元素有哪一些? ⭐
- 块级元素:div、p、h1~h6、ol、ul、li、table、form。
- 行内元素:a、span、img、input、lable、button。
- 行内块元素:img、input、button。
8. css盒子模型有几种类型?它们区别是什么 ?⭐
- 根据盒子大小的计算方式不同,css 盒子模型有两种类型。分别是
标准盒子模型(W3C盒子模型)
和怪异盒子模型(IE盒子模型)
。 - 标准盒子模型:
设置的宽度 = 内容的宽度
,盒子的宽度 = 内容的宽度 + padding*2 + border*2
(此时盒子的宽度为F12看样式时显示的宽度。不包括margin,若要算则还需要 +margin*2 )。 - IE盒子模型:
设置的宽度 = 盒子的宽度
,内容的宽度 = 盒子的宽度 - padding*2 - border*2
(此时盒子的宽度为F12看样式时显示的宽度。不包括margin,若要算则还需要 -margin*2 )。 - 默认情况下都是标准盒子模型。
- 设置为怪异盒子模型:
box-sizing:border-box
,设置为标准模型:box-sizing:content-box
9. 标签上title与alt属性有什么区别?
- title属性:用于为元素提供额外的信息,这些信息在用户将鼠标悬停在元素上时显示为工具提示(tooltip)。几乎所有的HTML元素都可以使用title属性,它是一个全局属性。
- alt属性:专门用于<img>标签的属性,用来为图像提供替代文本。当图像因为各种原因(如网络问题、文件损坏、用户禁用图像等)无法显示时,替代文本将被显示。搜索引擎使用alt文本来理解图像内容,有助于搜索引擎优化。
10. H5新特性有哪些?⭐⭐
- 新增
语义化标签
。有助于改善文档的结构,使得页面内容更易于理解和搜索引擎优化。如:<header> <footer> <nav> <section> <article><aside>等 - 新增
多媒体支持
。包括音频元素<audio>和视频元素<video>,允许直接在网页中嵌入音频和视频,无需额外插件。 - 新增
表单增强
。新增输入类型
,<input>的type属性值增加了email、url、tel、date、time等。新增表单验证
,<input>的pattern属性和required属性,pattern属性用于验证 <input> 元素的值的正则表达式;required属性校验必填。新增自动完成功能
,<form>和<input>的autocomplete属性,当用户在自动完成域中开始输入时,浏览器应该在该域中显示填写的选项。 - 新增
Canvas绘图
。引入了<canvas>元素,允许开发者使用JS在网页上绘制图形、动画和游戏,提供了更强大的图形处理能力。 - 新增
SVG 绘图
。可以通过代码绘制复杂的图形和动画,为网页增添更多的交互性和视觉效果。 - 新增
本地存储
。LocalStorage和SessionStorage
,可以在浏览器端存储数据,提供了比传统的Cookie更强大更便捷的数据存储方案。 - 新增
拖拽释放API
。可以创建拖拽和放置的功能,增加用户的交互体验。 - 新增
地理API
。Geolocation API
允许Web应用获取用户的地理位置信息,有助于实现基于位置的服务和功能。 - 新增
JavaScript API
。如History API
、Web Workers
、Web Sockets
等,扩展了浏览器端的功能,使得开发更加灵活和强大。
11. Canvas和SVG 的区别?
- SVG是用XML绘制的,Canvas是用JS绘制的。
- SVG是一种矢量图,Canvas是位图(由像素组成),依赖于分辨率。所以SVG放大不会失真,但是Canvas绘制的图形会失真。
- SVG 支持事件处理器,而Canvas不支持事件处理器。
- Canvas绘制的图片不会出现在DOM树中,SVG绘制的图片会出现在DOM树中。
- SVG中的文字独立于图像,文字可保留,可编辑和可搜索,Canvas的文本渲染能力弱。
- Canvas适合图像密集型的游戏,频繁地重绘图像,SVG 绘制的复杂度高时减慢渲染的速度。
- Canvas绘制的图形可以多种格式 (jpg、png) 保存图片,但是 SVG 绘制的只能以 .svg 格式保存,使用时可以引入 html 文件。
- Canvas 适合开发游戏,SVG不适合游戏应用。
12. LocalStorage、SessionStorage、Cookie的对比?⭐⭐⭐⭐
LocalStorage、SessionStorage、Cookie都是浏览器中用于存储数据的机制,但它们在存储的范围、持久性和使用场景上有显著的区别。
-
LocalStorage
- 作用范围:可以在
同一浏览器的同一域名下的所有窗口和标签页中访问。
- 持久性:
数据永久保存在浏览器中,直到通过代码删除或用户清除浏览器数据为止,即使关闭浏览器和计算机,数据仍然存在。
- 隐私性:
不会随每个HTTP请求发送到服务器。
- 存储容量:一般为
5MB
左右,不同浏览器可能会有所不同。 - 使用场景:适用于需要长期保存的、与特定用户相关的数据,例如用户偏好设置、主题选择等。
- 作用范围:可以在
-
SessionStorage
- 作用范围:存储的数据仅在
同一浏览器的同一个域名下的同一窗口或标签页中可用,不同的标签页和窗口间不能共享SessionStorage数据。
- 持久性:
数据在浏览器窗口或标签页关闭时被自动清除。
- 隐私性:
不会随每个HTTP请求发送到服务器。
- 存储容量:一般为
5MB
左右,不同浏览器可能会有所不同。 - 使用场景:适用于仅在单个浏览会话期间需要保存的数据,例如表单输入临时保存、单次操作步骤记录等。
- 作用范围:存储的数据仅在
-
Cookie
- 作用范围:可以在
同一浏览器的同一域名下的所有窗口和标签页中访问。
通过设置 Domain 属性,甚至可以在子域之间共享 Cookie,可以被设置为仅限于特定路径。 - 持久性:
可以设置 Expires 或 Max-Age 属性来控制 Cookie 的过期时间。如果没有设置,则 Cookie 在浏览器关闭后会被删除(即所谓的“会话性 Cookie”)。
- 隐私性:
Cookie会随每次HTTP请求发送到服务器,可能涉及隐私问题。
- 存储容量:每个 Cookie 的大小通常限制为
4KB
,总数也有限制(不同浏览器有所不同,通常一个域名下最多 20-50 个)。 - 使用场景:适用于与服务器之间的会话管理、保存用户登录状态、身份验证等。由于 Cookie 会在每次 HTTP 请求时发送到服务器,所以通常用于需要与服务器交互的场景。
- 作用范围:可以在
13. CSS3的新特性有哪些?⭐⭐
- Flex弹性布局
- Grid网格布局(display: grid;)
- 选择器:层级选择器,属性选择器,状态伪类选择器,结构伪类选择器,伪元素选择器
- 文本效果:文本阴影 ,文本自动换行,文本溢出,(单词拆分,文本拆分)
- 边框:圆角边框 border-radius,边框阴影 box-shadow,边框图片 border-image
- 背景:渐变背景,多重背景 (设定背景图像的尺寸,指定背景图像的位置区域,背景的绘制)
- 透明度:opacity ( 取值0-1,通常用于做元素的遮罩效果)
- 高斯模糊:filter
- 渐变:background: linear-gradient (线性渐变,径向渐变 ,文字渐变)
- 过渡:transition
- 2D转换 / 3D转换: transform
- 动画:Animation (@keyframes 动画帧)
- 媒体查询:@media
- 多列布局 (将文本内容设计成像报纸那样的多列布局。兼容性不好,还不够成熟)
14. css的引用有哪些?⭐
内联方式
:直接在 html 标签中书写 style 样式嵌入方式
:在<style> 标签内书写CSS代码链接方式
:使用link引入外部的.css文件
15. link和@import的区别?⭐
- link 和 import 写法不同。link 通过 <link> 标签的 href 属性引入;import 通过 @import url() 引入。
- link 是 XHTML 标签,还可以定义其他事务;@import 属于 CSS 范畴,只能加载 CSS。
- link 无兼容问题;@import 兼容 IE5 以上。
- link 支持使用 JavaScript 控制 DOM 去改变样式;@import 不支持改变样式。
- link 是连接整个 css 文件;@import 可以模块化引入 css 文件。
- link 引用 CSS 时,在页面加载时同时加载 css;而 @import 会把 css 文件放在页面的最底部,导致 css 最后才加载完毕,等到页面完全加载才加载 css,导致页面留白时间长,影响用户体验。
16. href和src的区别?⭐
- 请求资源不同。href 超文本引用,用来建立当前元素和文档之间的连接,常用的是link、a 标签。src 会将指向的资源下载并引用到当前文档中,常用的标签有 script,img,iframe 标签。
- 作用结果不同。href 用于在当前文档和引用资源之间确立联系,src 用于替换当前内容。
- 浏览器解析方式不同。herf 引用的资源时,浏览器会将其识别为 CSS 文档,并行下载资源并且不会停止对当前文档的处理。当浏览器解析到 src 时,会暂停其他资源的下载和处理,直接将该资源下载,编译,执行完毕。
17. CSS常用尺寸单位有哪些?应用场景?⭐⭐
- px:像素,相对长度单位,它的大小取决于屏幕的分辨率,是一个固定值,不能自适应。
- em:相对长度的单位,相对于当前对象内文本的字体尺寸,未设置则默认是浏览器默认字体尺寸。
- rem:CSS3 中新增的一个相对长度单位,相对于根元素 <html> 的 font-size 字体大小,根元素字体大小未设置,使用浏览器默认字体大小。
- vw:相对于视口的宽度。视口被均分为100单位的 vw。
- vh:相对于视口的高度,视口被均分为100单位的 vh。
- vmin:相对于视口宽度或高度中较小的那个。其中最小的那个被均分为100单位的 vmin。
- vmax:相对于视口宽度或高度中较大的那个。其中最大的那个被均分为100单位的 vmax。
- cm:厘米,绝对长度单位。
- mm:毫米,绝对长度单位。
- in:英寸,绝对长度单位。
- %:百法比,是相对于父元素的值来计算。
- rpx:是微信小程序中特有的单位,用于定义小程序界面的尺寸。rpx可以自适应不同设备的像素密度,确保小程序在不同设备上具有一致的外观。
应用场景:
- 在移动端网页开发中,页面要做成响应式的,可使用 rem 配合媒体查询实现。原理:通过媒体查询,能够在屏幕尺寸发生改变时,重置 html 根元素的字体大小,页面中的元素都是使用rem 为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改。
- 利用 vw 和 rem 实现响应式。原理:由于 vw 被更多浏览器兼容之后,在做移动端响应式页面时,通常使用 vw 配合 rem。原理是使用 vw 设置根元素 html 字体的大小,当窗口大小发生改变,vw 代表的尺寸随着修改,无需加入媒体查询,页面中的其他元素仍使用 rem 为单位,就可实现响应式。
18. 移动端适配方案有哪些?⭐
- vw、rem、em、rpx、%
19. 什么是浮动?⭐
- float浮动。可以指定浮动的方向。
- 影响:使盒子脱离文档流.如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成后面的盒子布局受到影响。
- 应用场景:设置浮动的图片,可以实现文字环绕图片。
20. 清除浮动有哪些方法? ⭐
清除浮动,主要是为了解决父级元素因为子级浮动引起的内部高度为 0 的问题
- 父级div定义height
- 结尾处加空div标签然后添加一个clear:both样式 (浮动多的话要加很多个div)
- 父级 div 定义 overflow:hidden 超出盒子部分会被隐藏 (不推荐)
- 父级div定义伪类:(推荐)
clearfix:after{content:"";display:block;visibility:hidden;height:0;line-height:0;clear:both;}
21. css选择器有哪些?⭐
- 基本选择器
标签选择器
。如:div { }id 选择器
。如:id=”a” #a { }类选择器
。如:class=”b” .b { }通配符选择器
。如: * { }
- CSS3新增选择器
- 层级选择器
后代选择器
。选择子类元素,包括间接子类。如:ul li { }子代选择器 >
。只选择直接子类元素。如:ul > li { }相邻兄弟选择器 +
。只选择紧跟在元素后面的兄弟元素。如:div+p { }通用兄弟选择器 ~
。选择元素后面的所有兄弟元素。如:div~p { }共享选择器 ,
。选择符合的所有元素。如:.div1, .div { }
状态伪类选择器
。结构伪类选择器
。表单伪类选择器
。伪元素选择器
。属性选择器
。
- 层级选择器
22. CSS 样式覆盖规则?⭐
- !important 的样式属性不被覆盖。
- 由于继承而发生样式冲突时,最近祖先获胜。
- 继承的样式和直接指定的样式冲突时,直接指定的样式获胜。
- 直接指定的样式发生冲突时,样式权值高者获胜。
- 样式权值相同时,位置靠后的获胜。
23. CSS 样式的优先级?⭐⭐
!important > 内联样式 > id 选择器 > 类选择器/伪类选择器/属性选择器 > 标签选择器/后代选择器/伪元素选择器 > 子选择器/相邻选择器 > 通配符 * > 继承样式 > 浏览器默认样式
- 引入方式:内联样式的优先级高于嵌入和外链,嵌入和外链的选择器相同就看他们在页面的插入顺序,后面插入的会覆盖前面的。
- 选择器优先级:id 选择器高于 ( 类选择器/伪类选择器/属性选择器 ) 高于 ( 标签选择器/后代选择器/伪元素选择器 ) 高于 ( 子选择器/相邻选择器 ) 高于通配符 *。
- 继承样式:继承样式是所有样式中优先级比较低的,浏览器默认样式优先级最低。
- !important:!important 最高权重。只有在需要覆盖全站或外部 CSS 的特定页面中使用!important。永远不要在插件中使用 !important ,永远不要在全站范围的 CSS 代码中使用 !important。
24. display: none 和 visibily: hidden 区别? ⭐⭐
- display:none:隐藏对应的元素,整个元素消失不占空间。
- visibily:hidden:隐藏对应的元素,元素还会占用空间。
- dispaly 还可以转换元素类型,可以转换成块级元素、行内元素,行内块元素、弹性布局、网格布局等。visibility 只能做隐藏。
25. 相对定位,绝对定位,固定定位的区别?⭐⭐⭐
- position:relative:相对定位,相对于自己进行定位。
- position:absolute:绝对定位,相对于有相对定位的父级元素进行定位,没有就找 body。
- position:fixed:固定定位,相对于浏览器定位。
26. 说几个未知宽高的元素进行水平垂直居中的方法? ⭐⭐⭐
绝对定位
:通过 left,top 和 transform 属性实现水平垂直居中。
其中 translate 属性取值为负数时表示向左和向下移动。这种方式兼容性好,被广泛使用的一种方式。
<style>.box {position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);}
</style>
弹性布局
:设置父级为弹性盒子且水平和垂直居中。
这种方式代码简洁,但是兼容性 IE11 以上支持。
<style>.parent {display: flex;justify-content: center;/** 水平居中*/align-items: center;/** 垂直居中*/}
</style>
表格布局
:设置父级为表格元素且内部元素水平和垂直居中;设置子元素为行内块元素。
兼容性好。
<style>.parent {width: 400px;height: 300px;display: table-cell;/** 设置父级为表格元素*/text-align: center;/** 水平居中*/vertical-align: middle;/** 垂直居中*/}.child {width: 200px;height: 150px;display: inline-block;/** 设置子级为行内块元素*/}
</style>
网格布局
:设置父级为网格元素且内容居中。
这种方式代码简洁,但是兼容性IE10 以上支持。
<style>.parent {display: grid;/** 设置父级为网格元素*/ place-items: center;/** 设置内容居中*/}
</style>
27. calc的注意点?
- calc()是CSS函数,允许开发者在指定长度、百分比、数值等时,进行加减乘除运算。
- calc()在使用时,运算符(+ - * /)前后必须有空格
28. CSS的布局类型?
流动布局(Flow Layout)
:最基本的布局方式,也是最常用的布局方式。它将元素按照一定顺序从左到右、从上到下排列固定布局(Fixed Layout)
:将容器的大小固定,不随屏幕尺寸的变化而变化。固定宽度的布局可能会给高分辨率屏幕用户带来巨大的页面空白;小屏幕上可能会出现滚动条,影响用户体验。弹性布局(Flexible Box Layout)
:可以将容器内的元素自动排布,使其适应不同的屏幕尺寸。非常适合用来制作响应式设计。定位布局(Positioned Layout)
:这种布局方式可以通过使用position属性,将元素定位在任何位置。它适合用来制作复杂的布局,比如悬浮按钮、导航栏等。浮动布局(Floating Layout)
:浮动布局允许元素沿着一行或多行移动,从而使多个元素在同一行中并排放置。它主要用于创建多列布局和文字环绕效果。响应布局(responsive layout)
:目标是确保一个页面在所有终端上(各种尺寸的PC、手机、平板的Web浏览器等)都能显示出令人满意的效果。通常是结合了百分比流式布局+rem弹性布局,再搭配媒体查询技术(@media)使用。
29. CSS画三角形?
- 裁剪路径(clip-path):使用polygon()按指定坐标路径裁剪想要的形状。
<style>.box{ width: 200px;height:200px;clip-path: polygon(50% 0px, 100% 100%, 0px 100%);background-color:red;}
</style>
- CSS边框样式:设置宽高为0,设置不同的边框颜色和宽度,来让一个元素显示为三角形。
<style>.box{ width: 0;height: 0;border-style: solid;border-width: 0 100px 100px 100px;border-color: transparent transparent red transparent;}
</style>
30. CSS实现三列布局(左右固定宽度,中间自适应)?⭐
- 可以使用flex布局 或者grid布局。
- flex布局:父元素设置为flex布局,左右子元素设置固定宽度,中间子元素设置flex为1。
<div class="container"><div class="column-left">左侧栏</div><div class="column-middle">中间栏</div><div class="column-right">右侧栏</div>
</div>
<style>
.container {display: flex;
}
.column-left {width: 200px; /* 左侧固定宽度 */
}
.column-right {width: 200px; /* 右侧固定宽度 */
}
.column-middle {flex: 1; /* 中间自适应,占用剩余空间 */
}
</style>
- grid布局:父元素设置为grid布局和grid-template-columns: 200px auto 200px;
<div class="container"><div class="column-left">左侧栏</div><div class="column-middle">中间栏</div><div class="column-right">右侧栏</div>
</div>
<style>
.container {display: grid;grid-template-columns: 200px auto 200px; /* 左右固定,中间自适应 */
}
</style>
31. 如果要做优化,CSS提高性能的方法有哪些?⭐⭐
- 内联首屏关键CSS
- 异步加载CSS
- 资源压缩
- 合理使用选择器
- 减少使用昂贵的属性
- 不要使用@import
JS
1. JS数据类型有哪些?区别?⭐⭐⭐
JS 的数据类型分为两类,分别是
基本数据类型
和引用数据类型
。它们主要区别是在内存中的存储方式不同
。
- 基本数据类型
- number 数字、string 字符串、boolean 布尔值、null 空值、undefined 未定义、symbol 唯一值、BigInt 最大值。
- Symbol 是 Es6 新出的一种数据类型,这种数据类型的特点是
没有重复的数据
,可以做 object 的 key。 - BigInt 也是 ES6 新出的一种数据类型,BigInt 可以表示任意大的整数。
能够解决解精度缺失的问题
(超过 Number 类型支持范围的数值都会失去精度)。使用方法:1. 整数末尾直接加n,如:647326483767797n。2. 调用 BigInt() 构造函数,如:BigInt(“647326483767797”)。
- 引用数据类型
- Object (包括普通对象,数组,正则,日期,Math 数学函数等)。
- 二者区别
- 基本数据类型有固定的大小和值,存放在栈中,可以直接访问。
- 引用数据类型不确定大小,但引用地址是固定的,它的地址存在栈中,指向存储在堆中的对象。
2. JS中检测数据类型的方法有哪些?⭐⭐
1. typeof
:常用于判断基本数据类型
,除了 null 检测为 object。对于引用数据类型,除了 function 返回 function,其余全部返回 object。
2. object.prototype.toString.call()
:判断数组和对象类型比较有用
,但对于基本类型(如数字、字符串、布尔值等)并不能准确判断。
3. Array.isArray()
:判断是不是数组
4. instanceof
:不能检测基本数据类型,检测引用类型时会顺着原型链往上找,只要原型链上存在就会返回true。但我们可以随意更改原型链的指向,导致检测结果不准确。
5. constructor
:可以检测基本数据类型,也能分辨出数组和对象。但因为我们可以更改constructor,所以会导致检测结果不准确。
6. 判断是不是null:===null
3. JS中的栈和堆是什么?优缺点?⭐⭐⭐
JS的变量都储存在内存中。内存中开辟了两个区域储存变量,分别是栈区域和堆区域。栈与堆实际上是操作系统对进程占用的内存空间的两种管理方式。
- 栈
- 栈是一种先进后出的数据结构,由操作系统自动分配内存空间,自动释放,占固定的大小空间。
- 栈存储的是基本数据类型的值,以及引用数据类型的引用地址。
- 栈中存储的数据的生命周期随着当前环境的执行完成而结束。
- 堆
- 堆由操作系统动态分配内存空间,大小不固定,不会自动释放,一般由程序员分配释放也可由垃圾回收机制回收。
- 堆可以存储大量的数据和复杂的数据结构,如对象和数组。
- 堆存储的数据可以在全局作用域中访问,适合存储长期保存的数据和共享数据。
- 引用数据类型只有在引用的它的变量不在时,被垃圾回收机制回收。
- 栈和堆的优缺点
- 栈相对于堆存取速度更快,且栈内存中数据是可以共享的,但内存空间有限。
- 堆存取效率相对较低,但内存空间大。
- 栈内存可以及时得到回收,相对来说更容易管理内存空间,但存储在栈中的数据大小和生存期必须是确定的,缺乏灵活性。
- 堆的内存是操作系统动态分配的,方便存储和开辟内存空间。有垃圾回收机制,生存周期比较灵活。
4. 深克隆和浅克隆?⭐⭐⭐
- 浅克隆
- 克隆对象的第一层属性。
- 如果是基本数据类型,直接将存储在栈中的值赋值给对应的变量,原始值改变不会影响。
- 如果是引用数据类型,则克隆的是对象的引用地址。改变引用地址,新对象也会跟着改变,想要改变这种继承的现象就要使用深度克隆。
- 在 JS 中可以通过
Object.assign()
、展开运算符(...)
、for...in循环
实现浅克隆。
- 深克隆
- 克隆对象各个层级的属性。
- 深克隆是指创建一个与原对象完全相同的新对象 (数据源不同,数据地址已变化)。
- 可以通过
递归
、JSON.parse(JSON.stringify(obj))
实现深克隆。
5. JS垃圾回收机制?⭐⭐
内存泄漏
- JS 代码运行时,需要分配内存空间存储变量和值,当这些变量不再作用时,需要释放内存,如果没有及时释放,就会引起内存泄漏。堆积起来会影响性能甚至造成系统崩溃。垃圾回收机制就是为了防止内存泄漏,及时释放不再使用的内存,提高程序性能。
垃圾回收机制
- 垃圾回收机制是一种
自动管理内存的机制
,它会自动监测和回收不再使用的对象,从而释放内存空间。实现的原理主要有标记清除算法、引用计数算法、复制算法。标记清除算法
:垃圾回收器会定期扫描内存中的对象,标记那些可达对象和不可达对象。可达对象指的是正在被使用的对象,不可达对象指的是不再被引用的对象。垃圾回收器会将不可达对象标记为垃圾对象,并将他们从内存中清除。该算法的优点是可以处理循环引用的情况,缺点是垃圾回收器的工作需要消耗一定的系统资源
。因此如果程序中存在大量的内存占用或者存在频繁创建和销毁对象的操作,执行垃圾回收操作的时间会比较长,对性能造成一定的影响。引用计数算法
:垃圾回收器会记录每个对象被引用的次数,当对象被引用的次数为0时,该对象就会被清除。该算法的优点是实现较为简单,但无法处理循环引用的情况
。即两个对象相互引用,但是它们的引用次数都不为 0,导致内存无法被释放,可能会导致内存泄漏。复制算法
:将内存空间划分为两个相等的区域,每次只使用一个区域,这个区域满时,将其中存活的对象复制到另外一个区域,再将原区域的对象全部清除。优点是可以避免由于产生大量内存碎片而引发的内存分配失败问题
。
- 垃圾回收机制是一种
6. JS垃圾回收机制存在问题,该如何优化?⭐⭐
虽然浏览器可以自动的进行垃圾回收,但是当代码比较复杂时,垃圾回收对性能的消耗比较大,所以应该尽量减少垃圾回收。需要根据具体应用的需求和环境进行优化。
对数组进行优化
。清空数组时,赋值为[ ],同时将数组的长度置为0。对象复用
。尽量减少对象的创建和销毁次数。将不再使用的对象就设置为 null
。函数优化
。使函数功能单一化。尽早释放资源
。使用闭包
。在闭包中的变量不会被垃圾回收机制回收。
7. JS哪些操作会造成内存泄露?⭐⭐
意外的全局变量
。由于使用未声明的变量而意外的创建了一个全局变量,而使用这个变量一直留在内存中无法被回收,因为垃圾回收器难以跟踪到它们是否真的不再被使用。没有清理的DOM元素引用
。如果存储了DOM元素引用但不再需要这些元素,这些引用会阻止元素的回收。未清理的事件监听器
。忘记移除事件监听器会导致相关对象无法被回收。未清理的定时器和异步操作
。忘记清除定时器或未处理的异步回调函数也会导致内存泄露循环引用
。两个或多个对象相互引用,如果这些对象没有被其他对象引用,那么引用计数无法将它们标记为垃圾回收的候选。闭包
。因为在闭包中的变量不会被垃圾回收机制回收。子元素存在引起的内存泄露
。如果父元素被删除,但其子元素仍然被外部引用,那么父元素的内存将无法释放。被遗忘的内部对象
。某些库或框架可能会在内部创建对象并保留对它们的引用,如果这些引用没有正确管理,会导致内存泄露。
8. 什么是原型链?⭐⭐⭐
- 每个函数身上都有一个
prototype 原型对象
,并且有一个__proto__指针
指向下一级原型对象。如果一个对象的属性或方法在自身中找不到,那么就会去 prototype 原型对象中查找,如果还找不到,就继续向上查找直到 null。当_proto_指针指向 null 时形成一个链条,这个链条叫做原型链
。 - 在原型链中,对象可以继承原型对象的属性和方法。如果想在构造函数中添加属性和方法,可以将它们添加到构造函数的 prototype 属性中,这样通过该构造函数创建的对象都可以访问到这些属性和方法。
- 原型链的特点是:对象可以沿着原型链向上查找属性和方法,
实现了属性和方法的共享和继承
。
9. 闭包?⭐⭐⭐
- 什么是闭包
- 因为作用域链的存在,函数的内部可以直接读取全局变量,而函数内部无法读取另一个函数内部的局部变量,如果想读取函数内部的局部变量,可以通过闭包来实现。
- 闭包就是在一个函数内部创建另外一个函数,让你可以在一个内层函数中访问到外层函数的局部变量。简单来说,
闭包就是可以读取其他函数内部局部变量的函数
。本质上,闭包是将函数内部和函数外部连接起来的桥梁
。
- 为什么要使用闭包
- 局部变量无法共享和长久的保存,而全局变量可能造成变量污染。
- 闭包可以读取函数内部的局部变量,且不会被垃圾回收机制回收,可以长期保存。
- 闭包的作用
- 在函数外部可以访问函数内部的局部变量。
- 可以使函数内部的变量在函数执行结束之后不被销毁 ,长久保存在内存中,不会被垃圾回收机制回收。
- 使用闭包,可以封装自己的函数代码,实现模块化。
- 保护:避免命名冲突。
- 保存:解决循环绑定引发的索引问题。
- 闭包的缺点
- 由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。
- 如何解决:在销毁函数之前,将不使用的局部变量全部删除。
- 闭包的应用
- 能够模仿块级作用域。
- 设计模式中的单例模式。
- for 循环中的保留 i 的操作。
- 防抖和节流。
- 函数柯里化。
- 在构造函数中定义特权方法。
- Vue 中数据响应式 Observer 中使用闭包。
10. JS继承的方法有哪些?优缺点?⭐⭐
JS继承的方法有以下几种:
原型链继承
、构造函数继承
、组合继承
、原型式继承
、寄生式继承
、寄生组合式继承
、ES6 Class实现继承
。
继承的目的是:重复利用另外一个对象的属性和方法
。
- 原型链继承:将父类的实例作为子类的原型,从而实现对父类属性和方法的继承。优点:写法方便简洁,容易理解。缺点:不能传递参数和共享所有继承的属性和方法,当一个发生改变另外一个随之改变。
- 构造函数继承:在子类的构造函数中调用父类的构造函数,使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上,从而实现对父类实例属性的继承。优点:解决了原型链继承不能传参的问题和父类的原型共享的问题。缺点:方法都在构造函数中定义,因此无法实现函数复用。
- 组合继承:将原型链继承和构造函数继承结合起来,既可以实现对父类原型属性和方法的继承,又可以实现对父类实例属性的继承。优点: 解决了原型链继承和构造函数继承造成的影响。缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
- 原型式继承:通过创建一个临时构造函数来实现对父类的属性和方法的继承。优点:不需要单独创建构造函数。缺点:属性中包含的引用值始终会在相关对象间共享。
- 寄生式继承:在原型式继承的基础上,通过在临时构造函数中添加方法和属性,从而实现对父类的继承。优点:写法简单,不需要单独创建构造函数。缺点:通过寄生式继承给对象添加函数会导致函数难以重用。
- 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。优点:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变。缺点:代码复杂。
- ES6 Class实现继承:ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法加到 this 上面 (所以必须先调用super方法),然后再用子类的构造函数修改 this。需要注意的是,class 关键字只是原型的语法糖,JS继承仍然是基于原型实现的。 优点:语法简单易懂,操作更方便。缺点:并不是所有的浏览器都支持 class 关键字 lass Person。
11. new操作符具体都干了什么?⭐⭐
- 创建一个新对象 obj。
- 将该对象与构造函数通过原型链连接起来(设置该对象的构造函数)。
- 将构造函数中的 this 绑定到该对象上。
- 根据构造函数返回类型作判断,如果是值类型则返回新对象 obj,如果返回对象,则返回构造函数里的对象。
// 手写 new 操作符
function mockNew(constructor, ...args) {// 1.创建一个新对象 objconst obj = {};// 2.把构造函数当参数传入,新对象指向构造函数原型对象obj.__proto__ = constructor.prototype;// 3.通过 apply 将构建函数的 this 指向新对象let result = constructor.apply(obj, args);// 4.根据返回值判断return result instanceof Object ? result : obj;
}
12. JS的几种具体异常类型(报错)?
语法错误
(SyntaxError)引用错误
(ReferenceError)范围错误
(RangeError)类型错误
(TypeError)与 url 相关参数不正确
(URLError)全局函数 eval 执行错误
(EvalError)
13. 什么是事件冒泡?
- 在一个对象上触发某类事件,这个事件会向这个对象的的父级传播,从里到外,直至它被处理或者到达了对象层次的最顶层,即 document 对象。这个过程就是事件冒泡。
14. 什么是事件委托?
- 事件委托就是利用事件冒泡,指定一个事件处理程序,就可以管理某一类型的所有事件。
- 原理:在元素的父级元素添加事件,点击元素时,因为事件冒泡的作用实现事件委托。简单来说,
事件委托就是将子元素的事件通过冒泡的形式交由父元素来执行
。 - 优点:
使用事件委托可以减少代码执行,优化资源
。
15. undefined 和 null 区别?⭐
undefind 是全局对象的一个属性
。当一个变量没有被赋值,一个函数没有返回值,某个对象不存在某个属性却去访问,函数定义了形参但没有传递实参,这时候都是 undefined。undefined 通过 typeof 判断类型是 undefined。null 代表空值
。代表一个空对象指针,代表对象的值未设置,相当于一个对象没有设置指针地址就是 null。null 通过 typeof 判断类型是 object。undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态
。当需要释放一个对象时,直接赋值为 null 即可,对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,垃圾回收器会择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。null 是JS的关键字
,和其它语言一样都是代表空值。undefined 却是 JS才有的
。是为了区分空指针对象和未初始化的变量,它是一个预定义的全局变量。
16. 伪数组和数组的区别?
伪数组的类型不是 Array,而是 Object;数组类型是 Array
。- 伪数组可以使用的 length 属性查看长度,可以使用 index 获取某个元素,但不能使用数组的其他方法,也不能改变长度。伪数组遍历使用 for…in 方法。
- 伪数组转换成真数组方法:
Array.prototype.slice.call(伪数组)
、[].slice.call(伪数组)
、Array.from(伪数组)
、for循环
、ES6 扩展运算符(...)
。转换后的数组长度由 length 属性决定,索引不连续时转换结果是连续的,会自动补位。
17. 数组去重都有哪些方法?⭐⭐
双重 for 循环
。这是一个最笨的方法。对象属性 key
。利用对象属性名 key 不可以重复这一特点,如果对象中不存在,就 push 进空数组。for 循环 + indexOf
。主要是利用 indexOf 的特性,查找元素返回下标,找不到就返回-1。-1就 push 进空数组。for 循环 + sort 排序
。利用数组的 sort 排序方法去重,如果第 i 项 和 i-1 项不一致,就 push 进空数组。filter + indexOf
。利用 filter 过滤配合 indexOf 查找元素,判断返回元素下标和过滤数组的 index 是否相等。Set
。Es6 中新增了数据类型 Set,Set 的最大一个特点就是数据不重复,可以用作数组去重。new Set 方法,返回是一个类数组,需要结合扩展运算符…,转成真实数组。set + Array.from()
。Set 去重结合 Array.from 转成真实数组。Array.from(new Set(原数组))for 循环 + includes
。includes 用来判断一个数组是否包含一个指定的值,是就返回 true,否则返回 false。判断空数组是否包含原数组的某个元素,不包含就 push 进空数组。reduce + includes
。利用 reduce 遍历结合 includes去重。
18. 数组的基本操作方法?⭐⭐
数组和字符串的相互转换
字符串转数组: split()
。括号里指定以什么分隔符分割字符串。数组转字符串: join()
。括号中可以指定连接符,默认逗号。如:[1,2,3,4]通过join(‘-’)转为"1-2-3-4"。数组转字符串: toString()
。将每个元素计算后转成字符串并用逗号分隔。如:[‘1#3’,2-1,3,4]通过toString()转为"1#3,1,3,4"。
- 数组的操作方法
length
:获取当前数组的长度。push
:往数组尾部添加一个元素。 返回数组的长度。unshift
:往数组头部添加一个元素。返回数组的长度。pop
:从数组尾部删除一个元素。返回删除的元素。shift
:从数组头部删除一个元素。返回删除的元素。slice(开始位置,结束位置)
:截取数组。只有一个元素则从开始位置截取全部。不改变原数组,返回截取的部分。splice(开始位置,长度,插入元素)
:删除、修改数组中的一部分元素。改变原数组,返回被删除的元素。reverse
:反转数组。返回反转后的数组。sort
:对数组的元素进行排序。返回排序后的数组。join
:把数组变成字符串。以括号里内容分隔;若括号里什么都不写,则默认用逗号分隔。toString
:把数组变成字符串。split
:把字符串变成数组。concat
:合并数组,并返回结果。扩展运算符(…)也可以合并数组。Math.min
:返回数组最小值元素。Math.max
:返回数组最大值元素。
- 数组的查找方法
indexOf
:查找数组元素,返回第一个找到的元素下标,找不到返回-1。lastIndexOf
:查找数组元素,返回最后一个找到的元素下标,找不到返回-1,从后向前搜索。includes
: 查找数组是否包含某一元素,包含则返回 true,不包含返回 false。不适用于检查对象。find
:查找满足函数条件的第一个值,找不到返回 undefined。findIndex
: 查找满足函数条件的第一个值得下标,找不到返回 -1。
- 数组类的静态方法
Array.of
:将一数值转化为数组。Array.from
:将类数组转化为数组。
- 数组填充
fill(value,start,end)
:用一个固定值填充一个数组中特定的元素。
- 迭代方法
for循环
:遍历数组。for…of
:遍历数组。forEach
:遍历数组。不会生成新数组,也不改变原数组,回调函数接收三个值:( 数组的元素,索引,当前数组)map
:通过指定函数处理数组的每个元素,并返回处理后的数组。不会对空数组进行检测,不会改变原始数组。filter
:过滤,检测数组元素,并返回符合条件所有元素的数组。every
:检测数组元素的每个元素是否都符合条件,都符合则返回 true,否则为 false。some
:检测数组元素中是否有元素符合指定条件,有则返回 true,若所有元素都不满足判断条件,则返回 false。reduce
:适用于数组求和/平均值/最大值/最小值,返回最后的值。
修改原数组:push、pop、shift、unshift、splice、reverse、sort、fill。
不修改原数组:slice、concat、indexOf、lastIndexOf、join、toString、filter、every、some、forEach、map、find、findIndex。
19. forEach、for…of、for…in的区别?⭐⭐⭐
forEach、for…of、for…in是用于遍历数组或对象的不同方式。
- forEach
- 用于遍历数组和类数组对象,不能遍历普通对象。
- 无法使用break或continue跳出循环。
//forEach实例
let arr=[1,2,3];
arr.forEach((currentValue,index,arr)=>{console.log(currentValue);//当前元素console.log(index);//当前索引console.log(arr);//数组本身
})
//1 0 [1,2,3]
//2 1 [1,2,3]
//3 2 [1,2,3]
- for…of
- 用于遍历可迭代对象,如数组、字符串、Set、Map 等。是ES6新增的语法。
- 遍历的是对象的值而非索引。
- 可以使用 break 和 continue控制循环流程。
//for...of实例
//遍历字符串String
let str="Hello";
for (let e of str){console.log(e)//依次输出:H、e、l、l、o
}
//遍历数组Array
var arr=["a","b","c"];
for (let e of arr){console.log(e)//依次输出:a、b、c
}
//遍历集合Set
var set=new Set([1,2,3,3,4]);//Set(4) {1, 2, 3, 4}
for (let e of set){console.log(e)//依次输出:1、2、3、4
}
//遍历字典Map
var map=new Map([["a",1],["b",2],["c",3]]);
for (let e of map){console.log(e[0]+"=>"+e[1])//依次输出:a=>1、b=>2、c=>3
}
//遍历arguments对象(arguments对象是JavaScript中的一个特殊对象,它包含了函数运行时接收的所有参数)
function f(){for (let e of arguments){console.log(e)//依次输出:1、2、3、4、5}
}
f(1,2,3,4,5)
//遍历DOM NodeList 对象
var parent = document.getElementById('parent');
var child_nodes = parent.childNodes;
for (let e of child_nodes){console.log(e)//依次输出parent节点的所有子节点
}
- for…in
- 用于遍历对象,可遍历对象的属性,包括继承的可枚举属性。
- 也可以遍历数组,但是会遍历数组的索引(字符串类型)而非元素本身。不建议用于遍历数组。
- 可以使用 break 和 continue控制循环流程。
//for...in实例
//遍历普通对象
let obj={a:1, b:2, c:3}
for (let i in obj){console.log(i)//依次输出key:a、b、c
}
//遍历字符串
let str="Hello"
for (let i in str){console.log(i)//依次输出索引:0、1、2、3、4
}
//遍历数组
let arr=["a","b","c"]
for (let i in arr){console.log(i)//依次输出索引:0、1、2
}
forEach:适用于遍历数组和类数组对象,无法使用 break或continue。
for…of:适用于遍历可迭代对象,遍历的是对象的值,可以使用break和continue。
for…in:适用于遍历对象的属性,也可以遍历数组的索引(不建议用于遍历数组),可以使用break和continue。
20. for循环和forEach有什么区别?⭐⭐
- for循环
- 用于循环次数已知的情况,可以通过索引访问数组或列表中的元素。
- 可以通过break或return语句中断循环。
- for循环性能更好,但性能差异通常不足以成为选择依据。
- forEach
- 用于遍历集合类型的数据,如数组等。
- 不能使用break或return语句中断循环。
21. indexOf方法与includes方法的区别?⭐
- indexOf
- 查找数组元素,若包含某一元素,则返回第一个找到的元素下标,找不到返回-1。
- 内部使用的是严格等式运算符(===)进行判断,会导致对NaN的误判(因为NaN === NaN会返回false,如果数组中存在该元素,则无法找到)。
- includes
- 查找数组元素,若包含某一元素,则返回 true,不包含返回 false。
- 能正确识别数组中的NaN,返回正确结果。
//indexOf
[1, 2, 3].indexOf(2); // 找到则返回索引:1
[1, 2, 3].indexOf(4); // 找不到则返回:-1
[1, 2, NaN].indexOf(NaN); // 对NaN的判断有误:-1//includes
[1, 2, 3].includes(2); // 找到则返回:true
[1, 2, 3].includes(4); // 找不到则返回:false
[1, 2, NaN].includes(NaN); // 对NaN的判断正确:true
22. JS中call、apply、bind有什么区别?⭐⭐
原生 JS 提供了 call、apply、bind 三种方式来修改 this 指向。
- call 和 bind 是选项式参数,apply 是数组式参数。
- call、apply 会立即执行,bind 返回一个新函数,不会立即执行。
- call、apply 临时改变 this 指向一次,bind 永久改变 this 指向。
- 应用场景:
call 用于对象的继承 、伪数组转换成真数组
。apply 用于找出数组中的最大值和最小值以及数组合并
。bind 用于 vue 或者 react 框架中改变函数的 this 指向
。
23. 箭头函数和普通函数有什么区别?⭐⭐
- 普通函数的 this 指针指向调用者,可以修改。
- 箭头函数没有自己的 this,它的 this 是继承而来,默认指向在定义它时所处的对象 (父级作用域),不能修改。
24. 说一下this指向?⭐⭐
- 全局作用域:无论是否在严格模式下,this 指向 window 对象。严格模式下全局作用域中函数中的 this 等于 undefined。不是严格模式时,全局作用域中函数中的 this 指向 window。
- 对象中的函数:对象的函数中的 this 指向调用函数的对象实例。谁调用函数 this,this 就指向谁。
- 事件处理函数:在事件处理函数中,this 指向触发事件的目标对象。
- 构造函数:构造函数中的 this 指向构造函数创建的对象实例。
- 箭头函数:this 对应定义时所在的对象,也就是上一级作用域中的 this。箭头函数的 this 不会被改变。
- 嵌套函数:嵌套函数中的 this 不会继承外层函数的 this 值。
- new:由 new 调用的话,this 指向新创建的对象。
- call、apply、bind:由 call、apply、bind 调用,this 指向指定的对象。
- 定时器:定时器中的 this,指向的是 window。
25. JQuery对象和DOM元素之间如何转换?
- DOM转JQ对象:
$(DOM对象)
或$("div")
。 - JQ对象转DOM:可以通过
[index]
或者.get(index)
方法,例如 $(“div”)[0] 或者 $(“div”).get(1)。
26. 如何操作DOM元素?⭐
- 原生操作DOM元素
- 直接给相应的元素加id,然后再用document.getElementById(“id”) 获取。
- vue操作DOM元素
- 使用 ref,给相应的元素加
ref=“name”
然后再this.$refs.name
获取到该元素。 - 获取/操作根元素 DOM:
$root
。 - 获取/操作父元素 DOM:
$parent
。 - 获取/操作子元素 DOM:
$refs $children
。
- 使用 ref,给相应的元素加
27. 防抖与节流的区别?并分别用代码表示? ⭐⭐
- 防抖
触发高频事件后,n 秒内函数只会执行一次;如果 n 秒内高频事件再次被触发,则重新计算时间
。- 作用:在一定时间内,如果方法没用再次被触发,则执行最后一次。
防止事件连续或高频触发,让它只触发一次或者最后一次
。
- 节流
高频事件触发,但在 n 秒内只会执行一次
,所以节流会稀释函数的执行频率。- 作用:在规定时间内只只执行一次或数次。
降低事件触发的频率,比如1s内最多执行一次
。
- 区别
防抖是将多次执行变为最后一次执行
,节流是将多次执行变成每隔一段时间执行一次
//防抖和节流 都是用于防止事件高频率的重复触发
//防抖: 在一定时间内,如果方法没用再次被触发,则执行最后一次
<body><button>点我</button><script>var btn = document.querySelector("button");var tiemr = null;btn.onclick = function () {//500毫秒内被多次触发,则只执行最后一次clearInterval(tiemr);tiemr = setTimeout(() => {console.log("我触发了")}, 500);}</script>
</body>//节流:在规定时间内只只执行一次或数次
<body><button>按钮</button><script>var btn = document.querySelector("button");var jie = true;btn.onclick = function() {if(jie) {jie = false;console.log('发送了请求');//两秒内只执行一次请求setTimeout(()=>{jie = true;},2000)}} </script>
</body>
28. 防抖和节流应用场景?⭐
- 防抖和节流都是防止某一时间频繁触发,但是原理却不一样。
防抖是将多次执行变为只执行一次
,节流是将多次执行变为每隔一段时间执行
。 - 防抖的应用场景
search搜索联想
:用户在不断输入值时,用防抖来节约请求资源。window触发resize的时候
:不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 节流的应用场景
鼠标不断点击触发
:mousedown(单位时间内只触发一次)监听滚动事件
:比如是否滑到底部自动加载更多,用throttle来判断
29. JS模块化有哪些?
- CommonJS、ES6、AMD、cmd
30. 使用JQuery和vue的区别?
- JQuery 是JS库,主要用于简化DOM操作、事件处理和Ajax请求。
- vue 是 mvvm 框架,核心是双向数据绑定,一般情况下不需要操控 DOM 元素,而是操控数据为主。
31. 加密方式?⭐
- 可逆加密算法(即加密后,密文可以反向解密得到密码原文)
- 对称加密
- 在对称加密算法中,数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法,对密文进行解密,才能使其恢复成可读的明文。在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解密方事先知道加密密钥。
- 优点:算法公开、计算量小、加密速度快、加密效率高。
- 缺点:文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,没有非对称加密安全。
- 用途:一般用于保存用户手机号、身份证等敏感但能解密的信息。
- 常见的对称加密算法:AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256。
- 非对称加密
- 有两个密钥,即公有密钥和私有密钥,公有密钥加密,私有密钥解密,私钥隐秘保存,公钥可以下发给信任客户端。私钥签名,持有公钥进行验证是否被篡改过。
- 优点:非对称加密与对称加密相比,其安全性更好。
- 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
- 用途: 一般用于签名和认证。私钥服务器保存,用来加密;公钥客户拿着用于对于令牌或者签名的解密或者校验使用。
- 常见的非对称加密算法: RSA、DSA(数字签名用)、ECC(移动设备用)、RS256 (采用SHA-256 的 RSA 签名) 。
- 对称加密
- 不可逆加密算法(即 一旦加密就不能反向解密得到密码原文)
- 用途:一般用于效验下载文件正确性,一般在网站上下载文件都能见到;存储用户敏感信息,如密码、卡号等不可解密的信息。
- 常见的不可逆加密算法: MD5、SHA、HMAC。
Base64编码
- Base64编码是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64编码解码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。注意:Base64只是一种编码方式,不算加密方法。
32. 前端加密方式?⭐
Base64加密
- Base64并不是一种加密算法,而是一种编码方式。
- 优点
- Base64适合不同平台、不同语言的传输
- 页面中内嵌使用Base64格式的小图片,可减少了服务器访问次数
- 二进制位转换Base64算法简单,对性能影响不大
- 缺点
- 二进制文件转换为Base64后,体积大概增加 1/3;
- Base64无法缓存,要缓存只能缓存包含Base64的文件,比如 js 或者 css;
- 面对大文件时,会消耗一定的 CPU 进行编解码。
// 原生使用方法
const btoa = window.btoa('hello, my name is FuChaoyang ') // 编码
const atob = window.atob(btoa) // 解码
<script>
// vue使用方法,需先安装:npm install --save js-base64
import { Base64 } from 'js-base64';
export default {mounted() {var encode = Base64.encode('hello, my name is FuChaoyang'); // 编码var decode = Base64.decode(encode ); // 解码}
}
</script>
MD5加密
(不可逆)- 相同的密码每次加密结果都一样
<script>
// vue使用方法,需先安装:npm install js-md5 -s
import { md5 } from 'js-md5';
export default {mounted() {var encode = md5('hello, my name is FuChaoyang'); // 编码}
}
</script>
加盐MD5加密
(不可逆)- 在MD5的基础上手动加盐(salt)处理,加密多次值是不相同的,因为加入了随机字符串
<script>
// vue使用方法,需先安装:npm install js-md5 -s
import { md5 } from 'js-md5';
export default {mounted() {const salt = 'AbC$123'; // 定义一个随机的盐值let encode = md5('hello, my name is FuChaoyang' + salt);// 编码}
}
</script>
Bcrypt密码加密
- 对密码进行加密,然后存放在数据库中。在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 相对来说,BCrypt比MD5更安全。
33. webpack?⭐⭐
- webpack是什么?
- webpack是一个现代JavaScript应用程序的静态模块
打包工具
,其主要作用包括模块打包、代码转换、资源管理和代码分割等。webpack是一个强大的工具,可以帮助开发者有效地管理和打包现代JavaScript应用程序,提高开发效率和项目性能。
- webpack是一个现代JavaScript应用程序的静态模块
- webpack是解决什么问题而生的?
- 如果像以前开发时一个html文件可能会引用十几个js文件,而且顺序还不能乱,因为它们存在依赖关系。同时对于ES6等新的语法,less、sass等CSS预处理都不能很好的解决。此时就需要一个处理这些问题的工具。
- webpack的作用是什么?
模块打包
- webpack可以将项目中的各种模块进行静态分析,并将它们打包成一个或多个 bundle。这使得管理项目中的模块变得更加方便,也能提高页面加载速度,因为浏览器可以并行加载多个资源。
代码转换
- webpack可以处理JavaScript、TypeScript、CSS、SCSS、LESS 等不同类型的文件,并通过loader转换它们。如:将ES6/ES7代码转换为ES5,将Sass/LESS转换为CSS。
模块依赖分析
- webpack能够分析模块之间的依赖关系,包括直接依赖和间接依赖,然后构建出依赖关系树,以确保在打包时正确地引入依赖的模块。
代码拆分
- webpack允许将代码拆分成多个bundle,从而实现按需加载,提高页面加载性能。这对于大型应用程序特别有用,因为它们可能包含大量的代码,但并非所有代码都需要在初始加载时下载。
插件系统
- webpack提供了丰富的插件系统,使得开发者能够扩展其功能,例如压缩代码、优化图片、提取公共代码等。
开发服务器
- webpack提供了一个开发服务器,可以在本地快速启动一个服务器,支持热模块替换(Hot Module Replacement),使得开发过程更加高效。
生态系统
- webpack拥有庞大的社区和丰富的生态系统,提供了大量的loader和插件,可以满足各种不同项目的需求。
- webpack的工作原理?
- webpack通过分析项目中的模块依赖关系,将JavaScript、CSS、图片等资源打包成一个或多个代码包(bundle),以便在浏览器中加载和执行。
- 这一过程不仅减少了网络请求次数,提高了页面加载性能,还通过使用各种loader将ES6、TypeScript、Sass等文件转换为浏览器可识别的代码。此外,webpack模块热替换(HMR),在开发过程中可以实时预览修改结果,无需手动刷新浏览器,从而提高了开发效率。
- webpack打包原理?
- 把一切都视为模块。不管是 css、JS、Image 还是 html 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。
- 按需加载。打包过程中 webpack通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载。
- webpack的核心概念?
Entry:入口
。webpack执行构建的第一步将从 Entry 开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
。Output:出口
。告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
。Module:模块
。在webpack里一切皆模块,一个模块对应着一个文件。webpack会从配置的 Entry 开始递归找出所有依赖的模块。Chunk:代码块
。一个 Chunk 由多个模块组合而成,用于代码合并与分割。Loader:模块转换器
。用于把模块原内容按照需求转换成新内容。Plugin:扩展插件
。在webpack构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
- webpack的基本功能有哪些?
代码转换
。TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等。文件优化
。压缩 JavaScript、CSS、html 代码,压缩合并图片等。代码分割
。提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。模块合并
。在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件。自动刷新
。监听本地源代码的变化,自动构建,刷新浏览器。代码校验
。在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过。自动发布
。更新完代码后,自动构建出线上发布代码并传输给发布系统。
- gulp/grunt 与 webpack 的区别是什么?
- gulp、grunt、webpack 都是前端构建工具。
- grunt和gulp在早期比较流行,现在webpack相对来说比较主流。不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
- grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个或一类文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
- webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
TS
1. 什么是 TypeScript ?
- TS是JS的超集,扩展了JS的语法。通过在JS的基础上添加静态类型定义构建而成。TS的主要目的是增强JS的功能,使其更适合大型项目的开发。
- TS通过类型注解提供编译时的
静态类型检查
,可以快速地找出代码中潜在的错误。 - TS代码会被编译成 JS 代码,JS 代码才是真正被执行的代码。
- JS 是弱类型的,而 TS 提供了强类型以及更多的面向对象的内容。
2. TypeScript 的特点是什么?
跨平台
:TypeScript 编译器可以安装在任何操作系统上,包括 Windows、Linux 和 macOS。面向对象的语言
:TypeScript 提供所有标准的 OOP 功能,如类、接口和模块。静态类型检查
:TypeScript 使用静态类型并帮助在编译时进行类型检查。可以在编写代码时发现编译时错误,而无需运行脚本。可选的静态类型
:如果你习惯了 JavaScript 的动态类型,TypeScript 还允许可选的静态类型。ES6 特性
:TypeScript 包含计划中的 ES6 的大部分特性,例如箭头函数。DOM 操作
:您可以使用 TypeScript 来操作 DOM 以添加或删除客户端网页元素。
3. 使用 TypeScript 有什么好处?
- TypeScript 更具表现力,这意味着它的
语法混乱更少
。 - 由于高级调试器专注于在编译时之前捕获逻辑错误,因此
调试很容易
。 - 静态类型使 TypeScript 比 JavaScript 的动态类型
更易于阅读和结构化
。 - 由于通用的转译,它
可以跨平台使用
,在客户端和服务器端项目中。
4. TypeScript 的内置数据类型有哪些?
number
:表示数字类型,包括整数和浮点数。string
:表示字符串类型,用于表示文本数据。boolean
:表示布尔类型,只有两个可能的值:true 和 false。null
: 空值。undefined
:未定义的值。object
:表示非原始类型的对象,可以是任意结构的对象。array
:表示数组类型,可用于存储多个相同类型的元素。tuple
:表示元组类型,用于表示具有固定数量和特定顺序的元素组合。enum
:表示枚举类型,用于定义一组命名的常量值。any
:表示动态类型,可以接受任何类型的值。void
:表示空类型,没有任何返回值的类型。never
:表示永远不存在的类型,通常用于表示永远不会返回的函数的返回类型。unknown
:表示未知类型,类似于 any,但对类型安全要求更严格。
5. TypeScript 中的接口是什么?
- 接口为使用该接口的对象定义契约或结构。接口是用关键字定义的 interface,它可以包含使用函数或箭头函数的属性和方法声明。
interface IEmployee {Code: number;Name: string;getSalary: (number) => number; getName(number): string;
}
ES6
1. ES6新增的内容有哪些?⭐⭐⭐
变量声明方式
:新增let
、const
关键字,用于声明变量。与var的函数作用域不同,具有块级作用域。let声明的变量可以重新赋值,但不能在同一作用域内重复声明;const声明的变量是常量,必须在声明时赋值,且之后不可再赋值(但如果是对象或数组,可以修改其内部属性或元素)。解构赋值
:允许按照一定模式从数组或对象中提取值,对变量进行赋值。
//1. 数组解构
const numbers = [1, 2, 3];
const [a, b, c] = numbers;// 使用数组解构赋值
console.log(a); // 输出: 1
console.log(b); // 输出: 2
console.log(c); // 输出: 3//2. 对象解构
const person = {name: 'Alice',age: 30,city: 'New York'
};
const { name, age, city } = person;// 使用对象解构赋值
console.log(name); // 输出: Alice
console.log(age); // 输出: 30
console.log(city); // 输出: New York//3. 嵌套解构
const user = {name: 'Bob',age: 25,address: {street: '123 Main St',city: 'San Francisco',state: 'CA'}
};
const { name, age, address: { city, state } } = user;// 使用嵌套解构赋值
console.log(name); // 输出: Bob
console.log(age); // 输出: 25
console.log(city); // 输出: San Francisco
console.log(state); // 输出: CA//4. 指定默认值
const settings = {theme: 'dark',language: 'en'
};
const { theme = 'light', language = 'zh', notifications = false } = settings;// 使用解构赋值并指定默认值
console.log(theme); // 输出: dark
console.log(language); // 输出: en
console.log(notifications); // 输出: false//5. 剩余参数
const items = ['apple', 'banana', 'cherry', 'pear'];
const [first, second, ...rest] = items;// 使用解构赋值和剩余参数
console.log(first); // 输出: apple
console.log(second); // 输出: banana
console.log(rest); // 输出: ['cherry', 'pear']//6. 函数参数解构
// 定义一个函数,使用对象解构作为参数
function displayUserInfo({ name, age, city }) {console.log(`Name: ${name}`);console.log(`Age: ${age}`);console.log(`City: ${city}`);
}
// 调用函数
const user = {name: 'Charlie',age: 35,city: 'Los Angeles'
};
displayUserInfo(user);
-
展开运算符(...)
:使用…将数组或对象中的元素展开为单独的项,可以用于数组的合并、函数的参数传递等场景。
//1. 展开数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // 输出: [1, 2, 3, 4, 5, 6]//2. 展开对象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出: { a: 1, b: 2, c: 3 }
函数的新特性
:箭头函数
、默认参数值和剩余参数
、模板字符串
。箭头函数:提供了一种更简洁的函数书写方式
,不使用function关键字,且没有自己的this、arguments、super或new.target。箭头函数中的this始终指向其外层(函数或全局)作用域中的this值。默认参数值和剩余参数:允许为函数参数设置默认值,并且可以使用剩余参数语法...args来接收函数被调用时传入的多余参数
。模板字符串:使用反引号(``)标识,可以在其中嵌入变量和表达式,使得字符串的拼接更加简洁和直观
。新增数据结构
:Set和Map
。Set是一种新的数据结构,成员的值都是唯一的,没有重复的值,提供了add、delete、has等方法。Map类似于对象,但“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map结构可以记住键的原始插入顺序。模块化
:ES6引入了模块的概念,使用import和export语句来实现模块的导入和导出。这有助于代码的封装和重用,同时也提高了代码的可维护性。- 其他新特性:
Symbol类型
、Promise
、for...of循环
。Symbol类型:ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。Promise:提供了一种更好的异步编程解决方案,用于处理异步操作及其结果。for…of循环:用于遍历具有迭代器接口的数据结构,如数组、Map、Set等。
2. ES6中,Set和Map的区别 ?⭐⭐⭐
Set
- ES6 提供的新数据结构Set,类似于数组,但是
成员的值都是唯一的,没有重复的值
。 可以用来做数组去重
,但是Set 是伪数组,可以通过 Array.from() 或者扩展运算符…转换成数组。- Set 结构没有键名,只有键值 (或者说键名和键值是同一个值)。可以使用 keys()、values()、entries()、forEach() 方法遍历成员,使用 for…of循环遍历 Set。如果想直接在遍历操作中改变原来的 Set 结构,可以利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构,或者使用 Array.from() 方法。
- 实例操作方法:size、add、delete、has、clear。
new Set()
:创建Set结构。size
:返回 set 实例的成员总数。add(value)
:添加某个值,返回 set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为 set 的成员。clear()
:清除所有成员,没有返回值。
- ES6 提供的新数据结构Set,类似于数组,但是
//利用Set进行数组去重
let array = [1, 2, 3, 2, 4, 5];
let uniqueSet = new Set(array);
console.log([...uniqueSet]); // [1, 2, 3, 4, 5]
Map
- “键值对” 的数据结构,可以实现数组重组。JavaScript 对象 object 本质上是键值对的集合,缺陷是只能使用字符串作为键,而 Map 结构优化了这个缺陷,它提供了 “值-值” 对的形式,让键名不再局限于字符串,是一种更完善的 Hash 结构实现。简单来说:
Map 类似于对象,也是键值对的集合,但是 "键" 的范围不限于字符串, 各种类型的值(包括对象)都可以当作键
。 - 遍历方法:keys()、values()、entries()、forEach()、for…of。注意:
由于一个 key 只能对应一个 value,所以多次对一个 key 放入 value,后面的值会把前面的值冲掉
。 - 实例操作方法:size、set、get、has、delete、clear。
new Map()
:创建Map结构。size
:返回 Map 实例的成员总数。set
:添加新的 key-value。如:map.set(‘key1’, ‘value1’)get
:传入一个 key 返回一个 val。如:let value = map.get(‘key1’); // ‘value1’delete
:传入一个 key,删除这个 key-value。如:map.delete(‘key1’); // truehas
:查看是否有这个 key。如:let exists = map.has(‘key1’); // falseclear()
:清除所有成员,没有返回值。如:map.clear();
- “键值对” 的数据结构,可以实现数组重组。JavaScript 对象 object 本质上是键值对的集合,缺陷是只能使用字符串作为键,而 Map 结构优化了这个缺陷,它提供了 “值-值” 对的形式,让键名不再局限于字符串,是一种更完善的 Hash 结构实现。简单来说:
//Map数据结构
let map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map);
/**
[["key1","value1"],["key2","value2"]
]
**/
Set 和 Map 的区别
- Set 类似于数组,Set 对象是值的集合。Map 类似于对象,也是键值对的集合,但 ‘键’ 的范围不限于字符串,也可以是各种数据类型。
- Set 只存储 key,没有 value,相当于value 就是 key。Map 存储 key-value 键值对,是一组映射关系。
- Set 的值是唯一的、不重复的。Map 的 key 是唯一的。
- Set 不能通过 get 方法获取值,因为它只有值。Map 可以通过 get 方法获取值。
- Set 的值是唯一的,可以用于数组去重。Map 由于没有格式限制,可以做数据存储。
- Set 初始化需要的是一维数组。Map 初始化需要的是一个二维数组。
- Set 和 Map都能通过迭代器进行 for…of 遍历。
3. map()和forEach的区别?⭐
map()
:有返回值
。可以开辟新空间,return 出来一个 length 和原数组一致的数组,即便数组元素是 undefined 或者是 null,map 能新增删除元素
。
//普通数组
let numbers = [1, 2, 3, 4];
let doubled = numbers.map(num=>{return num * 2;
});
console.log(numbers ); // 输出:[1, 2, 3, 4]
console.log(doubled); // 输出:[2, 4, 6, 8]//对象数组
let students = [{name: "a", age: "23", sex: "男"},{name: "b", age: "24", sex: "女"},{name: "c", age: "25", sex: "男"}
];
let ages = students.map(student => student.age);
console.log(ages); // 输出 ["23", "24", "25"]
forEach()
:无返回值
,返回结果为 undefined。可以通过在函数体内部使用索引修改数组元素
,forEach不能新增删除元素
。
4. ES6 中的箭头函数?⭐⭐⭐
- 箭头函数相当于匿名函数,简化了函数定义。
- 箭头函数有两种写法:
当函数体是单条语句时,可以省略 {} 和 return
;当函数体是多条语句时,不可以省略 {} 和 return
。 - 箭头函数最大的特点就是
没有自己的 this
,它的 this 是从外部获取,就是继承外部的执行上下文中的 this。 - 由于没有 this 关键字所以箭头函数也不能作为构造函数。
- 箭头函数也没有原型和 super。
- 不能使用 yield 关键字,因此箭头函数不能用作 Generator 函数。
- 适用场景:简单的函数表达式,内部没有 this 引用,没有递归、事件绑定、解绑定。适用于 map、filter 等方法中。
5. 什么是扩展运算符,用于什么场景?⭐⭐
扩展运算符(…) 是一个展开语法,用于取出对象或数组中的所有可遍历属性或元素,然后将它们展开到当前的位置,展开的内容可以放在任何它可以存在的地方(数组,函数,对象)
。- 扩展运算符可以提高代码的可读性和简洁性,减少重复代码的编写。
- 在对象中的使用场景:扩展运算符可以用于
创建新的对象
、将多个对象合并成一个对象
、复制一个对象
。 - 在数组中的使用场景:
在函数调用时将一个数组参数转为用逗号分隔的参数序列
、数组拷贝[...arr]
、合并数组[...arr1, ...arr2]
、与解构赋值结合,用于生成数组
、将伪数组转为真正的数组
、数组去重 [...new Set(arr)]
、做为函数传递参数时不确定形参个数的时候使用
。
6. JS变量提升?⭐⭐⭐
- 变量提升是指
JS 的变量和函数声明会在代码编译期,提升到代码的最前面
。 - 变量提升成立的前提是使用
var关键字进行声明
的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升
,同时函数的声明提升会比变量的提升优先
。
7. 怎么实现Module模块化?⭐
- 模块化是一种
把代码分割成多个模块
的方法,每个模块包含一定的功能,并且可以被其他模块引用。ES6引入了import(导入)
和export(导出)
关键字来支持模块化。 - 每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过 import 来引用其它模块提供的接口。
- 同时还为模块创造了命名空间,防止函数的命名冲突。
// 模块化示例1
// 模块1:math.js
export function add(a, b) {return a + b;
}
export function subtract(a, b) {return a - b;
}// index.js
import { add, subtract } from './math.js';
console.log(add(5, 3)); // 输出:8
console.log(subtract(5, 3)); // 输出:2
// 模块化示例2
// 模块1:math.js
function add(a, b) {return a + b;
}
function subtract(a, b) {return a - b;
}
export default {add,subtract
};// index.js
import math from './math.js';
console.log(math.add(5, 3)); // 输出:8
console.log(math.subtract(5, 3)); // 输出:2
8. 微任务和宏任务的区别?⭐⭐
- 宏任务:当前调用栈中执行的代码成为宏任务。如:主代码块、I/O、setTimeout、setInterval、setImmediate、requestAnimationFrame等等。
- 微任务:当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。如:Promise.then/catch/finally、process.nextTick、MutationObserver等等。
- 宏任务中的事件放在callback queue中,由事件触发线程维护;微任务的事件放在微任务队列中,由JS引擎线程维护
9. JS事件循环机制?
- JavaScript的事件循环是其异步执行的核心机制之一,用于处理非阻塞I/O操作和回调函数。简单来说,它是浏览器或Node.js环境中的一个重要调度器,
负责监听并管理任务队列
。 - 了解了事件循环机制有助于优化异步代码,避免阻塞UI线程,提高应用性能。
- 任务队列
- 分为两种类型:宏任务(macro-task)和微任务(micro-task)。
- 宏任务包括setTimeout、setInterval、DOM操作、网络请求等。
- 微任务如Promise.resolve、MutationObserver等。
- 执行栈:当前正在运行的同步代码所构成的栈。
事件循环过程
- 先运行
执行栈中的同步代码
。 - 运行完执行栈后,会检查是否有微任务,
如果有微任务则先执行微任务直到队列为空
。 再执行下一个宏任务
。这个过程中如果遇到新的微任务,会插入到微任务队列中,并继续执行当前宏任务。- 当宏任务完成,
再次检查微任务队列
。如有微任务,则执行微任务。 - 如此反复。
- 先运行
- 轮询检测
- 事件循环不是被动等待,而是通过定期的轮询检查是否有可执行的任务。
10. 异步函数 ⭐⭐⭐⭐⭐
- JS代码的执行顺序
- JS是
单线程语言
,同一个时间只能做一件事,执行顺序是由上往下执行的。这样很容易造成阻塞,所以把任务分成了同步任务
和异步任务
两种。 - 当主线程遇到异步任务,比如说计时器或者 ajax 请求回调问题,就把异步任务放进
"任务队列"
里,执行完同步任务后,再循环去检查任务队列里面有哪些异步任务要执行。这是一个异步执行,分开来执行的。如果想要变成同步执行,比如说等到计时器执行完后再执行或者等请求服务器响应后再继续运行,可以使用异步函数,把异步执行变成同步执行
。
- JS是
- 同步和异步的区别?
同步就是同时执行
,异步就是分开来执行
。同步任务是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
。异步任务是那些不进入主线程、而进入任务队列的任务,执行完同步任务后再进入主线程执行
。
- 同步函数和异步函数的区别?
同步函数就是把同步执行变成异步执行
,异步函数就是把异步执行变成同步执行
。- 场景:同步和异步的问题通常是指 ajax 的回调问题。如果是
同步调用,在发起请求的时候就会暂停,等到服务器响应后继续运行
。如果是异步调用,发起请求后不会暂停,立即执行后面的代码,服务器响应后会自动触发回调函数进行处理
。 异步的性能好
,同步则用于需要立即获取结果并实时处理的情况
。
如下例,代码运行过程中:先执行完fn2再执行fn1。输出结果是2 1。
- 因为当主线程遇到异步任务(此例中为setTimeout)的时候,会把异步任务(此例中为fn1)放进 “任务队列” 里,
当执行完同步任务后,再去循环检查任务队列里面有哪些异步任务要执行。- 这是一个异步,因为他是分开执行的,并没有同时执行。
- 如果想要让它变成同步执行,执行完fn1后再执行fn2,可以使用异步函数,把异步变成同步执行。
- 有三种方法可以实现异步函数,分别是
回调函数
、Promise
、async和await
。
function fn1() {console.log(1);
}
function fn2() {console.log(2);
}
setTimeout(()=>{fn1()
},2000)
fn2()
- 异步函数的实现方法?
回调函数
- 回调函数是异步操作最基本的方法
- 优点是
简单,容易理解和实现
。缺点是不利于代码的阅读和维护,有回调地狱问题
(多个回调函数嵌套的情况)。
Promise
- Promise是ES6中的一个内置对象,实际是一个构造函数,是一个异步函数,是异步编程的一种解决方案,把异步执行变成同步执行。
- 比回调函数更加合理强大,
解决了回调函数中的回调地狱、代码不规范、可读性低、不便维护等问题
。通过 Promise调用多个 then 方法来把地狱回调转化为链式编程,并且可以通过 .catch 捕捉错误。缺点是错误需要通过回调函数捕获,只能异步的处理错误,Promise 链式调用相当于一个新的回调地狱
。
async和await
- async和await 是 ES7 的新特性,也是一个异步函数,把异步执行变成同步执行。它是基于 Promise实现的,是一个语法糖。async和await 使异步代码看起来像同步代码。
- 优点是
使用方法清晰明了,更加优雅,可读性强,可以以通过 try-catch 捕捉错误,同步的处理错误,且完美解决了回调地狱的问题
。缺点是await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await ,会导致性能上的降低
,代码没有依赖性的话,完全可以使用 Promise 去实现。
- 具体实现异步函数的代码?
回调函数
- 运行时,解析fn1和fn2,但是此时还没有执行。
- 先执行计时器,2秒后执行fn1,执行fn1的打印,输出1。
- 然后再执行callback回调函数,执行fn2,执行fn1的打印,输出2。
function fn1(callback) {console.log(1);callback();
}
function fn2() {console.log(2);
}
setTimeout(()=>{fn1(fn2)
},2000)
Promise
- 通过Promise构造函数 new 一个实例,
Promise构造函数接收一个函数作为参数
,这个函数有两个参数, 分别是成功回调 resolve
和失败回调 reject
。- 在
异步操作成功时调用 resolve
,并将结果作为参数传递出去;在异步操作失败时调用 reject
,并将错误作为参数传递出去。- 根据 Promise状态,
成功时执行 .then,失败时执行 .catch
,并接收对应的参数执行相应的处理。一般把成功的处理逻辑写在 .then()里,把失败的处理逻辑写在 .cache() 里。- then 方法返回的是一个新的 Promise 实例,因此可以采用链式写法,即
then 方法后面再调用另一个 then 方法
,把回调地狱转为链式编程。
function fn1() {return new Promise((resolve, reject)=>{if(请求成功){console.log(1);resolve()//也可以把参数放到resolve中传出去,如resolve(1)}else{console.log("err");reject()//也可以把参数放到reject中传出去,如resolve("err报错")}})
}
function fn2() {console.log(2);
}
setTimeout(()=>{fn1().then(()=>{fn2()})
},2000)
async和await
- async和await
基于Promise实现的
。async 写在方法前面
,用于声明一个 function 是异步的。async 和 await 是配对使用的,await 只能写在 async 的内部
,await 后面跟着的是一个 promise 对象。async 执行期间一旦遇到 await,会等里面的 Promise(异步)执行完毕再执行后面的代码
。- 如果是 reject 状态,可以使用 try-catch 捕捉。
function fn1() {return new Promise((resolve)=>{console.log(1);resolve(1);})
}
function fn2() {return new Promise((resolve)=>{console.log(2);resolve(2);})
}
function fn3() {console.log(3);
}
setTimeout(async ()=>{let res1 = await fn1();let res2 = await fn2();fn3();
},2000)
-
Promise 和 async await 区别是什么?
- 相同点:
- promise 和 async await
都是异步编程的优化和解决方案
。
- promise 和 async await
- 不同点:
- Promise 是 ES6 的一个内置对象,是
应用层的解决方案
。async await 是 ES7 的新特性,是基于 Promise
的,可以说是 Promise 的补充,是语言层的解决方案
。 Promise 更多应用在函数封装中
。async await 用在函数的使用中
。Promise 链式调用相当于一个新的回调地狱
。await async 完美解决了回调地狱的问题
。- Promise 通过 catch 来捕捉,只能
异步的处理错误
。async await 可以通过 try-catch同步处理错误
。 - async await 相对于 Promise 来讲,写法更加优雅,async await 用同步的写法使得可读性更强,使异步代码看起来像同步代码。
- Promise 是 ES6 的一个内置对象,是
- 相同点:
-
测试题目
输出:5 1 3 7 9 4 2 8 6
解析:
- 遇到两个定义 async 的函数,继续往下走,直接输出 5。
- 继续向下执行,遇到setTimeout,放入任务队列中。
- 继续向下执行,遇到 async1() 函数,进入async1()函数。async 函数中在 await 之前的代码是立即执行的,所以直接输出 1。然后执行 async2() 函数,进入async2()函数,输出 3。然后执行 resolve,将 .then 里面的 console.log(‘4’) 放到事件队列中。回到async2() 的调用处,把 console.log(‘2’) 放到事件队列中。
- 继续向下执行,遇到 Promise。Promise 中的代码是被当做同步任务立即执行的,所以直接输出 7。然后执行 resolve,将 .then 里面的 console.log(‘8’) 放到事件队列中。
- 继续向下执行,直接输出 9。
- 至此主线程上的宏观任务执行完毕,开始查找清空微任务队列。
- 微观任务队列里有三个任务,先执行 async2 里面的 .then,输出 4。再执行 async1 里面的 console.log(‘2’), 输出 2。然后再执行 console.log(‘2’),输出 8。
- 至此,事件队列里的也执行完了。
- 第二轮循环宏观任务开始,此时宏任务中只有一个 setTimeout,执行并输出 6。
async function async1() {console.log('1');await async2();console.log('2');
}
async function async2() {new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4');});
}
console.log('5');
setTimeout(function() {console.log('6');
}, 0)
async1();
new Promise(function(resolve) {console.log('7');resolve();
}).then(function() {console.log('8');
});
console.log('9');
输出:4 1 6 8 7 5 3 2
解析:
- 遇到两个定义 async 的函数,继续往下走,直接输出 4。
- 继续向下执行,遇到setTimeout的console.log(‘5’),放入宏观任务队列中。
- 继续向下执行,遇到 async1() 函数,进入async1()函数。async 函数中在 await 之前的代码是立即执行的,所以直接输出 1。然后执行 async2() 函数,进入async2()函数,遇到setTimeout的console.log(‘3’),放入宏观任务队列中。回到async2() 的调用处,继续向下遇到setTimeout的console.log(‘2’),放入宏观任务队列中。
- 继续向下执行,遇到 Promise。Promise 中的代码是被当做同步任务立即执行的,所以直接输出 6。然后执行 resolve,将 .then 里面的 console.log(‘7’) 放到微观事件队列中。
- 继续向下执行,直接输出 8。
- 至此主线程上的宏观任务执行完毕,开始查找清空微观任务队列。
- 微观任务队列里有一个任务,即 console.log(‘7’),输出 7。
- 至此,微观任务队列里的也执行完了。
- 第二轮循环宏观任务开始,此时宏任务中只有三个 setTimeout,先执行console.log(‘5’),输出 5。再执行console.log(‘3’),输出 3。再执行console.log(‘2’),输出 2。
async function async1() {console.log('1');await async2();setTimeout(function() {console.log('2')},0)
}
async function async2() {setTimeout(function() {console.log('3')},0)
}
console.log('4');
setTimeout(function() {console.log('5');
}, 0)
async1();
new Promise(function(resolve) {console.log('6');resolve();
}).then(function() {console.log('7');
});
console.log('8');
输出:4 1 3 8 10 6 2 7 9 5
解析:
- 遇到两个定义 async 的函数,继续往下走,直接输出 4。
- 继续向下执行,遇到setTimeout的console.log(‘5’),放入宏观任务队列中。
- 继续向下执行,遇到 Promise。Promise 直接执行 resolve,将 .then 里面的 console.log(‘6’) 放到微观事件队列中。
- 继续向下执行,遇到 async1() 函数,进入async1()函数。async 函数中在 await 之前的代码是立即执行的,所以直接输出 1。然后执行 async2() 函数,进入async2()函数,直接输出 3。回到async2() 的调用处,将 await 之后的console.log(‘2’),放入微观任务队列中。
- 继续向下执行,遇到Promise,将 7 通过resolve传出去,赋值给 promise2 。继续向后执行,直接输出 8。
- 继续向下执行,将promise2 后的 .then 里面的console.log(7)和Promise 放到微观任务队列中。
- 继续向下执行,直接输出10。
- 至此主线程上的宏观任务执行完毕,开始查找清空微观任务队列。
- 第一轮循环微观任务开始,此时,微观任务队列里有三个任务。先执行即 console.log(‘6’),输出 6。再执行async2()中的console.log(‘2’),输出2。再执行promise2 后的 .then ,res为7,直接输出7。继续向下执行,遇到了Promise,Promise 直接执行 resolve,将 .then 里面的 console.log(‘9’) 放到微观事件队列中。
- 至此,第一轮微观任务队列循环完成,开始第二轮围观任务队列循环。
- 第二轮循环微观任务开始,此时,微观任务队列里有一个任务。直接输出9。
- 至此,微观任务队列里的也执行完了。
- 第二轮循环宏观任务开始,此时宏任务中只有一个 setTimeout,输出 5 。
async function async1() {console.log('1')await async2()console.log('2')
}
async function async2() {console.log('3')
}
console.log('4')
setTimeout(() => {console.log('5')
}, 0)
Promise.resolve().then(() => {console.log('6')
})
async1()
let promise2 = new Promise((resolve) => {resolve('7')console.log('8')
})
promise2.then((res) => {console.log(res)Promise.resolve().then(() => {console.log('9')})
})
console.log('10')
小结:
- resolve()不会阻止后面的代码执行,所以遇到console会继续执行;但遇到 .then 会阻止,此时会将 .then 中的内容放到微观任务队列中。
- await 会阻止后面的代码执行,直接将后面的内容放到微观任务队列中。
- setTimeout()会被放到宏观任务队列中,最后执行。
- 执行顺序:主进程从上到下执行;再循环执行微观任务队列,直到微观任务队列中没有内容;最后循环执行宏观任务队列,直到宏观任务队列中没有内容。
VUE
1. vue的常用指令 ⭐⭐
指令的本质:语法糖。在编译阶段会把指令编译成 JavaScript 代码。
v-for
:遍历 data 中的数据,实现列表的渲染(数组或对象)。v-if
:通过 true 和 false 控制元素是否需要被渲染。v-else
:搭配 v-if 使用,不需要表达式,当 vi-if 为 false 时才被渲染出来。v-else-if
: 搭配 v-if 使用,通过 true 和 false 控制元素是否需要被渲染。v-show
:通过 true 和 false 控制元素是否显示或隐藏。v-model
:实现表单控件和数据的双向绑定。如:<input>、<select>、<textarea>、components。v-bind
:简写:
。动态绑定元素属性。v-on
:简写@
,事件绑定。v-text
:渲染文本,会覆盖原先的字符串。可以避免XSS攻击
,因为它不解析HTML标签,只渲染纯文本内容。v-html
:渲染 HTML标签,{{}} 和 v-text 都是输出文本。v-html 输出 HTML (可能会遭受XSS攻击)。v-once
:只渲染元素和组件一次,当数据发生改变时,元素或组件也不会再变化。常用于优化更新性能,避免不必要的重新渲染。v-slot
:简写#
。定义一个具名插槽或作用域插槽。
2. v-model 实现原理?⭐
- Vue 中使用 v-model 指令来实现
表单控件和数据的双向绑定
,将表单中用户提交的信息和设定的变量进行绑定。 - v-model 其实是一个语法糖,他本质上是包含两个操作:
v-bind 绑定一个 value 属性
;v-on 指令给当前元素绑定 input 事件
。 - 用户提交的信息不一定与 value 值绑定。在
text 类型
中用户提交的信息与value绑定,value 与框中内容绑定。在radio 类型
中,只有设定了 value 值之后,用户提交的信息才与 value 值绑定,value 值与设定值绑定。在checkbox 类型
中,只有设定了 value 值,且用户提交的信息的数据类型设置为数组后,用户信息才与 value 值绑定,value 值与设定值绑定。
3. v-if 和 v-show 的区别?⭐⭐
- 相同点
- 都是
条件渲染指令
,通过 true 和 false 控制元素的显示和隐藏。
- 都是
- 不同点
- v-if 当条件为 true 时,把元素创建并渲染到 HTML,
为 false 时把元素删除,不会渲染到 HTML 上
。是一个创建和删除的过程,删除时并不会占用空间位置
。 - v-show
无论是 true 还是 false 元素都会被渲染出来
。当条件为 false 时通过display: none
控制元素隐藏。是一个显示和隐藏的过程,隐藏时还是会占用空间位置
。
- v-if 当条件为 true 时,把元素创建并渲染到 HTML,
- 应用场景
- v-if:适用于
切换不频繁
,且元素内容很多
,渲染一次消耗很大性能
的元素上。 - v-show:适用于
切换频繁
的元素上(显示/隐藏)。
- v-if:适用于
v-show是css切换,v-if是完整的销毁和重新创建。如果频繁切换时用v-show,运行时较少改变用v-if
4. v-if 和 v-for 那个优先级更高?⭐⭐
在 vue2 中, v-for 比 v-if 的优先级更高
。这意味着 v-if 将分别重复运行于每个 v-for 循环中,比较消耗性能,所以不建议 v-for 和 v-if 一起使用。可以把 v-if 放在 v-for 的外层或者把需要使用 v-for 遍历的属性先从计算属性中过滤一次。在 vue3 中, v-if 比 v-for 的优先级更高
。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名,会报错。可以把 v-for 放在 v-if 的外层。- 总之,
不要把 v-for 和 v-if 一起使用
。
5. 常用的事件修饰符有哪一些(v-on 指令的修饰符)? ⭐
prevent
:阻止事件的默认行为stop
:阻止冒泡self
:只监听触发该元素的事件,其他不管(currentTarget 和 target为同一个元素时候触发)capture
:阻止捕获once
:事件只触发一次left
:左键事件right
:右键事件middle
:中间滚轮事件
6. 虚拟 DOM?⭐⭐⭐
- 什么是虚拟 DOM?
- 虚拟 DOM 是一种
在内存中构建和操作的JS对象树
,用来描述真实 DOM 的结构信息
,是为了解决浏览器性能问题引入的概念。虚拟 DOM 是相对于浏览器渲染出来的真实 DOM 而言的。 - 在传统的前端开发中,当数据发生改变时,我们需要直接操作真实的 DOM 来更新页面,但是这种直接操作会带来性能上的问题。因为
改变 DOM 会触发浏览器的重绘和重排,频繁操作消耗了大量的计算资源
。而且直接操作 DOM 几乎都要遍历整颗 DOM 树,相比之下查找JS对象属性变化要比查询 DOM 树开销小
。 - 虚拟 DOM 的出现解决了这个问题。当数据发生改变的时候,我们
首先在内存中创建一个虚拟 DOM 树
,然后与之前保存的旧的虚拟 DOM 树进行对比
,然后通过 diff 算法找出差异
,并只更新差异部分对应的真实 DOM
。这样就避免了直接操作真实 DOM,减少了性能的开销
。 - 虚拟 DOM 的好处是
通过 diff 算法只更新需要更新的部分,减少了对真实 DOM 的操作次数,避免频繁的重构和重排,减少了性能的开销,提高了渲染效率
。支持跨平台
,虚拟 DOM 本质上是一个 JS 对象,而 DOM 与平台强相关。相比之下虚拟 DOM 可以在不同的平台上使用,例如浏览器、移动端和桌面应用等。简化逻辑
,虚拟 DOM 提供了一个抽象层,让开发者可以更简洁的操作 DOM,提高了代码的可读性和可维护性,简化了代码逻辑。 - 虚拟 DOM 的结构通常包含以下属性
tagName
:DOM 元素的标签名。properties
:包含 DOM 元素属性的对象。children
:子元素的数组,可以是更深层次的虚拟 DOM 节点或者文本节点。key
:一个可选的属性,用于优化 DOM 的 diff 算法。
- 虚拟 DOM 是一种
//简单虚拟 DOM 结构的 JavaScript 代码示例
const virtualDom = {tagName: 'div',properties: {id: 'container',style: 'color: red;'},children: [{tagName: 'ul',properties: { },children: [{tagName: 'li',properties: { },children: ['列表项 1']}]}]
};
- 虚拟 DOM 是怎么更新数据的?
- 虚拟 DOM 可以很好的跟踪当前 DOM 的状态,他会根据当前数据生成一个描述当前 DOM 的虚拟 DOM。然后数据改变的时候又会生成一个新的虚拟 DOM,然后通过
diff 算法
计算出前后两个虚拟 DOM 之间的差异,得出一个最优的更新方法。
- 虚拟 DOM 可以很好的跟踪当前 DOM 的状态,他会根据当前数据生成一个描述当前 DOM 的虚拟 DOM。然后数据改变的时候又会生成一个新的虚拟 DOM,然后通过
- 虚拟 DOM 是怎么生成的?
- 首先代码运行会走生命周期,当生命周期走到
created 到 beforeMount 之间
的时候,会编译 template 模板成 render 函数
。 - 然后在
beforeMount 和 mounted 之间执行 render 函数
。当 render 函数运行时,h 函数会被调用
,而 h 函数最主要的就是执行 vnode 函数
。vnode 函数主要作用就是将 h 函数传进来的参数转换成 JS 对象,即生成虚拟 DOM
。 - 之后当数据发生改变时会
重新编译生成一个新虚拟 DOM (vdom)
,然后通过diff 算法
计算出前后两个虚拟 DOM 之间的差异
,得出一个最优的更新方法。从而减少了不必要的 DOM 操作,提高了页面的渲染速度和性能。
- 首先代码运行会走生命周期,当生命周期走到
- 虚拟 DOM 是怎么更新数据的?
- 首先调用
patch 函数
比较两个虚拟 DOM 的根节点
是否是相同节点。如果不同直接替换。 - 如果相同,则调用
patchVnode 函数
比较两个节点的子级
,即属性、文本和子节点
。此时,要么新增,要么删除,要么替换。只有都存在子节点时,会执行 updateChildren 函数
进一步比较他们的子节点。 - 子节点在 updateChildren 函数中进行比较更新。首先会分别添加两个指针指向子节点的第一个节点和最后一个节点,然后会进行
头头比较,头尾比较,尾头比较,尾尾比较
。如果匹配上,那么会将旧节点对应的真实 DOM 移到新节点的位置上,指针向中间移动,同时匹配的两个节点会继续调用 patchVnode 函数进行进一步的对比
。当指向第一个节点的指针大于指向最后一个节点指针的时候表示匹配结束
。此时,多余的元素删除,新增的元素新增。 - 如果上面这几种情况都没有出现,key 就起到了关键性作用。
存在 key 时,可以直接通过 key 去找到节点的原来的位置
。如果没有找到,就新增节点。找到了就移动节点位置,执行 patchVnode 函数进行进一步的对比,指针向后移。这样查找效率非常高。而如果没有 key 呢,那么压根就不会去原来的节点中查找了,而是直接新增这个节点。这就导致这个节点下的所有子节点都会被重新新增。会出现明显的性能耗。所以,合理的使用 key,也是一种性能上的优化。
- 首先调用
简单来说,实现 diff 算法的过程,就是一个执行 patch 函数,然后循环执行patchVnode 函数,updateChildren 函数……这样递归的过程。
patch 只执行一次,patchVnode 函数,updateChildren 函数循环递归执行
。
- 真实 DOM 的优缺点?
- 优点
可靠性高
:在浏览器中直接操作实际的 DOM 元素,确保了可靠的渲染和交互。功能丰富
:提供了各种原生的 API 和事件处理机制,可以直接操作 DOM 元素。
- 缺点
内存消耗大
:每次数据变化都需要更新整个 DOM 树,如果页面庞大,内存消耗也会相应增加。性能差
:频繁操作真实 DOM,需要频繁地进行重排、重绘
,性能下降。
- 优点
- 虚拟 DOM 的优缺点?
- 优点
减少DOM操作,效率高,性能好
。虚拟 DOM 通过 diff 算法只更新需要更新的部分,减少了对真实 DOM 的操作次数,避免频繁的重构和重排,减少了性能的开销,提高了渲染效率。(重绘和回流)支持跨平台
。虚拟 DOM 本质上是一个 JS 对象,而 DOM 与平台强相关。相比之下虚拟 DOM 可以在不同的平台上使用,例如浏览器、移动端和桌面应用等。(服务器渲染)简化逻辑
。虚拟 DOM 提供了一个抽象层,让开发者可以更简洁的操作 DOM,提高了代码的可读性和可维护性,简化了代码逻辑。提升用户体验
。
- 缺点
首次渲染可能较慢
。由于虚拟 DOM 需要在内存中构建一颗 DOM 树,然后再将其转为真实的 DOM 树,所以首次渲染的耗时可能比直接操作 DOM 要长一些。内存使用可能增加
。虚拟 DOM 需要在内存中维护一个额外的数据结构,并且在每次更新时都要进行比较和计算差异,可能会增加一定的内存消耗和性能的开销。不适合所有场景
。对于静态内容或较少变化的页面,使用虚拟 DOM 可能会带来不必要的开销。无法直接操作 DOM
。虚拟 DOM 是对 DOM 的抽象,需要通过特定方法才能进行操作。
- 优点
7. v-for 为什么要加 key?不加 key 会怎么样?⭐⭐
- 为什么要在 v-for 上加 key?
- key 是唯一标识。
- v-for 中加上 key 是为了
提高列表的渲染性能
和提供更好的更新策略
。
- 不加 key 会怎么样?key 有什么用?
- 性能问题
- 如果没有 key,VUE在更新列表时会使用一种
就地复用策略
。即复用已经存在的 DOM 元素,而不是删除重新插入。这可能会导致错误的元素被复用,导致渲染错误或者不符合预期的结果
。 加上 key 可以确保正确地复用和更新元素,提高渲染性能
。
- 如果没有 key,VUE在更新列表时会使用一种
- 顺序问题
- 如果顺序发生改变,没有设置 key,VUE 将无法识别新旧节点的对应关系,从而
导致重新渲染整个列表
,而不仅仅是更新需要修改的部分。 加上 key 可以帮助 vue 更好的识别新旧节点对应的关系,只更新变化的部分
。
- 如果顺序发生改变,没有设置 key,VUE 将无法识别新旧节点的对应关系,从而
- 组件状态问题
- 如果列表中的组件包含状态,没有设置 key 会
导致状态丢失或混乱
。 加上 key 可以确保组件在重用时保持自己的状态
。
- 如果列表中的组件包含状态,没有设置 key 会
- 性能问题
- 什么是就地复用策略?
就地复用策略
是 VUE 在 DOM 更新时采用的一种优化方式
。- 在更新现有 DOM 树时,VUE 会
尽可能地复用已存在的 DOM 元素
,而不是删除并重新插入。这样可以减少不必要的 DOM 操作,提高性能。 - VUE 在执行 DOM 更新时会进行四个步骤:
-
- 创建一个新的虚拟 DOM 树。
-
- 对比新旧虚拟 DOM 树,找出差异。
-
- 根据差异对现有的 DOM 树进行更新。
-
- 触发 DOM 更新后的钩子函数。
-
- 在第二步中,VUE 会对比新旧虚拟 DOM 树的节点,找出它们之间的差异。对于相同的节点,在更新时 VUE 会尽可能地复用已存在的 DOM 元素。这样做可以避免不必要的 DOM 操作,提高性能。
- 就地复用策略只适用于
同一层级的元素之间进行比较
。如果两个元素的父元素不同,Vue 会直接删除旧元素并在新父元素中创建新元素。
8. v-for 写在 template 上,不能加 key 怎么办?
- 在 template 里面套上两层 div,v-for 和 key 写在里面的 div 上面。
- 在下一层需要循环遍历的真实 DOM 上加一个 key 就可以了。
9. vue2 生命周期⭐⭐⭐
- 什么是 VUE 的生命周期?
- VUE 生命周期是指
VUE 对象从创建到销毁的过程
。
- VUE 生命周期是指
- 什么是钩子函数?
每个生命周期都有对应的函数
,我们把这些函数称为钩子函数。
- VUE2 的生命周期有哪些?
beforeCreate 创建前
:在实例初始化之后,数据观测和事件配置之前被调用。此时无法访问到data、computed、watch上的内容。created 创建后
:在实例创建完成后被立即调用。此时可以访问到data、computed、watch、methods
上的内容,但还未挂载到DOM上
。常用于简单的 ajax 请求。如果要进行 dom 操作,那就要用 $nextTick 函数。beforeMount 挂载前
:在挂载开始之前被调用。在 beforeMount 之前已经将 template 模板编译成 render 函数。在 beforeMount 的时候render 函数首次被调用
。此时,还没有挂载到界面。mounted 挂载后
:实例被挂载后调用。此时组件已经被渲染到DOM中,可以访问到DOM元素
。可进行数据请求。beforeUpdate 更新前
:在数据发生改变后,DOM 被更新之前
被调用。适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器 (此时页面旧数据、data 新数据,还没有同步)。updated 更新后
:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后
调用。组件 DOM 已经更新( 此时页面和 data 的数据已经同步,都是最新数据 )。beforeDestroy 销毁前
:实例销毁之前调用
。this 仍能获取到实例
,常用于销毁定时器
、解绑全局事件
、销毁插件对象
等操作。destroyed 销毁后
:实例销毁之后调用
。该钩子被调用后,对应 VUE 实例的所有指令都被解绑
,所有的事件监听器被移除
,所有的子实例也都被销毁
。
mounted 和 updated 不会保证所有的子组件都被挂载或重新渲染,
如果希望等到整个视图都渲染完毕再执行某些操作
,可以在 mounted 或 updated 内部使用$nextTick 函数
。
- 父子组件生命周期的执行顺序?
- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
- VUE 生命周期的作用是什么?
- VUE 的生命周期中有多个事件钩子,通过
不同阶段对应不同的钩子函数
,来实现组件数据管理
和DOM渲染
两大功能。
- VUE 的生命周期中有多个事件钩子,通过
- 第一次页面加载会触发哪几个钩子?
beforeCreate,created,beforeMount,mounted
- 如果加入了 keep-alive 多哪两个生命周期?
activated、deactivated
- activated:被 keep-alive 缓存的组件激活时调用。
- deactivated:被 keep-alive 缓存的组件失活时调用。
- 如果加入了 keep-alive ,第一次进入组件会执行那些生命周期?
beforeCreate,created,beforeMount,mounted,activated
- 如果加入了 keep-alive,第 n 次进入组件会执行那些生命周期?
只执行 activated
- 创建后和渲染后有什么区别?
- created 是实例创建完成后调用,只能访问 data、computed、watch、methods 上的方法和数据,未挂载 DOM,还不能获取 DOM 节点。如果要进行 DOM 操作,那就要用 $nextTick 函数。常用于简单的 ajax 请求。
- mounted 是实例被挂载后调用,VUE 实例已经初始化完成了,已经可以获取 DOM 节点和操作 DOM 节点了。
- 渲染后和更新后有什么区别?
- 在
每一次的 DOM 结构更新时,VUE 都会调用一次 updated() 钩子函数
,而mounted 仅执行一次
。
- 在
- 简述每个周期具体适合哪些场景?
beforecreate
:无法访问到数据,可以在这个周期处理整个系统加载的Loading。created
:可以进行数据初始化,进行异步请求
,但不要修改DOM,因为还没挂载。mounted
:可以访问到真实的DOM,进行DOM操作,发起网络请求,定时器
等updated
:访问更新后的DOM。注意避免在此函数内进行大量操作,因为这会导致更新过程中不必要的性能开销。beforeDestroy
:清除定时器,解除非VUE管理的事件监听器
等。destroyed
:清除定时器,取消网络请求
。$nextTick
: 更新数据后立即操作 DOM。
10. VUE3 生命周期 ⭐⭐⭐
- VUE2 和 VUE3 生命周期有什么区别?
- 流程设计上的区别
VUE3 是先将 el 挂载了之后再进行 beforeCreate
。VUE2 是 created 之后进行判断是否挂载 el
。如果没挂载,则流程终止。beforeCreate 和 created 两个钩子冗余使用,因此 VUE3 进行了优化。
- 钩子名称上的区别
beforeDestroy 改为 onBeforeUnmounted
;destroyed 改为 onUnmounted
。
- 新增的钩子函数
renderTracked
:跟踪虚拟 DOM 重新渲染时调用。renderTriggered
:当虚拟 DOM 重新渲染被触发时调用。
- 使用上的区别
- VUE2 生命周期钩子是被暴露在 VUE实例上的选项,可以直接使用。
VUE3 中需要将生命周期钩子导入项目,才能使用
。
- 流程设计上的区别
VUE2 生命周期 | VUE3 生命周期 |
---|---|
beforeCreate | setup |
created | created |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmounted |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
errorCaptured | onErrorCaptured |
11. VUE中的 el 是什么?
- VUE中的el是
指定VUE实例挂载的DOM元素
。 - 在VUE中,el是element的缩写,代表
挂载点
。 - 它的作用是
提供一个在页面上已存在的DOM元素,作为VUE实例的挂载目标
。这个元素可以是CSS选择器字符串,也可以是一个实际的DOM元素对象。当VUE实例初始化时,它会将模板渲染到指定的el元素中
,并将该元素下的所有内容替换为VUE实例的内容
。
12. 小程序生命周期? ⭐⭐
- 全局生命周期
- onLaunch:第一次全局执行。
- onShow:第一次执行,或者关闭后重新打开后执行。
- onHide:关闭小程序,但是没完全关闭,切换到后台。
- onError:小程序报错。
- 页面生命周期
- onLoad:当前页面第一次加载的时候。
- onShow:第一次执行当前页面,或者关闭后重新打开后执行。
- onReady:当渲染页(视图层-wxml)加载完毕执行。
- onHide:关闭当前页面,但是没完全关闭,切换到后台。
- onUnload:销毁当前页。
- onPullDownRefresh:下拉刷新。
- onReachBottom:页面触底时执行,一般用于做上拉加载。
- onShareAppMessage:分享页面执行。
- onPageScroll:当前页面滚动执行。
- onResize:放大缩小页面的时候执行。
- onTabItemTap:点击底部导航栏触发。
- 组件生命周期
- created:创建组件时执行。
- attached:被插入到父组件节点时执行。
- ready:渲染完后执行。
- moved:移动组件节点。
- detached:从父组件节点移除时执行。
- error:组件方法抛出错误时执行。
13. uni-app 生命周期 ⭐⭐
uniapp 生命周期是以小程序生命周期为基础实现的,分为
应用生命周期
、页面生命周期
、组件生命周期
。 其中组件生命周期就是 VUE 生命周期
。
- 应用生命周期
- onLaunch:当 uniapp 初始化完成时调用 (只触发一次)。
- onShow:当 uniapp 启动或从后台进入前台时发生调用 (监听用户进入小程序)。
- onHide:当 uniapp 从前台进入后台时发生调用 (监听用户离开小程序)。
- onError:当 uniapp 报错时被触发。
- onUniNViewMessage: 对 nvue 页面的数据进行监听。
- onUnhandledRejection:对未处理的 Promise 拒绝事件进行监听。
- onPageNotFound:页面不存在监听函数。
- onThemeChange:监听系统主题的变化。
- 页面生命周期
- onLad:监听页面加载。
- onShow:监听页面显示 (每次页面显示都触发)。
- onReady:监听页面初次渲染完成。
- onHide:监听页面隐藏。
- onUnload:监听页面卸载。
- onResize:监听窗口尺寸的变化。
- onPullDownRefresh:监听用户下拉动作。
- onReachBottom:监听页面上拉触底事件。
- onTabItemTab:点击 TabBar 时触发。
- onShareApplessage:点击右上角分析时触发。
- onShareTimeline:点击右上角转发到朋友圈时触发。
- onAddToFavorites:点击右上角收藏时触发。
- onPageScroll:监听页面滚动。
- onNavigationBarButtonTap:监听标题栏按钮点击事件。
- onNavigationBarSearchInputchanged:监听标题栏搜索输入框输入内容变化事件。
- onNavigationBarSearchInputClicked:监听标题栏搜索输入框点击事件。
- onBackPress:监听用户点击右上角收藏。
- 组件生命周期
- uniapp 的组件生命周期和 VUE 标准组件生命周期相同。在当前版本的 uniapp中,你既可以选择使用 VUE2 进行开发,也可以使用 VUE3 进行开发。
14. VUE 里边如何让 CSS 样式只在当前组件中生效? ⭐
- 在组件中的 style 前面加上
scoped
即可。
15. VUE 的 watch 和 computed 有什么区别?应用场景?⭐⭐⭐⭐⭐
- 区别
- computed:
计算属性,可以修改数据
。通过get 获取数据
,set 修改数据
。而且它是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值,否则就拿上一次已经计算好存在栈里的值。 - watch:
监听属性
,可以监听数据 (data 值) 的改变,支持异步,每当监听的数据变化时都会执行回调进行后续操作。有三个属性:handler 事件句柄
,immediate 默认监听
,deep 深度监听
。
- computed:
- 应用场景
- computed:在
需要进行数值计算,并且依赖于其它数据时
,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。 - watch:
需要在数据变化时执行异步或开销较大的操作时
,应该使用 watch。使用 watch 选项允许我们执行异步操作,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
- computed:在
- 计算属性示例
地址:<input type="text" v-model="province">{{ getAddress }}
类型:<input type="text" v-model="type">{{ getType(type) }}{{ getType(1) }}/{{ getType(2) }}/{{ getType(3) }}
get和set:<p>初始字符串initStr:{{ initStr }}</p><p>changeStr计算结果(获取initStr的最后一个字符):{{ changeStr }}</p><button @click="changeValue">点击改变值为valueNew</button>
<script>
export default {data() {return {province: '湖北省',type: 1,initStr: 'initValue'};},computed: {// 1. 获取地址:当在输入框中输入内容,getAddress内容也随之改变。getAddress() {return this.province + '黄冈市'},// 2. 获取类型(type为传过来的值):当在输入框中输入内容,getType的内容也随之改变。getType() {return ((type) => type == 1 ? '个人' : type == 2 ? '企业' : '未知')},// 3. get和set用法changeStr: {get() {// changeStr取值为initStr的最后一个字符return this.initStr.slice(-1);},set(value) {//changeStr值改变,则initStr值同步修改this.initStr = value;}}},methods: {//改变changeStr的值changeValue() {this.changeStr = 'valueNew';}}
};
</script>
- 监听示例
data() {return {age: 22,user:{name:'zhangsan',},getAge: '',getName: ''}
},
watch: {// 1. 普通监听getAge(newVal, oldVal){// getAge变化后,age=getAge+3this.age = newVal+ ' ' + 3 },// 2. 深度监听getName: {// 深度监听对应的函数名必须为 handler,否则无效handler(newValue, oldValue) {// 监视到数据变化时执行的逻辑:更新user对象中的name字段this.user.name = newValue},deep: true, // 深度监听对象内部属性的变化immediate: true // 初始化时立即执行一次回调},// 3. 监听对象的单个属性'user.name':{handler (newVal, oldval) {console.log(newVal,oldval)},immediate: true, // 该属性会先执行一次handler},// 4. 监听路由变化'$route.path':function(newVal, oldVal){if(newVal === '/login'){console.log('欢迎进入登录页面');} else if(newVal === '/register'){console.log('欢迎进入注册页面');}}
}
16. $nextTick 事件轮询?⭐⭐
- $nextTick 是什么?
- 事件轮询。即把所有的触发事件都放在一个事件栈 (事件队列)里,再根据实际情况先后执行。
- $nextTick 有什么作用?
- 在 DOM 下一次更新完成后延迟回调。
- VUE 在更新 DOM 时是异步执行的,在修改数据后视图不会立刻更新,而是等同一事件循环中的所有数据修改完成之后,再统一进行视图更新。
- 所以修改完数据,立即在方法中获取 DOM,获取的仍是未修改的 DOM。为了在修改数据后执行一些操作,可以在数据变化之后立即使用 $nextTick。这样
回调函数将在 DOM 更新完成后被调用,可以获取更新后的 DOM
,解决了异步渲染获取不到更新后 DOM 的问题
。
- $nextTick 的原理?
- $nextTick 本质是返回一个
Promise 对象
。
- $nextTick 本质是返回一个
- $nextTick 的应用场景?
- 在钩子函数
created
里面想要获取操作 DOM ,把操作 DOM 的方法放在 $nextTick 中。 mounted 和 updated
不会保证所有的子组件都被挂载或重新渲染,如果希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 或 updated 内部使用 $nextTick 。
- 在钩子函数
17. 哪个阶段无法获取 DOM 节点?怎么处理?⭐
- beforeCreated,created,beforeMount。
- 在 mounted 这个阶段挂载后才能操作 DOM 节点,要想在 created 阶段操作 DOM 节点,可以将获取节点的操作放在 $nextTick 中,在 DOM 下一次更新完成后延迟回调。
18. VUE 中使用 refs 获取组件的 DOM 元素报 undefined 如何解决?⭐⭐⭐
把 ref 加在普通元素上,可以通过
this.$refs.name 获取到 DOM 元素
。用 ref 注册子组件,父组件可以通过this.$refs.name 调用子组件里的函数
。
问题:在页面初始化的时候调用 this.$refs.name 的时候提示 undefined。
- 主要有以下几种情况以及解决方案
- 情况一
- 原因:
在 created 钩子函数中调用
,created 在实例创建完成后被立即调用,这个时候还不能操作 DOM 节点。 - 解决方案:
在 mounted 阶段挂载后执行
。
- 原因:
- 情况二
- 原因:
ref 组件使用了条件渲染,即 v-if,v-show
。这些 DOM 节点无法在 mounted 中访问。因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们,它们还不存在。ref 也不是响应式的
,所有动态加载的模板更新它都无法相应的变化。 - 解决方案:①
不在 mounted 中获取元素,在 updated 中获取
。②调用 VUE 的全局API$nextTick
,延迟到 DOM 下一次更新后执行,在渲染完成后获取 ref。
- 原因:
- 情况三
- 原因:
使用了 v-for 循环动态绑定 ref,this.$ref.name 获取的是数组
。 - 解决方案:由 this.$refs.name 改为
this.$refs.name[index]
。
- 原因:
- 情况一
19. 数据修改后页面不刷新案例?怎么解决?⭐⭐⭐
- 案例一
- 问题:VUE
无法检测实例被创建时不存在于 data 中的变量
。 - 原因:由于 VUE2 双向数据绑定的核心原理,在初始化时要对所有的已有属性进行遍历,所以变量必在 data 对象上存在才能让 VUE 将它转换为响应式的。
- 解决方法:
把变量放到 data 里面
。
- 问题:VUE
- 案例二
- 问题:VUE 无法检测到 data 中对象,数组的动态添加和删除,不能通过索引直接修改或者赋值,也不能修改数组的长度。
- 原因:VUE2 是通过
Object.defineProperty 数据劫持
结合订阅发布模式
实现双向数据绑定的。通过 Object.defineProperty 来劫持各个已有属性的 getter 和 setter,它不能劫持整个对象,只能劫持已有对象属性。所以只能监听到已有数据是否被修改,不能监听到对象的新增删除属性,而且无法监控到数组下标的变化,引入不能修改数组长度。 - 解决方法:通过
this.$set
动态添加,通过this.$delete
动态移除。通过Object.assign({},{})
合并对象并返回新对象和数组。通过push、pop、shift、unshift、splice、sort、reverse
等直接改变原数组的方法会触发更新。
- 案例三
- 问题:异步获取接口数据,DOM 数据不发现变化。
- 原因:VUE 在更新 DOM 时是异步执行的。在修改数据后视图不会立刻更新,而是等同一事件循环中的所有数据修改完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取 DOM,获取的仍是未修改的 DOM。
- 解决方法:可以在数据变化之后立即使用
$nextTick
,这样回调函数将在 DOM 更新完成后被调用,可以获取更新后的 DOM,$nextTick 解决了异步渲染获取不到更新后 DOM 的问题。
- 案例四
- 问题:循环嵌套层级太深,视图不更新。
- 原因:当嵌套太深时,页面也可能不更新,此时可以让页面强制刷新。
- 解决方法:可以让
页面强制刷新
,this.$forceUpdate
(不建议用)
- 案例五
- 问题:路由参数变化时,页面不更新(数据不更新)。
- 原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。路由切换时页面只会渲染第一次路由匹配到的参数。
- 解决方法:
通过 watch 监听 $route 的变化
。给<router-view> 绑定 key 属性
,这样 VUE 就会认为这是不同的<router-view>。弊端:若跳转到某些路由下没有这个问题,key 属性就是多余的。
20. Vue.extend 和 Vue.component 的区别?
- 区别
- vue.extend 的写法步骤更加繁琐一些。要创建构造器,还要 new 一个实例,还要将实例通过 $mount 挂载到特定的元素上。Vue.component 注册组件会自动使用给定的 id 设置组件的名称,然后想在哪里用就在哪里写组件名就可以了。
- vue.extend 只能通过自身初始化结构,使用范围可以挂载在某元素下,可以被 Vue 实例的components 引用,可以被 Vue.component 组件引用。vue.component 可通过自身初始化组件结构,可通过引入 vue.extend 初始化组件结构,可通过第三方模板 temple.html 初始化组件结构,使用范围是任何已被 VUE 初始化过的元素内。
- vue.component 是用来注册全局组件的方法,可以在任何地方使用。而 Vue.extend 是用来创建一个 vue 子类的方法 (一个可复用的组件模板),可以在局部组件中使用。
- vue.component 注册的组件可以直接在模板中使用。而 vue.extend 创建的子类需要先进行实例化,然后才能在模板中使用。
- extend 的基础用法:
- 使用基础 VUE 构造器函数,创建一个子类,参数是一个包含组件选项的对象。然后 new 一个实例并通过 $mount 挂载到特定的元素上,就可以把这个实例 append 到页面 body 里。
- extend 的应用场景:
- vue.extend 属于 VUE 的全局 API,在实际业务开发中我们很少使用,因为相比常用的 vue.component 写法,使用 extend 步骤要更加繁琐一些。但是在一些独立组件开发场景中,vue.extend + $mount 这对组合是我们需要去关注的。比如:如 alert 弹窗组件,你肯定希望 alert 可以动态插入到 body 中,而不需要在 DOM文档流中放一个莫名其妙的 alert,大部分时间他还是隐藏的。
21. VUE2 中为什么组件 date 是一个函数而不是一个对象?⭐⭐
- VUE 中组件是用来复用的,
为了防止 Data 复用
,将其定义为函数。 - 如果组件中的 Data 是对象,那么作用域没有隔离,组件中的 Data 值会相互影响。也就是说组件可能被用来创建多个实例,如果 Data 是一个对象,修改其中一个实例的数据其他实例的数据也会跟着改变。
- Data 如果是一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 Data,
拥有自己的作用域
,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。改变其中一个数据,不会影响其他的数据。
22. VUE 是什么?说一下对 VUE 的理解?⭐⭐⭐⭐
VUE 是一款渐进式 JavaScript 框架,用于构建用户界面。综合了 Angular 的模块化和 React 的虚拟 DOM 的优点。采用了 MVVM 的设计模式,实现了双向数据绑定。通过数据驱动和组件化的开发方式,使得前端开发变得更加简单、高效。
- vue 核心概念(优点):
双向数据绑定
- VUE 框架采用的是 mvvm 模式,相比 MVC 模式,不再局限于数据单向绑定,而是能够实现数据双向绑定,将数据和视图进行实时的同步更新。
- 实现的原理:vue2 是通过
Object.defineProperty 数据劫持
结合订阅发布模式
实现双向数据绑定的。vue3 是通过new Proxy 数据代理
实现的。实现了当数据发生改变时,视图会自动更新;当用户操作视图时,数据也会相应地进行更新。
组件化开发
- Vue 鼓励使用组件化开发,将页面拆分成多个独立的组件,每个组件负责特定的功能。组件可以复用、嵌套和组合,提高代码的可维护性和复用性。
虚拟 DOM
- Vue 采用了虚拟 DOM 技术,通过在内存中构建虚拟 DOM,并与真实 DOM 进行比较,只更新需要更新的部分,减少真实 DOM 操作,提高了渲染性能。
渐进式
- Vue 是一款
渐进式 JavaScript 框架
,所谓渐进式就是逐步实现新特性
的意思,如实现模块化开发、路由、状态管理等新特性,根据业务需求逐步选择需要的 VUE 功能模块。
- Vue 是一款
生态丰富
- VUE 拥有一个活跃的社区,有大量的第三方插件和工具可以使用。同时,VUE 也提供了官方的路由、状态管理和构建工具等配套库,方便开发者进行项目开发。
23. 什么是渐进式?⭐⭐⭐
- VUE 是一款渐进式 JavaScript 框架,所谓渐进式就是
逐步实现新特性
的意思。 - 技术上的渐进式,就是
不需要一上来就要选 VUE 的全家桶
,可以根据自己的业务逐步的选择你需要的 VUE 功能模块
。 - 业务模块的渐进式,涉及 VUE 中的一个重要概念-组件化。
组件在不需要的时候不加载,当业务需要时再加载
。
24. VUE 是如何实现双向数据绑定? (原理与缺陷)⭐⭐⭐
- 什么是数据双向绑定?
- VUE 框架采用的是 mvvm 模式,实现了双向数据绑定。当数据发生改变时,立即触发视图的更新。当用户操作视图时,数据也会相应地进行更新。
- VUE2 是如何实现双向数据绑定的?
- 原理
- VUE2 是通过
Object.defineProperty 数据劫持
结合订阅发布模式
实现双向数据绑定的。通过 Object.defineProperty 来劫持各个属性的 getter 和 setter,当数据对象的属性被读取或修改时,会触发相应的 getter 和 setter 函数。当数据发生变化时,触发 setter 函数会通知相应的 Dep 调度器,Dep 调度器通知相应的订阅者,让订阅者执行自己的 update 逻辑进行更新视图。
- VUE2 是通过
- 缺点
- Object.defineProperty 数据劫持,只能劫持对象的属性。因此我们需要对每个对象的每个属性进行遍历,如果对象里面还有对象,需要递归。所以
性能差
。 - Object.defineproperty 只能监听到已有数据是否被修改,
不能监听到对象的新增删除属性
,需要调用相应的方法更新,如 this.$set 和 this.$delete。由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。 - Object.defineproperty 也
不能监听到数组的添加删除
,而且无法监控到数组下标的变化
,导致不能通过索引直接修改或者赋值
,也不能修改数组的长度
。依然可以 this.$set 和 this.$delete 去更新数据,当然也可以使用数组重写的方法实现了数组的响应,比如使用 push 给数组添加一个数据,不需要调用 this.$set 页面也能够更新响应。( 数组方法:push 添加、pop 删除、shift 删除、unshift 添加、splice 插入、sort 排序、reverse 反转)
- 原理
- VUE3 是如何实现双向数据绑定的?
- 原理
- VUE3 是通过
Proxy 数据代理
来实现的数据双向绑定。Proxy 是 ES6 中新增的一个特性,可以劫持整个对象,并返回一个新对象。 - 实现过程是当一个普通的 JavaScript 对象传入 VUE 实例的时候,VUE 会使用 Proxy 对象包装该对象,并在外界访问和修改对象属性时进行拦截。当属性发生改变的时候,VUE 就能捕获到变化并更新相应的视图。
- ES6 提供 Proxy 构造函数,用来生成 Proxy 实例,接收两个参数 target 和 handler。target 是用 Proxy 包装的被代理对象,可以是任何类型的对象,包括原生数组,函数,甚至是另外一个代理。handler 是一个对象,其声明了代理 target 的一些操作,当任何一个对象属性发生变化时,就会触发 handler 中的 set 方法,从而更新对应的DOM节点。
- VUE3 是通过
- 优点
可以劫持整个对象
,而非对象属性,并返回一个新的对象。- 代理对象可以劫持对象属性的访问、新增、删除等操作,
不需要初始化时遍历所有属性
。 - 如果有多层属性嵌套,
只有在访问到某个属性的时候才会递归处理下一级属性
。 可以监听到数组变化
,例如索引。可以代理对象
,可以代理数组
,可以代理动态增加的属性
。
- 缺点
存在兼容性问题,IE 无法兼容
。VUE2 兼容到了 IE8,如果不考虑兼容 IE 浏览器,可以考虑使用 VUE3。
- 原理
25. 什么是订阅发布模式?⭐
订阅发布模式是一种
软件设计模式
,其原理是基于一个中心的事件调度器
(或者称为消息队列)。 组件可以订阅感兴趣的事件,当事件发生时,调度器会通知所有订阅了该事件的组件进行相应的处理
。
- 组成和具体实现
observer 发布者
:通过 Object.defineProperty 来劫持各个属性的 getter 和 sette,对数据进行监听。读取数据时 (绑定数据),触发 getter 添加订阅者 watcher 到调度器 Dep。当数据发生变化时,触发 setter 函数会通知相应的调度器 Dep。简单来说,发布者负责监听和发布事件
。当某个事件发生时,发布者会将事件发布到调度器中
。Watcher 订阅者
:当订阅的值/事件发生变化时,会接收到来自 Dep 订阅者的通知,从而触发回调函数。简单来说,订阅者可以订阅感兴趣的事件。订阅者通过向调度器注册自关注的某个事件,以便在事件发生时接收通知,并执行相应的处理逻辑
。(v-model,{{}},v-bind)Dep 调度器
:负责接收发布者发布的事件,并将事件分发给所有订阅了该事件的订阅者。收集订阅者 Watcher,每个属性拥有自己的 Dep 调度器,用于存放所有订阅了该属性的订阅者。当数据发生改变时,会遍历订阅者列表,通知所有的订阅者
,让订阅者执行自己的 update 逻辑。
- 优点
- 订阅发布模式通过
调度器
实现了组件之间的解耦,使得发布者和订阅者可以独立地进行开发和演化,具有灵活性和可扩展性。同时,由于订阅者只关注自己感兴趣的事件,可以有效地减少不必要的通信和处理开销
。
- 订阅发布模式通过
26. VUE2 和 VUE3 有什么区别?⭐⭐⭐
VUE2 只支持一个根节点,VUE3 支持多个根节点
。生命周期发生了改变
。- VUE2 生命周期:beforeCreate、created、beforeMount、mounted、beforeupdate、updated、beforeDestroyed、destroyed
- VUE3 生命周期:setup开始创建组件、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
VUE2 使用的是选项式API,VUE3 使用的是组合式API
。核心原理不同
- VUE2是通过
Object.defineProperty 数据劫持
结合订阅发布模式
实现双向数据绑定的 - VUE3 是通过
Proxy 数据代理
来实现的数据双向绑定的
- VUE2是通过
组件通信书写方式不同
- VUE2 通过
props属性
, 需要加 .sync实现多个双向绑定。 - VUE3 通过
defineProps
,v-model可以绑定多个属性。
- VUE2 通过
VUE3 中默认支持TS
setup函数是 VUE3 特有的选项
,作为组合式API的起点。在VUE3 的项目中几乎用不到 this , 所有的东西通过函数获取。
27. 选项式API和组合式API的优缺点?⭐
- 选项式API
- 优点:
学习成本低
。 - 缺点:
不适合大项目的开发
,一个功能点的代码太分散了,代码难以理解和维护。
- 优点:
- 组合式API
- 优点:①可以将一个功能的所有 methods、data 封装在一个独立的函数中。
代码复用性更强,代码组织更清晰,维护更轻松,提高开发效率
。②所有的功能都是按需引入
的,没有使用到的功能在打包时会被清理掉,减小了包的大小。③通过使用 return 等语句,可以将逻辑分离得更清晰,使代码更易于理解和维护
。④由于组合式API的优化,VUE3 的性能相比 VUE2 有所提升
。
- 优点:①可以将一个功能的所有 methods、data 封装在一个独立的函数中。
28. VUE2 数据劫持和 VUE3 数据代理的优缺点?⭐
- VUE2数据劫持
只能劫持对象的属性
,因此我们需要对每个对象的每个属性进行遍历,如果对象里面还有对象,需要递归。所以性能差
。- 只能监听到已有数据是否被修改,
不能监听到对象的新增删除属性
,需要调用相应的方法更新,如 this.$set 和 this.$delete。 不能监听到数组的添加删除
,而且无法监控到数组下标的变化
,也不能修改数组的长度
,依然可以 this.$set 和 this.$delete 去更新数据。也可以使用数组重写的方法实现了数组的响应,比如使用 push 给数组添加一个数据,不需要调用 this.$set 页面也能够更新响应。
- VUE3数据代理
- 代理对象可以劫持对象属性的访问、新增、删除等操作,
不需要初始化时遍历所有属性
。 可以劫持整个对象
,而非对象属性,并返回一个新的对象。可以代理对象
,可以代理数组
,可以监听到数组变化
,例如索引。可以代理动态增加的属性
。- 缺点:
存在兼容性问题,IE 无法兼容
。如果不考虑兼容 IE 浏览器,可以考虑使用 VUE3 。
- 代理对象可以劫持对象属性的访问、新增、删除等操作,
29. MVC、MVP、MVVM 架构模式?⭐⭐⭐
MV系列框架中,M和V分别指Model层和View层。
Model层是数据模型,用来存储数据
。View层是视图,展示Model层的数据
。在不同的框架中,可能会有所差别,但主要变的是数据的传输方式
。
- MVC(Model-View-Controller)(JavaEE 中的 SSH 框架)
- M:
Model 模型层
,数据模型及其业务逻辑。 - V:
View 视图层
,用于与用户实现交互的页面。 - C:
Controller 控制器
,用于连接 Model 层和 View 层,完成 Model 层和 View 层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将 Model 层返回给用户。 - MVC 是模型-视图-控制器。
MVC 是单向通信
,必须通过 Controller 来承上启下。Controller 不会更新 View 的数据,View 依赖于 Model,通过监听 Model 层数据变化自动更新。在 Model 发生改变时,需要手动的去更新 View。MVC 是 DOM 驱动的,大量操作 DOM 使页面渲染性能降低,使加载速度变慢,影响用户体验
。 - MVC 执行步骤:用户输入 => 被 Controller 层拦截 => Controller 调用 Model 进行业务逻辑处理 / Controller 通知 View 描画页面 => Model 将结果反馈给 View =>View 描画页面
- M:
- MVP(Model-View-Presenter)(Android)
- M:
Model 模型层
,数据模型及其业务逻辑。 - V:
View 视图层
,用于与用户实现交互的页面。 - P:
Presenter 表示器
,用于连接 Model 层和 View 层,完成 Model 层和 View 层的交互,还可以进行业务逻辑的处理。 - MVP 是模型-视图-表示器。
MVP 是从 MVC 模式演变而来
的,将 Controller 改名为 Presenter 的同时改变了通信方向,各部分之间的通信是双向的
。view 不能直接访问 model,要通过 Presenter 层提供的接口,然后 Presenter 层再去访问 model 层。没有绑定数据,所有数据都需要在 Presenter 层进行手动同步。由于 view 层和 model 层都需要经过 Presenter 层,所有的交互都发生在 Presenter 内部
,导致Presenter 层比较复杂,维护起来也会有一定的问题
。 - MVP 执行步骤:用户输入(View需要展示某些数据) => 通知 Presenter 加载数据 => Presenter 层会调用 Model 层请求数据 => Model 层数据加载成功后通过回调方法通知 Presenter 层数据加载情况 => Presenter 层再调用 View 层的接口将加载后的数据展示给用户。
- M:
- MVVM(Model-View-ViewModel)(VUE / React / Angular)
- M:
Model 数据模型
,存放数据的地方 - V:
View 视图
,template 本身,渲染数据的地方。 - VM:
View-Module 视图模型
,转换机制 ( VUE 本身 ),把 Model 层上的数据自动渲染到 View 层,且 Model 层和 View 层的数据是同步的。 - MVVM 是模型-视图-视图模型。
MVVM 是双向数据绑定,是响应式的
。View 和 Model 之间没有直接的关系,通过 ViewModel 来完成数据双向绑定,视图和数据自动同步更新。MVVM 是数据驱动的,避免了大量操作 DOM
。 - 如何实现双向数据绑定的?
- VUE2 是通过
Object.defineProperty 数据劫持 结合 订阅发布模式
实现双向数据绑定的。VUE3 是通过Proxy 数据代理
来实现的数据双向绑定的。当数据发生改变的时候视图会同步更新,当用户操作视图的时候数据也会相应的更新。
- VUE2 是通过
- MVVM 缺点
bug 很难调试
。因为数据双向绑定,所以问题可能在 View 中,也可能在 Model 中,要定位原始 bug 的位置比较难。- 一个大的模块中的 Model 可能会很大,
长期保存在内存中会影响性能
。 - 对于大型的图形应用程序,视图状态越多,
ViewModel 的构建和维护的成本都会比较高
。
- M:
30. Vuex 状态管理?⭐⭐⭐⭐
- 什么是 Vuex状态管理?
- Vuex 是 VUE 框架中状态管理库,是集中管理项目公共数据的库。
- 简单来说,状态管理就是把组件的共享状态抽出来,以一个全局单例模式管理,不管任何组件在任何地方都能直接获取这个状态或者触发里面的行为,并且这个状态是响应式的。
- Vuex 状态管理的实现原理?
- 把多个组件需要共享的变量全部储存在一个对象里面,然后将这个对象放在顶层的 VUE 实例中,让其他组件可以使用,并且让这个对象里面的属性做到响应式。
- Vuex 的核心概念(五个状态)?
state:数据状态
。用来存储公共管理的数据,通过this.$store.state
调用数据。getter:计算属性
。类似于 VUE 中的 computed,对于 store 中的数据进行加工处理形成新的数据 (过滤数据),getter 的返回值会根据它的依赖缓存起来,只有当它的依赖值发生改变才会重新计算,通过this.$store.getters
调用该属性 (获取过滤后的数据)。mutation:同步方法
。用来存放更改 state 状态的同步方法,不可以写异步方法,理论上是修改 state 状态的唯一途径。通过this.$store.commit
来调用 mutation 中的同步方法。action:存放异步方法
。类似于 mutation,区别是action 不能直接更改 state 里的状态
,需要通过 context.commit 提交 mutation,触发 mutation 里面的逻辑方法去修改 state 里面的状态,action 可以包含任意的异步操作。可以通过this.$store.dispatch
去调用 action 里面的异步方法。moudle:模块化
。将 store 分割成模块,每个模块拥有自己的 state、getter、mutation、action、甚至是嵌套子模块。
- Vuex 的优点?
- 集中管理共享的数据,
易于开发和后期维护
。 - 多层嵌套的组件、兄弟组件间的状态会更好管理维护,
高效的实现组件间的数据共享,提高开发效率
。 - Vuex 的状态管理是响应式的,如果 state 里面的状态发生变化,相应的组件也会渲染更新,能够
实时保持数据与页面的同步
。 - JS 原生的数据对象写法,比起 localStorage 不需要做转换,
使用方便
。 减少 http 请求,提高浏览器性能
。
- 集中管理共享的数据,
- ==Vuex 的缺点? ==
刷新浏览器,Vuex 中的 state 会重新变为初始状态
。(解决方案:插件 vuex-persistedstate)
- 什么时候适合用 Vuex?
- 如果不打算开发大型单页应用,使用 Vuex可能是繁琐冗余的。如果项目比较小比较简单,使用 prop 和 $emit 完成父子间的传参,或者使用 $eventBus 实现复杂的组件间的通信也是可以满足需求的。
- Vuex 具体应用在哪,取决于项目的规模以及具体的业务场景。可能是为了
解决多层嵌套组件之间的通信问题
,或是为了更好地管理应用中错综复杂的状态关系
,而不能为了用 Vuex 而在项目中使用 Vuex。
- vuex 的应用场景?
登录状态(token)
购物车
权限码
复杂的组件通信
(兄弟组件的通信,多层嵌套的组件的通信等)Vuex 可配合 sessionStorage 做用户基本信息的持久化存储
- Vuex 刷新页面后数据丢失如何解决?
- 在刷新前
把 state 的数据通过 localStorage 或者 sessionStorage 在本地存储起来,刷新之后再把本地存储中的数据放回 store 的根状态里
。 - 使用插件
vuex-persistedstate
。
- 在刷新前
- Vuex的基本使用方法?
- 安装Vuex:在项目根目录下执行npm install vuex --save来安装Vuex。
- 创建Store:在src目录下新建一个文件夹store,并在其中创建一个index.js文件。在该文件中引入Vue和Vuex,并创建一个Vuex.Store实例。
- 引入Store:在main.js文件中引入创建的Store,并将其注入到Vue实例中。
- 使用Store:在需要使用Vuex的页面中,通过this.$store来访问和修改状态。
31. Vue Router 路由?⭐⭐⭐⭐
- Vue Router 是什么?
- vue-router 路由是 VUE 的一个插件库,用于构建单页面应用。路由模块的本质就是建立起 url 路径和页面之间的映射关系,在 vue-router 的单页面应用中,页面的路径的改变就是组件的切换。路由主要负责将浏览器请求映射到特定的组件代码中,也就是通过解析 URL 来实现页面切换和跳转。
- 什么是路由懒加载?
- 路由懒加载也叫延迟加载,即在需要的时候加载,随用随载。不用一开始就去加载所有页面,只加载当前页面。当切换页面的时候,再加载对应的路由。避免不必要的资源加载,提高应用程序的加载速度。
- 为什么要路由懒加载?
- 如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,影响页面加载。例如进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了 loading 也是不利于用户体验。使用懒加载,在路由被访问的时候才加载对应组件,可以减少页面的加载用时,提高页面的加载速度,提升用户体验。
- 如何实现路由懒加载?
-
VUE 异步组件
component: resolve => require(['需要加载的组件地址'], resolve)
{path: '/home',name: '/Home',component:resolve=>require(['@/pages/Home'],resolve),meta: {show: true}
}
-
ES6动态加载模块 import()
(常用方法)
const 组件名 = () => import('组件路径')
- webpackChunkName值相同的会打包成一个js文件
{path: '/home',name: 'Home',component:()=>import(/* webpackChunName:"chunkName" */, '@/pages/Home'),meta: {show: true}
}
-
webpack 提供的 require,ensure() 方法
const 组件名 = r => require.ensure([],()=>r(require('组件地址')),'list')
{path: '/home',name: 'Home',component:r=>require.ensure([],()=>r(require('@/pages/Home')),'chunkName'),meta: {show: true}
}
- 三种方法实现路由懒加载的区别?
- 使用
VUE 的异步组件
技术实现懒加载,这种情况会导致一个组件会生成一个 js 文件
。 - 推荐使用
ES6的 import() 方法
,指定相同的 webpackChunkName,会合并打包成一个 js 文件
。 - 使用
webpack 的 .ensure 技术
,多个路由指定相同的 chunkName,会合并打包成一个js 文件
。 - require 在 node 中 ADM 的引入方式,import 是 ES6 中模块化的引入方式。require 是赋值过程并且是运行时才执行,import 是解构过程并且是编译时执行。require 使用的是函数调用的方式,import 使用的是声明式的方式。require 是同步加载模块,import 是异步加载模块。require 的性能相对于 import 稍低,因为 require 是在运行时才引入模块并且还赋值给某个变量。
- 使用
- 什么是动态路由?
路由是动态的
不是写死的。我们可以根据不同的需求加载不同的路由,渲染不同的页面组件
。动态路由的使用一般结合角色权限控制一起使用。例如后台管理系统可以根据动态路由实现不同角色权限显示不同菜单。
- 如何实现动态路由?
- 动态路由的具体实现方法?
- 方法一
- 首先
构建基础的静态路由
,如登录页,修改密码页,404页。 - 用户登录后,服务端返回一个 token,
将 token 存储在本地 cookie 中,记住用户的登录状态
,跳到首页。 配置路由守卫 beforeEach
,在页面跳转前进行拦截。判断是否已获得 token,没有 token 就去登录;获得 token 之后,再判断是否有用户信息,当有用户权限信息的时候,说明所有可访问路由已生成;没访问权限的页面会自动进入404页面。没有用户信息就发起请求获取用户基本信息,将用户基本信息和用户权限信息存到 Vuex 里。- 将用户权限和路由表每个页面的需要的权限作比较,生成最终该用户可访问的路由表。
- 调用 addRoutes 动态添加用户可访问路由表。
- 更新路由。
- 首先
- 方法二
- 首先
构建基础的静态路由
,如登录页,修改密码,404页。 - 配置路由守卫 beforeEach,在页面加载前进行拦截,判断 Vuex 中有没有缓存路由,有的话直接渲染。没有的话发起请求获取路由信息。后端根据该用户的角色权限返回对应的路由信息,前端获取后解析并保存到 Vuex 中。
- 调用 addDynamicRoutes 方法,生成动态路由,通过 addRoutes 方法将动态路由注入到 Router 实例中。
- 更新路由。
- 首先
- 方法一
基本思路:使用
全局前置守卫
,在页面加载前将当前用户所用到的路由列表通过addRoutes 方法
注入到 Router 实例中。
- 如何实现带参数的动态路由匹配?
- 可以在路径中使用一个动态字段来实现,我们称之为
路径参数
。通过在路径后添加一个冒号
以及一个动态字段
实现动态路由。
- 可以在路径中使用一个动态字段来实现,我们称之为
{path: '/home/users/:pid',name: 'users',component:()=>import('../views/users')
}
- $route 和 $router 的区别?
- $router是一个全局的对象,用来
操作路由
的。它包含了所有的路由,包括路由的跳转方法
,钩子函数
等。可以进行跳转到指定的路由、前进、后退等路由操作。 - $route是一个局部的对象,用来
获取当前路由信息
的。可以接收路由参数
和读取路由路径
等,包括当前 URL 路径、查询参数、路径参数等信息。$route 对象是只读的
。
- $router是一个全局的对象,用来
- $router的用法?
this.$router.push("/login");
:常规方法,跳转到指定路由,不带参数。this.$router.push({ path:"/login" });
:使用对象的形式,跳转到指定路由,不带参数。this.$router.push({ path:"/login",query:{username:"jack"} });
:使用对象的形式,跳转到指定路由, 参数为地址栏上的参数。this.$router.push({ name:'user' , params: {id:123} });
:使用对象的形式,跳转到指定路由,参数为params,不会显示在地址栏。
- $route的用法?
this.$route.path
:获取当前路由的路径字符串。如:/home/ews。this.$route.params
:获取动态路径参数。包含路由中的定义的动态片段和全匹配片段的键值对,是不会拼接到url后面的部分。this.$route.query
:获取路由中查询参数的键值对。会拼接到路由url后面。如:对于 /home/01?favorite=yes,this.$route.query.favorite的值是 ‘yes‘ 。this.$route.router
:获取路由规则所属的路由器。this.$route.name
:获取当前路由的名字,如果没有使用具体路径,则名字为空。
- 如何实现 Vue 路由跳转?
router-link
:声明式导航,在页面中调用,通过:to跳转,通常在不需要逻辑跳转时使用。this.$router.push
:编程式导航,在函数里面调用,通常在需要逻辑跳转时使用。this.$router.replace
:类似于 push,但 history 栈中添加记录。this.$router.resolve
:打开新窗口跳转。this.$router.go(n)
:相当于当前页面向前或向后跳转多少个页面。
- 路由跳转方式和参数传递方式?
- 路由跳转方式:path 和 name。
- 参数传递方式: params 和 query。
- path 跳转和 name 跳转的区别?
- path 和 name 都可以实现页面的切换。都需要在路由中配置。编程式和声明式都可以使用。
- path 会忽略 params参数,所以
path 不能使用 params 传参,只能使用 query 传参
。name 可以使用 params 和 query 传参
。 name 跳转过去的页面刷新之后,参数会失效
,而 path 的方式不会。
- params 传参和 query 传参的区别?
params 传参的参数不会显示在的 URL 中
。query 传参的参数会显示在 URL 中
。- params 接收参数:
this.$route.params.参数
。query 接收参数:this.$route.query.参数
。 params 刷新后参数消失,需要设置动态路由,在路径后加冒号(:)绑定动态参数
。query 刷新后参数还在
。- params 只能通过 name 引用路由,name 要在路由内配置。query 可以通过 path 和 name 引用。
- 都不能传对象和数组引用类型数据,
只能传字符串类型数据
。
- vue 路由钩子函数 (导航守卫)?
- 全局前置守卫(beforeEach):在每一个路由进去之前进行拦截。接收两个参数,to 即将要进入的目标,from 当前导航正要离开的路由。
- 全局解析守卫(beforResolve):在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
- 全局后置守卫(afterEach):在每一个路由进去之后进行拦截。
- 路由独享守卫:写在路由,控制具体的某一个路由。
- 组件守卫:在组件内直接定义路由导航守卫。
- HashRouter 和 HistoryRouter 的实现原理?
- hash:hash 模式是通过
监听 hashChange 事件
来实现前端路由。hash 通过window.onhashchange 方法获取新 URL 中 hash 值,如果 hash 值有变化就会自动调用 hashChange 的监听事件,在这个回调事件中,相应的执行无刷新页面跳转。 - history:history 是
利用浏览历史记录栈的 API
来实现前端路由。history 通过 pushState 和 replaceState 方法更新浏览器 URL 地址,而不重新发起请求 (替换URL页面跳转且不刷新页面),并且将地址记录到历史记录中。通过 onpopstate 监听浏览器的前进和后退。注意:URL发生了变化,但是不会立即向后端服务器发送请求,但是如果点击刷新,就会重新向后端服务器发送请求。
- hash:hash 模式是通过
- HashRouter 和 HistoryRouter 的区别?
- hash 模式的 url 中有 # 号,不太优雅。history 模式的 url 中没有 # 号,更美观。
- hash 模式不需要在服务器层面上处理,但不利于 SEO。history 模式依赖 H5 API 和后台配置,没有后台配置的话,页面刷新时会出现 404,需要和后端人员配合配置一下 url 重定向,如果 URL 匹配不到任何静态资源,则重定向到首页路由。由于依赖 H5 所以 history 模式有浏览器兼容问题。
- hash 模式 url 发生变化时,变化的是 url 的#号后面的 hash 值,hash 虽然包含在 url 中,但是没有被包含在 http 请求中,对后端没影响,所以不会向后端发送请求,所以 hash 改变也不会重新加载页面。history 模式的 URL 改变会向服务器发出请求,同时也会在浏览器历史记录中添加一条记录,可能会重新加载页面。
- 相同的 url,history 会触发添加到浏览器历史记录栈中,hash 不会触发。
- 因为 vue 是一个单页面应用,所以默认只有 hash 模式,没有 history 模式,需要手动设置。使用 history 模式,需要适当的服务器配置,将页面请求全部重定向到 index.htm 。
32. VUE 组件?⭐⭐⭐⭐
- 什么是 VUE 组件?
- 组件是 Vue 强大的功能之一,Vue 鼓励使用组件化开发,将页面拆分成多个独立的组件,每个组件负责特定的功能,我们可以在每个组件内封装自定义内容与逻辑,组件可以复用、嵌套和组合,提高代码的可维护性和复用性。
- 组件开发的好处?
- 组件可以
提升项目的开发效率
,避免重复的工作量。 提高复用性
。- 能够把页面抽象成多个相对独立的模块,
使代码逻辑清晰,方便项目的后期维护
。 便于协同开发
,不用把大量精力放在内部的实现上,实现模块化开发。
- 组件可以
- 什么情况下需要封装组件?
解决重复工作量的问题
:在项目中多次出现相同特征的模块,该组件未来还有可能出现在其他的地方,且开发这样一个功能模块成本比较高,或者是该组件后续会发生部分特征的改变,且会影响类似特征的模块。使代码逻辑更加清晰
:如果每一个模块都写到同一个页面,这样模块的交互功能和处理逻辑很多,造成页面代码量很大,各个模块中也会有相类似的方法,后续代码的可读性不高,功能的维护复杂度提高。不用把大量的精力放在内部实现上
:提高团队的协作问题,在一个团队里,每个人擅长部分都不一样的,针对这样的情况,我们可以将其单独进行封装,让使用组件者不用把大量精力放在内部的实现上。
- 组件封装流程?怎么封装组件的?
- 使用 Vue.extend 方法创建一个组件,然后使用 Vue.component 方法注册组件。
- 但是我们一般用脚手架开发项目,每个 .vue 单文件就是一个组件。
在另一组件 import 导入,并在 components 中注册
。 - 首先
建立好自定义模板
,准备好组件的数据的输入
,子组件定好 props 里面的数据和类型
,接收父组件传过来的数据
,然后通过 $emit( )暴露输出子组件的数据返回给父组件
。 - 传统流程
- 创建 .vue 文件,对组件进行封装
- 通过 import 的方式将组件导入 import Xxx from ‘…’
- 对组件进行注册 components:{ Xxx }
- 使用组件:<xxx/>
组件封装过程
- 建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。
- 准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
- 准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
- 封装完毕了,直接调用即可。
- vue 的组件通信有哪些方法?
- 父子传参
父传子:props
子传父:$emit
- 边界访问 (父子通信):
- 访问父组件:$parent
- 访问子组件:$children
- 访问根实例:$root
- 访问子组件实例或子元素:$refs
- 兄弟通信
- 通过父组件进行兄弟组件之间通讯(不建议)
- 事件总线:$eventBus ($emit / $on / $off)
- 状态管理模式:vuex
- 跨级通信
- 依赖注入:provide / inject (父传子)
- $attrs / $listeners (inheritAttrs)
- 状态管理模式:vuex
- 父子传参
- 事件总线 $eventBus如何使用?
-
- 创建事件总线
// main.js
import Vue from 'vue'
// 创建事件总线,就相当于创建了一个新的 vue 实例
const bus = new Vue()
// 把 bus 挂载到了 Vue 的原型上, 保证所有的组件都能通过 this.$bus 访问到事件总线
Vue.prototype.$bus = bus
-
- 页面使用:发布事件-传递值
this.$bus.$emit('send', 'hello') //(事件名,参数)
-
- 订阅事件,接收组件值
// 在 created 中订阅,('事件名', 事件回调函数)
this.$bus.$on('send', msg => {console.log(msg)
})
在子组件销毁后,需要
取消订阅事件
。否则会导致多余调用事件监听,造成资源浪费,导致数据错乱。通常在 beforeDestroy 子组件销毁前调用this.$bus.$off
移除。
- 为什么组件的 data 必须是一个函数?
- 可以反复的调用
- 可以反复创建新对象
- 避免组件间的数据冲突
- keep-alive 是什么?
- keep-alive 是 VUE 内置的一个组件,是一个抽象组件。它自身不会渲染 DOM 元素,也不会出现在父组件链中。使用 keep-alive 包裹动态组件时,会缓存不活动的组件实例,保留组件状态,避免重新渲染,缓存组件能加快速度,降低消耗资源。(组件进行切换的时候,默认会进行销毁,消耗资源)
- keep-alive 如何实现?
- 当组件在 <keep-alive> 内被切换的时候,activated (激活) 和 deactivated (取消激活) 这两个生命周期钩子函数将会被对应执行。
- 页面第一次进入的时候,钩子触发的顺序是 created->mounted->activated。页面退出的时候会触发 deactivated。当再次前进或者后退的时候只触发 activated。当 keep-alive 组件激活时,触发 activated。keep-alive 组件停用时调用 deactivated。
33. VUE 的优缺点?⭐
VUE 优点
轻量级框架
:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb。双向数据绑定
:VUE 框架采用的是 MVVM模式,实现数据双向绑定、将数据和视图进行实时的同步更新。组件化
:保留了 React 的优点,实现了组件的封装和复用,在构建单页面应用方面有着独特的优势。虚拟 DOM
:VUE 采用了虚拟 DOM 技术,通过在内存中构建虚拟 DOM,并与真实 DOM 进行比较,只更新需要更新的部分,减少真实 DOM 操作,提高了渲染性能。渐进式
:VUE 是一款渐进式 JavaScript 框架,可以根据业务需求逐步选择需要的 VUE 功能模块。简单易学
:国人开发,中文文档,不存在语言障碍 ,相对易于理解和学习。运行速度更快
:相比 React,同样是操作虚拟 DOM,就性能而言,VUE 存在很大的优势。
VUE 缺点
存在兼容问题
:vue2 不支持 IE8 以下,vue3 不支持 IE 浏览器。- 初次加载时耗时多。
- 单页面应用,不利于 SEO(搜索引擎优化) 优化。
- VUE 不缺入门教程,可是缺乏高阶教程与文档。
- 社区可能没有 Angular 和 React 那么丰富。
34. React 的优缺点?⭐
React 优点
速度快
:在渲染过程中,React通过在虚拟DOM中的微操作来实现对实际DOM的局部更新。跨浏览器兼容
单向数据流
:Flux 是一个用于在 JavaScript 应用中创建单向数据层的架构,它随着 React 视图库的开发而被 Facebook 概念化。模块化
:为你程序编写独立的模块化 UI 组件,这样当某个或某些组件出现问题是,可以方便地进行隔离。兼容性好
:比如使用 RequireJS 来加载和打包,而 Browserify 和 Webpack 适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。- 数据驱动视图,持服务器端渲染。
React 缺点
- 不适合单独做一个完整的框架,做大型项目需要和其他框架组合使用(ReactRouter 和 Flux)
- 库非常庞大,新手很难理解需要花费一定的时间,使用内联模板和 JSX,使编码变得复杂。
35. Anguar 的优缺点?⭐
Anguar 优点
模板功能强大丰富
,自带了极其丰富的 angular 指令。- 是一个比较完善的前端框架,采用了 MVC 模式,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能。
- 自定义指令,自定义指令后可以在项目中多次使用。
- angularjs 模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助。
- angularjs 由谷歌开发,这也意味着他有一个坚实的基础和社区支持。
- 调试与维护:有专门用于浏览器的插件 batarang。
Anguar 缺点
- Angular 框架过重,不像 VUE 和 React 是非常轻量级的框架,过大的框架导致性能方面消耗严重。适用在大型的浏览器端,大型网站项目。
- Angular 入门很容易但深入后概念很多,学习中较难理解,学习成本相对来说较高。
- 文档例子非常少,官方的文档基本只写了API,一个例子都没有。很多时候具体怎么用都是google 来的,或直接问 misko,angular 的作者。
- 指令的应用的最佳实践教程少,Angular 其实很灵活,如果不看一些作者的使用原则,很容易写出四不像的代码。
36. 说一下 VUE 的服务端渲染 SSR?⭐
- 什么是 服务端渲染 SSR?
- SSR即
服务端渲染
。顾名思义,页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的 html 就可以了。 - 传统使用的是 CSR,客户端在请求时,服务端不做任何处理,直接将前端资源打包后生成的 html 返回给客户端,客户端去加载执行 js 代码才能渲染生成页面内容,同时完成事件绑定,然后客户端再去通过 ajax 请求后端的 API 获取数据更新视图。
- SSR即
- 服务端渲染的优势?
首屏渲染快
,减少网络传输,响应快,用户体验好。对浏览器搜索引擎友好
,搜索引擎爬虫可以看到完整的程序源码,有利于SEO。- 服务端渲染
没有浏览器兼容性问题
,运算过程都在服务端完成。
- 服务端渲染的缺点?
网络传输数据量大
,占用部分服务器运算资源,不容易维护,前端修改部分 html/css 后端也要改。增加开发的复杂程度
,涉及构建和部署的更多要求。服务器渲染应用程序,需要处于 Node.js server 运行环境。更多的服务器端负载
。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,需要准备相应的服务器负载,并明智地采用缓存策略。
37. VUE 的插槽(slot)有哪几类?
- 默认插槽,具名插槽,作用域插槽。
- VUE 中的插槽,指的是子组件中提供给父组件使用的一个占位符。用标签表示,父组件可以在这个占位符中填充任何模板代码,比如HTML、组件等,填充的内容会替换掉子组件的标签(替换占位符)。
38. VUE 自定义指令?
- 在VUE中,声明一个自定义指令可以帮助你在模板中添加更丰富的功能,比如处理元素的交互或修改数据。
- 定义局部自定义指令,使用directives;定义全局自定义指令,使用Vue.directive()。
- 定义局部自定义指令
<template><!-- 2. 使用指令:v-focus --><input v-focus />
</template>
<script>
export default {directives: {focus: {// 1. 定义指令inserted: function (el) {el.focus();//指令内容:自动获取焦点}}}
}
</script>
- 定义全局自定义指令
// 1. main.js中:定义自定义指令
<script>
// 注册一个全局自定义指令 v-focus1
Vue.directive('focus1', {// 当被绑定的元素插入到 DOM 中时,指令生效inserted: function (el) {el.focus()//指令内容:自动获取焦点}
})
</script>// 2. vue文件中:使用自定义指令
<template><!-- 2. 使用指令:v-focus1 --><input v-focus1 />
</template>
如果全局自定义指令和局部自定义指令的内容重复(如都是获取焦点),则优先级:全局>局部。
39. VUE有了数据响应式,为何还需要diff?
- VUE的数据响应式并不完全等同于diff算法。
数据响应式让VUE知道何时更新视图,diff算法让VUE知道如何高效地更新视图
。 - 数据响应式使VUE能够在数据变化时自动更新视图,但它并不知道如何有效地比对和更新所有的数据变化。这就需要使用diff算法。
- diff算法用于比对虚拟DOM树的新旧版本之间的差异,并生成一个最小的更新策略,以应用到实际的DOM上。这样,VUE就可以高效地更新DOM,只改变必要的部分,而不是重新渲染整个视图。
40. 什么是时间分片?
- 时间分片(Time Slicing)是一项技术策略,旨在优化网页性能,特别是在处理长任务时。根据 W3C,超过50ms的任务被视为长任务。当延迟超过100ms,用户会感到轻微延迟,从而影响体验。为避免此情况,可采用时间切片。
- 时间分片可以将CPU的执行时间分割成多个小的时间段(称为时间片),然后轮流分配给多个任务或线程执行的技术。每个时间片结束后,CPU会被剥夺并分配给另一个任务,从而实现多任务并发处理。
- 原理:将CPU的执行时间分割成若干个时间片段,每个线程或任务被分配一个时间片。当时间片结束时,如果任务尚未完成,CPU将被剥夺并分配给另一个任务;如果任务在时间片结束前完成或阻塞,则CPU立即进行切换。这种机制确保了所有任务都能获得公平的执行时间,避免了单个任务长时间独占CPU资源的情况。
41. VUE3 为什么不需要时间分片?
VUE3 引入了Composition API
,旨在提供更好的逻辑组织和复用性。通过Composition API,开发者可以将相关功能按逻辑关系组织在一起,从而提高代码的可维护性和可读性。这种设计使得代码结构更加清晰,尤其是在处理复杂组件时,提升了代码的易读性和易维护性。VUE3 采用了基于Proxy的方式实现响应式
,相比VUE2性能得到了极大的提升。减少了不必要的重新渲染,提高了渲染速度,从而减少了时间分片的需求。VUE3 全面支持TypeScript
,提供了更好的类型检查和代码提示,帮助开发者在开发过程中捕获潜在的错误和问题,进一步提高代码的可维护性。这些特性使得 VUE3 在处理大型应用和复杂界面时表现更加出色,减少了因性能问题而需要进行时间分片的必要性。
42. VUE3为什么要引入Composition API?
- 在VUE2中,主要通过选项式API(Options API)来定义组件,如data, methods, computed, watch, props, lifecycle hooks等。这些选项很直观,对新手友好,但在处理大型组件或多个组件共享逻辑时,代码往往会变得复杂和冗长。这是因为相关的逻辑分散在一个组件的多个选项部分,难以追踪和维护。
- VUE3的组合式API(Composition API)提供了一种更灵活的方式来组织和复用逻辑。通过Composition API,你可以更容易地将组件逻辑按功能组织起来,而不是按选项类型。
- Composition API主要围绕setup函数展开,作为组件内部所有响应式特性、生命周期钩子和其他逻辑的入口。setup函数在组件创建之前执行,不会访问到this。
uni-app
1. uniapp 打包过程(安卓 Android)⭐
- 如果是
使用私有证书,打包前得生成安卓的 .keystore 证书文件
。 - 打开
manifest.json 配置文件进行配置
: 基础配置 (AppID、应用名称、应用描述、应用版本号、vue 版本号)、App 图标配置 (不配置默认是 uni-app 图标)、App 启动界面配置 (不配置启动时是 uni-app 默认图)、APP 模块配置、APP 权限配置等。 - 配置好后,
点击 HbuilderX 的发行,选择原生APP-云打包
,勾选 Android 选项,填写 Android 包名,勾选私有证书 (也可以选择使用 DCloud 公有证书),填写证书别名 alias,证书私钥密码 (自己设置的),上传证书文件 (生成 keystore 文件的地址),取消广告联盟中的所有广告 (个人选择),点击打包。 - 打包成功后 HbuilderX 控制台会自动返回 APP 下载地址,然后可以对该链接在电脑中进行下载,也可以将该链接复制到手机浏览器,在手机中对该软件进行下载。
2. uniapp 打包过程(苹果 ISO)⭐
- ISO 系统打包需要有苹果开发者账号,新建一个 APPid,设置 Bundle ID,申请 IOS 发布证书并保存证书私钥密码,申请 IOS 发布描述文件。
- 打开 hbuilderx 点击发行-原生 APP 云打包,勾选 ISO 选项,填写 Bundle ID 和证书私钥密码,上传描述文件和发布证书,点击打包。
- 等待控制后台返回下载链接,点击链接进行下载 IPA 文件。
- 在 iTunes connect 上创建 APP,上传 IPA 到 APP store,设置 app 信息提交到商店进行审核
3. uniapp 小程序打包过程 ⭐
- 进入微信公众平台注册微信小程序获取 AppID
- 打开 manifest.json 配置文件,点击微信小程序配置,填写 AppID 和相关配置
- 点击 HbuilderX 的发行,选择小程序微信,填写小程序名称和 AppID 点击发布
- HbuilderX 自动调开微信开发者工具,点击右上角上传按钮,填写版本号和备注,点击上传
- 登录微信公众平台,选择版本管理,点击提交审核
4. uniapp 的基本配置?
- page.json:uni-app 全局配置,配置页面的文件路径、窗口样式、原生的导航栏、底部 tabbar
- App.vue:页面入口文件(主组件)
- main.js:项目入口文件
- pages:页面管理部分
- manifest.json:配置文件,配置应用名称,appid,logo,版本等打包信息
- unpackage:打包目录
- static:静态资源目录
- components:uni-app 组件目录 (符合 vue 规范)
- wxcomponents:存放小程序组件目录
- uni.scss:全局样式
- uni_modles:存放插件
5. uniapp 上传文件时用到的 API 是什么?格式是什么?
- API:
uni.uploadFile
。 - 格式:上传的地址 url,上传类型 fileType,图片路径 filePath,文件对应的key name,成功的回调
uni.uploadFile({url: '要上传的地址',fileType:'image',filePath:'图片路径',name:'文件对应的key',success: function(res){console.log(res)}
})
6. uniapp 获取地理位置的API是什么?
uni.getLocation
7. px、rpx、em、rem、%、vh、vw 的区别是什么?⭐
- px:绝对单位,页面按精确像素展示。
- rpx:相当于把屏幕宽度分为750份,1份就是1rpx。
- em:相对单位,相对于它的父节点字体进行计算。
- rem:相对单位,相对根节点html的字体大小来计算。
- %:一般来说就是相对于父元素。
- vh:视窗高度,1vh等于视窗高度的1%。
- vw:视窗宽度,1vw等于视窗宽度的1%。
8. uniapp 如何监听页面滚动?
使用 onPageScroll 监听
9. 如何让图片宽度不变,高度自动变化,保持原图宽高比不变?
给 image 标签添加 mode='widthFix'
10. uni-app 的优缺点?
优点
一套代码可以生成多端
。学习成本低
,语法是 VUE 的,组件是小程序的。- 拓展能力强。
- 使用HBuilderX开发,支持 VUE 语法。
- 突破了系统对H5条用原生能力的限制。
缺点: - 问世时间短,很多地方不完善。
- 社区不大。
- 官方对问题的反馈不及时。
- 在Android平台上比微信小程序和iOS差。
- 文件命名受限。
11. 分别写出 jQuery、VUE、小程序、uni-app 中的本地存储?
- jQuery
- 存:cookie(’key’,’value’)
- 取:cookei(’key’)
- VUE
- 存:
localstorage.setItem('key', 'value')
- 取:
localstorage.getItem('key')
- 存:
- 微信小程序
- 存:wx.setStorage/wx.setStorageSync
- 取:wx.getStorage/wx.getStorageSync
- uni-app
- 存:uni.setStorage({key:“属性名”,data:“值”})
- 取:uni.getStorage({key:“属性名”,success(e){e.data//这就是你想要取的token}})
12. JQuery、VUE、uni-app、小程序的页面传参方式? ⭐
- JQuery传参
- 通过url拼接参数进行传参。
- VUE传参
- 可以通过标签
<router-link>
跳转传参,通过 path+ 路径,query+ 参数。 - 可以通过事件里的
this.$router.push
跳转传参。
- 可以通过标签
- uni-app / 小程序传参
- 通过跳转路径后面拼接参数来进行跳转传参。
13. VUE、微信小程序、uni-app 绑定变量属性区别?
VUE 和 uni-app
动态绑定一个变量的值为元素的某个属性的时候,会在属性前面加上冒号 ":"
。小程序
绑定某个变量的值为元素属性时,会用两个大括号 {{}}
括起来,如果不加括号,为被认为是字符串。
14. 小程序组件传参有哪些方式?
全局数据
、Storage存储
、eventBus
。
HTTP请求
1. 浏览器输入url后都经历了什么?⭐⭐⭐
查看缓存
(浏览器缓存\系统缓存\路由器缓存),如果缓存中有则直接显示页面内容。域名解析
(DNS解析),获取相应的 IP 地址。浏览器向服务器发起 TCP 连接
,建立 TCP 三次握手。浏览器向服务器发起 HTTP 请求
,请求数据包。服务器处理请求
,返回响应数据至浏览器。关闭 TCP 连接
(四次握手)。浏览器解析 HTML 代码并请求资源
(js、css、图片等)。浏览器进行页面布局渲染
。
2. 浏览器地址栏的完整URL都包含哪些内容都各代表什么?
例如:http://www.baidu.com/index.html?name=mo&age=25#dowell
这个URL包括:协议部分
、域名
、端口
、路径(虚拟路径)
、携带的参数
、哈希值
。
协议部分
:http,https。(传输协议是用来完成客户端和服务器端之间数据传输的)域名
:www.baidu.com。baidu.com 为一级域名,www 为服务器。(用于解析对应的IP地址,也可以使用IP地址作为域名使用)端口
: http:默认端口号 80。https:默认端口号 443。ftp:默认端口号 21。(一般都是隐藏)路径
(虚拟路径):index.html 虚拟目录。携带的参数
:?后面拼接携带的参数, 多个参数用 & 连接。(可有可无)哈希值
:#dowell。可做页面中的锚点定位。(可有可无)
3. cookie、sessionStorage、localStorage的区别?⭐⭐⭐
- 共同点
- 都是
存储在浏览器本地的
、相对不安全。
- 都是
- 不同点
- 写入方式
cookie 是由服务端写入的,需要服务器支持
。而localStorage 和 sessionStorage 都是由前端写入的
。
- 生命周期
- 如果没有设置过期时间,
cookie 默认关闭浏览器立即消
失。而localStorage 默认是永久储存,除非手动清除
。sessionStorage 是页面关闭的时候就会自动清除
。
- 如果没有设置过期时间,
- 存储大小
cookie 的存储大小大概4kb,存储条数为 20 条
,超过20条后面的数据会替代前面的数据。localStorage 和 sessionStorage 存储大小大概5M,存储条数不限制
。
- 兼容性
cookie 可以兼容低版本浏览器
。localStroage 和 sessionStorage 是 H5 新特性,无法兼容 ie8 及以下的浏览器
。
- 多Tab
localStorage 可以在多个 Tab(窗口) 打开
,sessionStorage 限制必须在同一个页面
,多 tab(窗口) 的时候不可以共享数据。
- 应用场景
- 前端向后端发起请求时会自动携带 cookie 里面的数据,但是 SessionStorage 和 localStorage 不会,所以他们的应用场景也不同。
cookie 一般用于存储登录验证信息 SessionID 或者 token
。localStorage 常用于存储不易变动的数据
,减轻服务器的压力。sessionStorage 可以用来检测用户是否刷新进入页面
,如音乐播放器恢复播放进度条的功能。
- 前端向后端发起请求时会自动携带 cookie 里面的数据,但是 SessionStorage 和 localStorage 不会,所以他们的应用场景也不同。
- 写入方式
4. 如何实现可过期的 localstorage 数据?
- localStorage 只能用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。所以要实现可过期的 localStorage 缓存的中重点就是:
如何清理过期的缓存
。 目前有两种方法,一种是惰性删除,另一种是定时删除。 惰性删除
:指某个键值过期后,该键值不会被马上删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除
。实现方法是,存储的数据类型是个对象,该对象有两个key,一个是要存储的 value 值,另一个是当前时间
。获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除缓存。定时删除
:每隔一段时间执行一次删除操作
,并通过限制删除操作执行的次数和频率,来减少删除操作对 CPU 的长期占用。另一方面定时删除也有效的减少了因惰性删除带来的对localStorage 空间的浪费
。实现过程,获取所有设置过期时间的 key 判断是否过期,过期就存储到数组中,遍历数组,每隔1S(固定时间)删除5个(固定个数),直到把数组中的 key从 localstorage 中全部删除。
5. 请求行、请求头、请求体、响应行、响应头、响应体?
请求行
- Request URL:请求地址。
- Request Method:请求方法。
- Status Code:状态码。
- Remote Address:请求的远程地址。
- Referrer Policy:策略。
请求头
- Accept:指定客户端接收的内容类型。
- Accept-Charset:浏览器可以接受的字符编码。
- Accept-Encoding:指定浏览器支持的压缩格式。
- Accept-Language:浏览器可接受的语言。
- Cache-Control:指定请求和响应遵循的缓存机制。
- Connection : 表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
- Cookie:客户端暂存的服务端的信息。
- Content-Length:请求的内容长度。
- Content-Type:请求的与实体对应的MIME信息。
- Date:请求发送的日期和时间。
- From:发出请求的用户的Email。
- User-Agent:客户端信息。
- Host:指定请求的服务器的域名和端口号。
- Origin:指定当前请求资源所在页面的协议和域名,用来说明请求从哪里发起的。
- Referer:告诉服务器,是从哪个资源(url)来访问服务器的。
- Authorization:HTTP授权的授权证书。
请求体
- 一般用来存储 post 的参数和参数数据。常见请求体的格式有纯文本、JSON、XML等。
响应行
- 报文协议及版本。
- Status Code:状态码及状态描述。
响应头
- Allow:服务器支持哪些请求方法 (GET、POST等)。
- Cache-Control:告诉所有的缓存机制是否可以缓存及哪种类型。
- Connection:表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
- Content-Encoding:响应资源所使用的压缩编码类型。
- Content-Length:响应体的长度。
- Content-Type:返回内容的MIME类型。
- Date:原始服务器消息发出的时间。
- Expires:响应过期的日期和时间。
- Server:服务器的名称。
- Set-Cookie:设置Http Cookie。
- Last-Modified:请求资源的最后修改时间。
- Access-Control-Allow-Origin:指定哪些网站可以跨域资源共享。
- ETag:请求变量的实体标签的当前值。
- Location:页面重定向redirect的时候,设置Location的属性值(地址)跳转到该地址。
响应体
- 服务器返回给客户端的文本信息。
6. http 和 https 有何区别?
- http:
HTTP 协议运行在 TCP 之上
。所有传输的内容都是明文
,客户端和服务器端都无法验证对方的身份。 - https:
HTTP 运行在 SSL/TLS 之上,SSL/TLS 运行在 TCP 之上
。所有传输的内容都经过加密
,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份。
7. get 和 post 的区别?
get 参数写在 url 上
,即 HTTP 协议头上。post 参数放在 HTTP 的请求体内
。get 以 ? 分割url 和传输数据,参数之间以 & 相连。get 提交的数据最大是2k
,post 理论上没有限制
。其中,get 发送的数据在原则上 url 长度无限制,实际上取决于浏览器,浏览器会限制 url 的长度
。get 在浏览器回退时不产生影响
,post 会再次提交请求
。(get请求中的回退操作实际上浏览器会从之前的缓存中拿结果)get 请求参数会被完整保留在浏览器历史记录里
,而post 中的参数不会被保留
。get 请求只能进行 url 编码
,post 支持多种编码方式
。get 比 post 更不安全
,因为参数直接暴露在 url 上,所以不能用来传递敏感信息。get 产生一个 TCP 数据包
,浏览器会把 http header 和 data 一并发送出去,服务器响应200(返回数据)。post 产生两个 TCP 数据包
,浏览器先发送 header,服务器响应100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。
在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而
在网络环境差的情况下,两次包的 TCP 在验证数据包完整性上,有非常大的优点
。并不是所有浏览器都会在 POST 中发送两次包,Firefox就只发送一次。
8. 常见的HTTP状态码?
1开头(收到请求,正在处理)
- 100:服务器接收到请求的部分,客户端可以继续发送剩余部分。
- 101:客户端请求服务器升级协议,服务器同意切换协议。
2开头(请求成功)
200(成功)
- 201(已创建):服务器创建了新资源。
- 202(已接受)
- 203(非授权信息)
- 204(无内容):服务器成功处理了请求但不需要返回任何内容。
- 205(重置内容)
- 206(部分内容)
3开头(请求被重定向)
- 300(多种选择)
- 301(永久重定向):资源永久移动到新位置。
- 302(临时重定向):资源临时移动到不同位置。
- 303(查看其他位置)
- 304(未修改):资源未修改,可以使用缓存版本。
- 305(使用代理)
- 307(临时重定向)
- 308(永久重定向)
4开头 (请求错误)
- 400(错误请求):服务器无法理解请求的语法。
- 401(未授权):要求进行身份验证。
- 403(禁止):服务器拒绝请求。
404(未找到)
:请求的资源不存在。- 405(方法禁用)
- 406(不接受)
- 407(需要代理授权)
- 408(请求超时)
- 409(冲突)
- 410(已删除)
- 411(需要有效长度)
- 412(未满足前提条件)
- 413(请求实体过大)
- 414(请求的 URI 过长)
- 415(不支持的媒体类型)
- 416(请求范围不符合要求)
- 417(未满足期望值)
5开头(服务器错误)
500(服务器内部错误)
:服务器遇到意外错误。- 501(尚未实施)
- 502(错误网关):作为网关或代理的服务器从上游服务器收到无效响应。
503(服务不可用)
:服务器暂时不可用。- 504(网关超时)
- 505 (HTTP 版本不受支持)
9. Token 能放在 cookie 中吗?
- token 一般是用来判断用户是否登录的。它内部包含的信息有
uid(用户唯一的身份标识)
、time(当前时间戳)
、sign(签名)
。token 可以存放在 cookie 中
,token 是否过期,应该由后端来判断,不该前端来判断,所以token 存储在 cookie 中只要不设置 cookie 的过期时间就可以了
。如果 token 失效,就让后端在接口中返回固定的状态表示 token 失效,需要重新登录,再重新登录的时候,重新设置 cookie 中的 token 就行。
10. Token 认证流程?
- 客户端通过用户名和密码请求登录。
- 服务端收到请求,去验证用户名与密码。
- 验证成功后,服务端签发一个 token ,并把它发送给客户端。
- 客户端接收 token 后会把它存储起来,比如放在 cookie 里或者 localStorage 里。
- 客户端每次发送请求时都需要带着服务端签发的 token (把 token 放到 HTTP 的 Header 里)
- 服务端收到请求后,需要验证请求里带有的 token ,如验证成功则返回对应的数据。
11. 什么是同源策略?为什么要有同源策略?⭐⭐
- 同源策略:
域名、协议、端口号都相同才能互相访问
。 - 目的:
防止黑客攻击
,xss、csrf 攻击(导致了前后端无法访问 ,也就是跨域问题)
12. XSS攻击是什么?
- XSS是
跨站脚本攻击、向目标网站插入恶意代码、大量用户访问网站时运行恶意脚本获取信息
。 - 原理:
通过向 Web 页面里面插入 script 代码,当用户浏览这个页面时,就会运行被插入的script 代码,达到攻击者的目的
。 - 危害:
泄露用户的登录信息 cookie
,攻击者可以通过 cookie 绕过登录步骤直接进入站点。 - XSS类型:
反射型
和存储型
。反射型就是临时通过 url 访问网站
,网站服务端将恶意代码从 url 中取出,拼接在 HTML 中返回给浏览器,用户就会执行恶意代码。存储型就是将恶意代码以留言的形式保存在服务器数据库,任何访问网站的人都会受到攻击
。
13. CSRF攻击是什么?
CSRF跨站点请求伪造
,攻击者盗用了用户的身份,以用户的身份发送恶意请求,但是对服务器来说这个请求是合理的
,这样就完成了攻击者的目标。- 原理:
用户打开浏览器,访问目标网站A,输入用户名和密码请求登录,用户信息在通过认证后,网站A产生一个 cookie 信息返回给浏览器,这个时候用户以可正常发送请求到网站A。用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B,新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A。浏览器收到这些攻击性代码之后,根据新网站B的请求在用户不知道的情况下,以用户的权限操作了cookie 并向网站A服务器发起了合法的请求
。
14. 什么是跨域?为什么有跨域问题?⭐⭐⭐
- 跨域是
由于浏览器的同源策略,当一个页面中某个请求的地址的 “协议”,“域名“,”端口“ 三者之间任意一个与当前页面的地址不同,在前后端分离的情况下,导致无法直接通讯和访问
。 - 出现跨域,主要是因为
浏览器的同源策略导致
的。浏览器同源策略主要是为了防止黑客攻击
,像 xss、csrf 攻击,但也导致了域名、协议、端口其中一个不同的话前后端无法访问,也就是跨域问题。其实跨域就是不允许跨域请求资源,是浏览器的安全限制
。
15. 跨域的解决方案有哪几种?⭐⭐⭐
CORS 跨域资源共享
,是目前最常用的一种解决办法。通过设置后端允许跨域实现
,类似后端给前端开了个后门一样,允许所有请求通过。通过setHeader 设置 'Access-Control-Allow-Origin' 为通配符
,允许所有请求通过。如:res.setHeader(‘Access-Control-Allow-Origin’, ‘*’);nodejs 中间件代理跨域
。原理是让请求发给代理服务器,静态页面和代理服务器是同源的,然后代理服务器向后端服务器发请求,服务器和服务器之间不存在同源限制
。具体实现:在vue.config.js 文件下配置
,首先开启本地服务器 devServer,更改端口号 port,设置代理 proxy,设置代理目标(baseURL),是否允许跨域,设置反向代理路径,在接口地址前添加反向代理路径。jsonp
,只能发送 get 请求。 原理是script 标签可以跨域请求资源
,将回调函数作为参数通过 ”?“ 拼接在 url 中,后端收到请求,调用该回调函数,并将数据作为参数返回去,前端通过回调函数获取响应数据。注意设置响应头返回文档类型,应该设置成 javascript。
16. 什么是 options 请求?
- options 请求就是
预检请求
。当发起跨域请求时,由于安全原因,触发一定条件时,浏览器会在正式请求之前自动先发起 options 请求,即CORS 预检请求
。服务器若接受该跨域请求,浏览器才继续发起正式请求
。
17. 什么是浏览器内核?有什么用?有哪一些?
- 什么是浏览器内核?
- 是浏览器最核心部分,
Rendering Engine 渲染引擎
,一般叫浏览器内核。负责对网页语法的解释并渲染网页
,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息
。
- 是浏览器最核心部分,
- 浏览器内核有什么用?
- 浏览器内核对于浏览器而言,是基础,是依托,
决定了网页的呈现的内容、格式以及效果
。
- 浏览器内核对于浏览器而言,是基础,是依托,
- 浏览器内核有哪一些?
Trident内核
:即IE
的内核Gecko内核
: 即Firefox
(火狐浏览器)的内核WebKit内核
:即Safari
的内核Blink内核
:即Chrome
和Microsoft Edge
的内核(基于webkit,Google与Opera Software共同开发)presto内核
:即Opera
的内核(新版本已改用Google Chrome的Blink内核)
18. 什么是浏览器兼容问题?
- 因为
不同浏览器的浏览器内核不一样,所以对同一段代码有不同的解析,造成页面显示效果不统一的情况
。在大多数情况下,我们的需求是,无论用户用什么浏览器来查看我们的网站或者登陆我们的系统,都应该是统一的显示效果。
19. 优雅降级和渐进增强(浏览器兼容问题)
优雅降级
:一开始就构建站点的完整功能,然后针对浏览器测试和修复
。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行测试和修改,使其可以在低版本浏览器上正常浏览。渐进增强
:一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互等测试修改,追加功能以达到更好的体验
。- 在传统软件开发中,经常会提到向上兼容和向下兼容的概念。
渐进增强相当于向上兼容
,优雅降级相当于向下兼容
。
20. 说一说前端性能优化手段?⭐⭐⭐
前端性能优化分为两类,一类是
文件加载更快
,另一类是文件渲染更快
。
- 文件加载更快的方法
让传输的数据包更小
:图片压缩和文件压缩。减少网络请求的次数
:雪碧图/精灵图、节流防抖。减少渲染的次数
:缓存(HTTP缓存、本地缓存、VUE 的 keep-alive 缓存等)。
- 文件渲染更快的方法
提前渲染
:ssr 服务器端渲染。避免渲染阻塞
:CSS 放在 HTML 的 head 中,JS 放在 HTML 的 body 底部。避免无用渲染
:懒加载。减少渲染次数
:对 DOM 查询进行缓存、将 DOM 操作合并、使用减少重排的标签。
21. 网站性能优化的好处?怎么优化?
- 网站性能优化的好处
- 用户角度:优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
- 服务商角度:优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。
- 怎么优化网站性能?
- 页面级别的优化:HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等
- 代码级别的优化:JS 中的 DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等。
- 页面级优化有哪些方法?
- JavaScript 压缩和模块打包
- 按需加载资源
- 在使用 DOM 操作库时用上 array-ids
- 缓存
- 启用 HTTP/2
- 应用性能分析
- 使用负载均衡方案
- 为了更快的启动时间考虑一下同构
- 使用索引加速数据库查询
- 使用更快的转译方案
- 避免或最小化 JavaScript 和 CSS 的使用而阻塞渲染
- 用于未来的一个建议:使用 service workers + 流
- 图片编码优化
22. axios的拦截器原理及应用?
- axios的拦截器
- axios的拦截器机制允许我们
在请求发送之前和响应返回之后,对请求和响应进行全局处理
。 - 拦截器分为请求拦截器和响应拦截器。
请求拦截器:在接口请求之前做的处理
。返回拦截器:在接口返回之后做的处理
。
- axios的拦截器机制允许我们
- axios拦截器的应用场景
添加认证信息
:在请求拦截器中添加认证信息
。如设置token或cookie,确保每个请求都包含必要的认证信息。设置请求头
:在请求拦截器中设置全局的请求头
。如Content-Type等。错误处理
:在响应拦截器中处理可能的错误
。例如对401或403状态码进行特殊处理,引导用户重新登录或处理权限问题。数据转换
:在响应拦截器中对返回的数据进行统一处理
。如格式化日期、转换数据结构等。性能优化
:通过拦截器进行一些性能优化操作
。如缓存处理、请求合并等。
23. 创建 ajax 过程?
创建XHR对象
:new XMLHttpRequest()。设置请求参数
:request.open(Method, 服务器接口地址)。发送请求
:request.send(),如果是 get 请求不需要参数,post 请求需要参数request.send(data)。监听请求成功后的状态变化
:根据状态码进行相应的处理。
// 使用原生JavaScript创建 ajax 过程
var xhr = new XMLHttpRequest();
xhr.open("GET", "你的接口地址", true);
xhr.onreadystatechange = function() {if (xhr.readyState == 4 && xhr.status == 200) {var response = xhr.responseText;console.log(response);}
};
xhr.send();
24. Fetch 请求方式?
- Fetch 是一种
HTTP 数据请求的方式
,是XMLHttpRequest 的一种替代方案
。 - Fetch 函数就是原生 js,没有使用 XMLHttpRequest 对象。
- Fetch() 方法返回一个 Promise 解析 Response 来自 Request显 示状态(成功与否)的方法。
//使用Fetch创建 ajax 过程
fetch("你的接口地址").then(response => response.text()).then(data => console.log(data)).catch(error => console.error('Error fetching data: ', error));
25. 浏览器是如何渲染页面的?
- 浏览器拿到 HTML字符串,先
解析HTML
。将 HTML 转换成 DOM 树,再将 CSS 样式转换成 CSSOM树。 样式计算
。遍历得到的DOM树,依次为树的每个节点计算出最终样式。布局
。遍历DOM树,计算出每一个节点的几何信息,用layout树来保存这些几何信息。注,DOM树和layout树并不一一对应
,如display:none;等样式会导致二者不同。分层
。用一套复杂的逻辑进行分层,优点是将来某一层内容变化后只用改变当前层,从而提高效率。绘制
。主线程会为每一层生成指令集,用于描述这一层如何画出来。分块
。合成线程会把每一层进行分块处理,分成更小的区域,分块过程会启用多个线程来处理。光栅化
。由GPU进程处理,优先处理靠近视图窗口的块,处理后的结果就是一个个位图。(此过程会⽤到 GPU 加速)。画
。 合成线程拿到每个块后会生成一个个 quad(指引)信息。指引会标记出每个位图应该画到屏幕的某个位置,还会考虑缩放,旋转,变形等。合成线程会把quad交给GPU进程,再由GPU进程交给GPU硬件处理,最后画到了屏幕上。
26. 有什么方法可以保持前后端实时通信?
- 轮询
- 客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。
- 优点:实现简单,无需做过多的更改。
- 缺点:①连接数会很多,一个接受,一个发送。②每次发送请求都会有 http 的 Header,很耗流量,也会消耗CPU 的利用率。③轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担。
- 长轮询
- 对轮询的改进版,客户端发送 HTTP 给服务器之后,如果没有新消息,就一直等待。有新消息,才会返回给客户端。
- 优点:相比于轮询,在某种程度上减小了网络带宽和CPU利用率等问题。
- iframe流方式
- 在页面中插入一个隐藏的 iframe,利用其 src 属性在服务器和客户端之间创建一条长连接,服务器向 iframe 传输数据,来实时更新页面。
- 优点:消息能够实时到达,浏览器兼容好。
- 缺点:服务器维护一个长连接会增加开销,IE、chrome、Firefox 会显示加载没有完成,图标会不停旋转。
- WebSocket
- 类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。
- 优点:在海量并发和客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
- 缺点:浏览器支持程度不一致,不支持断开重连。
- SSE
- 建立在浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。SSE 是单向通道,只能服务器向浏览器发送,因为 streaming 本质上就是下载。
- 优点:SSE 使用 HTTP 协议,现有的服务器软件都支持。SSE 属于轻量级,使用简单,SSE 默认支持断线重连。
轮询
适用于小型应用,实时性不高。
长轮询
适用于一些早期的对及时性有一些要求的应用像web IM 聊天。
iframe
适用于客服通信等。
WebSocket
适用于微信、网络互动游戏等。
SSE
适用于金融股票数据、看板等。
Git
1. Git 是什么?⭐
- Git是一个免费的、开源的
分布式版本控制系统
。git 支持分布式部署,可以有效、高速的处理从很小到非常大的项目版本管理。 - 分布式相比于集中式的最大区别,在于
开发者可以提交到本地,每个开发者通过克隆,在本地机器上拷贝一个完整的 Git 仓库
。 - 我们可以在本地建一个版本库,每当我们需要修改时,就可以把之前的版本提交并标明此版的特点。这样文件夹里就只有一个编程文档了。当你需要哪个版本时,只要在版本库中恢复一下就可以了。
2. git 常用的命令?⭐
git clone Git地址
:克隆git add .
:提交到暂存区git commit -m "描述内容"
:提交到本地仓库git push
:推送到远程仓库git pull
:拉取远程仓库git merge 要合并的分支名
:合并分支(如:把dev合并到master。①把分支切换到master ②运行git merge dev)git branch 分支名
:创建分支git branch -a
:查看所有分支git checkout 分支名
:切换分支git checkout -b 分支名
:创建分支的同时切换到新分支git branch -d 分支名
:删除分支(用于删除已经合并入主分支的分支)git branch -D 分支名
:删除分支(强制删除分支)git status
:检查文件所处的状态git reset --hard 哈希值(前6位)
: 版本回退git log
:打印日志git remote -v
:查看当前路径rm -r 文件名
:删除文件夹
3. Git 和 SVN 的区别?⭐⭐
Git是分布式版本控制工具,SVN是集中式版本控制工具
。Git没有一个全局的版本号,而SVN有
。Git和SVN的分支不同
。Git把内容按元数据方式存储,而SVN是按文件方式存储
。Git内容的完整性要优于SVN
。Git无需联网就可使用
(无需下载服务端),而SVN必须要联网
(须下载服务端)。因为Git的版本区就在自己电脑上,而 SVN 在远程服务器上。
4. Git项目如何配置?如何上传至GitHub?
- 注册登录 GitHub
- 创建 GitHub仓库
- 安装 Git 客户端
- 绑定用户信息
- 设置 ssh key
- 创建本地项目以及仓库
- 关联 GitHub仓库
- 推送项目到 GitHub仓库
5. 你们公司Git分支是怎么管理的?⭐⭐
- 实际公司项目中,每个项目有自己的Git仓库。主要的分支有三类:开发分支(develop)、测试分支(release)、正式分支(master)。
- 项目开始,开发阶段在develop分支基础上创建自己的feature功能分支,在feature功能分支上进行各自功能开发,开发联调自测完成后,将各自的feature功能分支合并到develop分支由测试人员进行系统测试。
- 预上线阶段,使用release分支。将develop分支合并到release分支,将release分支发布到客户环境,由测试人员和客户进行真实环境真实数据测试。
- 正式上线阶段,使用master分支。将release分支合并到master分支,将master分支发布到客户环境,客户开始正式使用系统进行操作。
6. Git 工作流⭐
- master:主分支(上线发布的稳定版)
- dev(develop):开发分支
- feature:功能分支(从 develop 拉取出来做新功能的分支)
- fix:普通补丁分支(从 develop 拉取出来做修复bug的分支)
- hotfix:维护分支(从 master 拉取出来的用于做紧急修复的分支,修复完后应马上合并回master 和 develop 分支)
- release:预发布分支(从 develop 拉取出来的用于做测试的分支,检测完后合并到 master 分支发布,release 上做的 bug 修改要合并回 develop 分支)
7. Git版本冲突是什么?⭐
- Git版本冲突,即
两个开发者对同一个文件同一个位置做出了不同的内容修改
,然后在进行分支合并
或从远程仓库拉取代码到本地库
时,就会产出冲突报错。因为两个不同的版本,导致 Git 不知道要接受那个。
8. 如何解决Git版本冲突?⭐
寻找冲突
。首先要找到出现冲突的位置,通过git merge 分支名
,如果有冲突的话会提示那些文件有冲突。修改冲突
。找到冲突文件对比差异,然后手动修改为正确的。如果使用命令的话,使用 cat 冲突文件 查看不同分支代码的差异,然后 vim 进入冲突文件进行删除修改正确代码。提交修改后的冲突文件
。分别执行命令 git add 添加到暂存区、git commit -m 提交到本地库、最后 git push 推送到远程库就可以了。
微服务
微服务讲解
1. 微服务是什么?
- 微服务是一种架构风格,即
一个应用应该是一组小型服务
,每个服务器只负责一种服务,服务之间可以通过 HTTP 的方式进行互通。每一个功能元素最终都是一个可独立替换和独立升级的软件单元。
2. 微服务的特征?
根据业务模块划分服务种类
。每个服务可以独立部署并且互相隔离
。通过轻量的 API 调用服务
。- 服务需要保证良好的
高可用性
。
3. 什么是单体应用?
- 单体应用与微服务相对,即现在常用的开发风格,一个应用中包含所有服务。
- 优点
①. 调试方便
②. 运维简单 - 缺点
①. 软件变更受到了很大的限制。系统一个很小的变更,也需要将整个单块应用系统进行重新构建和部署。
②. 当对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。
4. 微服务架构需要的功能或使用场景?
- 我们把整个系统
根据业务拆分成几个子系统
。 - 每个
子系统可以部署多个应用
,多个应用之间使用负载均衡
。 - 需要一个
服务注册中心
。所有的服务都在注册中心注册,负载均衡也是通过在注册中心注册的服务来使用一定策略来实现。 - 所有的客户端都通过同一个
网关地址
访问后台的服务,通过路由配置,网关来判断一个 URL 请求由哪个服务处理。请求转发到服务上的时候也使用负载均衡。 服务之间需要可以相互访问
。例如有一个用户模块,其他服务在处理一些业务的时候,要获取用户服务的用户数据。- 需要一个
断路器
,及时处理服务调用时的超时和错误,防止由于其中一个服务的问题而导致整体系统的瘫痪。 - 需要一个
监控功能
,监控每个服务调用花费的时间等。
手撕代码
引用大佬博客:前端面试常见手写代码
(本文是自己边看边整理的,基本引用于大佬博客:原文链接)
项目中遇到过哪些性能优化的问题?
1. 表格数据量大导致卡顿问题。
- 问题
- 使用<el-table>表格时,现场数据量大,导致卡顿,用户体验差。
- 解决方法
- 虚拟滚动。
-
使用<u-table>
。
- <u-table>是umy-ui库中的一个组件,主要用于解决大数据量表格的渲染问题。通过虚拟滚动(use-virtual)和限制高度(height)来实现高效渲染,从而避免在渲染大量数据时出现卡顿。
- 使用方法
- 通过npm安装umy-ui库。
- 引入u-table组件,设置use-virtual和height属性,否则虚拟滚动无效。
-
使用vxe-table
。
- vxe-table也可以通过虚拟滚动方式解决数据量大页面卡顿的问题。
- 使用方法
- 通过npm安装vxe-table。
- 使用<vxe-table>组件。
2. 表格树(有展开功能)数据量大导致渲染慢问题
详情在:表格树(有展开功能)数据量大导致渲染慢问题
3. 系统上线后,首屏加载慢问题。
详情在:首屏加载慢问题
面试过程
- 自我介绍
- 你是怎么样优化代码的
- 你是如何优化项目的
- 你是如何进行性能优化的
- 开发中都使用了哪些工具
- 开发中遇到过什么bug
- 你对加班的看法
- 为什么离开上一家公司
- 你是如何学习的
- 对未来的规划
- 为什么选择这份工作
- 说说你的缺点
- 你对我们公司有什么了解