HTML

1. HTML5 新特性

  1. 新的语义化元素:main、footer、header、nav、section
  2. 新的表单控件:date、time、email、url、search
  3. 新的 API:
  • 音视频:audio 和 video 元素
  • 绘图图形:canvas 元素
  • 本地存储:localStorage,sessionStorage
  • 多线程操作:Web Worker

引申的内容:

  1. 语义化:是什么?有什么用?
  • 有利于 SEO,代码更加规范和可读
  • SEO 是搜索引擎优化:提升网站在搜索引擎排名
    • 可以通过 meta 标签设置 description / keyword 等值,一般值由产品需求提供
    • 可以通过服务端渲染技术 SSR (react - next / vue - nuxt)
  1. canvas
  • 概念:画布,用来绘制图形图表
  • 项目中主要用来完成数据可视化功能:比如数据大屏展示/适配/实时更新等
  1. localStorage / sessionStorage
  • 区别
    • localStorage 永久存储,通常用来存储用户的登录唯一标识 token
    • sessionStorage 临时会话存储(关闭浏览器会自动清空内容)
  • 扩展其他存储方案
    • cookie
      • 储存体积小(4KB)
      • 发送请求,cookie 会自动携带
    • webStorage
      • 储存体积更大(2mb)
      • 必须手动携带(封装 axios 中请求拦截器在请求头中携带 token 参数)
  1. WebWorker
  • 多线程操作,可以用于项目性能优化:比如将计算量大的任务交给 web worker 处理(大文件上传)

2. 常见的行内元素和块级元素有哪些?它们的区别?

  1. 行内元素 span i a
  2. 块级元素 div p ul/li header/footer form
  3. 行内块元素 img input

区别:

  1. 行内和行内块元素一行多个,块级元素独占一行
  2. 行内元素不能设置 width/height,行内块和块级元素可以设置

3. 谈谈 iframe

  1. 概念:能在当前页面嵌套其他页面(能在当前应用嵌套其他子应用)

  2. 问题

  • 同源情况下, 可以通过 iframe 标签直接获取子页面的数据(比如,iframe.documentWindow 获取到 window 对象)
  • 不同源情况下,postMessage 方法 和 message 事件的方式进行通信
    • postMessage 方法,用来发送数据
    • message 事件,用来接受数据
  1. 引申

公司有多个项目,需要汇总到一个项目中一起使用,简单实现可以用 iframe 标签,也可以使用前端微应用框架:qiankun / micro 来实现

CSS

1. CSS3 新特性

  1. 新增了选择器
  • :last-child 匹配父元素的最后一个子元素
  • :nth-child(n) 匹配父元素的第 n 个子元素
  1. 边框特性
  • border-radius 圆角
  1. 颜色与不透明度
  • opacity: 0.5;
  • color: rgba(0, 0, 0, 0.5)
  1. 阴影
  • text-shadow 文字阴影
  • box-shadow 盒子阴影
  1. transform 变形
  • transform: rotate(9deg) 旋转
  • transform: scale(0.5) 缩放
  • transform: translate(100px, 100px) 位移
  1. 过渡与动画
  1. 媒体查询
  • @media 用来做响应式布局

至少说 4 个点

2. 盒模型

  1. 概念:页面渲染时,DOM 元素所采用的布局模型。 可通过 box-sizing 进行设置。

  2. 分类:

  • content-box (W3C 标准盒模型)
    当给元素设置 width 和 height 时,只会改变 width + height。
  • border-box (IE 盒模型)
    当给元素设置 width 和 height 时,会改变 width + height + padding。
  • 其他未实现了

3. BFC

  1. 概念
  • BFC,又称为块级格式化上下文,指的是:一个独立的渲染区域.
  1. 触发条件(开启 BFC)
  • 设置 overflow,即 hidden,auto,scroll
  • 设置浮动,不包括 none
  • 设置定位,absolute 或者 fixed 等方式
  1. 具体规则
  • BFC 是一个块级元素,块级元素在垂直方向上依次排列。
  • BFC 是一个独立的容器,内部元素不会影响容器外部的元素。
  • 属于同一个 BFC 的两个盒子,外边距 margin 会发生重叠,并且取最大外边距。
  • 计算 BFC 高度时,浮动子元素也要参与计算。
  1. 应用
  • 阻止 margin 重叠
  • 清除浮动,防止高度塌陷
  • 阻止标准流元素被浮动元素覆盖

4. 选择器权重&优先级

  • !important > 行内样式 > #id > .class > tag > * > 继承 > 默认
  • CSS 选择器浏览器是 从右往左 依次解析

5. CSS 预处理器(Sass/Less/Stylus)

  1. 概念:CSS 预处理器定义了一种新的语言,可以更方便的维护和管理 CSS 代码

  2. 比如我最常用的 sass

  • 可以使用 $ 来定义变量
  • 可以样式嵌套
  • 可以通过 mixin 来定义混合,复用代码
  1. Vue 项目中使用
  • 通过安装 sass sass-loader 依赖就可以直接使用 sass 了
  • 开发中我会定义全局变量文件,那么这个文件每个 sass 使用都要引入,太麻烦了。所以我通常在项目中会进行脚手架配置,scss - additionalData 让其给每个 sass 文件自动追加引入全局变量文件的代码,从而让其全局生效
  1. 扩展
  • 现阶段样式变量 css 也支持了
    • 通过 :root{ --xxx-xx: xxx } 定义
    • 通过 var(--xxx-xx) 使用

6. flex 布局

  1. 概念

Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

容器默认存在两根轴:主轴和交叉轴(也叫做侧轴)。默认水平方向的为主轴,垂直方向为侧轴。

  1. 容器的属性(2-3 个)
  • flex-direction 定义主轴的方向
  • flex-wrap 定义是否换行
  • flex-flow 是 flex-direction 属性和 flex-wrap 属性的简写形式
  • justify-content 定义项目在主轴上的对齐方式
  • align-items 定义项目在侧轴上的对齐方式
  • align-content 定义项目在交叉轴上如何对齐
  1. 项目的属性(2-3 个)
  • order 定义项目的排列顺序。数值越小,排列越靠前,默认为 0。
  • flex-grow 定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。
  • flex-shrink 定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。
  • flex-basis 定义了在分配多余空间之前,项目占据的主轴空间。它的默认值为 auto,即项目的本来大小。
  • flex 是 flex-grow, flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto。
  • align-self 允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。
  1. 扩展:flex: 1 啥意思
  • flex-grow: 1 如果存在剩余空间, 该项目会放大。
  • flex-shrink: 1 如果剩余空间不足,该项目会缩小。
  • flex-basis: 0% 设置为 0% 之后,即不占据主轴空间,但是因为有 flex-grow 和 flex-shrink 的设置,该项目会自动放大或缩小。

7. 实现两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.container {
display: flex;
height: 100px;
}
.left {
width: 200px;
height: 100%;
background: pink;
}
.right {
flex: 1;
height: 100%;
background: deeppink;
}

8. 隐藏页面元素方式

  1. display: none 不占位。不会响应 DOM 事件。
  2. opacity: 0 占位,但不可见。会响应 DOM 事件。
  3. visibility: hidden 占位,但不可见。不会响应 DOM 事件。
  4. position: absolute; left: -10000px 移动到屏幕外
  5. z-index: -1 将别的定位元素遮盖掉当前元素

9. 让元素水平垂直居中方式

  1. 利用绝对定位, 子元素未知宽高
1
2
3
4
5
6
7
8
9
.father {
position: relative;
}
.son {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
  1. 利用绝对定位, 子元素未知宽高
1
2
3
4
5
6
7
8
9
10
11
.father {
position: relative;
}
.son {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
  1. 利用绝对定位, 子元素必须明确宽高
1
2
3
4
5
6
7
8
9
10
11
12
.father {
position: relative;
}
.son {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
margin-left: -100px;
margin-top: -100px;
}
  1. 利用 flex
1
2
3
4
5
.father {
display: flex;
justify-content: center;
align-items: center;
}

JavaScript

1. 说说 JS 的数据类型

  1. 基本数据类型
  • number
  • string
  • boolean
  • null
  • undefined
  • symbol
    • 用来给变量唯一的值
    • 用来给对象唯一的属性名(对象属性名的类型只能是:string,symbol)
  • bigint
    • 当 number 类型计算超过最大数(Number.MAX_SALE_NUMBER)或小于最小值,计算就会出现问题,这个时候可以用 bigint 解决(实际开发很少见)
  1. 引用数据类型
  • object
  • function
  • array

2. 如何判断 JS 数据类型

  1. typeof

大部分类型都能检查出来,但是对于 null\object\array 无法区分

  1. A instanceof B

简单理解:检查 A 是否是 B 的实例
真正理解:检查 A 的某一个__proto__是否和 B.prototype 指向同一个对象

主要用来检测引用数据类型

  1. Object.prototype.toString.call(xxx).slice(8, -1)

完美解决方案,可以检测所有数据类型,开发中使用 toString 方法去封装工具函数去检测类型

  1. Array.isArray

检测是否是数组类型

  1. A === B

检测 A 和 B 的值和类型都要相等

3. 说说常见的数组方法

  1. 更新数组的方法
  • push 给数组最后添加一个元素
  • pop 删除数组最后一个元素
  • unshift 给数组最前面添加一个元素
  • shift 删除数组最前面一个元素
  • splice 删除/新增指定下标元素
  • sort 排序
  • reverse 反转
  1. 遍历元素的方法
  • forEach 遍历
  • map 返回一个新数组,新数组长度和原数组一致,但内部的值往往会发生变化。(长度不变,值变)
    • 在 React 中更新数据中某个值,往往使用 map 方法。
  • filter 返回一个新数组,新数组长度往往比原数组更少,但内部的值和原数组一致。(长度变,值不变)
    • 在 React 中删除数据中某个值,往往使用 filter 方法。
  • reduce 常用于统计、累加和求和等功能。
    • 购物车模块,计算总价。
  • find 查找某个元素,找到返回这个元素,找不到返回 undefined。
  • findIndex 查找某个元素的下标,找到返回这个元素下标,找不到返回 -1。
  • every 所有返回 true 整体才返回 true,只要有一个返回 false,整体就返回 false。
  • some 只要有一个返回 true,整体就返回 true,只有全部返回 false,整体才返回 false。
  1. 其它方法
  • slice 截取数组中某些元素
  • concat 拼接数组
  • join 将数组内部元素以某种方式拼接成字符串
  • includes 判断是否包含某个元素,包含返回 true,不包含返回 false
  • indexOf 判断是否包含某个元素,包含返回其下标,不包含返回-1

至少能说个 6-7,并且相对复杂的方法

4. 常见的 DOM 操作有哪些

  1. 新增 DOM 元素
  • document.createElement() 创建 DOM 元素
  • xxxDom.appendChild() 将某个 dom 元素插入到 xxxDom 内
  1. 删除 DOM 元素
  • xxxDom.removeChild() 将 xxxDom 下面某个子元素删除
  • xxxDom.remove()将 xxxDom 自身删除
  1. 修改 DOM 元素
  • xxxDom.innerText / xxxDom.textContent 设置元素的文本内容
  • xxxDom.innerHTML 设置元素的 html 内容
  1. 获取/查询 DOM 元素
  • document.getElementById() 根据 id 选择器获取某个 DOM 元素
  • document.querySelector()根据任意选择器获取找到的第一个 DOM 元素
  • document.querySelectorAll() 根据任意选择器获取找到的所有 DOM 元素集合

5. 说说你对闭包的理解

  1. 概念

通过 chrome 开发者调试工具得知: 闭包本质是内部函数中的一个容器(非 js 对象), 这个容器中包含了引用的局部变量

  1. 产生原因
  • 函数嵌套
  • 内部函数引用外部函数的局部变量
  • 调用外部函数时就会产生闭包
  1. 闭包的生命周期
  • 产生:当内部函数创建时

  • 死亡:当内部函数没有变量引用,成为垃圾对象,就会自动被 GC(垃圾回收机制)自动回收

  1. 闭包作用:
  • 延长局部变量生命周期(活的久点)
  • 让函数外部可以间接操作函数内部的局部变量数据
  1. 闭包在项目中的应用
  • React 中复用函数的手段,高阶函数形式
  • 实际开发中很少使用闭包,我一般研究源码时发现闭包的场景,vue2 响应式原理中数据劫持阶段,内部通过闭包形式保存 dep 对象,这个 dep 对象在 vue2 响应式原理非常重要

6. 谈谈 this 指向

普通情况下,this 指向看函数的调用方式:

  1. 直接调用(默认绑定),this 指向 window, 严格模式(‘use strict’)下指向 undefined

  2. 对象调用函数(隐式绑定),this 指向调用的对象

  3. call/apply/bind 调用函数(显示绑定),this 指向传入第一个参数

  • call/apply/bind 区别和联系:
    • call/apply 都会立即执行函数,bind 返回一个新函数
    • call/apply 执行函数时函数的 this 指向第一个参数,bind 方法返回的新函数的 this 指向第一个参数,原函数不变
    • call/bind 方法传参是一致的, 可以 n 个参数, apply 只能两个参数,第二个参数是数组
  1. new 调用函数,this 指向生成的实例对象

特殊情况:

  1. 箭头函数:this 指向包裹它离它最近的函数的 this(指向外部函数的 this)

  2. 回调函数:

  • 定时器回调函数:window,严格模式下 undefined
  • DOM 事件回调函数:指向绑定的事件的 DOM 元素
  • Vue 生命周期函数 / methods 中的函数 / 微信小程序生命周期函数:指向组件实例对象

7. 说说原型与原型链

  1. 原型
  • 我们说的原型,指的是两个原型属性:__proto__ 和 prototype
  • prototype 叫做显示原型属性
  • __proto__ 叫做隐式原型属性
  • 每个函数都有一个显式原型属性,它的值是一个对象,我们叫做原型对象。
  • 这个原型对象上默认会有一个 constructor 方法,指向函数本身,有一个 __proto__ 属性,指向 Object 的原型对象
  • 每个实例都有一个隐式原型属性,它的值指向其对应构造函数的原型对象。

特殊情况:

  • Function.prototype === Function.__proto__ 他们指向同一个对象
  • Object.prototype.__proto__ === null 这里是原型链的尽头
  1. 原型链
  • 概念:从对象的 __proto__ 开始, 连接的所有对象, 这个结构叫做原型链,也可称为“隐式原型链”
  • 作用:用来查找对象的属性
  • 规则:在查找对象属性或调用对象方法时,会先在对象自身上查找, 找不到就会沿着原型链查找,找到就返回属性的值,最终来到 Object.prototype.__proto__,找不到返回 undefined
  • 应用:利用原型链可以实现继承
    • Vue 中全局事件总线 $bus
    • 项目中 $api / $http 汇总所有接口函数

8. 说说 JS 的垃圾回收机制

在 JS 中对象的释放(回收)是靠浏览器中的垃圾回收器来回收处理的

  1. 垃圾回调器
  • 浏览器中有个专门的线程, 它每隔很短的时间就会运行一次
  • 主要工作:判断一个对象是否是垃圾对象,如果是,清除其内存数据,并标记内存是空闲状态
  1. 垃圾回收策略
  • 机制 1:标记清除法
    • 简述:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
    • 缺点:
      • 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块
      • 分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。
    • 解决:可以使用 标记整理(Mark-Compact)算法,标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存

至少说上面内容

  • 机制 2:引用计数法
    • 简述:它把对象是否不再需要简化定义为对象有没有其他对象引用到它。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。
    • 缺点:
      • 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
      • 解决不了循环引用导致的无法回收问题。
  1. JS 的 V8 引擎垃圾回收机制使用的是基于标记清除算法,不过对其做了一些优化。
  • 针对新生区采用并行回收。
  • 针对老生区采用增量标记与惰性回收。

9. 说一下 JS 的事件循环机制

  1. 概念:异步代码执行机制

  2. 流程:

  • js 主线程会依次执行所有代码,遇到同步代码依次执行,遇到异步代码会交给相应管理模块(分线程)去处理:
    • 比如遇到定时器,会交给定时器管理模块管理,它会计时,到点时会自动将回调函数添加到回调队列中(宏任务队列)
    • 比如遇到 DOM 事件,会交给 DOM 管理模块管理,它会绑定事件,当触发事件时会自动将回调函数添加到回调队列中(宏任务队列)
    • 比如遇到 ajax 请求,会交给 ajax 管理模块管理,它会发送请求,等响应回来时会自动将回调函数添加到回调队列中(宏任务队列)
  • js 主线程并不会等,会依次执行后面的代码,直到所有代码执行完毕,js 主线程就会开启事件轮询
  • js 主线程会遍历回调队列中回调函数,依次取出,同步执行
    • 回调队列分为两种队列:宏任务队列和微任务队列
    • 宏任务队列:定时器回调函数、ajax 回调函数、DOM 事件回调函数
    • 微任务队列:Promise.then/catch/finally、mutationObserver(nextTick 方法的原理)
    • 优先执行微任务队列中的回调函数,直到全部执行完毕,
    • 在执行宏任务队列中第一个回调函数,执行完又会执行,微任务队列中的回调函数,直到全部执行完毕,
    • 在执行下一个宏任务队列中回调函数,执行完又会执行,微任务队列中的回调函数,直到全部执行完毕,以此类推

10. Web Worker

  1. 概念

Web Worker 是 H5 新特性,允许我们开辟分线程,运行 js 代码。

  1. 使用
  • const worker = new Worker('./xxx.js') 创建分线程执行 js 脚本
  • 主线程通过 worker.onmessage 事件接受分线程的消息
  • 主线程通过 worker.postMessage 方法向分线程发送消息
  • 分线程通过 self.onmessage 事件接受主线程的消息
  • 分线程通过 self.postMessage 方法向主线程发送消息
  1. 应用

主要用于 js 中大量计算工作,比如大文件上传中计算文件的 hash,使用了 web worker

11. 说出 ES6 常用新语法

  1. 简单的语法
  • const 与 let
  • 解构赋值
  • 形参默认值
  • 扩展运算符: …
  • 模板字符串
  • 对象的属性与方法简写
  • 模块化语法
  1. 比较复杂的语法
  • 箭头函数
    • 编码简洁
    • 不能通过 new 来创建实例对象,没有 constructor 方法
    • 内部没有 arguments, 可以通过 rest 参数来代替
    • 没有自己的 this, 使用外部作用域中的 this, 不能通过 bind 来绑定 this
    • 说到 this 指向,平时我专门总结过 this 指向
  • 还有 class 与 extends
    • 主要用来做继承的,是构造函数+原型的语法糖
    • 平时在项目主要还是通过原型的方式去继承:比如$bus
  • promise / generator / async & await(引申 js 事件循环机制)
    • promise…(细说 promise)
    • promise 并不是解决回调地狱的最佳方案,后面推出了 generator
    • generator 惰性函数(懒),暂停 yield 执行 next,但是使用太复杂了,后面又推出了 async 函数
    • async、await、promise 一起使用,是项目中解决回调地狱的最佳方案
  • Proxy(引申 vue2 和 vue3 响应式原理)
    • 可以对对象进行代理,当读取/设置对象上的属性时,会执行相应的 get/Set
    • 这个内容主要在 Vue 响应式原理中使用了,Vue3 响应式通过 Proxy 实现的,原有和新增属性都是响应式。
  • Map / Set / WeakMap / WeakSet (引申 vue3 响应式原理)
    • 他们都是新的存储数据的结构
    • Map 类似于对象,存储 key-value 的结构,特点:1. 有序 2. key 是任意类型
    • Set 类似于数组,特点:1. 值是唯一的
    • WeakMap 类似于 Map,存储 key-value 的结构,特点:1. 有序 2. key 必须是对象类型 3. 一旦 key 没有外部引用,会自动删除
    • WeakSet 类似于 Set,特点:1. 值是唯一的 2. 值必须是对象类型 3. 一旦值没有外部引用,会自动删除

12. 说说 ES6 的 promise

  1. 概念

异步代码解决方案,用于解决回调地狱问题。

  1. promise 对象内部有 3 种状态
  • pending 初始化状态
  • resolved / fulfilled 成功状态
  • rejected 失败状态

状态只能变化一次,只能有以下两种变化:

  • pending --> resolved
  • pending --> rejected

状态发生变化后不能在改了。

  1. 如何改变 promise 的状态
  • 调用 resolve(), 改成成功状态
  • 调用 reject(), 改为失败状态
  • throw new Error(), 改为失败状态
  1. promise 实例对象上的方法
  • then 接受两个回调(一般只接受一个),第一个是成功回调,第二个是失败回调
  • catch 接受一个回调,是失败回调
  • finally 接受一个回调,不管成功/失败都会触发
  1. Promise 构造函数上的方法
  • Promise.resolve() 返回一个成功的 promise 对象
    • 也可能返回失败的 promise 对象,比如 Promise.resolve(Promise.reject())
  • Promise.reject() 返回一个失败的 promise 对象
  • Promise.all([promise1, promise2, …]) 只有所有 promise 成功才成功,只要有一个 promise 失败就会失败
  • Promise.allSettled([promise1, promise2, …]) 只要所有 promise 状态发生变化就成功,结果值包含所有 promise 的结果值(不管成功/失败)
  • Promise.race([promise1, promise2, …]) 只要有一个 promise 成功/失败,就成功/失败。
  1. 应用
  • 在项目中一般是使用 axios 发送请求时会使用,返回值是一个 promise 对象,结合 async await 来处理
  • 如果同时要发送多个请求的话,可以使用 Promise.all() 方法来处理

13. 谈谈模块化语法

  1. Commonjs 模块化语法
  • 主要用于 NodeJS 端
  • 语法:
    • 引入:require
    • 暴露:exports / module.exports
  1. ES6 模块化语法
  • 主要用于浏览器端

  • 语法:

  • 引入:import

  • 暴露:export

  • 总结

    • 如果模块采用默认暴露:import xxx from ‘xxx’
    • 如果模块采用分别/统一暴露:
      • 如果需要引入模块部分内容:import { xxx } from ‘xxx’
      • 如果需要引入模块全部内容:import * as xxx from 'xxx'
  • 扩展

    • 无论什么暴露方式,暴露的一定是一个对象
      • 默认暴露:对象上添加一个 default 属性,值为暴露的内容
      • 分别暴露/统一暴露:直接在对象上暴露内容
    • import { xxx } from './xxx' 引入暴露内容的某个属性(部分)
    • import xxx from './xxx' 引入暴露内容的 default 属性(只要 default)
    • import * as xxx from './xxx' 引入暴露内容的所有内容(全部)

14. 谈谈箭头函数

  • 编码简洁
  • 不能通过 new 来创建实例对象,没有 constructor 方法
  • 内部没有 arguments, 可以通过 rest 参数来代替
  • 没有自己的 this, 使用外部作用域中的 this, 不能通过 bind 来绑定 this
  • 说到 this 指向,平时我专门总结过 this 指向

15. 浅度克隆和深度克隆

  1. 浅度克隆
  • object.assign()
  • 扩展运算符: { …obj }
  • Array.prototype.slice()
  • Array.prototype.concat()
  1. 深度克隆
  • JSON.parse(JSON.stringify())
  • 自定义深度克隆
  • lodash 中 cloneDeep
  1. 区别
  • 浅度克隆: 对当前对象进行克隆,基本类型克隆生成新值,引用类型克隆的是地址值(所以当修改对象内部引用类型数据时,原对象也会发生变化)
  • 深度克隆: 会完全复制整个对象,包括这个对象所包含的内部对象(所以不管如何修改对象数据,原对象都不会发生变化)。
  1. 深度克隆应用

权限管理功能中使用深度克隆克隆了异步路由表

16. 节流和防抖

TypeScript

1. JavaScript 和 TypeScript 的区别

  1. JavaScript 没有重载概念,TypeScript 有可以重载和接口类型合并
  2. TypeScript 扩展更多的类型: 接口 interface、泛型、enum、unknown、any、void、never 等;
  3. TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中;

2. interface 和 type 的区别

  1. interface 只能定义引用数据类型,不能定义基本数据类型。而 type 都可以;
  2. interface 定义对象类型不够灵活,type 都可以;
  3. type 声明的类型不能重名,interface 声明的重命名会声明合并;
  4. 两者继承的方法不同;
  • interface 使用 extends 继承
  • type 使用 & 关键字

总结: 一般开发中对象类型用 interface 定义,数组类型用 type

3. 泛型

  1. 概念

在接口、类、函数的时候,不预先定义类型,而是在使用时定义类型

  1. 泛型默认值

<T = string>

  1. 泛型约束

<T extends U>

  1. 泛型工具类型:Pick(挑选)、Omit(排除)、Partial(可选)、Required(必填)

  2. 泛型应用:

  • axios: request<any, Xxx>()
  • react: useState<Xxx>()
  • vue3: ref<Xxx>() reactive<Xxx>()

Server

1. 谈谈 http 协议

  1. 概念

HTTP 是超文本传输协议(Hypertext Transfer Protocol),是浏览器与服务器通信的协议。

  1. 报文

协议通信的内容我们称为报文。

浏览器发送给服务器叫做请求报文,服务器返回给浏览器的叫做响应报文。

报文由报文首行、报文头部、空行、报文体组成。

  1. 请求报文

请求报文:请求首行、请求头、空行、请求体组成。

请求首行主要有:请求方式、请求地址、get 请求的查询字符串参数、协议和版本号

请求头主要有:

  • Connection: keep-alive 保持长链接, 能共享一个 TCP 链接通道来发送请求,通信效率更高
  • Content-Type 请求体参数类型
    • 我们使用 axios 发送请求,一般是:application/json 格式
    • 我们使用原生 form 表单发送请求,一般是:application/x-www-form-urlencoded 格式
  • User-Agent 用户代理, 可以识别用户的客户端环境
  • Referer 请求来源地址, 防盗链
  • Cookie / token 用户唯一标识

请求体主要有:POST 请求的参数

  1. 响应报文

响应报文:响应首行、响应头、空行、响应体组成。

响应首行主要有:协议和版本号、响应状态码

响应状态码分为 5 种类型:1xx、2xx、3xx、4xx、5xx(1 开头、2 开头、3 开头、4 开头、5 开头)

  • 1xx 请求正在处理
  • 2xx 请求处理成功
    • 200 请求成功
  • 3xx 请求需要进一步处理(请求重定向)
    • 301 永久重定向
    • 302 临时重定向
    • 304 重定向到浏览器缓存中(协商缓存)
  • 4xx 客户端错误
    • 401 认证/授权失败
    • 403 禁止访问
    • 404 找不到资源
    • 407 token 过期/失效
  • 5xx 服务器错误
    • 500 服务器内部错误

响应头:

  • 强制缓存字段 Cache-Control / Expires
  • 协商缓存字段 Etag / Last-Modified
  • CORS 解决跨域字段:Access-Control-Allow-Origin

响应体:响应的具体数据

2. HTTP 1.1 和 HTTP 2.0 的区别

  1. 二进制格式(Binary Format)
  • HTTP/1.1 通信内容可以是文本或二进制
  • HTTP/2 通信内容都是二进制
  1. 多路复用(MultiPlexing)
  • HTTP/1.1 长链接(connection: keep-alive)是串行的,后面的请求等待前面请求的返回才能发送
  • HTTP/2 多路复用,一个连接里,客户端和服务器都可以同时发送多个请求或响应,而且不用按照顺序一一发送
  1. 头部压缩
  • HTTP 1.1 通信的请求头内容太多了,多次通信字段重复
  • HTTP/2 实现了头部压缩( gzip 或 compress ),体积更小,同时只会发送不同的字段,和相同字段的索引号,体积更小
  1. 服务器推送(server push)
  • HTTP 1.1 协议不能服务器向浏览器发送消息,需要借助 ajax 轮询或 WebSocket 实现
  • HTTP/2 允许服务器向浏览器主动发送消息

3. HTTP 和 HTTPS 协议的区别

  1. HTTP 协议是明文传输的,HTTPS 通过 SSL 加密传输的;
  2. 使用不同的连接方式,端口也不同,HTTP 协议默认端口是 80,HTTPS 协议默认端口是 443;

4. TCP 的三次握手和四次挥手

三次握手的目的:是为了确认双方的接收能力和发送能力是否正常。

第一次握手:由浏览器发送给服务器,服务器确定浏览器的发送能力
第二次握手:由服务器发送给浏览器,浏览器确定的服务器接受和发送能力
第三次握手:由浏览器发送给服务器,服务器确定的浏览器的接受能力

四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据,连接处于的是半释放的状态。(确保双方都接受完毕和发送完毕数据)

第一次挥手:由浏览器发送给服务器,告诉服务器数据发送完了
第二次挥手:由服务器发送给浏览器,告诉浏览器数据接受完了
第三次挥手:由服务器发送给浏览器,告诉浏览器数据发送完了
第四次挥手:由浏览器发送给服务器,告诉服务器数据接受完了,此时浏览器会自动断开链接,服务器也会

5. 当在浏览器输入一个地址,按下回车发生了什么

  1. 解析 URL

解析 URL 是否合法,合法进行下一步,不合法就直接出错。

  1. 缓存判断

判断该请求是否命中缓存,命中直接读取缓存,没有命中进行下一步

  1. DNS 解析

将域名地址解析为 ip 地址

流程:

  • 首先读取 DNS 缓存:浏览器 DNS 缓存 - 计算机 DNS 缓存 - 路由器 DNS 缓存 - 网络运营商
  • 然后 DNS 递归查询:根域名服务器 - 顶级域名服务器 - 权威域名服务器
  1. TCP 三次握手

为了确认双方的接收能力和发送能力是否正常。

第一次握手:由浏览器发送给服务器,服务器确定浏览器的发送能力
第二次握手:由服务器发送给浏览器,浏览器确定的服务器接受和发送能力
第三次握手:由浏览器发送给服务器,服务器确定的浏览器的接受能力

  1. 发送请求

将请求以请求报文形式发送给服务器。

请求报文由请求首行、请求头、空行、请求体组成。

请求首行主要有:请求方式、请求地址、get 请求的查询字符串参数、协议和版本号

请求头主要有:

  • Connection: keep-alive 保持长链接, 能共享一个 TCP 链接通道来发送请求,通信效率更高
  • Content-Type 请求体参数类型
    • 我们使用 axios 发送请求,一般是:application/json 格式
    • 我们使用原生 form 表单发送请求,一般是:application/x-www-form-urlencoded 格式
  • User-Agent 用户代理, 可以识别用户的客户端环境
  • Referer 请求来源地址, 防盗链
  • Cookie / token 用户唯一标识

请求体主要有:POST 请求的参数

报文内容简单说说也行,可以不用这么复杂

  1. 返回数据

服务器以响应报文形式返回响应

响应报文:响应首行、响应头、空行、响应体组成。

响应首行主要有:协议和版本号、响应状态码

响应状态码分为 5 种类型:1xx、2xx、3xx、4xx、5xx(1 开头、2 开头、3 开头、4 开头、5 开头)

  • 1xx 请求正在处理
  • 2xx 请求处理成功
    • 200 请求成功
  • 3xx 请求需要进一步处理(请求重定向)
    • 301 永久重定向
    • 302 临时重定向
    • 304 重定向到浏览器缓存中(协商缓存)
  • 4xx 客户端错误
    • 401 认证/授权失败
    • 403 禁止访问
    • 404 找不到资源
    • 407 token 过期/失效
  • 5xx 服务器错误
    • 500 服务器内部错误

响应头:

  • 强制缓存字段 Cache-Control / Expires
  • 协商缓存字段 Etag / Last-Modified
  • CORS 解决跨域字段:Access-Control-Allow-Origin

响应体:响应的具体数据

报文内容简单说说也行,可以不用这么复杂

  1. 页面渲染
  • 将 DOM 结构解析成 DOM 树
  • 将 CSS 样式解析成 CSSOM 树
  • 将 DOM 树和 CSSOM 树合成渲染(render)树
  • 根据渲染(render)树首先进行布局,然后进行渲染
  • 整个过程会触发多次渲染,也就是说构建了部分 DOM 树和 CSSOM 树就会开始合成渲染了
  • 如果遇到 script 标签会同步执行里面的 js 代码,会阻塞渲染
  • 如果 script 标签 js 操作了 DOM 或 CSS,重新生成 DOM 树和 CSSOM 树,合成渲染树,布局(重排),渲染(重绘)
  1. TCP 四次挥手

四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据,连接处于的是半释放的状态。(确保双方都接受完毕和发送完毕数据)

第一次挥手:由浏览器发送给服务器,告诉服务器数据发送完了
第二次挥手:由服务器发送给浏览器,告诉浏览器数据接受完了
第三次挥手:由服务器发送给浏览器,告诉浏览器数据发送完了
第四次挥手:由浏览器发送给服务器,告诉服务器数据接受完了,此时浏览器会自动断开链接,服务器也会

6. 强制缓存和协商缓存

强制缓存字段:Cache-Control(优先级更高) 、Expires

协商缓存字段:位于响应头的:Etag / Last-Modified 和位于请求头的 If-None-Match / If-Modified-Since

整体流程:

  1. 第一次请求该资源,没有缓存,服务器返回新资源,同时在响应头携带:强制缓存字段 Cache-Control 和 协商缓存字段:Etag / Last-Modified,此时响应状态码 200

  2. 第二次以后请求该资源,首先判断该资源强制缓存是否过期,没有过期,直接读取缓存,不会发送请求了,响应状态码 200

  3. 如果强制缓存过期了,就会发送请求给服务器,同时在请求头自动携带协商缓存字段: 此时 Etag 会改名为 If-None-Match, Last-Modified 会改名为 If-Modified-Since

  4. 由服务器检查和服务器的 Etag 和 Last-Modified 是否匹配

  5. 如果匹配没变,就返回响应状态码 304,浏览器接受到会自动去缓存中找该资源使用

  6. 如果变了,就会返回新的资源,同时在响应头携带新的:强制缓存字段 Cache-Control 和 协商缓存字段:Etag / Last-Modified,此时响应状态码 200

7. 谈谈 WebSocket

WebSocket 是一个全双工通讯协议。

特点:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。

使用:

new WebSocket(url) 传入 url 就可以连接上 WebSocket 服务器了

通过 message:接受服务器发送的消息

通过 send 方法:可以给服务器发送消息

除此以外还有其他方法:close 关闭连接

其他事件:open:打开事件 error 错误事件 close 关闭事件

功能:WebSocket 连接时可能因为不好的网络环境会断开链接,此时可以使用心跳检测机制来进行断开重连

心跳检测:

web socket 在连接过程中可能会因为网络问题断开连接,我们需要重连,这里用到心跳检测机制

具体来说:

  1. 检测是否断开链接:

当我们连接上 ws 服务时,需要开启定时器,隔一段时间(10s - 30s)向 ws 服务器发送一个特殊信号(ping),同时开启定时器(2s)检测服务器是否有回应信号(pong)

如果在规定时间内有回应,继续此操作,如果没有回应,说明断开连接了,我们要开始重连 ws

  1. 重连机制

每隔一段时间发起 ws 的重连,同时重连次数增加,一旦重新连接成功,就归零重连次数,方便下次重连,如果重连次数超过最大次数,就要提示错误,不在尝试了

8. 谈谈 ajax

可以发送异步请求,进行局部页面更新

流程:

  1. new XMLHttpRequest() 来创建 xhr 对象
  2. 通过 xhr 调用 open 方法,设置请求方式和请求地址(请求地址可以添加查询字符串参数)
  3. 通过 xhr.send 方法发送请求(可以携带请求体参数)
  4. 通过 xhr.onreadystatechange 或 onload 事件监听响应回来的结果,往往需要判断 xhr 的响应状态码是否 2 开头,来判断请求成功还是失败

9. 跨域

  1. 什么是跨域?
    违背同源策略产生跨域。

  2. 什么是同源策略?
    同源指的是:协议、域名、端口号三者必须完全一致。

  3. 什么样的请求存在跨域问题?

只有浏览器端的 ajax 请求存在跨域。

服务器与服务器之间没有跨域,浏览器的 script、img、form 等标签都没有跨域。

  1. 解决跨域的方案:
  • jsonp: 利用 script 标签可以跨域特性进行跨域。

可以说说具体做法

  • cors: 服务器设置特定响应头即可实现。access-control-allow-Origin

  • NodeJS 代理服务器: 这是我开发中主要使用解决跨域的方案

比如 vue2 项目,可以在 vue.config.js 中进行配置,配置 devServer 的 proxy,设置代理请求前缀和目标服务器地址等来完成

  • Nginx 代理服务器:项目上线时公司采用 nginx 反向代理解决跨域

10. Nginx

  1. 概念

Nginx 是开源、高性能、高可靠的 Web 服务器和反向代理服务器。

  1. 基本使用
  • 反向代理解决跨域
1
2
3
4
5
6
7
8
server {
listen 3000;
server_name localhost;
location /app-prod {
proxy_pass http://xxx.com/; # 转发到目标服务器地址
rewrite ^/app-prod/(.*) /$1 break; # 路径重写
}
}
  • 解决 history 模式前端路由 404 问题

try_files $uri $uri/ /index.html;

Builder

1. 谈谈 Git 常见指令

2. 谈谈 Git Flow / Git 工作流程

  1. 概念

是一个 git 标准化操作流程

  1. Git 分支
  • master 是长期分支,一般用于管理对外发布版本,每个 commit 对一个 tag,也就是一个发布版本
  • develop 是长期分支,一般用于作为日常开发汇总,即开发版的代码
    开发一个新的 feature 直接新在 develop 新开一个临时的 feature 分支,开发完成向 develop 提 Pull Request。
  • feature 是短期分支,一般用于一个新功能的开发
  • hotfix 是短期分支 ,一般用于正式发布以后,出现 bug,需要创建一个分支,进行 bug 修补。
  • release 是短期分支,一般用于发布正式版本之前(即合并到 master 分支之前),需要有的预发布的版本进行测试。release 分支在经历测试之后,测试确认验收,将会被合并的 develop 和 master。
  1. 提交 commit 规范
  • feat:开发新功能(feature)

  • fix:修复 bug

  • style: 调整格式(不影响代码运行的变动)

  • build:修改构建相关内容,如 npm、maven 内容。

  • imp:优化已有功能(improve)

  • refactor:重构功能

  • test:添加测试

  • docs:撰写文档(documentation)

  • ci:修改持续集成相关内容(Continues Intergration)

记住一部分就好

  1. 流程概述
  • 开发时,以 develop 分支为模板新建一个新的分支:feature/{任务 id}-{任务名称}
  • 克隆仓库,拉取 feature 开发分支,切换到 feature 开发分支上进行开发
  • 开发完成,提交合并请求(Pull Request, 也叫 PR),合并 feature 和 develop 分支代码
  • 由组长审核通过,经过流水线(eslint 检查、测试等),完成合并
  • 当所有特性开发&基本测试完成,会以 develop 分支为模板新建一个新的分支:release/{版本号}
  • release 是预发布分支,会进行进一步测试
  • 没问题后,会将 release 分支合并到 master 分支,同时生成一个 tag 备份代码,上线代码从 master 分支取出使用
  • 将来上线代码出现问题,会以 master 分支为模板创建新的分支:hotfix/{任务 id}-{bug 名称}
  • 解决 bug 后,会合并到 master 分支上,生成新的 tag

3. 谈谈 Webpack

  1. 概念

静态模块打包工具,可以将静态模块编译、打包和输出成一个或多个文件(bundles)。

  1. 5 个核心概念
  • 入口(entry): 指示 webpack 从哪个文件开始打包。
  • 输出(output): 指示 webpack 编译、打包后的文件输出到哪里去。
  • 加载器(loader): webpack 只能识别 js、json 文件,其他类型的文件需要通过 loader 转化成有效模块才能识别。
    • 比如:处理样式文件可以使用 css-loader\style-loader\less-loader\sass-loader
    • 处理 vue 文件可以使用 vue-loader
  • 插件(plugin): 相对 loader,plugin 可以做范围更广的工作,比如:打包优化,资源管理,注入环境变量。
    • 比如:处理 html 资源需要使用 html-webpack-plugin
    • eslint 语法风格检查需要使用 eslint-webpack-plugin
  • 模式(mode): 可以选择 development, production 或 none 之中的一个。不同模式会加载不同的配置。

4. webpack 优化

5. vite 和 webpack 的区别

  1. 底层语言不同

Vite 是基于 esbuild 采用 go 语言编写,go 语言的操作是纳秒级别

Webpack 是基于 Nodejs,以毫秒计数

所以 vite 比 webpack 更快。

  1. 启动方式

webpack 启动慢:webpack 首先分析各个模块之间的依赖,然后将所有内容进行打包,模块越多打包速度越慢,所以启动慢。

vite 启动快:vite 采用了一种懒加载的方式,它在启动的时候不需要打包,而是需要某个模块时,再对模块内容进行编译,所以启动很快

  1. 首屏渲染

webpack 渲染快:webpack 启动时已经将所有内容进行打包了,渲染时直接获取资源渲染即可

vite 渲染慢:Vite 渲染时才会打包编译文件,然后再渲染,打包越慢,渲染速度越慢(但是 vite 有缓存,所以第二次渲染速度没问题)

  1. 生态

webpack 诞生很久了,生态基本完善

vite 生态不够全,对代码分割不够友好

React

1. React 和 Vue 的区别

  1. 数据流
  • Vue 默认支持双向数据绑定
  • React 提倡单向数据流
  1. 虚拟 DOM
  • Vue 宣称可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于 React 而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate 这个生命周期方法来进行控制,但 Vue 将此视为默认的优化。
  1. 组件化
  • Vue 提供单文件组件方式,类似于 HTML 语法。
  • React 通过 JSX 语法来定义组件。
  1. 更新用户界面方式
  • Vue 的数据是响应式的,更新数据同时用户界面也会更新
  • React 要触发用户界面更新需要使用 this.setState 方法或者 useState 提供的函数
  1. 构建工具
  • Vue 有 vite-cli / vue-cli
  • React 有 create-react-app
  1. 跨平台
  • Vue 有 Weex
  • React 有 React Native
  1. 数据可变性
  • Vue 使用的是可变数据
  • React 强调不可变数据

2. 谈谈 redux

3. 对 React Hook 的理解

4. React 组件间通信

5. React 生命周期

6. 谈谈 React Router

7. 什么是虚拟 DOM?

8. React 中虚拟 DOM Diff 算法

Vue

1. 生命周期函数

  1. 基础
  • 初始化阶段
    • beforeCreate
    • created
  • 挂载阶段
    • beforeMount
    • mounted
  • 更新阶段
    • beforeUpdate
    • updated
  • 卸载阶段
    • Vue2: beforeDestroy / destroyed
    • Vue3: beforeUnmount / unmounted
  1. 重要的生命周期

其中重要生命周期:mounted 和 beforeDestroy(beforeUnmount)

  • mounted 用来发送请求、设置定时器、绑定事件等任务
  • beforeDestroy 用来解绑定时器,解绑事件等收尾工作,防止内存泄漏
  1. 其他生命周期函数

除此之外,还有一些别的生命周期函数:

  • 被 keep-alive 缓存的组件会自动拥有两个生命周期函数

    • activated
    • deactivated
  • 用来捕获后代组件产生的错误

    • errorCaptured
  1. Vue3 提供了两种开发模式:选项式和组合式,其中组合式 API 生命周期用法有一些变化
  • setup:相当于之前的 beforeCreate/created
  • 剩下的生命周期函数需要引入使用,比如:
    • onMounted
    • onBeforeUnmount
  • 每个生命周期函数都能使用多次

2. 组件间通信

  1. props 父 -> 子

可以展开说说 props 具体内容

  1. 自定义事件 子 -> 父
  • Vue2 中给组件绑定的事件默认都是自定义事件,加上.native才是 DOM 事件
  • Vue3 中给组件绑定的事件默认是 DOM 事件(当然实际要满足条件才会绑定:1. 事件名需要是 DOM 事件名称 2. 子组件必须有根标签 3. 子组件内部不能 defineEmits 声明接受),不满足条件就是自定义事件
  1. v-model 父 <-> 子

vue2 和 vue3 用法也不一样:

  • vue2 中给组件绑定 value 属性和 input 自定义事件
  • vue3 中默认给组件绑定 modelValue 属性和 update:modelValue 事件
    • 也可以通过 v-model:xxx 的方式修改属性名和事件名
  1. v-bind:xxx.sync 父 <-> 子
  • 只有 vue2 才能使用,vue3 不能使用了
  • 绑定 xxx 属性和 update:xxx 自定义事件
  1. 插槽 父 <-> 子
  • 通信的内容主要是标签数据(之前通信方案都是普通数据)
  • 分类:默认插槽、具名插槽和作用域插槽
  • 一般我设置组件时,会优先最重要内容用默认插槽(因为简单),其他内容考虑具名插槽,如果需要子向父通信用作用域插槽(table)
  1. vuex / pinia 兄弟、祖孙
  • 一般 vue2 项目用 vuex,vue3 项目用 pinia
  1. 还有其他通信方案
  • 全局事件总线(兄弟):vue2 中能使用,vue3 不能使用了, 因为删除了$on/$off等自定义事件方法,想要使用必须用第三方库,比如 mitt 实现
  • $parent/$children/$refs(父 <-> 子),其中 vue3 删除了$children
  • $attrs/$listeners (父 -> 子), 其中 vue3 删除了$listeners, 内容放入了$attrs
  • provide/inject 祖孙

3. 谈谈 Vuex

一个集中式状态管理方案,通常用于管理多个组件共享的状态数据。

开发时需要定义主模块和其他分模块

主模块主要定义 modules 属性用来汇总其他分模块

其他分模块主要定义 state、getters、actions、mutations、namespaced。

  • state 模块管理的状态数据
  • getters 只读计算属性数据
  • actions 一般用来与服务器进行交互的函数,比如:发送请求
  • mutations 直接更新数据的函数
  • namespaced 的值为 true,开启命名空间,这样每个 vuex 模块的内容就被隔离,不会互相影响。

组件读取/更新 vuex 数据有两种方式,一种通过$store,一种 map 函数形式

一般如果数据只用一两次,我会用$store,比较简单。数据比较频繁使用,用 map 函数形式

4. 谈谈 Pinia

一个集中式状态管理方案,通常用于管理多个组件共享的状态数据。

相对于 Vuex 来说,pinia 优点:

  • 没有 mutations (流程更简洁)
  • Typescript 支持更友好
  • pinia 模块定义即可使用,不用汇总

开发时需要定义主模块和其他分模块:主模块定义好后,就定义分模块即可,不用汇总

分模块主要由以下内容:

  • state 模块管理的状态数据
  • getters 只读计算属性数据
  • actions 与服务器进行交互(发送请求),同时更新数据

组件引入分模块暴露的 useXxxStore 函数,得到 Store 对象,即可操作数据和方法了

  • 一般如果我需要直接更新一个数据:就直接操作数据即可
  • 如果我需要直接更新多个数据:store.$patch 方法更新数据
  • 如果我需要更新数据并且发送请求:就需要定义 action 函数,通过 store 调用 action 函数来更新

5. Vue2 和 Vue3 的区别

  1. 生命周期不一样

卸载阶段

  • vue2 beforeDestroy destroyed
  • vue3 beforeUnmount unmounted
  1. 组件间通信方案用法不一样
  • vue3 删除了$on/$off/$onceAPI,所以默认不能全局事件总线,如果想要使用全局事件总线,需要使用第三方库 mitt
  • vue3 删除了 v-bind:xxx.sync 修饰符,父子组件双向通信只能使用 v-model
  • v-model 对组件用法不一样
    • vue2 v-model 绑定的是 value 属性和 input 自定义事件
    • vue3 v-model 绑定的是 modelValue 属性和 update:modelValue 自定义事件
  • vue3 删除$listeners,整合到$attrs
  • vue3 删除$children,获取子组件实例对象必须使用 ref
  1. 指令不一样
  • v-for 和 v-if 优先级不同
    • vue2 是 v-for 优先级更高
    • vue3 是 v-if 优先级更高
  • vue3 新增了一个指令:v-memo 用来缓存 DOM 元素
  • vue3 删除了 v-bind:xxx.sync 修饰符
  • vue3 v-model 用法不一样
  1. 开发模式不一样
  • vue2 只有选项式开发模式
  • vue3 除了有选项式开发模式以外,新增了组合式开发模式
    • setup
    • ref / reactive / watch / computed
    • onMounted / onBeforeUnmount
  1. 响应式原理不一样
  • vue2 通过 Object.defineProperty 实现的响应式
  • vue3 通过 Proxy 实现的响应式

6. 谈谈 Vue 的指令

  1. 常用指令
  • v-if / v-else-if / v-else 条件渲染(控制元素的显示和隐藏)
  • v-show 条件渲染(控制元素的显示和隐藏)
    • v-if 和 v-show 区别:
      • v-if 隐藏时,销毁元素(卸载组件)
      • v-show 隐藏时,通过 display:none 来隐藏的
      • 结论:频繁切换用 v-show,不频繁用 v-if
  • v-for 遍历展示(列表渲染)
    • key 属性作用:在 diff 算法中,尽可能复用相同 key 的元素,更新性能更好
    • key 属性取值:一般用 id,用 index 可能导致更新性能不好
    • v-for 和 v-if 优先级:
      • vue2 是 v-for 更高
      • vue3 是 v-if 更高
  • v-on 绑定事件 @
    • 事件修饰符 .prevent .stop .once .self
    • 按键修饰符 .enter .13
  • v-model 双向数据绑定
    • 双向数据绑定原理
      • 给普通 input 元素(text)绑定,绑定的是 value 属性和 input 事件
      • 给单选(radio)或多选(checkout)绑定,绑定的是 checked 属性和 change 事件
      • 给下拉列表(select)绑定,绑定的是 value 属性和 change 事件
      • 给组件绑定
        • vue2 中,组件绑定和普通元素绑定效果一样
        • vue3 中,默认绑定 modelValue 属性和 update:modelValue 事件
  • v-bind 单向数据绑定(强制绑定数据) :
  • v-slot 插槽 #
    • 默认插槽、具名插槽和作用域插槽
  • v-memo(新增的指令) 用于缓存部分 DOM 元素
  1. 不常用指令
  • v-text 设置元素 textContent
  • v-html 设置元素 innerHTML
  • v-once 元素只解析渲染一次,后续再也不变了
  • v-pre 跳过解析,直接渲染最原始的内容
  • v-cloak 防止解析时渲染表达式(用于隐藏尚未完成编译的 DOM 模板。)

7. ref 和 reactive 的区别

  1. ref 可以用来定义基本类型和引用类型数据,reactive 只能用来定义引用类型数据;
  2. ref 定义的数据如果是引用数据类型,实际是通过 reactive 来定义的。
  3. ref 定义的数据需要通过 .value 来读取或更新,reactive 可以直接操作数据;

8. 谈谈 Vue Router

  1. 概念
    用来实现 Vue 的单页面应用(single page web application,SPA)。

单页面应用特点:

  • 整个应用只有一个完整页面,所有更新只是这个页面的局部渲染
  • 点击页面链接不会刷新整个页面,只会更新浏览历史记录和页面局部更新
  1. 路由两种模式
  • hash
    • 特点
      • hash 路径带 #,# 后面的值(路由路径)不会提交到 server 端;
      • hash 可以改变 url ,准确来说改变的是 # 后面的值,页面不会刷新;
      • 兼容性更好, IE6+。
    • 原理
      • hash 通过 window.location.hash 的方式,实现路由跳转的功能。
      • hash 通过 window.onhashchange 的方式,来监听 hash 的改变,借此实现无刷新跳转的功能。
  • history
    • 特点
      • history 路径不带 #,更美观。
      • history 可以改变 url ,改变的是整个 url,页面不会刷新;
      • 兼容性稍差, IE10+;
      • 页面刷新时,history 可能会出现 404 问题。
    • 原理
      • history 通过 window.history.pushState / replaceState 等方式,实现路由跳转的功能。
      • history 通过 window.onpopstate 的方式,来监听 url 的改变,借此实现无刷新跳转的功能。
  1. 基本内容
  • 提供两个组件
    • router-link 用来路由跳转(声明式导航)
    • router-view 用来加载渲染路由组件
  • 提供两个属性
    • $router 用来路由跳转(编程式导航)
    • $route 用来获取路由参数和路由路径
  1. 路由跳转两种方式
  • 声明式导航 router-link
  • 编程式导航 this.$router.push/replace/go
  1. 路由传参
  • query
  • params
  • meta
  1. 路由导航守卫
  • 全局路由导航守卫
    • beforeEach
    • afterEach
  • 路由独享守卫
  • 组件独享守卫

最重要的是全局路由导航守卫的 beforeEach

主要用来:权限管理功能,控制用户的访问权限

  1. 路由懒加载

通过 import 动态加载组件,实现组件懒加载

内部做了两件事:

  • 代码分割:将路由组件单独打包成一个 js 文件
  • 按需加载:需要使用这个路由组件时,才会加载对应的 js 文件

9. nextTick 方法的原理

9.1. Vue2

  1. 定义一个数组(callbacks)用来存储回调函数
  2. 定义一个用来遍历数组,执行回调函数的方法(flushCallbacks)
  3. 定义一个用来将执行回调函数的方法添加到异步队列去的方法(timerFunc)
  • 这个方法内部会通过 4 种方式来操作:Promise、MutationObserver、setImmediate、setTimeout
  • 会从上到下依次判断来选择,一旦选择前面的方案,后面就不看了
  1. 到此准备工作完成了,接下来组件会调用 nextTick 方法

  2. 调用 nextTick 方法时,会将回调函数添加到数组(callbacks)中,在通过 timerFunc 将执行回调函数的方法 flushCallbacks 添加到异步队列等待将来执行

  3. 等主线程执行完所有同步代码,就会执行异步代码,此时会就执行 flushCallbacks 函数

  4. 执行 flushCallbacks 函数就会遍历所有回调函数依次执行

总结:

所以 nextTick 方法的真正理解就是将回调函数添加到异步队列中,等待将来执行。

它之所以可以等 DOM 元素渲染完成才触发回调函数,是因为我们先更新响应式数据,此时内部会将更新用户界面的方法通过 nextTick 添加到异步队列去,在调用 nextTick 方法,会将其他代码也添加到异步队列去,队列先进先出,所以先更新用户界面,在执行其他代码,此时就能操作更新后的 DOM 元素了

9.2. Vue3

调用 nextTick 方法,如果不传入参数,就会直接返回一个成功的 promise 对象。后续代码会添加到异步队列等待将来执行。

问题:为什么 Vue3 的 nextTick 这么简单?

  • 因为 Vue2 要考虑低版本浏览器的兼容性处理,所以用了 4 种方式来将回调函数添加到异步队列。
  • 而 Vue3 放弃了低版本浏览器的兼容,所以只需要考虑最佳方案:Promise 即可。

10. 双向数据绑定原理

v-model 主要用于双向数据绑定(收集表单数据),它给元素绑定时,不同元素做法不一样:

  1. 如果是文本类型元素(