2024前端面试题总汇(持续更新中...)

目录

一、Html5、Css3 篇

1、HTML、XHTML、XML 有什么区别?⭐

2、 XML和JSON的区别?

3、是否了解W3C的规范?⭐

4、什么是语义化标签?⭐⭐

5、常用的块级元素和行内元素有哪一些? ⭐

6、行内元素和块级元素的区别?⭐ 

7、css盒子模型有几种类型?它们区别是什么 ⭐ 

8、标签上title与alt属性有什么区别?

9、 H5新特性有哪些?⭐⭐

10、css3的新特性有哪些?⭐⭐

11、css的引用有哪些,link和@import的区别?⭐

12、href和src的区别?⭐

13、CSS常用尺寸单位有哪些?应用场景?⭐⭐

14、移动端适配方案有哪些?⭐

15、什么是浮动?⭐

16、清除浮动有哪些方法? ⭐

17、css选择器有哪些?⭐

18、CSS 样式覆盖规则?⭐

19、CSS 样式的优先级?⭐⭐

20、display: none 和 visibily: hidden 区别? ⭐⭐

21、相对定位,绝对定位,固定定位的区别?⭐⭐⭐

22、说几个未知宽高元素水平垂直居中方法? ⭐⭐⭐

二、JS 篇

1、JS数据类型有哪些?区别?⭐⭐⭐

2、JS中检测数据类型的有哪些?⭐⭐

3、JS中的栈和堆是什么?优缺点?⭐⭐⭐

4、深克隆和浅克隆?⭐⭐⭐

5、JS垃圾回收机制?⭐⭐

6、JS哪些操作会造成内存泄露?⭐⭐

7、闭包?⭐⭐⭐

8、什么是原型链?⭐⭐⭐

9、JS继承的方法有哪些?优缺点?⭐⭐

10、new操作符具体都干了什么?⭐⭐

11、JS的几种具体异常类型(报错)

12、什么是事件冒泡?什么是事件委托?

13、事件对象? 

14、undefined 和 null 区别?⭐

15、说一说伪数组和数组的区别?

16、对于数组去重都有哪些方法?⭐⭐ 

17、 数组的基本操作方法?⭐⭐ 

18、说一下this指向?⭐⭐ 

19、 js中call、apply、bind有什么区别?⭐⭐

20、箭头函数和普通函数有什么区别?⭐⭐ 

21、JQ对象和DOM元素之间如何转换? 

22、JS模块化有哪些?

23、如何操作DOM元素?⭐

24、防抖与节流的区别,并分别用代码表示 ⭐⭐

24、数组迭代的方法有哪些? ⭐

25、for循环和forEach有什么区别?⭐⭐

26、使用JQ和vue的区别?

三、TS 篇

1、什么是 TypeScript ?

2、TypeScript 的特点是什么?

3、使用 TypeScript 有什么好处?

4、TypeScript 的内置数据类型有哪些?

5、TypeScript 目前的稳定版本是什么?

6、TypeScript 中的接口是什么?

四、ES6 篇

1、ES6新增的内容有哪些?⭐⭐⭐

2、ES6中,Set和Map的区别  ?⭐⭐⭐

3、map和forEach的区别?⭐

4、es6 中的箭头函数?⭐⭐⭐

5、什么是扩展运算符,用于什么场景?⭐⭐

6、JS变量提升?⭐⭐⭐

7、怎么实现Module模块化?⭐

8、异步函数 ⭐⭐⭐⭐⭐

五、JQ 篇

1. jQuery 库中的 $() 是什么?

2. 网页上有 5 个div元素,如何使用JQ来选择它们?

3. jQuery 里的 ID 选择器和 class 选择器有何不同?

4. 如何在点击一个按钮时使用 jQuery 隐藏一个图片?

5.  $(document).ready() 是个什么函数?为什么要用它?

6. JavaScript window.onload 事件和 jQuery ready 函数有何不同?

7. 如何找到所有 HTML select 标签的选中项?

8. jQuery 里的 each() 是什么函数?你是如何使用它的?

9. 你是如何将一个 HTML 元素添加到 DOM 树中的?

10. 你能用 jQuery 代码选择所有在段落内部的超链接吗?

11. $(this) 和 this 关键字在 jQuery 中有何不同?

12. 你如何使用 jQuery 来提取一个HTML 标记的属性,例如链接的 href?

13. 你如何使用 jQuery 设置一个属性值?

14. jQuery 中 detach() 和 remove() 方法的区别是什么?

15. 如何利用 jQuery 来向一个元素中添加和移除 CSS 类?

16. 使用 CDN 加载 jQuery 库的主要优势是什么 ? 

17.  jQuery.get() 和 jQuery.ajax() 方法之间的区别是什么?

18. jQuery 中的方法链是什么?使用方法链有什么好处?

19. 你要是在一个 jQuery 事件处理程序里返回了 false 会怎样?

20. 哪种方式更高效:document.getElementbyId("Id") 还是 $("#Id")?

六、VUE 篇

1、写出vue的常用指令 ⭐⭐

2、v-model 实现原理?⭐

3、v-if 和 v-show 的区别?⭐⭐

4、v-if 和 v-for 那个优先级更高?⭐⭐

5、v-if 和 v-for 可以一起使用吗?⭐⭐

6、常用的事件修饰符有哪一些(v-on 指令) ⭐        

7、虚拟 DOM?⭐⭐⭐

8、v-for 为什么加 key?不加 key 会怎么样?⭐⭐

9、v-for 写在 template 上,不能加 key 怎么办? 

10、vue2 生命周期⭐⭐⭐

11、vue3 生命周期 ⭐⭐⭐

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、mvc、mvp、mvvm 架构模式?⭐⭐⭐

28、vuex 状态管理?⭐⭐⭐⭐

29、Vue Router 路由?⭐⭐⭐⭐

30、Vue 组件?⭐⭐⭐⭐

31、react、vue、Anguar 框架?⭐

32、说一下 vue 的服务端渲染 SSR?⭐

七、uni-app 篇

1、uniapp 打包过程(安卓 Android)⭐

2、uniapp 打包过程(苹果 ISO)⭐

3、uniapp 小程序打包过程 ⭐

4、uniapp 的基本配置?

5、uniapp 上传文件时用到的 api 是什么? 格式是什么?

6、uniapp 获取地理位置的API 是什么?

7、rpx、px、em、rem、%、vh、vw 的区别是什么?⭐

8、uniapp 如何监听页面滚动?

9、如何让图片宽度不变,高度自动变化,保持原图宽高比不变?

10、uni-app 的优缺点?

11、分别写出 jQuery、vue、小程序、uni-app 中的本地存储 

12、JQ、VUE、uni-app、小程序的页面传参方式? ⭐

13、vue、微信小程序、uni-app 绑定变量属性区别?

15、小程序组件传参有哪些方式

八、HTTP 请求篇

1、浏览器输入url后都经历了什么?⭐⭐⭐

2、浏览器地址栏的完整URL都包含哪些内容都各代表什么?

3、cookie、sessionStorage、localStorage的区别?⭐⭐⭐

4、如何实现可过期的 localstorage 数据?

5、请求行、请求头、请求体、响应行、响应头、响应体?

6、 get 和 post 的区别? 

8、常见的HTTP状态码?

6、Token 能放在 cookie 中吗?

7、Token 认证流程?

8、什么是同源策略?为什么要有同源策略?⭐⭐

9、XSS攻击是什么?

10、CSRF攻击是什么?

11、什么是跨域?为什么有跨域问题?⭐⭐⭐

12、跨域的解决方案有哪几种?⭐⭐⭐

13、什么是 options 请求?

13、什么是浏览器内核?有什么用?有哪一些?

14、什么是浏览器兼容问题?

15、优雅降级和渐进增强(浏览器兼容问题)

16、http 和 https 有何区别?

18、说一说前端性能优化手段?⭐⭐⭐

19、网站性能优化的好处?怎么优化?

20、axios的拦截器原理及应用?

21、创建 ajax 过程?

22、说一下 fetch 请求方式?

23、说一下浏览器如何渲染页面的?

24、说一下有什么方法可以保持前后端实时通信?

九、git 篇

1、git 是什么?⭐

2、git 常用的命令?⭐

3、git 和 svn 的区别?⭐⭐

4、Git项目如何配置,如何上传至GitHub?

5、你们公司git分支是怎么管理的?⭐⭐

6、git 工作流⭐

6、git版本冲突是什么?⭐

7、如何解决git版本冲突?⭐

十、功能的实现

1、vuex 实现购物车功能

2、用 js 实现一个轮播图的思路?

十一、手撕代码

十二、机试

十三、其他

1、自我介绍

2、你是怎么样优化代码的

3、你是如何优化项目的

4、开发中都使用了哪些工具

5、开发中遇到过什么bug

6、你对加班的看法

7、为什么离开上一家公司

8、你是如何学习的


一、Html5、Css3 篇

1、HTML、XHTML、XML 有什么区别?⭐

  1. XML: XML 是可扩展标记语言,主要是用来存储和传输数据,而非显示数据,可以用来标记数据,定义数据类型,允许用户对自己的标记语言进行定义。
  2. HTML:HTML 是超文本标记语言,主要是用来描述网页的一种标记语言,通过标记标签来描述网页。
  3. XHTML:XHTML 是可扩展超文本标记语言,XHTML 基于 XML 和 HTML 而来,也是用来描述网页的标记语言,是更严格的 HTML 版本。例如:XHTML 元素必须被正确地嵌套,标签名必须用小写字母, 文档必须拥有根元素,对于图片需添加 alt 属性等。XHTML 和 HTML 4.01 几乎是相同的,XHTML 是 W3C 标准。
  4. XML和HTML区别:XML 相比 HTML 语法要求严格。HTML 是预定义的,XML 标签是免费的、自定义的、可扩展的。HTML 的设计目的是显示数据并集中于数据外观,XML 的设计目的是描述数据、存放数据并集中于数据的内容。XML 是一种跨平台的,数据处理和传输的工具。总的来说,XML用来传输数据,而 HTML 和 XHTML 用来描述网页,XHTML 比 HTML 更为严格。 

2、 XML和JSON的区别?

  1. JSON:JSON 是一种轻量级的数据交换格式,它基于 JavaScript 的一个子集,简单的说 JSON 就是一串字符串用于不同平台的数据交换。
  2. XML:XML 是可扩展标记语言,是标准通用标记语言 (SGML) 的子集,XML 主要是用来存储和传输数据,而非显示数据,可以用来标记数据、定义数据类型,允许用户对自己的标记语言进行定义。
  3. 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 ,比如像标签闭合、标签元素和属性名字小写、标签不乱嵌套、属性必须有属性值、属性值必须用引号括起来、特殊符号用编码表示、定义语言编码等。标签规范可以提高搜索引擎对页面的抓取效率,对 SEO (搜索引擎优化) 很有帮助,越规范的网站搜索排名越靠前。
  • 表现标准主要包括 CSS,行为标准主要包括对象模型(像 W3C DOM,ECMAScript),比如说尽量使用外链的 css 和 js 脚本,提高页面的渲染效率,尽量少使用行内样式,类名要做到见名知意。遵循 w3c 标准可以让我们的页面,我们的程序能够支持所有浏览器,能够满足尽可能多的用户。
  • W3C 标准的体现,也就是说是开发者在开发过程中怎么去准守 W3C 标准,其实这里面很多规范是为了 XHTML 的,jQurry 不符合 W3C 标准。

4、什么是语义化标签?⭐⭐

  1. 语义化标签就是标签语义化,让标签有自己的含义,使浏览器和搜索引擎能直观的认识标签的用途和内容。
  2. 虽然可以采用 DIV + CSS 的方式布局页面,但 DIV 标签本身没有特殊含义,文档结构不够清晰,不利于浏览器对页面的读取,在分离 CSS 样式后,体验不友好。
  3. 使用语义化标签可以使代码结构清晰,可读性高,便于团队开放和维护。在页面没有加载 CSS 的情况下也能呈现良好的结构,易于阅读。有利于 SEO(搜索引擎优化)。
  4. 语义化标签是 H5 新特性,语义化标签有:<header> 定义页面的头部,<footer> 定义页面尾部,<nav> 定义导航链接,<article> 内容标签等。

5、常用的块级元素和行内元素有哪一些? ⭐

  • 块级元素:div、p、h1~h6、ol、ul、li、table、form。
  • 行内标签:a、span、img、input、lable、button。
  • 行内块元素:img、input、button。

6、行内元素和块级元素的区别?⭐ 

  • 块级元素:默认换行,独占一行,可设置宽高 (宽度是父容器的100%),块级元素可以嵌套任意元素,块级文字不能放入其他块级元素。
  • 行内元素:默认不换行,设置宽高无效 (默认宽度是本身内容宽度),不能包含块级元素,只能包含文本或者其它行内元素,设置 margin,padding 上下无效。
  • 行内块元素:综合块级元素与行内元素的特性,可设宽高(默认是内容宽高),也可以设置内外边距。
  • 转换:display:block,display:inline,display:inline-block。

7、css盒子模型有几种类型?它们区别是什么 ⭐ 

  • 根据盒子大小的计算方式不同,css 盒子模型分成了两种类型,分别是 W3C 标准盒子模型和怪异盒子模型也叫 IE 盒子模型。
  • 标准盒子模型:设置的宽度 = 内容的宽度,盒子的宽度 = 内容的宽度 + padding*2 + margin*2 + border*2。
  • IE盒子模型:设置的宽度 = 盒子的宽度,内容的宽度 = 盒子的宽度 - padding*2 - margin*2 - border*2。
  • 默认情况下都是标准盒子模型,设置 IE 盒子模型:box-sizing:border-box,设置标准模型:box-sizing:content-box 

8、标签上title与alt属性有什么区别?

  1. alt 是给搜索引擎识别,在图像无法显示时的替代文本。

  2. title 是元素的注释信息,主要是给用户解读。

  3. 当鼠标放到文字或是图片上时有 title 文字显示。在 IE 浏览器中 alt 起到了 title 的作用,变成文字提示。

9、 H5新特性有哪些?⭐⭐

  1. 语义化标签
    <header>定义文档的头部区域
    <nav>定义导航链接
    <article>内容标签
    <section>定义文档某个区域
    <aside>侧边栏标签
    <footer>定义了文档的尾部区域
    <figure>规定独立的流内容(图像、图表、照片、代码等)
    <figcaption>定义 <figure> 元素的标题
  2. 增强表单功能 (类型和属性)
  3. 音频视频:<Audio> 、<video>
  4. 画布:<Canvas>
  5. SVG 绘图
  6. 地理位置
  7. 拖拽 APL
  8. WebStorage (本地存储:LocalStorage / SessionStorage )

10、css3的新特性有哪些?⭐⭐

  1. 选择器:层级选择器,属性选择器,状态伪类选择器,结构伪类选择器,伪元素选择器
  2. 文本效果:文本阴影 ,文本自动换行,文本溢出,(单词拆分,文本拆分)
  3. 边框:圆角边框 border-radius,边框阴影 box-shadow,边框图片 border-image
  4. 背景:渐变背景,多重背景 (设定背景图像的尺寸,指定背景图像的位置区域,背景的绘制)
  5. 透明度:opacity ( 取值0-1,通常用于做元素的遮罩效果)
  6. 高斯模糊:filter
  7. 渐变:background: linear-gradient (线性渐变,径向渐变 ,文字渐变)
  8. 过渡:transition
  9. 2D转换 / 3D转换: transform
  10. 动画:Animation (@keyframes 动画帧)
  11. 媒体查询:@media
  12. 多列布局 (兼容性不好,还不够成熟)
  13. 弹性布局 (flex)
  14. 网格布局

11、css的引用有哪些,link和@import的区别?⭐

css 的引用有哪些:

  1. 内联方式(直接在 html 标签中的 style 样式)
  2. 嵌入方式(在 < style > 标签下书写 css 代码)
  3. 链接方式(使用 link 引入外部的 css 文件)

 link 和 @import 的区别:

  1. link 和 import 写法不同,link 通过 <link> 标签的 href 属性引入,import 通过 @import url() 引入。
  2. link 是 XHTML 标签,还可以定义其他事务,@import 属于 CSS 范畴,只能加载 CSS。
  3. link 无兼容问题,@import 兼容 IE5 以上。
  4. link 支持使用 Javascript 控制 DOM 去改变样式,@import 不支持改变样式。
  5. link 是连接整个 css 文件,@import 可以模块化引入 css 文件。
  6. link 引用 CSS 时,在页面加载时同时加载 css,而 @import 会把 css 文件放在页面的最底部,导致 css 最后才加载完毕,等到页面完全加载才加载 css,导致页面留白时间长,影响用户体验。

12、href和src的区别?⭐

  1. 请求资源不同:href 超文本引用,用来建立当前元素和文档之间的连接,常用的是link、a 标签。src 会将指向的资源下载并引用到当前文档中,常用的标签有 script,img,iframe 标签。
  2. 作用结果不同:href 用于在当前文档和引用资源之间确立联系,src 用于替换当前内容。
  3. 浏览器解析方式不同:herf 引用的资源时,浏览器会将其识别为 CSS 文档,并行下载资源并且不会停止对当前文档的处理。当浏览器解析到 src 时,会暂停其他资源的下载和处理,直接将该资源下载,编译,执行完毕。

13、CSS常用尺寸单位有哪些?应用场景?⭐⭐

  1. px:像素,相对长度单位,它的大小取决于屏幕的分辨率,是一个固定值,不能够自适应。
  2. em:相对长度的单位,相对于当前对象内文本的字体尺寸,未设置则默认是浏览器默认字体尺寸。
  3. rem:CSS3 中新增的一个相对长度单位,相对于根元素 <html> 的 font-size 字体大小,根元素字体大小未设置,使用浏览器默认字体大小。

  4. vw:相对于视口的宽度。视口被均分为100单位的 vw。

  5. vh:相对视口高度,视口被均分为100单位的 vh。

  6. vmin:相对于视口宽度或高度中较小的那个。其中最小的那个被均分为100单位的 vmin。

  7. vmax:相对于视口宽度或高度中较大的那个。其中最大的那个被均分为100单位的 vmax。

  8. cm:厘米,绝对长度单位。

  9. mm:毫米,绝对长度单位。

  10. in:英寸,绝对长度单位。

  11. %:百法比

应用场景:

  1. 在移动端网页开发中,页面要做成响应式的,可使用 rem 配合媒体查询实现。原理:通过媒体查询,能够在屏幕尺寸发生改变时,重置 html 根元素的字体大小,页面中的元素都是使用rem 为单位设置的尺寸,因此只要改变根元素字体大小,页面中的其他元素的尺寸就自动跟着修改。
  2. 利用 vw 和 rem 实现响应式。原理:由于 vw 被更多浏览器兼容之后,在做移动端响应式页面时,通常使用 vw 配合 rem。原理是使用 vw 设置根元素 html 字体的大小,当窗口大小发生改变,vw 代表的尺寸随着修改,无需加入媒体查询,页面中的其他元素仍使用 rem 为单位,就可实现响应式。

14、移动端适配方案有哪些?⭐

vw、rem、em、rpx、%

15、什么是浮动?⭐

设置浮动的图片,可以实现文字环绕图片。设置了浮动的块级元素可以排列在同一行。设置了浮动的行内元素可以设置宽高。浮动造成的影响:使盒子脱离文档流,如果父级盒子没有设置高度,需要被子盒子撑开,那么这时候父级盒子的高度就塌陷了,同时也会造成后面的盒子布局受到影响。

16、清除浮动有哪些方法? ⭐

清除浮动,主要是为了解决父级元素因为子级浮动引起的内部高度为 0 的问题

  1. 父级div定义height (只适合高度固定的布局)
  2. 结尾处加空div标签然后添加一个clear:both样式 (浮动多的话要加很多个div)
  3. 父级 div 定义 overflow:hidden 超出盒子部分会被隐藏 (不推荐)
  4. 父级div定义伪类:(推荐)
     clearfix:after
         {
              content:"";
              display:block;
              visibility:hidden;
              height:0;
              line-height:0;
              clear:both;
          }

17、css选择器有哪些?⭐

基本选择器:

  1. 标签选择器    ( div { } )
  2. id 选择器       ( id=”a”, #a { } )
  3. 类选择器 class (class=”b”, .b { } )
  4. 通配符选择器   ( * { } )

 CSS3新增选择器: 

  1. 层级选择器:
    后代选择器ul li { } : 选择子类元素,包括间接子类
    子代选择器ul>li { } :选择子类元素,只包括直接子类
    相邻兄弟选择器div+p { } : 选择紧跟在 div 元素后面的 p 元素
    通用兄弟选择器div~p { } : 选择 div 元素后面的所有 p 元素
    共享选择器div1,div2 { } :选择所有元素
  2. 状态伪类选择器
  3. 结构伪类选择器
  4. 表单伪类选择器
  5. 伪元素选择器
  6. 属性选择器

18、CSS 样式覆盖规则?⭐

  • 规则一:由于继承而发生样式冲突时,最近祖先获胜。
  • 规则二:继承的样式和直接指定的样式冲突时,直接指定的样式获胜。
  • 规则三:直接指定的样式发生冲突时,样式权值高者获胜。
  • 规则四:样式权值相同时,后者获胜。
  • 规则五:!important 的样式属性不被覆盖。

19、CSS 样式的优先级?⭐⭐

  1. 引入方式:内联样式的优先级高于嵌入和外链,嵌入和外链的选择器相同就看他们在页面的插入顺序,后面插入的会覆盖前面的。
  2. 选择器优先级:id 选择器高于 ( 类选择器/伪类选择器/属性选择器 ) 高于 ( 标签选择器/后代选择器/伪元素选择器 ) 高于 ( 子选择器/相邻选择器 ) 高于通配符 *。
  3. 继承样式:继承样式是所有样式中优先级比较低的,浏览器默认样式优先级最低。
  4. !important:!important 最高权重,无论引入方式是什么,选择器是什么,它的优先级都是最高的。所以 !important 使用要谨慎,一定要优先考虑使用样式优先级的规则来解决问题而不是 !important 。只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important。永远不要在插件中使用 !important ,永远不要在全站范围的 CSS 代码中使用 !important.

!important >> 内联样式 >> id 选择器 >> 类选择器/伪类选择器/属性选择器 >> 标签选择器/后代选择器/伪元素选择器 >> 子选择器/相邻选择器 >> 通配符 * >> 继承样式 >> 浏览器默认样式 

20、display: none 和 visibily: hidden 区别? ⭐⭐

  1. display:none:隐藏对应的元素,整个元素消失不占空间。
  2. visibily:hidden:隐藏对应的元素,元素还会占用空间。
  3. disapaly 还可以转换元素类型,可以转换成块级元素、行内元素,行内块元素、弹性布局、网格布局等。visibility 只能做隐藏。

21、相对定位,绝对定位,固定定位的区别?⭐⭐⭐

  1. position:relative:相对定位,相对于自己进行定位。

  2. position:absolute:绝对定位,相对于有相对定位的父级元素进行定位,没有就找 body。

  3. position:fixed:固定定位,相对于浏览器定位。

22、说几个未知宽高元素水平垂直居中方法? ⭐⭐⭐

  • 绝对定位:通过 left,top 和 transform 属性实现水平垂直居中,其中 translate 属性取值为负数时表示向左和向下移动。这种方式兼容性好,被广泛使用的一种方式。
<style>
    .box {
        position: absolute;
        width: 100px;
        height: 100px;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
    }
</style>
  • 弹性布局:设置父级为弹性盒子:display:flex ,设置水平和垂直居中:justify-content:center、align-items:center。这种方式代码简洁,但是兼容性 ie11 以上支持。
<style>
    .box {
        width: 400px;
        height: 300px;
        display: flex;
        justify-content: center;
        align-items: center;
        border: 1px solid black;
    }
    .child {
        width: 200px;
        height: 150px;
        background-color: red;
    }
</style>
<body>
    <div class="box">
        <div class="child"></div>
    </div>
</body>
  • 网格布局:设置父级为网格元素:display: table-cell,设置水平和垂直居中:justify-content:center、align-items:center。这种方式代码简洁,但是兼容性 ie10 以上支持
  • 表格布局  :设置父级为表格元素:display: table-cell,内部元素水平和垂直居中:text-align: center、vertical-align: middle,设置子元素为行内块:display: inline-block。兼容性好。
<style>
    .box {
        width: 400px;
        height: 300px;
        display: table-cell;
        text-align: center;
        vertical-align: middle;
        border: 1px solid black;
    }
    .child {
        width: 200px;
        height: 150px;
        background-color: red;
        display: inline-block;
    }
</style>
<body>
    <div class="box">
        <div class="child"></div>
    </div>
</body>

二、JS 篇

1、JS数据类型有哪些?区别?⭐⭐⭐

JS 的数据类型分为两类,分别是基本数据类型和引用数据类型。它们主要区别是在内存中的存储方式不同。

  • 基本数据类型:number 数字、string 字符串、boolean 布尔值、null 空值、undefined 未定义、symbol 唯一值、BigInt 最大值。基本数据类型有固定的大小和值,存放在栈中,可以直接访问,而引用数据类型不确定大小,但是其引用地址是固定的,因此,它的地址存在栈中,指向存储在堆中的对象。
  • 引用数据类型:Object (包括普通对象,数组,正则,日期,Math 数学函数等)。引用数据类型是存放在堆中的对象,在栈中保存的是对象在堆中的引用地址(引用变量),通过引用地址可以快速查找到保存在堆中的对象。
  • Symbol 是 Es6 新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以做 object 的 key。
  • BigInt 也是 ES6 新出的一种数据类型,BigInt 可以表示任意大的整数,能够解决解精度缺失的问题 (超过 Number 类型支持范围的数值都会失去精度)。使用方法:(1) 整数末尾直接加n:647326483767797n。(2) 调用 BigInt() 构造函数:BigInt("647326483767797")。

2、JS中检测数据类型的有哪些?⭐⭐

  • typeof:常用于判断基本数据类型,除了 null 检测为 object。对于引用数据类型除了 function 返回 function,其余全部返回 object。
  • instanceof:主要用于检测引用数据类型,不适合用来检测基本数据类型。如果检测的类型在当前实例的原型链上,则返回 true,说明这个实例属于这个类型,否则返回 false。例如: A instanceof B判断 B 在不在 A 的原型链上,如果在就返回 true,如果找到原型链的尽头 null 都没找到,就返回 false。(由于原型链的指向可以随意改动,导致检测不准确)
  • constructor:获取实例的构造函数判断和某个类型是否相同,如果相同就说明该数据是符合那个数据类型的。使用方法是:"实例.constructor"。constructor 可以检测出除了 undefined 和 null 以外的其他类型,因为 undefined 和 null 没有原生构造函数。(不可靠,容易被修改)
  • object.prototype.toString.call( ):适用于所有类型的判断检测,检测方法是: Object.prototype.toString.call(数据) ,返回的是该数据类型的字符串。

3、JS中的栈和堆是什么?优缺点?⭐⭐⭐

JS 的变量都储存到内存中,内存中开辟了两个区域储存变量,分别是栈区域和堆区域。 栈与堆实际上是操作系统对进程占用的内存空间的两种管理方式。

栈:

  1. 栈是一种先进后出的数据解构,由操作系统自动分配内存空间,自动释放,占固定的大小空间。
  2. 栈存储的是基本数据类型的值以及引用数据类型的引用地址。
  3. 栈中存储的数据的生命周期随着当前环境的执行完成而结束。

堆: 

  1. 堆由操作系统动态分配内存空间,大小不定也不会自动释放,一般由程序员分配释放也可由垃圾回收机制回收。
  2. 栈存储的是对象和复杂数据结构,存储的是对象的实际数据,而不是对象的引用。
  3. 引用数据类型只有在引用的它的变量不在时,被垃圾回收机制回收。

栈和堆的优缺点: 

  1. 栈相对于堆存取速度更快,且栈内存中数据是可以共享的,但内存空间有限。
  2. 堆存取效率相对较低,但内存空间大。
  3. 栈内存可以及时得到回收,相对来说更容易管理内存空间,但存储在栈中的数据大小和生存期必须是确定的,缺乏灵活性。
  4. 堆的内存是操作系统动态分配的,方便存储和开辟内存空间。有垃圾回收机制,生存周期比较灵活。

4、深克隆和浅克隆?⭐⭐⭐

浅克隆:

  1. 克隆对象的第一层属性。
  2. 如果是基本数据类型,直接将存储在栈中的值赋值给对应的变量,原始值改变不会影响。
  3. 如果是引用数据类型,则克隆的是对象的引用地址,改变引用地址,新对象也会跟着改变,想要改变这种继承的现象就要使用深度克隆。
  4. 在 JS 中可以通过 Object.assign( ) 或者扩展运算符 ... 合并对象实现浅克隆。

 深克隆:

  1. 克隆对象各个层级的属性。
  2. 深克隆是指创建一个与原对象完全相同的新对象 (数据源不同,数据地址已变化)。
  3. 可以通过递归的方式实现深克隆,也可以通过简单粗暴的方式实现 JSON.parse (JSON.stringify(obj))。但需要注意:时间对象会变成字符串的形式。RegExp、Error 对象序列化的结果将只得到空对象。函数、undefined 序列化的结果会把函数或 undefined 丢失。NaN、Infinity 序列化的结果会变成 null。如果对象中存在循环引用的情况也无法正确实现深拷贝。JSON.stringify() 只能序列化对象的可枚举的自有属性。

5、JS垃圾回收机制?⭐⭐

  • 内存泄漏:JS 代码运行时,需要分配内存空间存储变量和值,当这些变量不再作用时,需要释放内存,如果没有及时释放,就会引起内存泄漏,堆积起来会影响性能甚至造成系统崩溃。垃圾回收机制就是为了防止内存泄漏,及时释放不再使用的内存,提高程序性能。
  • 垃圾回收机制:垃圾回收机制是一种自动管理内存的机制,它会自动监测和回收不再使用的对象,从而释放内存空间。实现的原理主要有标记清除、引用计数、复制算法。
  •  标记清除算法:垃圾回收器会定期扫描内存中的对象,标记那些可达对象和不可达对象。可达对象指的是正在被使用的对象。不可达对象指的是不再被引用的对象。垃圾回收器会将不可达对象标记为垃圾对象,并将他们从内存中清除。该算法的优点是可以处理循环引用的情况,缺点是由于垃圾回收器的工作需要消耗一定的系统资源,因此如果程序中存在大量的内存占用或者存在频繁创建和销毁对象的操作,执行垃圾回收操作的时间会比较长,对性能造成一定的影响。
  • 引用计数算法:垃圾回收器会记录每个对象被引用的次数,当对象被引用的次数为0时,该对象就会被清除。该算法的优点是实现较为简单,但无法处理循环引用的情况,即两个对象相互引用,但是它们的引用次数都不为 0,导致内存无法被释放,可能会导致内存泄漏。
  • 复制算法:将内存空间划分为两个相等的区域,每次只使用一个区域,这个区域满时,将其中存活的对象复制到另外一个区域,再将原区域的对象全部清除。优点是可以避免由于产生大量内存碎片而引发的内存分配失败问题。

存在问题?怎么优化?

虽然浏览器可以自动的进行垃圾回收,但是当代码比较复杂时,垃圾回收对性能的消耗比较大,所以应该尽量减少垃圾回收。需要根据具体应用的需求和环境进行优化。

  1. 对数组进行优化。清空数组时,赋值为[ ],同时将数组的长度设置为0。
  2. 对象复用,尽量减少对象的创建和销毁次数。
  3. 不再使用的对象就设置为 null。
  4. 函数优化,函数功能单一化。
  5. 尽早释放资源。
  6. 使用闭包,在闭包中的变量不会被垃圾回收机制回收。

6、JS哪些操作会造成内存泄露?⭐⭐

  1. 意外的全局变量。由于使用未声明的变量而意外的创建了一个全局变量,而使用这个变量一直留在内存中无法被回收。
  2. 没有清理的DOM元素引用。获取一个DOM元素的引用,元素被删除了,由于保留了元素的引用,所以也无法被回收。
  3. 被遗忘的定时器或者回调函数。
  4. 闭包。

7、闭包?⭐⭐⭐

什么是闭包?

因为作用域链的存在,函数的内部可以直接读取全局变量,而函数内部无法读取另一个函数内部的局部变量,如果想读取函数内部的局部变量,可以通过闭包来实现。闭包就是在一个函数内部创建另外一个函数,让你可以在一个内层函数中访问到外层函数的局部变量。简单来说,闭包就是可以读取其他函数内部局部变量的函数,本质上,闭包是将函数内部和函数外部连接起来的桥梁。

为什么要使用闭包?

  1. 局部变量无法共享和长久的保存,而全局变量可能造成变量污染。
  2. 闭包可以读取函数内部的局部变量,且不会被垃圾回收机制回收,可以长期保存。

闭包的作用?

  1. 在函数外部可以访问函数内部的局部变量。
  2. 可以使函数内部的变量在函数执行结束之后不被销毁 ,长久保存在内存中,不会被垃圾回收机制回收。
  3. 使用闭包,可以封装自己的函数代码,实现模块化。
  4. 保护:避免命名冲突。
  5. 保存:解决循环绑定引发的索引问题。

闭包的缺点?

由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。如何解决:在销毁函数之前,将不使用的局部变量全部删除。

闭包的应用?

  1. 能够模仿块级作用域。
  2. 设计模式中的单例模式。
  3. for 循环中的保留 i 的操作。
  4. 防抖和节流。
  5. 函数柯里化。
  6. 在构造函数中定义特权方法。
  7. Vue 中数据响应式 Observer 中使用闭包。

8、什么是原型链?⭐⭐⭐

  • 每个函数身上都有一个 prototype 的原型对象,并且有一个__proto__的指针指向下一级原型对象,如果一个对象的属性或方法在自身中找不到,那么就会去 prototype 原型对象中查找,如果还找不到继续向上查找直到 null,当_proto_指针指向 null 时形成一个链条,这个链条叫做原型链。
  • 在原型链中,对象可以继承原型对象的属性和方法。如果想在构造函数中添加属性和方法,可以将它们添加到构造函数的 prototype 属性中,这样通过该构造函数创建的对象都可以访问到这些属性和方法。
  • 原型链的特点是:对象可以沿着原型链向上查找属性和方法,实现了属性和方法的共享和继承。

9、JS继承的方法有哪些?优缺点?⭐⭐

JS继承的方法有以下几种:原型链继承、构造函数继承、组合继承、原型式继承和寄生式继承,寄生组合式继承,ES6 Class实现继承。继承的目的是:重复利用另外一个对象的属性和方法。

  • 原型链继承:将父类的实例作为子类的原型,从而实现对父类属性和方法的继承。优点:写法方便简洁,容易理解。缺点:不能传递参数和共享所有继承的属性和方法,当一个发生改变另外一个随之改变。

  • 构造函数继承:在子类的构造函数中调用父类的构造函数,使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上,从而实现对父类实例属性的继承。优点:解决了原型链继承不能传参的问题和父类的原型共享的问题。缺点:方法都在构造函数中定义,因此无法实现函数复用。

  • 组合继承:将原型链继承和构造函数继承结合起来,既可以实现对父类原型属性和方法的继承,又可以实现对父类实例属性的继承。优点: 解决了原型链继承和构造函数继承造成的影响。缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

  • 原型式继承:通过创建一个临时构造函数来实现对父类的属性和方法的继承。优点:不需要单独创建构造函数。缺点:属性中包含的引用值始终会在相关对象间共享。

  • 寄生式继承:在原型式继承的基础上,通过在临时构造函数中添加方法和属性,从而实现对父类的继承。优点:写法简单,不需要单独创建构造函数。缺点:通过寄生式继承给对象添加函数会导致函数难以重用。

  • 寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。优点:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变。缺点:代码复杂。

  • ES6 Class实现继承:ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法加到 this 上面 (所以必须先调用super方法),然后再用子类的构造函数修改 this。需要注意的是,class 关键字只是原型的语法糖,JS继承仍然是基于原型实现的。 优点:语法简单易懂,操作更方便。缺点:并不是所有的浏览器都支持 class 关键字 lass Person。

10、new操作符具体都干了什么?⭐⭐

  1. 创建一个新对象 obj。
  2. 将该对象与构造函数通过原型链连接起来(设置该对象的构造函数)。
  3. 将构造函数中的 this 绑定到该对象上。
  4. 根据构造函数返回类型作判断,如果是值类型则返回新对象 obj,如果返回对象,则返回构造函数里的对象。
// 手写 new 操作符
function mockNew(constructor, ...args) {
    // 1.创建一个新对象 obj
    const obj = {};
    // 2.把构造函数当参数传入,新对象指向构造函数原型对象
    obj.__proto__ = constructor.prototype;
    // 3.通过 apply 将构建函数的 this 指向新对象
    let result = constructor.apply(obj, args);
    // 4.根据返回值判断
    return result instanceof Object ? result : obj;
}

11、JS的几种具体异常类型(报错)

  • SyntaxError:语法错误
  • ReferenceError:引用错误
  • RangeError:范围错误
  • ypeError:类型错误
  • URLError:与 url 相关参数不正确
  • EvalError:全局函数 eval 执行错误

12、什么是事件冒泡?什么是事件委托?

  • 事件冒泡:在一个对象上触发某类事件,这个事件会向这个对象的的父级传播,从里到外,直至它被处理或者到达了对象层次的最顶层,即 document 对象。这个过程就是事件冒泡。
  • 事件委托:事件委托就是利用事件冒泡,指定一个事件处理程序,就可以管理某一类型的所有事件。原理:在元素的父级元素添加事件,点击元素时,因为事件冒泡的作用实现事件委托。简单来说,事件委托就是将子元素的事件通过冒泡的形式交由父元素来执行。优点:使用事件委托可以减少代码执行优化资源。 

13、事件对象? 

  • event:事件对象。
  • currentTarget:绑定的事件。
  • target:触发的事件。
  • eventPhase:返回当前触发的阶段(捕获阶段1,事件派发阶段2,冒泡阶段3)
  • type:返回当前event对象的事件名。

14、undefined 和 null 区别?⭐

  1. undefind 是全局对象的一个属性,当一个变量没有被赋值或者一个函数没有返回值或者某个对象不存在某个属性却去访问或者函数定义了形参但没有传递实参,这时候都是 undefined ,undefined 通过 typeof 判断类型是 undefined。
  2. null 代表空值,代表一个空对象指针,代表对象的值未设置,相当于一个对象没有设置指针地址就是 null。null 通过 typeof 判断类型是 object。
  3. undefined 表示一个变量初始状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。当需要释放一个对象时,直接赋值为 null 即可,对象被赋值了null 以后,对象对应的堆内存中的值就是游离状态了,GC 会择机回收该值并释放内存。因此,需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。
  4. null 是 javascript 的关键字,和其它语言一样都是代表空值, undefined 却是 javascript 才有的。是为了区分空指针对象和未初始化的变量,它是一个预定义的全局变量。

15、说一说伪数组和数组的区别?

  1. 伪数组它的类型不是 Array,而是 Object,而数组类型是 Array。
  2. 伪数组可以使用的 length 属性查看长度,也可以使用 index 获取某个元素,但是不能使用数组的其他方法,也不能改变长度,遍历使用 for in 方法。
  3. 伪数组转换成真数组方法:(1)Array.prototype.slice.call(伪数组) 、(2)[].slice.call(伪数组) (3)Array.from(伪数组),转换后的数组长度由 length 属性决定,索引不连续时转换结果是连续的,会自动补位。

16、对于数组去重都有哪些方法?⭐⭐ 

  1. 双重 for 循环:这是一个最笨的方法。
  2. 对象属性 key:利用对象属性名 key 不可以重复这一特点,如果对象中不存在,就 push 进空数组。
  3. for 循环 + indexOf:主要是利用 indexOf 的特性,查找元素返回下标,找不到就返回-1。-1就 push 进空数组。
  4. for 循环 + sort 排序:利用数组的 sort 排序方法去重,如果第 i 项 和 i-1 项不一致,就 push 进空数组。
  5. filter + indexOf:利用 filter 过滤配合 indexOf 查找元素,判断返回元素下标和过滤数组的 index 是否相等。
  6. Set:Es6 中新增了数据类型 Set,Set 的最大一个特点就是数据不重复,可以用作数组去重。new Set 方法,返回是一个类数组,需要结合扩展运算符...,转成真实数组。
  7. set + Array.from:Set 去重结合 Array.from 转成真实数组。Array.from(new Set(原数组))
  8. for 循环 + includes:includes 用来判断一个数组是否包含一个指定的值,是就返回 true,否则返回 false。判断空数组是否包含原数组的某个元素,不包含就 push 进空数组。
  9. reduce + includes:利用 reduce 遍历结合 includes去重。

17、 数组的基本操作方法?⭐⭐ 

数组的基本操作方法:

  1. push:往数组尾部添加一个元素。 返回数组的长度。
  2. unshift:往数组头部添加一个元素。返回数组的长度。
  3. pop:从数组尾部删除一个元素。返回删除的元素。
  4. shift:从数组头部删除一个元素。返回删除的元素。
  5. slice(开始位置,结束位置):截取数组一部分。只有一个元素则是开始位置,直到截取全部
  6. splice(开始位置,长度,插入元素):删除、修改数组中的一部分元素。
  7. reverse:反转数组。返回反转后的数组。
  8. sort:对数组的元素进行排序。返回排序后的数组。
  9. join:把数组变成字符串。若括号里什么都不写,则默认用逗号分隔。
  10. toString:把数组变成字符串。
  11. split:把字符串变成数组。
  12. concat:合并数组,并返回结果。扩展运算符也可以合并数组。
  13. Math.min:返回数组最小值元素。
  14. Math.max:返回数组最大值元素。
  15. length:获取当前数组的长度。

查找方法:

  1. indexOf:查找数组元素,返回第一个找到的元素下标,找不到返回-1。
  2. lastIndexOf:查找数组元素,返回最后一个找到的元素下标,找不到返回-1,从后向前搜索
  3. includes: 查找数组是否包含某一元素,包含则返回 true,不包含返回 false。
  4. find:查找满足函数条件的第一个值,找不到返回 undefined。
  5. findIndex: 查找满足函数条件的第一个值得下标,找不到返回 -1。

数组类的静态方法:

  1. Array.of:将一数值转化为数组。
  2. Array.from:将类数组转化为数组。

数组填充:

  1. fill(value,start,end):用一个固定值填充一个数组中特定的元素。

迭代方法:

  1. for…in:遍历数组。会遍历数组内所有可枚举的属性,包括原型上的属性和方法。
  2. forEach:遍历数组。不会生成新数组,也不改变原数组,回调函数接收三个值:( 数组的元素,索引,当前数组)
  3. map:通过指定函数处理数组的每个元素,并返回处理后的数组。不会对空数组进行检测,不会改变原始数组。
  4. filter:过滤,检测数组元素,并返回符合条件所有元素的数组。
  5. every:检测数组元素的每个元素是否都符合条件,都符合则返回 true,否则为 false。
  6. some:检测数组元素中是否有元素符合指定条件,有则返回 true,若所有元素都不满足判断条件,则返回 false。

修改原数组:push、pop、shift、unshift、splice、reverse、sort、fill。

不修改原数组:slice、concat、indexOf、lastIndexOf、join、toString、filter、every、some、forEach、map、find、findIndex。

18、说一下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。

19、 js中call、apply、bind有什么区别?⭐⭐

  1. 原生 JS 提供了 call、apply、bind 三种方式来修改 this 指向。
  2. call 和 bind 是选项式参数,apply 是数组式参数。
  3. call、apply 会立即执行,bind 返回一个新函数,不会立即执行。
  4. call、apply 临时改变 this 指向一次,bind 永久改变 this 指向。
  5. 应用场景:call 用于对象的继承 、伪数组转换成真数组。apply 用于找出数组中的最大值和最小值以及数组合并。bind 用于 vue 或者 react 框架中改变函数的 this 指向。

20、箭头函数和普通函数有什么区别?⭐⭐ 

  1. 普通函数的 this 指针指向调用者,可以修改。
  2. 箭头函数没有自己的 this,它的 this 是继承而来,默认指向在定义它时所处的对象 (父级作用域),不能修改。

21、JQ对象和DOM元素之间如何转换? 

  • DOM转JQ对象:$(DOM对象) 或 $("div")。
  • JQ对象转DOM:可以通过[index] 或者 .get(index)方法,例如 $("div")[0] 或者 $("div").get(1)。

22、JS模块化有哪些?

 commonjs、es6、amd、cmd

23、如何操作DOM元素?⭐

原生操作DOM元素:

  • 直接给相应的元素加 id ,然后再 document.getElementById("id") 获取。

vue操作DOM元素:

  • 获取/操作根元素 DOM:$root。
  • 获取/操作父元素 DOM:$parent。
  • 获取/操作子元素 DOM: $refs $children。
  • 使用 ref,给相应的元素加 ref=“name” 然后再 this.$refs.name 获取到该元素。

24、防抖与节流的区别,并分别用代码表示 ⭐⭐

  • 防抖:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。作用:防止事件连续或高频触发,让它只触发一次或者最后一次。
  • 节流:高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。作用:降低事件触发的频率,比如1s内最多执行一次。
  • 区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行一次。
    //防抖和节流函数都是用于防止事件高频率的重复触发
    //防抖: 在一定时间内,如果方法没用再次被触发,则执行最后一次
    <body>
        <button>点我</button>
        <script>
            var btn = document.querySelector("button");
            var tiemr = null;
            btn.onclick = function () {
                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 = flase;
    				console.log('发送了请求');
                    setTimeout(()=>{
    				    jie = true;
    				},2000)
    			}
    		}	
    	</script>
    </body>

24、数组迭代的方法有哪些? 

  1. for。
  2. for in:可以遍历对象, 首个行参是 key。
  3. for of:只能遍历数组,首个行参是 value:。
  4. forEach:for的增强版,特殊简化版,不支持在循环中添加删除操作。
  5. while :先判断后执行。
  6. do while:先执行后判断。
  7. some:所有返回值都为真则返回真,否则返回假。
  8. every:反之。

25、for循环和forEach有什么区别?⭐⭐

  1. for 循环的 return 是终止循环 forEach 是返回参数。
  2. for 循环实际上是可以使用 break 和 continue 去终止循环的,但是 forEach 不行。
  3. forEach 不支持在循环中添加删除操作。
  4. for 多数时候都可以使用,一般我们需要知道循环次数。而 forEach 更适合于集合对象的遍历和操作。

26、使用JQ和vue的区别?

JQ 是函数库,本质上还是操控 JS,只不过更简便了。vue 是 mvvm 框架,核心是双向数据绑定,

一般情况下不需要操控 DOM 元素,而是操控数据为主。

三、TS 篇

1、什么是 TypeScript 

  • TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法。
  • TypeScript 通过类型注解提供编译时的静态类型检查。可以快速地找出代码中潜在的错误。
  • 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 目前的稳定版本是什么?

当前的稳定版本是 4.2.3。

6、TypeScript 中的接口是什么?

接口为使用该接口的对象定义契约或结构。接口是用关键字定义的 interface,它可以包含使用函数或箭头函数的属性和方法声明。

interface IEmployee {
    Code: number;
    Name: string;
    getSalary: (number) => number; 
    getName(number): string; 
}

四、ES6 篇

1、ES6新增的内容有哪些?⭐⭐⭐

let、const、结构赋值、class 类、箭头函数、map、set、promise、async await(es7)、扩展运算符...、Module 模块化。

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。

  • size:返回 set 实例的成员总数。
  • add(value):添加某个值,返回 set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为 set 的成员。
  • clear():清除所有成员,没有返回值。

Map:

"键值对" 的数据结构,可以实现数组重组。JavaScript 对象 object 本质上是键值对的集合,缺陷是只能使用字符串作为键,而 map 结构优化了这个缺陷,它提供了 "值-值" 对的形式,让键名不再局限于字符串,是一种更完善的 Hash 结构实现。简单来说:Map 类似于对象,也是键值对的集合,但是 "键" 的范围不限于字符串, 各种类型的值(包括对象)都可以当作键。遍历方法:keys( )、values( )、entries( )、forEach( )、for..of...。注意:由于一个 key 只能对应一个 value,所以多次对一个 key 放入 value,后面的值会把前面的值冲掉。

 实例操作方法:size、set、get、has、delete、clear。

  • size:返回 Map 实例的成员总数。
  • set:添加新的 key-value。
  • get:传入一个 key 返回一个 val。
  • delete:传入一个 key,删除这个 key-value。
  • has:查看是否有这个 key。
  • clear:清除所有成员,没有返回值。

Set 和 Map 的区别:

  1. Map 类似于对象也是键值对的集合,但 '键' 的范围不限于字符串可以是各种数据类型。Set 类似于数组,Set 对象是值的集合。
  2. Map 存储 key-value 键值对,是一组映射关系。Set 只存储 key,没有 value,value 就是 key。
  3. Set 的值是唯一的、不重复的,Map 的 key 是唯一的。
  4. Map 可以通过 get 方法获取值,而 Set 不能因为它只有值。
  5. Set 的值是唯一的可以做数组去重,Map 由于没有格式限制,可以做数据存储。
  6. 初始化需要值不一样,Map 需要的是一个二维数组,而 Set 需要的是一维 Array 数组。
  7. 都能通过迭代器进行 for...of 遍历。

3、map和forEach的区别?⭐

  • map:map 有返回值,可以开辟新空间,return 出来一个 length 和原数组一致的数组,即便数组元素是 undefined 或者是 null,map 能新增删除元素。
  • forEach 默认无返回值,返回结果为 undefined,可以通过在函数体内部使用索引修改数组元素,forEach 不能新增删除元素。

4、es6 中的箭头函数?⭐⭐⭐

  1. 箭头函数相当于匿名函数,简化了函数定义。
  2. 箭头函数有两种写法,当函数体是单条语句的时候可以省略 {} 和 return,另一种是包含多条语句,不可以省略 {} 和 return。
  3. 箭头函数最大的特点就是没有自己的 this,它的 this 是从外部获取,就是继承外部的执行上下文中的 this。
  4. 由于没有 this 关键字所以箭头函数也不能作为构造函数。
  5. 箭头函数也没有原型和 super。
  6. 不能使用 yield 关键字,因此箭头函数不能用作 Generator 函数。
  7. 适用场景:简单的函数表达式,内部没有 this 引用,没有递归、事件绑定、解绑定,适用于 map、filter 等方法中。

5、什么是扩展运算符,用于什么场景?⭐⭐

  1. 扩展运算符(…) 是一个展开语法,用于取出对象或数组中的所有可遍历属性或元素,然后将它们展开到当前的位置,展开的内容可以放在任何它可以存在的地方(数组,函数,对象)。
  2. 在对象中:扩展运算符可以用于创建新的对象,将多个对象合并成一个对象,或者复制一个对象。
  3. 在数组中:在函数调用时将一个数组参数转为用逗号分隔的参数序列。数组拷贝[...arr]。合并数组。扩展运算符与解构赋值结合,用于生成数组。将伪数组转为真正的数组。数组去重 [...new Set(arr)]。做为函数传递参数时不确定形参个数的时候使用。
  4. 扩展运算符可以提高代码的可读性和简洁性,减少重复代码的编写。

6、JS变量提升?⭐⭐⭐

变量提升是指 JS 的变量和函数声明会在代码编译期,提升到代码的最前面。 变量提升成立的前提是使用 Var 关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。

7、怎么实现Module模块化?

  1. 模块的功能主要由 export(导出) 和 import(导入) 组成。
  2. 每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过 import 来引用其它模块提供的接口。
  3. 同时还为模块创造了命名空间,防止函数的命名冲突。

8、异步函数 ⭐⭐⭐⭐⭐

function fn1() {
    console.log(1);
}
function fn2() {
    console.log(2);
}
setTimeout(()=>{
    fn1()
},2000)

fn2()

1、先执行完fn2再执行fn1,因为当主线程遇到异步任务的时候,会把异步任务放进 "任务队列" 里,
   执行完同步任务后,再循环的去检查任务队列里面有哪些异步任务要执行。
2、这是一个异步,因为他是分开执行的,并没有同时执行。
3、如果想要让它变成同步执行,执行完fn1后再执行fn2,可以使用异步函数,把异步变成同步执行。
   有三种方法可以实现,分别是回调函数、promise、async await。

同步和异步的区别? 同步函数和异步函数的区别?

  • 同步就是同时执行,异步就是分开来执行。
  • 同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
  • 异步任务是那些不进入主线程、而进入任务队列的任务,执行完同步任务后再进入主线程执行
  • 同步函数就是把同步执行变成异步执行,异步函数就是把异步执行变成同步执行。
  • 场景:同步和异步的问题通常是指 ajax 的回调问题。如果是同步调用,在发起请求的时候就会暂停,等到服务器响应后继续运行。如果是异步调用,发起请求后不会暂停,立即执行后面的代码,服务器响应后会自动触发回调函数进行处理。异步的性能佳,同步则用于需要立即获取结果并实时处理的情况。

js 代码的执行顺序?

js 是单线程语言,同一个时间只能做一件事,执行顺序是由上往下执行的,这样很容易造成阻塞,所以把任务分成了同步任务和异步任务两种。当主线程遇到异步任务,比如说计时器或者 ajax 请求回调问题,就把异步任务放进 "任务队列" 里,执行完同步任务后,再循环的去检查任务队列里面有哪些异步任务要执行。这是一个异步执行,分开来执行的,如果想要变成同步执行,比如说等到计时器执行完后再执行或者等请求服务器响应后再继续运行,可以使用异步函数,把异步执行变成同步执行。

异步函数实现?

  • 回调函数:回调函数是异步操作最基本的方法,优点是简单,容易理解和实现,缺点是不利于代码的阅读和维护,有回调地狱问题 (多个回调函数嵌套的情况)
  • promise:promise 是 es6 中的一个内置对象,实际是一个构造函数,是一个异步函数,是异步编程的一种解决方案,把异步执行变成同步执行。比传统的解决方案回调函数更加合理强大,解决了回调函数中的回调地狱,代码不规范,可读性低,不便维护等问题,通过 promise 调用多个 then 方法来把地狱回调转化为链式编程,并且可以通过 .catch 捕捉错误。缺点是错误需要通过回调函数捕获,只能异步的处理错误,Promise 链式调用相当于一个新的回调地狱。
  • async await:async await 是 ES7 新特性,也是一个异步函数,把异步执行变成同步执行,它是基于 promise 实现的,是一个语法糖。async await 使异步代码看起来像同步代码。优点是使用方法清晰明了,更加优雅,可读性强,可以以通过 try-catch 捕捉错误,同步的处理错误,且 async await 完美解决了回调地狱的问题。缺点是 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise 去实现。

回调函数具体实现:

function fn1(cb) {
    console.log(1);
    cb();
}
function fn2() {
    console.log(2);
}
setTimeout(()=>{
    fn1(fn2)
},2000)

// 事件堆栈  宏观  微观
先分析宏观,解析fn1和fn2,但是他们还没有执行,执行计时器,2秒后执行fn1进入微观里面
执行打印,然后再执行cb回调函数,执行fn2。

promise 具体实现:

function fn1() {
    return new Promise((resolve))=>{
        console.log(1);
        resolve()
    }
}
function fn2() {
    console.log(2);
}
setTimeout(()=>{
    fn1().then(()=>{
        fn2()
    })
},2000)

通过 promise 构造函数 new 一个实例,promise 构造函数接收一个函数作为参数,这个函数有两个参数, 分别是成功回调 resolve 和失败回调 reject。在异步操作成功时调用 resolve,并将结果作为参数传递出去,在异步操作失败时调用 reject,并将错误作为参数传递出去。根据 promise 状态,成功时执行 .then,失败时执行 .catch,并接收对应的参数执行相应的处理。一般把成功的处理逻辑写在 .then()里,把失败的处理逻辑写在 .cache() 里。then 方法返回的是一个新的 Promise 实例,因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法,把回调地狱转为链式编程。

async await 具体实现:

function fn1() {
    return new Promise((resolve)=>{
        console.log(1);
        resolve(1);
    }
}
function fn1() {
    return new Promise((resolve)=>{
        console.log(2);
        resolve(2);
    }
}
function fn1() {
    console.log(3);
}

setTimeout(async ()=>{
    let res1 = await fn1();
    let res2 = await fn2();
    fn3();
},2000)

async await 基于 promise 实现的,async 写在方法前面,用于声明一个 function 是异步的,async 和 await 是配对使用的,await 只能写在 async 的内部,await 后面跟着的是一个 promise 对象,async 执行期间一旦遇到 await,会等里面的 Promise(异步)执行完毕再执行后面的代码,如果是 reject 状态,可以使用 try-catch 捕捉。

promise 和 async await 区别:

相同点:promise 和 async await 都是异步编程的优化和解决方案。

不同点:

  1. promise 是 ES6 的一个内置对象,是应用层的解决方案,async await 是 ES7 的新特性,是基于 promise 的,可以说是 promise 的补充,是语言层的解决方案。
  2. promise 更多应用在函数封装中,async await 用在函数的使用中。
  3. Promise 链式调用相当于一个新的回调地狱。await async 完美解决了回调地狱的问题。
  4. Promise 通过 catch 来捕捉,只能异步的处理错误。async await 可以通过 try-catch 同步处理错误。
  5. async await 相对于 promise 来讲,写法更加优雅,async await 用同步的写法使得可读性更强,使异步代码看起来像同步代码。

题目1:输出执行顺序

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');
 
// 5 1 3 7 9 4 2 8 6

解析:

1、遇到两个定义 async 的函数,继续往下走,遇到 console 语句,直接输出 5

2、继续向下执行,遇到 async1() 函数,async 函数中在 await 之前的代码是立即执行的,        所以输出 1,然后执行 async2() 函数,输出 3,把 console.log('2') 放到事件队列里。

3、继续向下执行,遇到 Promise,Promise 中的代码是被当做同步任务立即执行的,所以          输出 7,然后执行 resolve,将 .then 里面的分配到事件队列中。

4、最后输出 9,至此主线程上的宏观任务执行完毕,开始查找清空微任务队列。

5、微观任务队列里有三个任务,先执行 async2 里面的 .then,输出 4,再执行 async1 里面的 console.log(2) 输出 2,然后再执行 8,事件队列里的也执行完了。

6、第二轮循环宏观任务开始,此时宏任务中只有一个 setTimeout,取出直接输出即可 6

题目2:输出执行顺序

sync 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 6 8 7 5 3 2 

题目3:输出执行顺序

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')
 
// 4 1 3 10 6 2 8 7 9 5
// 解析:6 也是被分配到任务队列中,而且是在 async1 前执行的,所以主线程结束后先执行。
// promise2 会直接输出,'resolve'不同于'await',不会阻止后面的执行,.then 才会阻止。
    

五、JQ 篇

1. jQuery 库中的 $() 是什么?

$() 函数是 jQuery() 函数的别称。$() 函数用于将任何对象包裹成 jQuery 对象,接着你就被允许调用定义在 jQuery 对象上的多个不同方法。你甚至可以将一个选择器字符串传入 $() 函数,它会返回一个包含所有匹配的 DOM 元素数组的 jQuery 对象。

2. 网页上有 5 个div元素,如何使用JQ来选择它们?

jQuery 支持不同类型的选择器,例如 ID 选择器、class 选择器、标签选择器等。你可以用标签选择器来选择所有的 div 元素。jQuery 代码:$("div"),这样会返回一个包含所有 5 个 div 标签的 jQuery 对象。

3. jQuery 里的 ID 选择器和 class 选择器有何不同?

ID 选择器使用 ID 来选择元素,比如 #element1,而 class 选择器使用 CSS class 来选择元素。当你只需要选择一个元素时,使用 ID 选择器,而如果你想要选择一组具有相同样式的 class 元素,就要用 class 选择器。

  • id选择器:$('#LoginTextBox')  。
  • class选择器:$('.active') 。

4. 如何在点击一个按钮时使用 jQuery 隐藏一个图片?

$('#ButtonToClick').click(function(){
    $('#ImageToHide').hide();
});

5.  $(document).ready() 是个什么函数?为什么要用它?

ready() 函数用于在文档进入 ready 状态时执行代码。当 DOM 完全加载,jQuery 允许你执行代码。使用 $(document).ready() 的最大好处在于它适用于所有浏览器,jQuery 帮你解决了跨浏览器的难题。

6. JavaScript window.onload 事件和 jQuery ready 函数有何不同?

  • JavaScript window.onload 事件和 jQuery ready 函数之间的主要区别是,前者除了要等待 DOM 被创建还要等到包括大型图片、音频、视频在内的所有外部资源都完全加载。如果加载图片和媒体内容花费了大量时间,用户就会感受到定义在 window.onload 事件上的代码在执行时有明显的延迟。
  • jQuery ready() 函数只需对 DOM 树的等待,而无需对图像或外部资源加载的等待,从而执行起来更快。使用 jQuery $(document).ready() 的另一个优势是你可以在网页里多次使用它,浏览器会按它们在 HTML 页面里出现的顺序执行它们,相反对于 onload 技术而言,只能在单一函数里使用。

7. 如何找到所有 HTML select 标签的选中项?

你能用下面的 jQuery 选择器获取所有具备 multiple=true 的 <select > 标签的选中项:结合使用了属性选择器和 :selected 选择器,结果只返回被选中的选项。

$('[name=NameOfSelectedTag] :selected')

8. jQuery 里的 each() 是什么函数?你是如何使用它的?

each() 函数允许你遍历一个元素集合。你可以传一个函数给 each() 方法,被调用的 jQuery 对象会在其每个元素上执行传入的函数。应用场景:如何在 alert 框里显示所有选中项。可以用上面的选择器代码找出所有选中项,然后我们在 alert 框中用 each() 方法来一个个打印它们。

$('[name=NameOfSelectedTag] :selected').each(function(selected) {
    alert($(selected).text());
});

9. 你是如何将一个 HTML 元素添加到 DOM 树中的?

可以用 jQuery 方法 appendTo() 将一个 HTML 元素添加到 DOM 树中。这是 jQuery 提供的众多操控 DOM 的方法中的一个。你可以通过 appendTo() 方法在指定的 DOM 元素末尾添加一个现存的元素或者一个新的 HTML 元素。

10. 你能用 jQuery 代码选择所有在段落内部的超链接吗?

<p id=“text”>
    <a href="#">a1</a>
    <p>
       <a href="#">a2</a>
       <a href="#">a2</a>
    </p>
</p>

$(’#text a’);  // 查出来a1,a2,a3
$(’#text>a’);  // 查出来a1

11. $(this) 和 this 关键字在 jQuery 中有何不同?

$(this) 返回一个 jQuery 对象,你可以对它调用多个 jQuery 方法,比如用 text() 获取文本,用 val() 获取值等等。而 this 代表当前元素,它是 JavaScript 关键词中的一个,表示上下文中的当前 DOM 元素,你不能对它调用 jQuery 方法。

12. 你如何使用 jQuery 来提取一个HTML 标记的属性,例如链接的 href?

attr() 方法被用来提取任意一个 HTML 元素的一个属性的值。

$('a').each(function(){
   alert($(this).attr('href'));
});

13. 你如何使用 jQuery 设置一个属性值?

attr() 方法也可以在调用的时候带上一个值,给当前属性赋值。例如:attr(name, value);

14. jQuery 中 detach() 和 remove() 方法的区别是什么?

都可以移除一个DOM元素。区别:

  • detach:会保持对过去被解除元素的跟踪,因此它可以被取消解除。
  • remove:会保持过去被移除对象的引用。( 向DOM中添加元素:appendTo )

15. 如何利用 jQuery 来向一个元素中添加和移除 CSS 类?

通过利用 addClass() 和 removeClass() 这两个 jQuery 方法。动态的改变元素的 class 属性可以使用类 .active 来标记它们的未激活和激活状态。

16. 使用 CDN 加载 jQuery 库的主要优势是什么 ? 

除了报错节省服务器带宽以及更快的下载速度这之外,最重要的是,如果浏览器已经从同一个 CDN 下载类相同的 jQuery 版本, 那么它就不会再去下载它一次。

17.  jQuery.get() 和 jQuery.ajax() 方法之间的区别是什么?

  • ajax:ajax 方法更强大,更具可配置性,让你可以指定等待多久,以及如何处理错误。
  • get:方法是一个只获取一些数据的专门化方法。

18. jQuery 中的方法链是什么?使用方法链有什么好处?

方法链是对一个方法返回的结果调用另一个方法。这使得代码简洁明了,同时由于只对 DOM 进行了一轮查找,性能方面更加出色。

19. 你要是在一个 jQuery 事件处理程序里返回了 false 会怎样?

这通常用于阻止事件向上冒泡。

20. 哪种方式更高效:document.getElementbyId("Id") 还是 $("#Id")?

第一种,因为它直接调用了 JavaScript 引擎。

六、VUE 篇

1、写出vue的常用指令 ⭐⭐

指令的本质:语法糖。在编译阶段 render 函数里,会把指令编译成 JavaScript 代码

  • v-for:遍历 data 中的数据,实现列表的渲染(数组或对象)
  • v-if:通 过 true 和 fal se 控制元素是否需要被渲染

  • v-else:搭配 v-if 使用,不需要表达式,当 vi-if 为 false 时才被渲染出来 

  • v-show:通过 true 和 false 控制元素是否显示或隐藏 

  • v-model:实现表单控件和数据的双向绑定(<input>、<select>、<textarea>、components)

  • v-bind:动态绑定元素属性

  • v-on:简写@,事件绑定

  • v-text:渲染字符串,会覆盖原先的字符串

  • 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 控制元素的显示和隐藏。

不同的是:

  1. v-if 当条件为 true 时, 把元素创建并渲染到 Html,为 false 时把元素删除,不会渲染到 Html 上。是一个创建和删除的过程,删除时并不会占用空间位置。

  2. v-shou 无论是 true 还是 false 元素都被渲染出来,当条件为 false 时通过 display: none 控制元素隐藏。是一个显示和隐藏的过程,隐藏时还是会占用空间位置。

应用场景: 

  1. v-show:适合使用在切换频繁的元素上 (显示/隐藏)
  2. v-if:适合使用在切换不频繁,且元素内容很多,渲染一次性能消耗很大的元素上

4、v-if 和 v-for 那个优先级更高?⭐⭐

  1. 在 vue2 中 v-for 比 v-if 的优先级更高。这意味着 v-if 将分别重复运行于每个 v-for 循环中,比较消耗性能,所以不建议 v-for 和 v-if 一起使用,可以把 v-if 放在 v-for 的外层或者把需要使用 v-for 遍历的属性先从计算属性中过滤一次。
  2. 在 vue3 中 v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名,会报错。可以把 v-for 放在 v-if 的外层。

5、v-if 和 v-for 可以一起使用吗?⭐⭐

  • Vue2:可以一起使用,但不建议。在 vue2,v-for 的优先级比 v-if 的高,这意味着 v-if 运行于每一个 v-for 循环中,会造成性能的消耗。解决办法:可以把 v-if 放在 v-for 的外层或者把需要使用 v-for 遍历的属性先从计算属性中过滤一次。
  • Vue3:不可以一起使用,在 vue3,v-if 的优先级比 v-for 的高,这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名,会报错。解决办法:可以把 v-for 放在 v-if 的外层。

6、常用的事件修饰符有哪一些(v-on 指令) ⭐        

  • prevent:阻止事件的默认行为
  • stop:阻止冒泡
  • self:只监听触发该元素的事件,其他不管(currentTarget 和 target为同一个元素时候触发)
  • capture:阻止捕获
  • once:事件只触发一次
  • left:左键事件
  • right:右键事件
  • middle:中间滚轮事件

7、虚拟 DOM?⭐⭐⭐

什么是虚拟 DOM?

  1. 虚拟 DOM 是一种在内存中构建和操作的 JavaScript 对象树,用来描述真实 DOM 的结构信息,是为了解决浏览器性能问题引入的概念。虚拟 DOM 是相对于浏览器渲染出来的真实 DOM 而言的。
  2. 在传统的前端开发中,当数据发生改变时,我们需要直接操作真实的 DOM 来更新页面,但是这种直接操作会带来性能上的问题。因为改变 DOM 会触发浏览器的重绘和重排,频繁操作消耗了大量的计算资源。而且直接操作 DOM 几乎都要遍历整颗 DOM 树,相比之下查找 js 对象属性变化要比查询 DOM 树开销小。
  3. 虚拟 DOM 的出现解决了这个问题。当数据发生改变的时候,我们首先在内存中创建一个虚拟 DOM 树,然后与之前保存的旧的虚拟 DOM 树进行对比,然后通过 diff 算法找出差异,并只更新差异部分对应的真实 DOM。这样就避免了直接操作真实 DOM,减少了性能的开销。
  4. 虚拟 DOM 的好处是通过 diff 算法只更新需要更新的部分,减少了对真实 DOM 的操作次数,避免频繁的重构和重排,减少了性能的开销,提高了渲染效率。支持跨平台,虚拟 DOM 本质上是一个 JS 对象,而 DOM 与平台强相关。相比之下虚拟 DOM 可以在不同的平台上使用,例如浏览器、移动端和桌面应用等。简化逻辑,虚拟 DOM 提供了一个抽象层,让开发者可以更简洁的操作 DOM,提高了代码的可读性和可维护性,简化了代码逻辑。
  5. 虚拟 DOM 结构:sel 标签,date 标签属性,text 标签的文本,children 嵌套的子标签。

虚拟 DOM 是怎么更新数据的?

虚拟 DOM 可以很好的跟踪当前 DOM 的状态,他会根据当前数据生成一个描述当前 DOM 的虚拟 DOM,然后数据改变的时候又会生成一个新的虚拟 DOM,然后通过 diff 算法计算出前后两个虚拟DOM 之间的差异,得出一个最优的更新方法。

虚拟 DOM 是怎么生成的?

首先代码运行会走生命周期,当生命周期走到 created 到 beforeMount 之间的时候,会编译 template 模板成 render 函数。然后在 beforeMount 和 mounted 之间执行 render 函数。当 render 函数运行时 h 函数会被调用,而 h 函数最主要的就是执行了 vnode 函数,vnode 函数主要作用就是将 h 函数传进来的参数转换成 js 对象,即生成虚拟 DOM。之后当数据发生改变时会重新编译生成一个新虚拟 DOM (vdom),然后通过 diff 算法计算出前后两个虚拟 DOM 之间的差异,得出一个最优的更新方法。从而减少了不必要的 DOM 操作,提高了页面的渲染速度和性能。

对 Diff 算法的了解?

简单来说,实现 diff 算法的过程,就是一个执行 patch 函数,patchVnode 函数,updateChildren 函数……这样的一个循环递归的过程。(patch 只执行一次)

  1. 首先调用 patch 函数比较两个虚拟 DOM 的根节点是否是相同节点。如果不同直接替换。
  2. 如果相同,则调用 patchVnode 函数比较两个节点的子级,既属性、文本和子节点。此时,要么新增,要么删除,要么替换。只有都存在子节点时,会执行 updateChildren 函数进一步比较他们的子节点。
  3. 子节点在 updateChildren 函数中进行比较更新,首先会分别添加两个指针指向子节点的第一个节点和最后一个节点,然后会进行 头头比较,头尾比较,尾头比较,尾尾比较,如果匹配上,那么会将旧节点对应的真实 DOM 移到新节点的位置上,指针向中间移动,同时匹配的两个节点会继续调用 patchVnode 函数进行进一步的对比。当指向第一个节点的指针大于指向最后一个节点指针的时候表示匹配结束,此时,多余的元素删除,新增的元素新增。如果上面这几种情况都没有出现,key 就起到了关键性作用,存在 key 时,可以直接通过 key 去找到节点的原来的位置。如果没有找到,就新增节点。找到了就移动节点位置,执行 patchVnode 函数进行进一步的对比,指针向后移。这样查找效率非常高。而如果没有 key 呢,那么压根就不会去原来的节点中查找了。而是直接新增这个节点。这就导致这个节点下的所有子节点都会被重新新增。会出现明显的性能耗。所以,合理的使用 key,也是一种性能上的优化。

真实 DOM 的优缺点

优点是易用,缺点是效率低:解析速度慢,内存占用高。性能差:频繁操作真实 DOM,容易导致重绘和回流。

虚拟 DOM 的优缺点

优点:

  1. 虚拟 DOM 通过 diff 算法只更新需要更新的部分,减少了对真实 DOM 的操作次数,避免频繁的重构和重排,减少了性能的开销,提高了渲染效率。(重绘和回流)
  2. 支持跨平台,虚拟 DOM 本质上是一个 JS 对象,而 DOM 与平台强相关。相比之下虚拟 DOM 可以在不同的平台上使用,例如浏览器、移动端和桌面应用等。(服务器渲染)
  3. 简化逻辑,虚拟 DOM 提供了一个抽象层,让开发者可以更简洁的操作 DOM,提高了代码的可读性和可维护性,简化了代码逻辑。
  4. 提升用户体验

缺点:

  1. 额外的内存消耗和性能开销,虚拟 DOM 需要在内存中维护一个额外的数据结构,并且在每次更新时都要进行比较和计算差异,可能会增加一定的内存消耗和性能的开销。
  2. 首次渲染时慢一些,由于虚拟 DOM 需要在内存中构建一颗 DOM 树,然后再将其转为真实的 DOM 树,所以首次渲染的耗时可能比直接操作 DOM 要长一些。
  3. 不适合所有场景,对于静态内容或较少变化的页面,使用虚拟 DOM 可能会带来不必要的开销。
  4. 提高学习成本。无法进行极致优化。

8、v-for 为什么加 key?不加 key 会怎么样?⭐⭐

什么是就地复用策略?

就地复用策略是 Vue 在 DOM 更新时采用的一种优化方式。在更新现有 DOM 树时,Vue 会尽可能地复用已存在的 DOM 元素,而不是删除并重新插入。这样可以减少不必要的 DOM 操作,提高性能。

Vue 在执行 DOM 更新时会进行四个步骤:

  1. 创建一个新的虚拟 DOM 树。
  2. 对比新旧虚拟 DOM 树,找出差异。
  3. 根据差异对现有的 DOM 树进行更新。
  4. 触发 DOM 更新后的钩子函数。

在第二步中,Vue 会对比新旧虚拟 DOM 树的节点,找出它们之间的差异。对于相同的节点,在更新时 Vue 会尽可能地复用已存在的 DOM 元素。这样做可以避免不必要的 DOM 操作,提高性能。就地复用策略只适用于同一层级的元素之间进行比较。如果两个元素的父元素不同,Vue 会直接删除旧元素并在新父元素中创建新元素。

key 是什么? 

唯一标识。

为什么要在 v-for 上加 key?

v-for 中加上 key 是为了提高列表的渲染性能和提供更好的更新策略。

不加 key 会怎么样?key 有什么用?

  1. 性能问题:如果没有 key,vue 在更新列表时会使用一种就地复用策略,既复用已经存在的 DOM 元素,而不是删除重新插入。这可能会导致错误的元素被复用,导致渲染错误或者不符合预期的结果。加上 key 可以确保正确地复用和更新元素,提高渲染性能。
  2. 顺序问题:如果顺序发生改变,没有设置 key,Vue 将无法识别新旧节点的对应关系,从而导致重新渲染整个列表,而不仅仅是更新需要修改的部分。加上 key 可以帮助 vue 更好的识别新旧节点对应的关系,只更新变化的部分。
  3. 组件状态问题:如果列表中的组件包含状态,没有设置 key 会导致状态丢失或混乱。加上 key 可以确保组件在重用时保持自己的状态。

9、v-for 写在 template 上,不能加 key 怎么办? 

  1. 方法一:在 template 里面套上两层 div,v-for 和 key 写在里面的 div 上面。
  2. 方法二:在下一层需要循环遍历的真实 DOM 上加一个 Key 就可以了。

10、vue2 生命周期⭐⭐⭐

什么是钩子函数?什么是生命周期?

  1. vue 生命周期是指 vue 对象从创建到销毁的过程。
  2. 每个生命周期都有对应的函数,我们把这些函数称为钩子函数。生命周期有11个阶段,即有11个构造函数。

 vue2 的生命周期有哪些?

  1. beforeCreate 创建前:在实例初始化之后,数据观测(data observer) 之前被调用。(只有一些默认的生命周期钩子和默认事件,没有 data,没有 el)
  2. created 创建后:在实例创建完成后同步调用。可访问 data、computed、watch、methods 上的方法和数据,未挂载到 DOM,不能访问到 el 属性,常用于简单的 ajax 请求。如果要进行 dom 操作,那就要用 $nextTick 函数。
  3. beforeMount 挂载前在挂载开始之前被调用。在 beforeMount 之前已经将 template 模板编译成 render 函数。在 beforeMount 的时候 render 函数首次被调用。(还没有挂载到界面,有data有 el)
  4. mounted 挂载后:实例被挂载后调用,Vue 实例已经初始化完成了。实例挂载到 DOM 上,此时可以通过 DOM API 获取到 DOM 节点,可进行数据请求。(el 被新创建的 vm.$el 替换》?)
  5. beforeUpdate 更新前:在数据发生改变后,DOM 被更新之前被调用。适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器 (此时页面旧数据,data 新数据,还没有同步)
  6. updated 更新后在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后调用。组件 DOM 已经更新 ( 此时页面和 data 的数据已经同步,都是最新数据 )
  7. beforeDestroy 销毁前:实例销毁之前调用。this 仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作。
  8. destroyed 销毁后: 实例销毁之后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
  9. activated 激活 :被 keep-alive 缓存的组件激活时调用。
  10. deactivated 取消激活:被 keep-alive 缓存的组件失活时调用。
  11. errorCaptured 错误捕捉:在捕获错误时被调用。收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传,防止组件无限的渲染循环。

注意:mounted 和 updated 不会保证所有的子组件都被挂载或重新渲染,如果希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted  或 updated 内部使用 $nextTick 函数。

父子组件生命周期执行顺序?

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

vue 生命周期的作用是什么? 

vue 的生命周期中有多个事件钩子,通过不同阶段对应不同的钩子函数来实现组件数据管理和 DOM渲染两大功能。

第一次页面加载会触发哪几个钩子?

beforeCreate,created,beforeMount,mounted 

如果加入了 keep-alive 多两个生命周期:

activated,deactiated

如果加入了 keep-alive ,第一次进入组件会执行那些生命周期?

beforeCreate,created,beforeMount,mounted,activated

如果加入了 keep-alive,第 n 次进入组件会执行那些生命周期?

只执行 activated

 创建后和渲染后有什么区别?

created 是实例创建完成后调用,只能访问 data、computed、watch、methods 上的方法和数据,未挂载 dom,还不能获取 dom 节点,如果要进行 dom 操作,那就要用 $nextTick 函数。常用于简单的 ajax 请求。不能访问到 el 属性。mounted 是实例被挂载后调用,vue 实例已经初始化完成了,已经可以获取 dom 节点和操作 dom 节点了。可以访问 el 属性。

渲染后和更新后有什么区别?

updated 与 mounted 不同的是,在每一次的 DOM 结构更新 vue 都会调用一次 updated()  钩子函数!而 mounted 仅仅只执行一次而已。

简述每个周期具体适合哪些场景?

  • beforecreate : 可以在这加个 loading 事件,在加载实例时触发。
  • created : 初始化完成时的事件写在这里,如在这结束 loading 事件,异步请求也适宜在这里调用。
  • mounted : 挂载元素,获取到 DOM 节点。
  • updated : 如果对数据统一处理,在这里写上相应函数。
  • beforeDestroy : 在组件销毁前做一些处理,this.$bus.$off 在子组件销毁后记得进行取消订阅事件,否则会导致多余调用事件监听,造成资源浪费,导致数据错乱。通常在 beforeDestroy 子组件销毁前调用 this.$bus.$off 移除。 例如通过  this.$bus.$off   移除 $eventBus 的订阅事件。
  • nextTick : 更新数据后立即操作 dom。

11、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生命周期
beforeCreatesetup
createdcreated
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmounted
destroyedonUnmounted
activatedonActivated
deactivated

onDeactivated

errorCapturedonErrorCaptured

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 进行开发,可以参考 vue2 和 vue3 的生命周期。

14、vue 里边如何让 css 样式只在当前组件中生效? ⭐

在组件中的 style 前面加上 scoped 就可以了。

15、vue 的 watch 和 computed 有什么区别?他们的应用场景?⭐⭐⭐

  • computed:计算属性,可以修改数据,通过 get 获取数据 set 修改数据,而且它是基于它的依赖缓存,只有相关依赖发生改变时才会重新取值,否则就拿上一次已经计算好存在栈里的值。
  • watch:监听属性,可以监听数据 (data 值) 的改变,支持异步,每当监听的数据变化时都会执行回调进行后续操作。有三个属性:handler 事件句柄,immediate 默认监听,deep 深度监听。

应用场景

  • computed 应用场景:在需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。
  • watch 应用场景:需要在数据变化时执行异步或开销较大的操作时,应该使用 watch。使用 watch 选项允许我们执行异步操作,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

16、$nextTick 事件轮询?⭐⭐

$nextTick 是什么?

事件轮询,把所有的触发事件都放在一个事件栈里 (事件队列),再根据实际情况先后执行。

$nextTick 有什么作用?

  1. 在 DOM 下一次更新完成后延迟回调。
  2. Vue 在更新 DOM 时是异步执行的,在修改数据后视图不会立刻更新,而是等同一事件循环中的所有数据修改完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取 DOM,获取的仍是未修改的 DOM。为了在修改数据后等待 Vue 更新 DOM,可以在数据变化之后立即使用 $nextTick ,这样回调函数将在 DOM 更新完成后被调用,可以获取更新后的 DOM,解决了异步渲染获取不到更新后 DOM 的问题。

$nextTick 的原理?

$nextTick 本质是返回一个 Promise 对象。

$nextTick 的应用场景?

  1. 在钩子函数 created 里面想要获取操作 Dom,把操作 DOM 的方法放在 $nextTick 中。
  2. 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、数据修改后页面不刷新案例?怎么解决?⭐⭐⭐

问题1:vue 无法检测实例被创建时不存在于 data 中的变量

原因:由于 vue2 双向数据绑定的核心原理 Object.defineProperty 数据劫持只能劫持对象的属性,因此在初始化时要对所有的已有属性进行遍历,也就是在初始化实例时通过 Object.defineProperty 劫持 data 中各个属性的  getter/setter,所以变量必在 data 对象上存在才能让 Vue 将它转换为响应式的。

解决方法:把变量放到 data 里面。

问题2:vue 无法检测到 data 中对象,数组的动态添加和删除,不能通过索引直接修改或者赋值,也不能修改数组的长度

原因:vue2 是通过 Object.defineProperty 数据劫持 结合 订阅发布模式 实现双向数据绑定的。通过 Object.defineProperty 来劫持各个已有属性的 getter 和 setter,它不能劫持整个对象,只能劫持已有对象属性,所以只能监听到已有数据是否被修改,不能监听到对象的新增删除属性,而且无法监控到数组下标的变化,引入不能修改数组长度。 

解决方法:通过 this.$set 动态添加,通过 this.$delete 动态移除。通过 Object.assign({},{}) 合并对象并返回新对象和数组。通过 push、pop、shift、unshift、splice、sort、reverse 等直接改变原数组的方法会触发更新。

问题3:异步获取接口数据,DOM 数据不发现变化

原因:Vue 在更新 DOM 时是异步执行的。在修改数据后视图不会立刻更新,而是等同一事件循环中的所有数据修改完成之后,再统一进行视图更新。所以修改完数据,立即在方法中获取 DOM,获取的仍是未修改的 DOM。

解决方法:可以在数据变化之后立即使用 $nextTick,这样回调函数将在 DOM 更新完成后被调用,可以获取更新后的 DOM,$nextTick 解决了异步渲染获取不到更新后 DOM 的问题。

问题4:循环嵌套层级太深,视图不更新

原因:当嵌套太深时,页面也可能不更新,此时可以让页面强制刷新。

解决方法:可以让页面强制刷新,this.$forceUpdate (不建议用)

问题5:路由参数变化时,页面不更新(数据不更新)

原因:路由视图组件引用了相同组件时,当路由参会变化时,会导致该组件无法更新,也就是我们常说中的页面无法更新的问题。路由切换时页面只会渲染第一次路由匹配到的参数。

解决方法:通过 watch 监听 $route 的变化。给 <router-view> 绑定 key 属性,这样 Vue 就会认为这是不同的<router-view>。弊端:跳转到某些路由下没有这个问题,key 属性是多余的

20、Vue.extend 和 Vue.component 的区别?

区别:

  1. vue.extend 的写法步骤更加繁琐一些,要创建构造器,还要 new 一个实例,还要将实例通过 $mount 挂载到特定的元素上。Vue.component 注册组件会自动使用给定的 id 设置组件的名称,然后想在哪里用就在哪里写组件名就可以了。
  2. vue.extend 只能通过自身初始化结构,使用范围可以挂载在某元素下,可以被 Vue 实例的components 引用,可以被 Vue.component 组件引用。vue.component 可通过自身初始化组件结构,可通过引入 vue.extend 初始化组件结构,可通过第三方模板 temple.html 初始化组件结构,使用范围是任何已被 vue 初始化过的元素内。
  3. vue.component 是用来注册全局组件的方法,可以在任何地方使用。而 Vue.extend 是用来创建一个 vue 子类的方法 (一个可复用的组件模板),可以在局部组件中使用。
  4. 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 核心概念(优点):

  1. 双向数据绑定:Vue 框架采用的是 mvvm 模式,相比 MVC 模式,不再局限于数据单向绑定,而是能够实现数据双向绑定、将数据和视图进行实时的同步更新。实现的原理:vue2 是通过 (Object.defineProperty 数据劫持)结合(订阅发布模式)实现双向数据绑定的。vue3 是通过 new Proxy 数据代理实现的。实现了当数据发生改变时,视图会自动更新;当用户操作视图时,数据也会相应地进行更新。
  2. 组件化开发:Vue 鼓励使用组件化开发,将页面拆分成多个独立的组件,每个组件负责特定的功能。组件可以复用、嵌套和组合,提高代码的可维护性和复用性。
  3. 虚拟 DOM:Vue 采用了虚拟 DOM 技术,通过在内存中构建虚拟 DOM,并与真实 DOM 进行比较,只更新需要更新的部分,减少真实 DOM 操作,提高了渲染性能。
  4. 渐进式:Vue 是一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性,根据业务需求逐步选择需要的 vue 功能模块。
  5. 生态丰富:Vue 拥有一个活跃的社区,有大量的第三方插件和工具可以使用。同时,Vue 也提供了官方的路由、状态管理和构建工具等配套库,方便开发者进行项目开发。

23、什么是渐进式?⭐⭐⭐

  1. Vue 是一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思。
  2. 技术上的渐进式选型,也就是说你不用一上来就要选 vue 的全家桶,可以根据自己的业务逐步的选择你需要的 vue 功能模块。
  3. 业务模块的渐进式的调用,这就涉及 vue 中的一个重要的概念-组件化,组件在不需要的时候不加载,当业务需要时再加载。

24、vue 是如何实现双向数据绑定? (原理与缺陷)⭐⭐⭐

什么是数据双向绑定?

Vue 框架采用的是 mvvm 模式,实现了双向数据绑定,当数据发生改变时,立即触发视图的更新。当用户操作视图时,数据也会相应地进行更新。

vue2 是如何实现双向数据绑定的?

原理:

vue2 是通过 Object.defineProperty 数据劫持 结合 订阅发布模式 实现双向数据绑定的。通过 Object.defineProperty 来劫持各个属性的 getter 和 setter,当数据对象的属性被读取或修改时,会触发相应的 getter 和 setter 函数。当数据发生变化时,触发 setter 函数会通知相应的 Dep 调度器,Dep 调度器通知相应的订阅者,让订阅者执行自己的 update 逻辑进行更新视图。

缺点:

  1. Object.defineProperty 数据劫持,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果对象里面还有对象,需要递归。所以性能差。
  2. Object.defineproperty 只能监听到已有数据是否被修改,不能监听到对象的新增删除属性,需要调用相应的方法更新,如 this.$set 和 this.$delete。由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。
  3. 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节点。

优点:

  1. 可以劫持整个对象,而非对象属性,并返回一个新的对象。
  2. 代理对象可以劫持对象属性的访问、新增、删除等操作,不需要初始化时遍历所有属性。
  3. 如果有多层属性嵌套,只有在访问到某个属性的时候才会递归处理下一级属性。
  4. 可以监听到数组变化,例如索引。
  5. 不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性

 缺点:存在兼容性问题,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 有什么区别?⭐⭐⭐

1、Vue2 只支持一个根节点,Vue3 支持多个根节点。

2、生命周期发生了改变 。

  • 没有 beforeCreate 和 Created,替换成 setup 创建实例前。
  • 除了 beforeDestroyed 替换成 onBeforeUnmount,destroyed 替换成 onUnmounted,其他钩子名字相同,但需要添加前缀 on。
  • 新增了两个钩子函数:onRenderTracked, 跟踪虚拟 DOM 重新渲染时调用,在onMounted前触发,页面更新后也会触发。onRenderTriggered,当虚拟 DOM 重新渲染被触发时调用,在onBeforeUpdate之前触发。(在生产环境中会被忽略)

3、Vue2 使用的是选项式 api (optionsApi),Vue3 使用的是组合式 api (CompositionApi)。

  • 选项式api的优点:学习成本低,缺点是不适合大项目的开发,一个功能点的代码太分散了,代码难以理解和维护。
  • 组合式api的优点:①可以将一个功能的所有 methods、data 封装在一个独立的函数中。代码复用性更强,代码组织更清晰,维护更轻松,提高开发效率。②所有的功能都是按需引入的,没有使用到的功能在打包时会被清理掉,减小了包的大小。③通过使用 return 等语句,可以将逻辑分离得更清晰,使代码更易于理解和维护。④由于组合式 api 的优化,Vue3 的性能相比 Vue2有所提升。

4、核心原理不同

  • vue2 是通过 【Object.defineProperty 数据劫持】结合【订阅发布模式】实现双向数据绑定的
  • vue3 是通过 Proxy 数据代理来实现的数据双向绑定的
  • 优缺点:
  • ①vue2 的 Object.defineProperty 数据劫持只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果对象里面还有对象,需要递归。所以性能差。②只能监听到已有数据是否被修改,不能监听到对象的新增删除属性,需要调用相应的方法更新,如 this.$set 和 this.$delete。③也不能监听到数组的添加删除,而且无法监控到数组下标的变化,也不能修改数组的长度,依然可以 this.$set 和 this.$delete 去更新数据。当然也可以使用数组重写的方法实现了数组的响应,比如使用 push 给数组添加一个数据,不需要调用 this.$set 页面也能够更新响应。
  • ①vue3 的 Proxy 数据代理,代理对象可以劫持对象属性的访问、新增、删除等操作,不需要初始化时遍历所有属性。②可以劫持整个对象,而非对象属性,并返回一个新的对象。③不仅可以代理对象,还可以代理数组,可以监听到数组变化,例如索引。还可以代理动态增加的属性。缺点是存在兼容性问题,IE 无法兼容,如果不考虑兼容 IE 浏览器,可以考虑使用 vue3

5、父子传参不同,setup 函数特性

  • 数据和方法写在 setup 中,而不是分别写在 date 和 methods 中。
  • setup 函数中要使用钩子函数和监听器和 reactive 这些,需要从 vue 中导出需要的钩子函数和 wath 和 reactive 等。
  • setup 函数中声明的变量默认不是响应式的,这与在 data 选项中声明的变量不同,不同类型的变量想要转换为响应式的,方法也不太相同:①通过 ref 函数可以将简单数据类型的变量定义为响应式数据。②通过 reactive 将复杂数据类型的变量定义为响应式数据。③通过toRefs转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的。④toRef 函数将响应式对象中的单个属性转换为响应式的。
  • 使用  return 语句返回变量和方法,这样才可以在 template  模板中及其他地方使用。
  • 无法在 setup 函数中使用 this。因为 setup 是在创建实例前执行 (在beforeCreate钩子函数之前执行) ,即不指向 vue 实例,这里的 this 返回 undefined。想要使用 this 需要借助一个方法getCurrentInstance ,该方法返回了当前的实例对象。通过解构赋值的方式拿到当前上下文 ctx 或 proxy,ctx 是普通对象,proxy 是 Proxy 对象,ctx 只能在开发环境下使用,生产环境下的 ctx 将访问不到,建议使用 Proxy。
  • setup 函数接受两个参数:①props:父组件传入的数据,②context:一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值。

27、mvc、mvp、mvvm 架构模式?⭐⭐⭐

MV系列框架中,M和V分别指Model层和View层。Model层是数据模型,用来存储数据。View层是视图,展示Model层的数据。在不同的框架中,可能会有所差别,但主要变的是数据的传输方式

MVC(Model-View-Controller)(JavaEE 中的 SSH 框)

MVP(Model-View-Presenter)(Android)

  • M:Model 模型层,数据模型及其业务逻辑。
  • V:View 视图层,用于与用户实现交互的页面。
  • C:Controller 控制器,用于连接 Model 层和 View 层,完成 Model 层和 View 层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将 Model 层返回给用户。
  • P:Presenter 表示器,用于连接 Model 层和 View 层,完成 Model 层和 View 层的交互,还可以进行业务逻辑的处理。

MVC 是模型-视图-控制器:MVC 是单向通信,必须通过 Controller 来承上启下。Controller 不会更新 view 的数据,view 依赖于 model,通过监听 model 层数据变化自动更新。在 model 发生改变时,需要手动的去更新 view。MVC 是 dom 驱动的,大量操作 dom 使页面渲染性能降低,使加载速度变慢,影响用户体验。

MVC 执行步骤:用户输入 => 被 Controller 层拦截 => Controller 调用 Model 进行业务逻辑处理 / Controller 通知 View 描画页面 => Model 将结果反馈给 View =>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 层的接口将加载后的数据展示给用户。

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 数据代理 来实现的数据双向绑定的。当数据发生改变的时候视图会同步更新,当用户操作视图的时候数据也会相应的更新。

MVVM 缺点

  • bug 很难调试,因为数据双向绑定,所以问题可能在 view 中,也可能在 model 中,要定位原始 bug 的位置比较难。
  • 一个大的模块中的 model 可能会很大,长期保存在内存中会影响性能。
  • 对于大型的图形应用程序,视图状态越多, viewModel 的构建和维护的成本都会比较高。

28、vuex 状态管理?⭐⭐⭐⭐

什么是 VueX、状态管理?

  • Vuex 是 vue 框架中状态管理库,是集中管理项目公共数据的库。
  • 简单来说,状态管理就是把组件的共享状态抽出来,以一个全局单例模式管理,不管任何组件在任何地方都能直接获取这个状态或者触发里面的行为,并且这个状态是响应式的。

VueX 状态管理实现原理?

把多个组件需要共享的变量全部储存在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,并且让这个对象里面的属性做到响应式。

VueX 的核心概念?(五个状态

  1. state:数据状态,用来存储公共管理的数据,通过 this.$store.state 调用数据。
  2. getter:计算属性,类似于 vue 中的 computed,对于 Store 中的数据进行加工处理形成新的数据 (过滤数据),getter 的返回值会根据它的依赖缓存起来,只有当它的依赖值发生改变才会重新计算,通过 this.$store.getters 调用该属性 (获取过滤后的数据)。
  3. mutation:同步方法,用来存放更改 state 状态的同步方法,不可以写异步方法,理论上是修改 state 状态的唯一途径。通过 this.$store.commit 来调用 mutation 中的同步方法。
  4. action:用来存放异步方法,类似于 mutation,区别是 action 不能直接更改 state 里面的状态,需要通过 context.commit 提交 mutation,触发 mutation 里面的逻辑方法去修改 state 里面的状态,action 可以包含任意的异步操作。可以通过 this.$store.dispatch 去调用 action 里面的异步方法。
  5. moudle:模块化,将 store 分割成模块,每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

vuex 的优点? 

  1. 集中管理共享的数据,易于开发和后期维护。
  2. 多层嵌套的组件、兄弟组件间的状态会更好管理维护,高效的实现组件间的数据共享,提高开发效率。
  3. vuex 的状态管理是响应式的,如果 state 里面的状态发生变化,相应的组件也会渲染更新,能够实时保持数据与页面的同步。
  4. js 原生的数据对象写法,比起 localStorage 不需要做转换, 使用方便。
  5. 减少 http 请求,提高浏览器性能。

vuex 的缺点? 

刷新浏览器,vuex 中的 state 会重新变为初始状态。(解决方案:插件 vuex-persistedstate)

什么时候适合用 VueX?

如果不打算开发大型单页应用,使用 vuex 可能是繁琐冗余的。如果项目比较小比较简单,使用 prop 和 $emit 完成父子间的传参,或者使用 $eventBus 实现复杂的组件间的通信也是可以满足需求的。vuex 具体应用在哪取决于项目的规模以及具体的业务场景,可能是为了解决多层嵌套组件之间的通信问题,或是为了更好地管理应用中错综复杂的状态关系,而不能为了用 vuex 而在项目中使用 vuex。

vuex 的应用场景?

  1. 登录状态(token)
  2. 购物车
  3. 权限码
  4. 复杂的组件通信 (兄弟组件的通信,多层嵌套的组件的通信等)
  5. vuex 可配合 sessionStorage 做用户基本信息的持久化存储。

VueX 刷新页面后数据丢失如何解决?

  1. 在刷新前把 state 的数据通过 localStorage 或者 sessionStorage 在本地存储起来,刷新之后再把本地存储中的数据放回 store 的根状态里。
  2. 使用插件 vuex-persistedstate。

29、Vue Router 路由?⭐⭐⭐⭐

Vue Router 是什么?

vue-router 路由是 vue 的一个插件库,用于构建单页面应用。路由模块的本质就是建立起 url 路径和页面之间的映射关系,在 vue-router 的单页面应用中,页面的路径的改变就是组件的切换。路由主要负责将浏览器请求映射到特定的组件代码中,也就是通过解析 URL 来实现页面切换和跳转。

什么是路由懒加载?

路由懒加载也叫延迟加载,既在需要的时候加载,随用随载。也就是不用一开始就去加载所有页面,只加载当前页面。当切换页面的时候,再加载对应的路由。避免不必要的资源加载,提高应用程序的加载速度。

为什么要路由懒加载?

如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,影响页面加载。像进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了 loading 也是不利于用户体验。使用懒加载当路由被访问的时候才加载对应组件,可以减少页面加载用时,提高页面加载速度,提升用户体验。

如何实现路由懒加载?

  1. Vue 异步组件
    {
        path: '/home',
        name: '/Home',
        component:resolve=>require(['@/pages/Home'],resolve),
        meta: {
            show: true
        }
    }
  2. ES6动态加载模块 import( )  ---------推荐使用
    {
        path: '/home',
        name: 'Home',
        component:()=>import(/* webpackChunName:"chunkName" */ '@/pages/Home'),
        meta: {
            show: true
        }
    }    
  3. webpack 提供的 require,ensure() 方法
    {
        path: '/home',
        name: 'Home',
        component:r=>require.ensure([],()=>r(require('@/pages/Home')),'chunkName'),
        meta: {
            show: true
        }
    }    
    // 注:r 就是 resolve。

区别:

  • 使用 Vue 的异步组件技术实现懒加载,这种情况会导致一个组件会生成一个 js 文件。
  • 推荐使用ES6的 import() 方法,指定相同的 webpackChunkName,会合并打包成一个 js 文件
  • 使用 webpack 的 .ensure 技术,多个路由指定相同的 chunkName,会合并打包成一个js 文件。
  • require 在 node 中 ADM 的引入方式,import 是 ES6 中模块化的引入方式。require 是赋值过程并且是运行时才执行,import 是解构过程并且是编译时执行。require  使用的是函数调用的方式,import 使用的是声明式的方式。require 是同步加载模块,import 是异步加载模块。require 的性能相对于 import 稍低,因为 require 是在运行时才引入模块并且还赋值给某个变量。

什么是动态路由?

顾名思义,路由是动态的不是写死的。我们可以根据自己不同的需求加载不同的路由,渲染不同的页面组件。动态路由的使用一般结合角色权限控制一起使用,例如后台管理系统可以根据动态路由实现不同角色权限显示不同菜单。

如何实现动态路由?

Vue 实现动态路由通常有两种方式:

  1. 前端将全部路由规定好,登录时根据用户角色权限来动态展示相应的路由。(企业中更常用)
  2. 路由存储在数据库中,前端通过接口获取当前用户对应的路由列表,后端根据用户的权限动态生成路由表返回,前端进行渲染。缺点:不利于项目的迭代,前端新开发一个页面还要让后端配一下路由和权限。

思路:使用全局前置守卫,在页面加载前将当前用户所用到的路由列表通过 addRoutes 方法注入到 Router 实例中。

具体实现(第1种): 

  1. 首先构建基础的静态路由,如登录页,修改密码,404页。
  2. 用户登录后,服务端返回一个 token,将 token 存储在本地 cookie 中,记住用户的登录状态,跳到首页。
  3. 配置路由守卫 beforeEach,在页面跳转前进行拦截,判断是否已获得 token,没有 token 就去登录,获得 token 之后就判断是否有用户信息,当有用户权限信息的时候,说明所有可访问路由已生成,如果访问没权限的页面会自动进入404页面。没有用户信息就发起请求获取用户基本信息,将用户基本信息和用户权限信息存到 Vuex 里。
  4. 将用户权限和路由表每个页面的需要的权限作比较,生成最终该用户可访问的路由表。
  5. 调用 addRoutes 动态添加用户可访问路由表。
  6. 更新路由。

具体实现(第2种):

  1. 首先构建基础的静态路由,如登录页,修改密码,404页。
  2. 配置路由守卫 beforeEach,在页面加载前进行拦截,判断 Vuex 中有没有缓存路由,有的话直接渲染。没有的话发起请求获取路由信息,后端根据该用户的角色权限返回对应的路由信息,前端获取后解析并保存到 Vuex 中。
  3. 调用 addDynamicRoutes 方法,生成动态路由,通过 addRoutes 方法将动态路由注入到 Router 实例中。
  4. 更新路由。

如何实现带参数的动态路由匹配?

有的时候,一个 user 组件,它应该对所有用户进行渲染,但是用户 ID 不同。可以在路径中使用一个动态字段来实现,我们称之为 路径参数 :,通过在路径后添加一个冒号以及一个动态字段实现动态路由。

{
    path: '/home/users/:pid',
	name: 'users',
	component:()=>import('../views/users')
}

$route 和 $router 的区别?

  • $router:是一个全局部的对象,是用来操作路由的,包括路由的跳转方法,钩子函数等。
  • $route:是一个局部的对象,是用来获取路由信息的,接收路由参数和读取路由路径等。

如何实现 Vue 路由跳转?

1. router-link:声明式导航,在页面中调用,通过:to跳转,通常在不需要逻辑跳转时使用。

2. this.$router.push:编程式导航,在函数里面调用,通常在需要逻辑跳转时使用。

3. this.$ router.replace:类似于 push,但 history 栈中添加记录。

4. this.$router.resolve:打开新窗口跳转。

5. this.$router.go(n):相当于当前页面向前或向后跳转多少个页面。

路由跳转方式和参数传递方式

  • 路由跳转方式:path 和 name。
  • 参数传递方式: params 和 query。

params 传参和 query 传参的区别:

  • params 传参的参数不会显示在的 URL 中。query 传参的参数会显示在 URL 中。
  • params 接收参数:this.$route.params.参数。query 接收参数:this.$route.query.参数
  • params 刷新参数消失,需要设置动态路由在路径后加冒号 :绑定动态参数。query 刷新后参数还在。
  • params 只能通过 name 引用路由,name 要在路由内配置。query 可以通过 path 和 name 引用。
  • 都不能传对象和着数组引用类型数据,只能传字符串类型数据。

path 跳转和 name 跳转的区别:

  • path 和 name 都可以实现页面的切换。都需要在路由中配置。编程式和声明式都可以使用。
  • name 可以使用 params 和 query 传参。path 会忽略 params,所以 path 不能结合 params 使用,只能使用 query 传参。
  • name 跳转过去的页面刷新之后,参数会失效,而 path 的方式不会      

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发生了变化,但是不会立即向后端服务器发送请求,但是如果点击刷新,就会重新向后端服务器发送请求

区别: 

  1. hash 模式的 url 中有 # 号,不太优雅。history 模式的 url 中没有 # 号,更美观。
  2. hash 模式不需要在服务器层面上处理,但不利于 SEO。history 模式依赖 H5 API 和后台配置,没有后台配置的话,页面刷新时会出现 404,需要和后端人员配合配置一下 url 重定向,如果 URL 匹配不到任何静态资源,则重定向到首页路由。由于依赖 H5 所以 history 模式有浏览器兼容问题。
  3. hash 模式 url 发生变化时,变化的是 url 的#号后面的 hash 值,hash 虽然包含在 url 中,但是没有被包含在 http 请求中,对后端没影响,所以不会向后端发送请求,所以 hash 改变也不会重新加载页面。history 模式的 URL 改变会向服务器发出请求,同时也会在浏览器历史记录中添加一条记录,可能会重新加载页面。
  4. 相同的 url,history 会触发添加到浏览器历史记录栈中,hash 不会触发。
  5. 因为 vue 是一个单页面应用,所以默认只有 hash 模式,没有 history 模式,需要手动设置。使用 history 模式,需要适当的服务器配置,将页面请求全部重定向到 index.htm

30、Vue 组件?⭐⭐⭐⭐

什么是 Vue 组件?

组件是 Vue 强大的功能之一,Vue 鼓励使用组件化开发,将页面拆分成多个独立的组件,每个组件负责特定的功能,我们可以在每个组件内封装自定义内容与逻辑,组件可以复用、嵌套和组合,提高代码的可维护性和复用性。

组件开发的好处?

  1. 组件可以提升整个项目的开发效率,避免重复的工作量。
  2. 提高复用性。
  3. 能够把页面抽象成多个相对独立的模块,使代码逻辑清晰,方便项目的后期维护。
  4. 便于协同开发,不用把大量精力放在内部的实现上,实现模块化开发。

提高开发效率、提高复用性、使代码逻辑更加清晰,方便项目的后期维护、便于协同开发 

什么情况下需要封装组件?

  1. 解决避免重复的工作量问题:在项目中多次出现相同特征的模块,该组件未来还有可能出现在其他的地方,且开发这样一个功能模块成本比较高,或者是该组件后续会发生部分特征的改变,且会影响类似特征的模块。
  2. 使代码逻辑更加清晰:如果每一个模块都写到同一个页面,这样模块的交互功能和处理逻辑很多,造成页面代码量很大,各个模块中也会有相类似的方法,后续代码的可读性不高,功能的维护复杂度提高。
  3. 不用把大量的精力放在内部实现上:提高团队的协作问题,在一个团队里,每个人擅长部分都不一样的,针对这样的情况,我们可以将其单独进行封装,让使用组件者不用把大量精力放在内部的实现上。

组件封装流程?怎么封装组件的?

使用 Vue.extend 方法创建一个组件,然后使用 Vue.component 方法注册组件。但是我们一般用脚手架开发项目,每个 .vue 单文件就是一个组件。在另一组件 import 导入,并在 components 中注册。首先建立好自定义模板,准备好组件的数据的输入,子组件定好 props 里面的数据和类型,接收父组件传过来的数据,然后通过 $emit( )暴露输出子组件的数据返回给父组件。

传统流程:

  1. 创建 Xxx.vue 文件,对组件进行封装
  2. 通过 import 的方式将组件导入 import Xxx from '...'
  3. 对组件进行注册 componments:{ Xxx }
  4. 使用组件:<xxx/>

views 放页面级的组件、commen 放公共组件、feature 放功能组件

vue 的组件通信有哪些方法?

父子传参:

  • 父传子:props
  • 子传父:$emit

边界访问 (父子通信): 

  • 访问父组件:$parent
  • 访问子组件:$children
  • 访问根实例:$root
  • 访问子组件实例或子元素:$refs

兄弟通信:

  • 通过父组件进行兄弟组件之间通讯(不建议)
  • 事件总线:$eventBus ($emit / $on / $off)
  • 状态管理模式:vuex 

跨级通信: 

  • 依赖注入:provide / inject (父传子)
  • $attrs / $listeners (inheritAttrs)
  • 状态管理模式:vuex

事件总线 $eventBus:

  1. 创建事件总线
    // main.js
    import Vue from 'vue'
    // 创建事件总线,就相当于创建了一个新的 vue 实例
    const bus = new Vue()
    // 把 bus 挂载到了 Vue 的原型上, 保证所有的组件都能通过 this.$bus 访问到事件总线
    Vue.prototype.$bus = bus
  2. 页面使用:发布事件-传递值

    this.$bus.$emit('send', 'hello')  //(事件名,参数)
  3. 订阅事件,接收组件值

    // 在 created 中订阅,('事件名', 事件回调函数)
    this.$bus.$on('send', msg => {
      console.log(msg)
    })

注意:在子组件销毁后记得进行取消订阅事件,否则会导致多余调用事件监听,造成资源浪费,导致数据错乱。通常在 beforeDestroy 子组件销毁前调用 this.$bus.$off 移除。

为什么组件的 data 必须是一个函数?

  1. 可以反复的调用
  2. 可以反复创建新对象
  3. 避免组件间的数据冲突

keep-alive 是什么?

keep-alive 是 vue 内置的一个组件,是一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中,使用 keep-alive 包裹动态组件时,会缓存不活动的组件实例,保留组件状态,避免重新渲染,缓存组件能加快速度,降低消耗资源。(组件进行切换的时候,默认会进行销毁,消耗资源)

具体实现

当组件在 <keep-alive> 内被切换的时候,activated (激活) 和 deactivated (取消激活) 这两个生命周期钩子函数将会被对应执行。页面第一次进入的时候,钩子触发的顺序是 created->mounted->activated。页面退出的时候会触发 deactivated,当再次前进或者后退的时候只触发 activated。当 keep-alive 组件激活时,触发 activated。keep-alive 组件停用时调用 deactivated。

31、react、vue、Anguar 框架?⭐

Vue 优点:

  1. 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb。
  2. 双向数据绑定:Vue 框架采用的是 mvvm 模式,实现数据双向绑定、将数据和视图进行实时的同步更新。
  3. 组件化:保留了 react 的优点,实现了组件的封装和复用,在构建单页面应用方面有着独特的优势。
  4. 虚拟 DOM:Vue 采用了虚拟 DOM 技术,通过在内存中构建虚拟 DOM,并与真实 DOM 进行比较,只更新需要更新的部分,减少真实 DOM 操作,提高了渲染性能。
  5. 渐进式:Vue 是一款渐进式 JavaScript 框架,可以根据业务需求逐步选择需要的 vue 功能模块。
  6. 简单易学:国人开发,中文文档,不存在语言障碍 ,相对易于理解和学习。
  7. 运行速度更快:相比 react,同样是操作虚拟 dom,就性能而言,vue 存在很大的优势。

Vue 缺点:

  1. 存在兼容问题,vue2 不支持 IE8 以下,vue3 不支持 IE 浏览器。
  2. 社区可能没有 Angular 和 React 那么丰富。
  3. Vue 不缺入门教程,可是缺乏高阶教程与文档。
  4. 因为是单页面应用,不利于 SEO 优化。
  5. 初次加载时耗时多。

React 优点:

  1. 速度快:在渲染过程中,React通过在虚拟DOM中的微操作来实现对实际DOM的局部更新。
  2. 跨浏览器兼容
  3. 单向数据流:Flux 是一个用于在 JavaScript 应用中创建单向数据层的架构,它随着 React 视图库的开发而被 Facebook 概念化。
  4.  模块化:为你程序编写独立的模块化 UI 组件,这样当某个或某些组件出现问题是,可以方便地进行隔离。
  5. 兼容性好:比如使用 RequireJS 来加载和打包,而 Browserify 和 Webpack 适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。
  6. 数据驱动视图,持服务器端渲染。

React 缺点:

  1. React 不适合单独做一个完整的框架,做大型项目需要和其他框架组合使用(ReactRouter 和 Flux)
  2. React 的库非常庞大,新手很难理解需要花费一定的时间,使用内联模板和 JSX,使编码变得复杂。

Anguar 优点:

  1. 模板功能强大丰富,自带了极其丰富的 angular 指令。
  2. 是一个比较完善的前端框架,采用了 MVC 模式,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能。
  3. 自定义指令,自定义指令后可以在项目中多次使用。
  4. angularjs 模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助。
  5. angularjs 由谷歌开发,这也意味着他有一个坚实的基础和社区支持。
  6. 调试与维护:有专门用于浏览器的插件 batarang。

Anguar 缺点:

  1. Angular 框架过重,不像 Vue 和 React 是非常轻量级的框架,过大的框架导致性能方面消耗严重。适用在大型的浏览器端,大型网站项目。

  2. angular 入门很容易但深入后概念很多,学习中较难理解,学习成本相对来说较高。

  3. 文档例子非常少,官方的文档基本只写了 api,一个例子都没有,很多时候具体怎么用都是google 来的,或直接问 misko,angular 的作者。

  4. 指令的应用的最佳实践教程少,angular 其实很灵活,如果不看一些作者的使用原则,很容易写出四不像的代码。

32、说一下 vue 的服务端渲染 SSR?⭐

什么是 服务端渲染 SSR?

  • SSR:服务端渲染,顾名思义页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的 html 就可以了。
  • CSR:传统使用的是 CSR,客户端在请求时,服务端不做任何处理,直接将前端资源打包后生成的 html 返回给客户端,客户端去加载执行 js 代码才能渲染生成页面内容,同时完成事件绑定,然后客户端再去通过 ajax 请求后端的 API 获取数据更新视图

服务端渲染的优势?

  • 首屏渲染快,减少网络传输,响应快,用户体验好。
  • 对浏览器搜索引擎友好,搜索引擎爬虫可以看到完整的程序源码,有利于SEO。
  • 服务端渲染不用关心浏览器兼容性问题,运算过程都在服务端完成。

服务端渲染的缺点?

  1. 网络传输数据量大,占用部分服务器运算资源,不容易维护,前端修改部分 html/css 后端也要改。
  2. 增加开发的复杂程度,涉及构建和部署的更多要求。服务器渲染应用程序,需要处于 Node.js server 运行环境。
  3. 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,需要准备相应的服务器负载,并明智地采用缓存策略。

七、uni-app 篇

1、uniapp 打包过程(安卓 Android)⭐

  1. 如果是使用私有证书,打包前得生成安卓的 .keystore 证书文件。
  2. 打开 manifest.json 配置文件进行配置: 基础配置 (AppID、应用名称、应用描述、应用版本号、vue 版本号)、App 图标配置 (不配置默认是 uni-app 图标)、App 启动界面配置 (不配置启动时是 uni-app 默认图)、APP 模块配置、APP 权限配置等。
  3. 配置好后,点击 HbuilderX 的发行,选择原生APP-云打包,勾选 Android 选项,填写 Android 包名,勾选私有证书 (也可以选择使用 DCloud 公有证书),填写证书别名 alias,证书私钥密码 (自己设置的),上传证书文件 (生成 keystore 文件的地址),取消广告联盟中的所有广告 (个人选择),点击打包。
  4. 打包成功后 HbuilderX 控制台会自动返回 APP 下载地址,然后可以对该链接在电脑中进行下载,也可以将该链接复制到手机浏览器,在手机中对该软件进行下载。

详细步骤:uni-app Android 打包详细步骤

2、uniapp 打包过程(苹果 ISO)⭐

  1.  ISO 系统打包需要有苹果开发者账号,新建一个 APPid,设置 Bundle ID,申请 IOS 发布证书并保存证书私钥密码,申请 IOS 发布描述文件。
  2.  打开 hbuilderx 点击发行-原生 APP 云打包,勾选 ISO 选项,填写 Bundle ID 和证书私钥密码,上传描述文件和发布证书,点击打包。
  3.  等待控制后台返回下载链接,点击链接进行下载 IPA 文件。
  4.  在 iTunes connect 上创建 APP,上传 IPA 到 APP store,设置 app 信息提交到商店进行审核

详细步骤: uni-app ISO 打包详细步骤

3、uniapp 小程序打包过程 ⭐

  1. 进入微信公众平台注册微信小程序获取 AppID
  2. 打开 manifest.json 配置文件,点击微信小程序配置,填写 AppID 和相关配置
  3. 点击 HbuilderX 的发行,选择小程序微信,填写小程序名称和 AppID 点击发布
  4. HbuilderX 自动调开微信开发者工具,点击右上角上传按钮,填写版本号和备注,点击上传
  5. 登录微信公众平台,选择版本管理,点击提交审核

详细步骤: uniapp 小程序打包详细步骤

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、rpx、px、em、rem、%、vh、vw 的区别是什么?⭐

  • rpx:相当于把屏幕宽度分为750份,1份就是1rpx。
  • px:绝对单位,页面按精确像素展示。
  • em:相对单位,相对于它的父节点字体进行计算。
  • rem:相对单位,相对根节点html的字体大小来计算。
  • %:一般来说就是相对于父元素。
  • vh:视窗高度,1vh等于视窗高度的1%。
  • vw:视窗宽度,1vw等于视窗宽度的1%。

8、uniapp 如何监听页面滚动?

使用 onPageScroll 监听

9、如何让图片宽度不变,高度自动变化,保持原图宽高比不变?

给 image 标签添加 mode=‘widthFix’

10、uni-app 的优缺点?

优点:

  1. 一套代码可以生成多端。
  2. 学习成本低,语法是vue的,组件是小程序的。
  3. 拓展能力强。
  4. 使用HBuilderX开发,支持vue语法。
  5. 突破了系统对H5条用原生能力的限制。

缺点: 

  1. 问世时间短,很多地方不完善。
  2. 社区不大。
  3. 官方对问题的反馈不及时。
  4. 在Android平台上比微信小程序和iOS差。
  5. 文件命名受限。

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、JQ、VUE、uni-app、小程序的页面传参方式? ⭐

JQ传参:通过url拼接参数进行传参。

VUE传参:

  1. 可以通过标签 router-link 跳转传参,通过 path+ 路径,query+ 参数。
  2. 可以通过事件里的 this.$router.push 跳转传参。

uni-app,小程序传参 :通过跳转路径后面拼接参数来进行跳转传参。

13、vue、微信小程序、uni-app 绑定变量属性区别?

vue 和 uni-app 动态绑定一个变量的值为元素的某个属性的时候,会在属性前面加上冒号 ":"。小程序绑定某个变量的值为元素属性时,会用两个大括号 {{}} 括起来,如果不加括号,为被认为是字符串。

15、小程序组件传参有哪些方式

全局数据、Storage存储、eventBus。

八、HTTP 请求篇

1、浏览器输入url后都经历了什么?⭐⭐⭐

  1. 查看缓存(浏览器缓存\系统缓存\路由器缓存),如果缓存中有则直接显示页面内容。
  2. 域名解析(DNS解析)获取相应的 IP 地址。
  3. 浏览器向服务器发起 TCP 连接,建立 TCP 三次握手。
  4. 浏览器向服务器发起 HTTP 请求,请求数据包。
  5. 服务器处理请求,返回响应数据至浏览器。
  6. 关闭 TCP 连接(四次挥手)。
  7. 浏览器解析 HTML 代码并请求资源(js、css、图片等)。
  8. 浏览器进行页面布局渲染。

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的区别?⭐⭐⭐

共同点:都是存储在浏览器本地的、相对不安全。

不同点:

  1. 写入方式:cookie 是由服务端写入的,需要服务器支持。而 localStorage 和 sessionStorage 都是由前端写入的。
  2. 生命周期:如果没有设置过期时间,cookie 默认关闭浏览器立即消失。而 localStorage 默认是永久储存,除非手动清除。sessionStorage 是页面关闭的时候就会自动清除。
  3. 存储大小:cookie 的存储大小大概4kb,存储条数为 20 条,超过20条后面的数据会替代前面的数据。localStorage 和 sessionStorage 存储大小大概5M,存储条数不限制。
  4. 兼容性:cookie 可以兼容低版本浏览器。localStroage 和 sessionStorage 是 H5 新特性,无法兼容 ie8 及以下的浏览器。
  5. 多Tab:LocalStorage 可以在多个 Tab(窗口) 打开,SessionStorage 限制必须在同一个页面,多 tab(窗口) 的时候不可以共享数据。
  6. 应用场景:前端向后端发起请求时会自动携带 cookie 里面的数据,但是 SessionStorage 和 localStorage 不会,所以他们的应用场景也不同。Cookie 一般用于存储登录验证信息 SessionID 或者 token。localStorage 常用于存储不易变动的数据,减轻服务器的压力。SessionStorage 可以用来检测用户是否刷新进入页面,如音乐播放器恢复播放进度条的功能。

4、如何实现可过期的 localstorage 数据?

  1. localStorage 只能用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。所以要实现可过期的 localStorage 缓存的中重点就是:如何清理过期的缓存。 目前有两种方法,一种是惰性删除,另一种是定时删除。
  2. 惰性删除:指某个键值过期后,该键值不会被马上删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。实现方法是,存储的数据类型是个对象,该对象有两个key,一个是要存储的 value 值,另一个是当前时间。获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除 Cookie。
  3. 定时删除:每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对 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、 get 和 post 的区别? 

  1. get 参数写在 url 上,即 HTTP 协议头上。post 参数放在 HTTP 的请求体内。get 以 ? 分割url 和传输数据,参数之间以 & 相连。
  2. get 提交的数据最大是2k,post 理论上没有限制。其中,get 发送的数据在原则上 url 长度无限制,实际上取决于浏览器,浏览器会限制 url 的长度。

  3. get 在浏览器回退时不产生影响,post 会再次提交请求。(get请求中的回退操作实际上浏览器会从之前的缓存中拿结果)

  4. get 请求参数会被完整保留在浏览器历史记录里,而 post 中的参数不会被保留。

  5. get 请求只能进行 url 编码,post 支持多种编码方式。

  6. get 比 post 更不安全,因为参数直接暴露在 url 上,所以不能用来传递敏感信息。

  7. get 产生一个 TCP 数据包,浏览器会把 http header 和 data 一并发送出去,服务器响应200(返回数据)。post 产生两个 TCP 数据包,浏览器先发送 header,服务器响应100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)。【在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的 TCP 在验证数据包完整性上,有非常大的优点。并不是所有浏览器都会在 POST 中发送两次包,Firefox就只发送一次】

8、常见的HTTP状态码?

1开头(收到请求,正在处理)

2开头(请求成功)

200   (成功)  201   (已创建)  202   (已接受) 203   (非授权信息)

204   (无内容)  205   (重置内容) 206   (部分内容)

3开头 (请求被重定向)

300   (多种选择)  301   (永久移动)  302   (临时重定向)  303   (查看其他位置)

304   (未修改) 305   (使用代理) 307   (临时重定向)

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 版本不受支持)

6、Token 能放在 cookie 中吗?

token 一般是用来判断用户是否登录的,它内部包含的信息有 uid(用户唯一的身份标识)、time(当前时间戳)、sign(签名)。token 可以存放在 Cookie 中,token 是否过期,应该由后端来判断,不该前端来判断,所以 token 存储在 cookie 中只要不设置 cookie 的过期时间就可以了,如果 token 失效,就让后端在接口中返回固定的状态表示 token 失效,需要重新登录,再重新登录的时候,重新设置 cookie 中的 token 就行。

7、Token 认证流程?

  1. 客户端使用用户名跟密码请求登录。
  2. 服务端收到请求,去验证用户名与密码。
  3. 验证成功后,服务端签发一个 token ,并把它发送给客户端。
  4. 客户端接收 token 以后会把它存储起来,比如放在 cookie 里或者 localStorage 里。
  5. 客户端每次发送请求时都需要带着服务端签发的 token (把 token 放到 HTTP 的 Header 里)
  6. 服务端收到请求后,需要验证请求里带有的 token ,如验证成功则返回对应的数据。

8、什么是同源策略?为什么要有同源策略?⭐⭐

  • 同源策略:域名、协议、端口号都相同才能互相访问。
  • 目的:防止黑客攻击,xss、csrf 攻击 ( 导致了前后端无法访问 ,也就是跨域问题)

9、XSS攻击是什么?

  • XSS是跨站脚本攻击、向目标网站插入恶意代码、大量用户访问网站时运行恶意脚本获取信息
  • 原理:通过向 Web 页面里面插入 script 代码,当用户浏览这个页面时,就会运行被插入的script 代码,达到攻击者的目的。XSS 的危害一般是泄露用户的登录信息 cookie,攻击者可以通过 cookie 绕过登录步骤直接进入站点。
  • XSS类型:XSS 的分类分为反射型和存储型。反射型就是临时通过 url 访问网站,网站服务端将恶意代码从 url 中取出,拼接在 HTML 中返回给浏览器,用户就会执行恶意代码。存储型就是将恶意代码以留言的形式保存在服务器数据库,任何访问网站的人都会受到攻击。

10、CSRF攻击是什么?

  • CSRF跨站点请求伪造,攻击者盗用了用户的身份,以用户的身份发送恶意请求,但是对服务器来说这个请求是合理的,这样就完成了攻击者的目标。
  • 原理:用户打开浏览器,访问目标网站A,输入用户名和密码请求登录,用户信息在通过认证后,网站A产生一个 cookie 信息返回给浏览器,这个时候用户以可正常发送请求到网站A。用户在没有退出网站A之前在同一个浏览器打开了另一个新网站B,新网站B收到用户请求之后返回一些攻击代码,并发出一个请求要求访问返回cookie的网站A,浏览器收到这些攻击性代码之后根据新网站B的请求在用户不知道的情况下以用户的权限操作了cookie 并向网站A服务器发起了合法的请求。

11、什么是跨域?为什么有跨域问题?⭐⭐⭐

  • 跨域是由于浏览器的同源策略,当一个页面中某个请求的地址的 “协议”,“域名“,”端口“ 三者之间任意一个与当前页面的地址不同,在前后端分离的情况下导致无法直接通讯和访问,这种就是跨域。
  • 出现跨域的话主要是因为浏览器的同源策略导致的,浏览器同源策略主要是为了防止黑客攻击,像 xss、csrf 攻击,但也导致了域名、协议、端口其中一个不同的话前后端无法访问,也就是跨域问题。其实跨域就是不允许跨域请求资源,是浏览器的安全限制。

12、跨域的解决方案有哪几种?⭐⭐⭐

  1. cors 跨域资源共享:目前最常用的一种解决办法,通过设置后端允许跨域实现,类似后端给前端开了个后门一样,允许所有请求通过。通过 setHeader 设置 'Access-Control-Allow-Origin' 为通配符,允许所有请求通过。res.setHeader('Access-Control-Allow-Origin', '*');
  2. nodejs 中间件代理跨域:原理是让请求发给代理服务器,静态页面和代理服务器是同源的,然后代理服务器向后端服务器发请求,服务器和服务器之间不存在同源限制。具体实现:在vue.config.js 文件下配置,首先开启本地服务器 devServer,更改端口号 port,设置代理 proxy,设置代理目标(baseURL),是否允许跨域,设置反向代理路径,在接口地址前添加反向代理路径。
  3. jsonp:只能发送 get 请求。 原理是 script 标签可以跨域请求资源,将回调函数作为参数通过 ”?“ 拼接在 url 中,后端收到请求,调用该回调函数,并将数据作为参数返回去,前端通过回调函数获取响应数据。注意设置响应头返回文档类型,应该设置成 javascript。

13、什么是 options 请求?

options 请求就是预检请求。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起 options 请求,即 CORS 预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。

13、什么是浏览器内核?有什么用?有哪一些?

什么是浏览器内核?

浏览器最核心部分, Rendering Engine 渲染引擎,一般叫浏览器内核。负责对网页语法的解释并渲染网页,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。

有什么用?

浏览器内核对于浏览器而言,是基础,是依托,决定了网页的呈现的内容、格式以及效果。

有哪一些?

  • Trident内核:IE内核
  • Gecko内核: Firefox内核(火狐浏览器)
  • WebKit内核:Safari
  • Blink内核:    Chrome(基于webkit,Google与Opera Software共同开发)
  • presto内核:  Opera(已改用Google Chrome的Blink内核)

14、什么是浏览器兼容问题?

因为不同浏览器的浏览器内核不一样,所以对同一段代码有不同的解析,造成页面显示效果不统一的情况。在大多数情况下,我们的需求是,无论用户用什么浏览器来查看我们的网站或者登陆我们的系统,都应该是统一的显示效果。

15、优雅降级和渐进增强(浏览器兼容问题)

  • 渐进增强:一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
  • 优雅降级:一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
  • 其实渐进增强和优雅降级并非什么新概念,只是旧的概念换了一个新的说法。在传统软件开发中,经常会提到向上兼容和向下兼容的概念。渐进增强相当于向上兼容,而优雅降级相当于向下兼容。

16、http 和 https 有何区别?

  • http:HTTP 协议运行在 TCP 之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。
  • https:HTTP 运行在 SSL/TLS 之上,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份。

18、说一说前端性能优化手段?⭐⭐⭐

前端性能优化分为两类,一类是文件加载更快,另一类是文件渲染更快。

加载更快的方法:

  1. 让传输的数据包更小:图片压缩和文件压缩。
  2. 减少网络请求的次数:雪碧图/精灵图、节流防抖。
  3. 减少渲染的次数:缓存(HTTP缓存、本地缓存、Vue 的 keep-alive 缓存等)。

 渲染更快的方法:

  1. 提前渲染:ssr 服务器端渲染。
  2. 避免渲染阻塞:CSS 放在 HTML 的 head 中,JS 放在 HTML 的 body 底部。
  3. 避免无用渲染:懒加载。
  4. 减少渲染次数:对 dom 查询进行缓存、将 dom 操作合并、使用减少重排的标签。

19、网站性能优化的好处?怎么优化?

好处:

  • 用户角度:优化能够让页面加载得更快、对用户的操作响应得更及时,能够给用户提供更为友好的体验。
  • 服务商角度:优化能够减少页面请求数、或者减小请求所占带宽,能够节省可观的资源。

怎么优化?

页面级别的优化:HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等

代码级别的优化:js 中的 DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等。

页面级优化:

  1. JavaScript 压缩和模块打包
  2. 按需加载资源
  3. 在使用 DOM 操作库时用上 array-ids
  4. 缓存
  5. 启用 HTTP/2
  6. 应用性能分析
  7. 使用负载均衡方案
  8. 为了更快的启动时间考虑一下同构
  9. 使用索引加速数据库查询
  10. 使用更快的转译方案
  11. 避免或最小化 JavaScript 和 CSS 的使用而阻塞渲染
  12. 用于未来的一个建议:使用 service workers + 流
  13. 图片编码优化

20、axios的拦截器原理及应用?

应用场景:axios 为开发者提供了这样一个 API:拦截器。拦截器分为请求拦截器和响应拦截器。请求拦截器:在接口请求之前做的处理,比如为每个请求带上相应的参数,像 token、时间戳等。 返回拦截器:在接口返回之后做的处理,像对返回的状态进行判断,例如判断 token 是否过期。

21、创建 ajax 过程?

  1. 创建XHR对象:new XMLHttpRequest()。
  2. 设置请求参数:request.open(Method, 服务器接口地址)。
  3. 发送请求:request.send(),如果是 get 请求不需要参数,post 请求需要参数request.send(data)。
  4. 监听请求成功后的状态变化:根据状态码进行相应的处理。
    XHR.onreadystatechange = function(){ 
        if(XHR.readyState == 4 && XHR.status == 200){ 
            console.log(XHR.responseText);
        }
    }

22、说一下 fetch 请求方式?

fetch 是一种 HTTP 数据请求的方式,是 XMLHttpRequest 的一种替代方案。Fetch 函数就是原生js,没有使用 XMLHttpRequest 对象。fetch() 方法返回一个 Promise 解析 Response 来自 Request显 示状态(成功与否)的方法。

23、说一下浏览器如何渲染页面的?

浏览器拿到 HTML,先将 HTML 转换成 dom 树,再将 CSS 样式转换成 stylesheet,根据 dom 树和 stylesheet 创建布局树,对布局树进行分层,为每个图层生成绘制列表,再将图层分成图块,紧接着光栅化将图块转换成位图,最后合成绘制生成页面。 

24、说一下有什么方法可以保持前后端实时通信?

  • 轮询:是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有 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 是什么?⭐

  1. Git是一个免费的、开源的分布式版本控制系统,git 支持分布式部署,可以有效、高速的处理从很小到非常大的项目版本管理。
  2. 分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone),在本地机器上拷贝一个完整的 Git 仓库。
  3. 我们可以在本地建一个版本库,每当我们需要修改时,就可以把之前的版本提交并标明此版的特点。这样文件夹里就只有一个编程文档了。当你需要哪个版本时,只要在版本库中恢复一下就可以了。

2、git 常用的命令?⭐

  • git clone 地址 :克隆
  • git status:检查文件所处的状态
  • git add . :提交到暂存区
  • git commit -m "描述":提交到本地仓库
  • git push:推送到远程仓库
  • git pull:拉取远程仓库
  • git reset --hard 哈希值(前6位): 版本回退
  • git log:打印日志
  • git branch 分支名:创建分支
  • git branch -a:查看所有分支
  • git checkout 分支名 (检出):切换分支
  • git checkout -b:创建分支的同时切换到新分支
  • git branch :删除分支
  • git remote -v:查看当前路径
  • rm -r 文件名:删除文件夹

3、git 和 svn 的区别?⭐⭐

  1. Git是分布式版本控制工具,SVN是集中式版本控制工具。
  2. Git没有一个全局的版本号,而SVN有。
  3. Git和SVN的分支不同。
  4. Git把内容按元数据方式存储,而SVN是按文件。
  5. Git内容的完整性要优于SVN。
  6. Git无需联网就可使用(无需下载服务端),而SVN必须要联网(须下载服务端)。因为Git的版本区就在自己电脑上,而 svn 在远程服务器上。

4、Git项目如何配置,如何上传至GitHub?

  1. 注册登录 github
  2. 创建 github 仓库
  3. 安装 git 客户端
  4. 绑定用户信息
  5. 设置 ssh key
  6. 创建本地项目以及仓库
  7. 关联 github 仓库
  8. 推送项目到 github 仓库

5、你们公司git分支是怎么管理的?⭐⭐

git 在开发中用来进行项目托管,项目中一般有三个分支,主分支master 只有项目负责人有操作权限,平时开发功能是在 develop 开发分支下创建的子分支也就是 feature 功能分支下开发的,功能开发完测试没问题了,切换到 develop 分支并拉取最新的 develop 分支,然后合并自己的任务分支并推送 develop 分支,再由项目负责人合并到 master 分支上。

6、git 工作流⭐

  • master:主分支(上线发布的稳定版)
  • dev(develop):开发分支
  • feature:功能分支(从 develop 拉取出来做新功能的分支)
  • fix:普通补丁分支(从 develop 拉取出来做修复bug的分支)
  • hotfix:维护分支(从 master 拉取出来的用于做紧急修复的分支,修复完后应马上合并回master 和 develop 分支)
  • release:预发布分支(从 develop 拉取出来的用于做测试的分支,检测完后合并到 master 分支发布,release 上做的 bug 修改要合并回 develop 分支)

6、git版本冲突是什么?⭐

所谓冲突指的就是,两个开发者对同一个文件同一个位置做出了不同的内容修改,然后在进行分支合并时或者是从远程仓库拉取代码到本地库时,就会产出冲突报错;两个不同的版本,导致 git 不知道要接受那个。

7、如何解决git版本冲突?⭐

  1. 寻找冲突:首先要找到出现冲突的位置,通过 git merge 分支名 如果有冲突的话会提示那些文件有冲突。

  2. 修改冲突:找到冲突文件对比差异,然后手动修改为正确的。如果使用命令的话,使用 cat 冲突文件 查看不同分支代码的差异,然后 vim 进入冲突文件进行删除修改正确代码。

  3. 提交修改后的冲突文件:分别执行命令 git add 添加到暂存区、git commit -m 提交到本地库、最后 git push 推送到远程库就可以了。

十、功能的实现

1、vuex 实现购物车功能

原理:比如购物车商品数量,在商品列表页面添加商品会更改这个值,在详情页增减商品数量会更改这个值,进入购物车删除商品也会更改这个值,通过提交 mutation 来更改这个值就很简单明了。当然也可以每次都调用获取购物车数量的接口,也可以实现,但每一次的 HTTP 请求都是对浏览器性能的消耗。

具体实现:

  1. 给加入购物车一个点击事件,并把当前点击商品的所有内容,即 item 作为参数,用来传参。
  2. 通过点击事件获取到商品参数,通过 vuex 的 this.$store.dispatch(’触发的函数’,‘参数’) 把商品参数传给 vuex 的 action 函数。
  3. 在 vuex 的 state 中初始化购物车
  4. 编写 action 里面的异步方法,获取传过来的商品参数 item,通过 context.commit 提交 mutation
  5. 编写 mutation 里面的逻辑,获取到传过来的商品参数后做一个判断去重,因为同一个商品,第一次点击是添加,第二次点击是商品数量的增加。遍历在 state 中定义保存到本地的数组,判断 state 中保存到本地的 id 和传过来的 id 是否一样,如果一样,当前的商品的数量加加。如果 id 不一样则是第一次点击添加商品,通过 push 将商品参数 item 和 复选框状态以及商品数量添加到本地数组,计算总价,遍历保存到本地的商品,如果选中的状态就计算总价,保存到本地。
  6. 从本地拿数据并渲染页面,从 vuex 中取值,获取到加购的商品参数和单价和总价,通过 computed 保存到本地,然后渲染购物车页面
  7. 点击单件商品上的复选框并同步底部的全选复选框,点击底部全选复选框并同步上面商品的复选框,并加加减减计算价钱,不管是选择单件商品还是全选还是减商品、加商品、删除商品都是通过 this.$store.dispatch 去调用 vuex 中的 action 里面的异步方法,通过 context.commit 触发 mutation 里面的相应逻辑。
  8. 点击商品前面的复选框:触发 mutation 里面的相应同步方法,首先先改变复选框的状态,false 改为 true ,true 改为 false。通过 every( ) 方法检测购物车商品数组里面的元素的复选框是否都选中(true),是的话返回 true 否则返回 false , 把值赋给全选按钮,同步全选按钮。然后遍历购物车商品数据,判断商品复选框是否选中,选中就计算商品总价,然后保存到本地。
  9. 点击底部全选按钮:触发 mutation 里面的相应同步方法,更改全选复选框状态,遍历购物车数据,把全选复选框状态赋值给每个商品的复选框状,让他们同步,遍历购物车所有数据,判断如果商品的复选框为 true,则计算总价,然后保存到本地。
  10. 减商品:触发 mutation 里面的相应同步方法,判断当前商品的件数是否大于1,大于1的话商品件数减一,然后遍历商品数据重新计算总价。保存商品数据到本地。
  11. 加商品:触发 mutation 里面的相应同步方法,商品件数加一,遍历商品数据,重新计算商品总价,然后保存商品数据到本地。
  12. 删除数据:触发 mutation 里面的相应同步方法,通过 splice 删除当前数据,遍历商品数据,重新计算商品总价,然后保存商品数据到本地

2、用 js 实现一个轮播图的思路?

十一、手撕代码

常见前端手写代码总汇(持续更新中...)-CSDN博客

十二、机试

十三、其他

1、自我介绍

2、你是怎么样优化代码的

3、你是如何优化项目的

4、开发中都使用了哪些工具

5、开发中遇到过什么bug

6、你对加班的看法

7、为什么离开上一家公司

8、你是如何学习的

  • 36
    点赞
  • 231
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值