前端面试题
Time: 2023-01-19 23:19:28
项目地址,这篇笔记是我的一些理解
JS ES6 系列
1. 说说var、let、const 之间的区别
var、let、const三者区别可以围绕下面五点展开:
| 类别 | var | let | const |
|---|---|---|---|
| 变量提升 | 存在 | 不存在 | 不存在 |
| 暂时性死区 | 不存在 | 存在 | 存在 |
| 块级作用域 | 不存在 | 存在 | 存在 |
| 重复声明 | 允许 | 同一作用域不允许 | 同一作用域不允许 |
| 修改声明的变量 | 可以 | 可以 | 不能 |
| 使用 | All | All | Arr,Obj |
变量提升 变量可以在声明之前调用,值为undefined
暂时性死区 只有等到声明变量的那一行代码出现,才可以获取和使用该变量
修改声明的变量 const 定义 对象Obj 和 数组Arr 可以更改内容
如何使用
数组和对象使用const定义,其他类型使用let定义
2. ES6中数组新增了哪些扩展?
扩展运算符 ... 将数组脱一层壳,变成一个个的个体
console.log(...[1, 2, 3])
// 1 2 3
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
主要用于函数调用的时候,
- 将一个数组变为参数序列
- 实现数组复制,数组合并
注意:通过扩展运算符实现的是浅拷贝,修改了引用指向的值,会同步反映到新数组
二、新增构造函数方法
Array.from() ,将类数组对象或可遍历对象(包括 ES6 新增的数据结构 Set 和 Map)转换为真正的数组
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
Array.from(arrayLike); // ['a', 'b', 'c']
Array.of() ,用于将一组值,转换为数组
Array() // []
Array(100); // [, , ,] [空属性 × 100]
Array(3, 11, 8) // [3, 11, 8]
三、实例对象新增的方法
copyWithin()
[1, 2, 3, 4, 5,6].copyWithin(0, 4); \\ (6) [5, 6, 3, 4, 5, 6] 从索引4开始复制到索引0开始
find()、findIndex()
返回值,返回位置
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
fill(),使用给定值,填充一个数组
3. ES6中对象新增了哪些扩展?
4. ES6中函数新增了哪些扩展?
5. ES6中新增的Set、Map两种数据结构怎么理解?
Set (成员的值都是唯一的数组)
是es6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合
Set 本身是一个构造函数,用来生成 Set 数据结构
Map类型 特别的 Obj
是键值对的有序列表,而键和值都可以是任意类型
Map本身是一个构造函数,用来生成 Map 数据结构
对于前端开发,业务场景有限。
6. 你是怎么理解ES6中 Promise的?使用场景?
使用场景:请求数据,定时器,事件绑定,文件读取等等异步操作的场景。
在 js 中实现异步操作的一种方式,配合 async await 使用
7. 怎么理解ES6中 Generator的?使用场景?
8. 你是怎么理解ES6中Proxy的?使用场景?
使用场景: Proxy其功能非常类似于设计模式中的代理模式
常用功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
vue3.0中使用了Proxy来实现响应式
9. 你是怎么理解ES6中 Module 的?使用场景?
使用场景: 某个功能需要多次在不同的组件使用,可以将这个功能封装成一个 Module
如何使用
模块功能主要由两个命令构成:
export:用于规定模块的对外接口
import:用于输入其他模块提供的功能
export
一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
或
// 建议使用下面写法,这样能瞬间确定输出了哪些变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
\\ 输出函数或类
export function multiply(x, y) {
return x * y;
};
\\ 通过as可以进行输出变量的重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
\\ 同样如果想要输入变量起别名,通过as关键字
import { lastName as surname } from './profile.js';
\\当加载整个模块的时候,需要用到星号 *
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
import * as circle from './circle';
console.log(circle) // {area:area,circumference:circumference}
动态加载
允许您仅在需要时动态加载模块,而不必预先加载所有模块,这存在明显的性能优势
ES6模块化已经深入我们日常项目开发中,像vue、react项目搭建项目,组件化开发处处可见,其也是依赖模块化实现
10. 你是怎么理解ES6中 Decorator 的?使用场景?
Javascript 系列
1. 说说Javascript中的数据类型?区别?
JS 中有六种简单数据类型
undefined、
null、
boolean、
string、
number、
symbol,
以及引用类型:object
但是我们在 声明的时候只有一种数据类型,只有到 运行期间才会确定当前类型
虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的,如果运算子的类型与预期不符合,就会触发类型转换机制
常见的类型转换有:
强制转换(显示转换)
Number()
parseInt()
String()
Boolean()
自动转换(隐式转换)
2. Javscript数组Arr的常用方法有哪些?
使用场景: 根据业务需求进行判断 Link
一、操作方法
数组 Arr 的增删改查
增 Add
-
push()接收任意数量的参数,并将它们添加到数组末尾,原数组发生变动,方法返回 <数组的最新长度>let colors = []; // 创建一个数组 let count = colors.push("red", "green"); // 推入两项 console.log(count) // 2 -
unshift()接收任意数量的参数,并将它们添加到开头,其他与push()相同 -
splice()增加,传入三个参数,分别是 <开始位置下标、要删除的元素数量、插入的元素>,原数组发生变动,方法返回 <空数组>let colors = ["A", "B", "C"]; let removed = colors.splice(1, 0, "D", "E") console.log(colors) // ["A","D","E","B","C"] console.log(removed) // [] -
concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组let Arr = [1,2,3,4] let Arr2 = Arr.concat(9,["b","c"]) console.log(Arr); // [1,2,3,4] console.log(Arr2); // [1,2,3,4,9,"b","c"]
删 Delete
-
pop()用于删除数组的最后一项,同时减少数组的 length 值,方法返回被删除的项let Arr=[1,2,3,4,666] let result=Arr.pop() console.log(Arr); //[1,2,3,4] console.log(result); //666 -
shift()用于删除数组的第一项,其他与pop()相同 -
splice()也可用于删除数组,传入两个参数,分别是 <开始位置,删除元素的数量>,原数组发生变动,方法返回 <删除元素的数组>let Arr = ["A","B","C","D","E"]; let removed = Arr.splice(1, 2) console.log(Arr) // ["A","D","E"] console.log(removed) // ["B","C"] -
slice() 用于创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组
let Arr = ["A","B","C","D","E"]; let result = Arr.slice(1) //传入参数为删除数量,返回剩下的 let result2 = Arr.slice(2,4) //传入参数为数组下标,返回两个下标之间的 console.log(Arr) // ["A","B","C","D","E"] console.log(result) // ["B","C","D","E"] console.log(result2) //["C","D"]
改
即修改原来数组的内容,常用 splice()
查
即查找元素,返回元素坐标或者元素值
-
indexOf()返回要查找的元素在数组中的下标,如果没找到则返回 -1 -
includes()返回要查找的元素在数组中的下标,找到返回 true ,否则 false -
find()返回第一个匹配的元素const people = [ { name: "Matt", age: 27 }, { name: "Nicholas", age: 29 } ]; people.find((element, index, array) => element.age < 28) // // {name: "Matt", age: 27}
二、排序方法
-
reverse()顾名思义,将数组元素反方向排列 -
sort()接受一个比较函数,用于判断哪个值应该排在前面function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } let values = [0, 1, 5, 10, 15]; values.sort(compare); alert(values); // 0,1,5,10,15
三、转换方法
join() 方法接收一个参数,即字符串分隔符,返回包含所有项的字符串
let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue
四、迭代方法
使用场景: 对数组每一项进行操作
常用来迭代数组的方法(都不改变原数组)有如下:
-
some() 对数组每一项都运行传入的函数,如果
有一项函数返回 true,则这个方法返回 true -
every() 对数组每一项都运行传入的函数,如果
对每一项函数都返回 true,则这个方法返回 true -
forEach() 对数组每一项都运行传入的函数,没有返回值
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; numbers.forEach((item, index, array) => { // 执行某些操作 }); -
filter() 对数组每一项都运行传入的函数,函数
返回条件为 true 的项会组成的数组 -
map() 对数组每一项都运行传入的函数,
返回由每次函数调用的结果构成的数组
3. Javascript字符串 string 的常用方法有哪些?
AAA
一、操作方法
增 Add
concat() 用于将一个或多个字符串拼接成一个新字符串
跟数组 concat 的差不多,但是参数为 String 类型
删
slice()
substr()
substring()
效果都差不多,如下
let stringValue="ABCDEFGHIGk"
stringValue.slice(3); // "xxx DEFGHIGk"
stringValue.substring(3); // "xxx DEFGHIGk"
stringValue.substr(3); // "xxx DEFGHIGk"
stringValue.slice(3,7); // "xxx DEFG xxx"
stringValue.substring(3,7); // "xxx DEFG xxx"
stringValue.substr(3,7); // "xxx DEFGHIG x"
改
这里改的意思也不是改变原字符串,而是创建字符串的一个副本,再进行操作
- trim()、trimLeft()、trimRight() 删除前、后或前后所有空格符,再返回新的字符串
- repeat() 接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
- padStart()、padEnd() 复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
- toLowerCase()、 toUpperCase() 大小写转化
查
- chatAt() 返回给定索引位置的字符,由传给方法的整数参数指定
- indexOf() 从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
- startWith()、includes() 从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
二、转换方法
split 把字符串按照指定的分割符,拆分成数组中的每一项
let str = "12+23+34"
let arr = str.split("+") // [12,23,34]
三、模板匹配方法
针对正则表达式,字符串设计了几个方法:
- match() 接收一个参数,可以是一个正则表达式字符串,也可以是一个 RegExp 对象,
返回数组 - search() 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp对象,
找到则返回匹配索引,否则返回 -1 - replaceAll() 接收两个参数,
第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
vue 系列
1. 说说你对 vue 的理解?
- 数据驱动(MVVM)
- 组件化
- 指令系统
- Vue 所有的界面事件,都是只去 操作数据 的,(Jquery 操作 DOM)
- Vue 所有界面的变动,都是 根据数据自动绑定出来的,(Jquery 操作 DOM)
Vue 和 React 对比
这里就做几个简单的类比吧,当然没有好坏之分,只是使用场景不同
相同点
都有组件化思想
都支持服务器端渲染
都有 Virtual DOM(虚拟 dom)
数据驱动视图
都有支持 native 的方案:Vue 的 weex、React 的 React native
都有自己的构建工具:Vue 的 vue-cli、React 的 Create React App
区别
-
数据变化的实现原理不同。react 使用的是不可变数据,而 Vue 使用的是可变的数据
-
组件化通信的不同。react 中我们通过使用回调函数来进行通信的,而 Vue 中子组件向父组件传递消息有两种方式:事件和回调函数
-
diff 算法不同。react 主要使用 diff 队列保存需要更新哪些 DOM,得到 patch 树,再统一操作批量更新 DOM。Vue 使用双向指针,边对比,边更新 DOM
-
vue与react生命周期对比
2. 说说你对双向绑定的理解?
当用户填写表单时,View 的状态就被更新了,如果此时可以自动更新 Model 的状态,那就相当于我们把 Model 和 View 做了双向绑定
双向绑定由三个重要部分构成
数据层(Model):应用的数据及业务逻辑
视图层(View):应用的展示效果,各类 UI 组件
业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来
而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM 这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理
理解 ViewModel
它的主要职责就是:
- 数据变化后更新视图
- 视图变化后更新数据
3. 说说你对 SPA(单页应用)的理解?
单页应用 SPA 是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验在单页应用中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面页面在任何时间点都不会重新加载。
单页应用与多页应用的区别
| 单页面应用(SPA) | 多页面应用(MPA) | |
|---|---|---|
| SEO搜索引擎优化 | 难实现,可使用SSR方式改善 | 容易实现 |
| url模式 | 哈希模式 | 历史模式 |
| 刷新方式 | 局部刷新 | 整页刷新 |
| 数据传递 | 容易 | 通过url、cookie、localStorage等传递 |
| 组成 | 一个主页面和多个页面片段 | 多个主页面 |
| 维护成本 | 相对容易 | 相对复杂 |
| 页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
如何给 SPA 做 SEO?
4. Vue 中的 v-show 和 v-if 怎么理解?
作用 都是用来控制元素的显示和隐藏的
| v-if | v-show | |
|---|---|---|
| 如何实现 | 元素不会渲染到页面 | 设置 display:none |
| 使用场景 | 运行时条件很少改变 | 非常频繁地切换 |
| 性能开销 | 稍大 | 较小,直接操作 dom 节点增加与删除 |
5. Vue 实例挂载的过程中发生了什么?
-
在调用 beforeCreate 之前,数据初始化并未完成,像 data、props 这些属性无法访问到
-
到了 created 的时候,数据已经初始化完成,能够访问 data、props 这些属性,但这时候并未完成 dom 的挂载,因此无法访问到 dom 元素
-
挂载方法是调用 vm.$mount 方法
6. 说说你对 Vue 的生命周期的理解?
我的 vue2 生命周期笔记写的很好不重复了 Link
7. 为什么Vue中的v-if和v-for不建议一起用?
<template v-show="item.show == true">
<li v-for="item in items" key="items.id">
{{item.centent}}
</li>
</template>
2.x 版本 v-for优先级比v-if高
3.x 版本中 v-if 总是优先于 v-for 生效。
注意
- 永远不要把 v-if 和 v-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
- 如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
- 如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项
computed: {
items: function() { this=?
return this.list.filter(function (item) {
return item.isShow
})
}
}
8. SPA(单页应用)首屏加载速度慢怎么解决
可能原因:
- 网络延时问题
- 资源文件体积是否过大
- 资源是否重复发送请求去加载了
- 加载脚本的时候,渲染内容堵塞了
解决方案
减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
常见的几种SPA首屏优化方式
-
减小入口文件体积
在vue-router配置路由的时候,采用动态加载路由的形式routes:[ path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue') ]以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件
-
静态资源本地缓存
后端返回资源问题:- 采用HTTP缓存,设置Cache-Control,Last-Modified,Etag等响应头
- 采用Service Worker离线缓存
- 前端合理利用localStorage
-
UI框架按需加载
-
图片资源的压缩
-
组件重复打包
假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置
minChunks: 3
minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件 -
开启GZip压缩
-
使用SSR
SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染
9. 为什么 data 属性是一个函数而不是一个对象?
产生这样的原因这是两者共用了同一个内存地址,componentA修改的内容,同样对componentB产生了影响
如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)
10. Vue中给对象添加新属性界面不刷新?
vue2是用Object.defineProperty实现数据响应式,所以不允许在已经创建的实例上动态添加新的响应式属性
若想实现数据与视图同步更新,可采取下面三种解决方案:
- Vue.set()
- Object.assign()
- $forcecUpdated()
小结
- 如果
为对象添加少量的新属性,可以直接采用Vue.set() - 如果需要
为新对象添加大量的新属性,则通过Object.assign()创建新对象 - 如果你实在不知道怎么操作时,可采取
$forceUpdate()进行强制刷新 (不建议)
PS:vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式
11. Vue中组件和插件有什么区别?
组件是什么?
组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件
插件是什么?
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者属性。如:
vue-custom-element - 添加全局资源:指令/过滤器/过渡等。如
vue-touch - 通过全局混入来添加一些组件选项。如
vue-router - 添加 Vue 实例方法,通过把它们添加到
Vue.prototype上实现。 - 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如
vue-router
12. Vue组件间通信方式都有哪些?
组件间通信的分类可以分成以下
- 父子组件之间的通信
- 兄弟组件之间的通信
- 祖孙与后代组件之间的通信
- 非关系组件间之间的通信
组件间通信的方案
- 通过 props 传递
- 通过 $emit 触发自定义事件
- 使用 ref
- EventBus
- \(parent 或\)root
通过共同祖辈\(parent或者\)root搭建通信侨联 - attrs 与 listeners
- Provide 与 Inject
- Vuex
13. Vue中的$nextTick有什么作用?
我们可以理解成,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue 将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新。
使用场景
如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()
- 第一个参数为:回调函数(可以获取最近的DOM结构)
- 第二个参数为:执行函数上下文
14. 说说你对vue的mixin的理解,有什么应用场景?
本质其实就是一个js vc对象,它可以包含我们组件中任意功能选项,如data、components、methods 、created、computed等等
我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来。
使用场景
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立
这时,可以通过Vue的mixin功能将相同或者相似的代码提出来
15. 说说你对slot的理解?slot使用场景有哪些?
在HTML中 slot 元素 ,作为 Web Components 技术套件的一部分,是Web组件内的一个占位符
该占位符可以在后期使用自己的标记语言填充
Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口
使用场景
比如布局组件、表格列、下拉选、弹框显示内容等
如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情
通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用。
slot可以分来以下三种:
-
默认插槽
子组件用<slot>标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面父组件在使用的时候,直接在子组件的标签内写入内容即可
-
具名插槽
父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值 -
作用域插槽
子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用
小结
- v-slot属性只能在
<template>上使用,但在只有默认插槽时可以在组件标签上使用 - 默认插槽名为default,可以省略default直接写v-slot
- 缩写为#时不能不写参数,写成#default
- 可以通过解构获取
v-slot={user},还可以重命名v-slot="{user: newName}"和定义默认值v-slot="{user = '默认值'}"
16. Vue.observable你有了解过吗?说说看
Vue.observable,让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象
使用场景
在非父子组件通信时,可以使用通常的bus或者使用vuex,但是实现的功能不是太复杂,而使用上面两个又有点繁琐。这时,observable就是一个很好的选择
17. 你知道vue中key的原理吗?说说你对它的理解?
设置key能够大大减少对页面的DOM操作,提高了diff效率
18. 怎么缓存当前的组件?缓存后怎么更新?说说你对keep-alive的理解是什么?
keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive可以设置以下props属性:
include - 字符串或正则表达式。只有名称匹配的组件会被缓存
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
max - 数字。最多可以缓存多少组件实例
使用场景
当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
19. Vue常用的修饰符有哪些?有什么应用场景?
在Vue中,修饰符处理了许多DOM事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事情,而能有更多的精力专注于程序的逻辑处理。
vue中修饰符分为以下五种:
- 表单修饰符
- 事件修饰符
- 鼠标按键修饰符
- 键值修饰符
- v-bind修饰符
20. 你有写过自定义指令吗?自定义指令的应用场景有哪些?
我们看到的v- 开头的行内属性,都是指令,不同的指令可以完成或实现不同的功能
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令
应用场景
使用自定义组件组件可以满足我们日常一些场景,这里给出几个自定义组件的案例:
- 防抖
- 图片懒加载
- 一键 Copy的功能
21. Vue中的过滤器了解吗?过滤器的应用场景有哪些?
过滤器 vue3 已经废除了,因为实现成本较高
21. 什么是虚拟DOM?
实际上它只是一层对真实DOM的抽象,以JavaScript对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
在Javascript对象中,虚拟DOM 表现为一个 Object 对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别
创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应
为什么需要虚拟DOM?
DOM是很慢的,其元素非常庞大,页面的性能问题,大部分都是由DOM操作引起的
真实的DOM节点,哪怕一个最简单的div也包含着很多属性。
很多人认为虚拟 DOM 最大的优势是 diff算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。
虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI
如何实现一个虚拟DOM?说说你的思路?
23. 了解过vue中的diff算法吗?说说看
diff 算法是一种通过同层的树节点进行比较的高效算法
其有两个特点:
- 比较只会在同层级进行, 不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较
diff 算法的在很多场景下都有应用,在 vue 中,作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
24. Vue项目中有封装过axios吗?怎么封装的
axios是什么?
axios 是一个轻量的 HTTP客户端
为什么要封装?
axios 的 API 很友好,你完全可以很轻松地在项目中直接使用。
不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍
这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以维护。为了提高我们的代码质量,我们应该在项目中二次封装一下 axios 再使用
如何封装?
封装的同时,你需要和 后端协商好一些约定,请求头,状态码,请求超时时间.......
-
设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
利用node环境变量来作判断,用来区分开发、测试、生产环境if (process.env.NODE_ENV === 'development') { axios.defaults.baseURL = 'http://dev.xxx.com' } else if (process.env.NODE_ENV === 'production') { axios.defaults.baseURL = 'http://prod.xxx.com' }在本地调试的时候,还需要在
vue.config.js文件中配置devServer实现代理转发,从而实现跨域 -
请求头 : 来实现一些具体的业务,必须携带一些参数才可以请求(例如:会员业务)
-
大部分情况下,请求头都是固定的,只有少部分情况下,会需要一些特殊的请求头,这里将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
-
状态码: 根据接口返回的不同
status, 来执行不同的业务,这块需要和后端约定好 -
请求方法:根据
get、post等方法进行一个再次封装,使用起来更为方便 -
请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问
-
响应拦截器: 这块就是根据 后端`返回来的状态码判定执行不同业务
这篇文章写的很好,可以参考一下真没必要再对 axios 进行过度的封装
25. 你了解Axios的原理吗?有看过它的源码吗?
不想看,挂链接
26. SSR解决了什么问题?有做过SSR吗?你是怎么做的?
Server-Side Rendering 我们称其为SSR,意为服务端渲染
指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程
单页应用SPA
单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS渲染出来,这种方式称为客户端渲染
服务端渲染SSR
SSR解决方案,后端渲染出完整的首屏的dom结构返回,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行
解决了什么?
SSR主要解决了以下两种问题:
- seo:搜索引擎优先爬取页面HTML结构,使用ssr时,服务端已经生成了和业务想关联的HTML,有利于seo
- 首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端)
用的到再读吧,挂链接
27. 说下你的vue项目的目录结构,如果是大型项目你该怎么划分结构和划分组件呢?
挂链接
28. Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?
权限管理是什么?
权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源
而前端权限归根结底是请求的发起权,请求的发起可能有下面两种形式触发
- 页面加载触发
- 页面上的按钮点击触发
总的来说,所有的请求发起都触发自前端路由或视图
所以我们可以从这两方面入手,对触发权限的源头进行控制,最终要实现的目标是:
- 路由方面,用户登录后只能看到自己有权访问的导航菜单,也只能访问自己有权访问的路由地址,否则将跳转 4xx 提示页
- 视图方面,用户只能看到自己有权浏览的内容和有权操作的控件
最后再加上请求控制作为最后一道防线,路由可能配置失误,按钮可能忘了加权限,这种时候请求控制可以用来兜底,越权请求将在前端被拦截
权限管理怎么做?
前端权限控制可以分为四个方面:
- 接口权限
- 按钮权限
- 菜单权限
- 路由权限
29. 跨域是什么?Vue项目中你是如何解决跨域的呢?
一、跨域是什么?
跨域本质是浏览器基于同源策略的一种安全手段,同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能,简而言之产生跨域是浏览器的锅
所谓同源(即指在同一个域)具有以下三个相同点
- 协议相同(protocol)
- 主机相同(host)
- 端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
二、如何解决?
而在vue项目中,我们主要针对CORS或Proxy这两种方案进行展开
CORS
CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应
CORS 实现起来非常方便,只需要增加一些 HTTP 头,让服务器能声明允许的访问来源
只要后端实现了 CORS,就实现了跨域
Proxy
代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击
两种方式
- vue脚手架打开node服务器代理
- 通过服务器转发代理
30. vue项目如何部署?有遇到布署服务器后刷新404问题吗?
使用docker部署nginx容器,将容器nginx映射到指定路径,将打包后的dist文件放到nginx映射的路径下,然后访问即可
HTTP 404 错误意味着链接指向的资源不存在,
产生问题的本质是因为我们的路由是通过JS来执行视图切换的,
当我们进入到子路由时刷新页面,web容器没有相对应的页面此时会出现404
所以我们只需要配置将任意页面都重定向到 index.html,把路由交由前端处理
对nginx配置文件.conf修改
server {
listen 80;
server_name www.xxx.com;
location / {
index /data/dist/index.html;
try_files $uri $uri/ /index.html;
}
}
这么做以后,你的服务器就不再返回 404错误页面,因为对于所有路径都会返回 index.html 文件
为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})
31. 你是怎么处理vue项目中的错误的?
在Vue 中,则是定义了一套对应的错误处理规则给到使用者,且在源代码级别,对部分必要的过程做了一定的错误处理。
主要的错误来源包括:
- 后端接口错误
通过axios的interceptor实现网络请求的response先进行一层拦截 - 代码中本身逻辑错误
如何处理错误?
代码逻辑问题,全局设置错误处理
设置全局错误处理函数
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
用到仔细看链接
32. Vue3 有了解过吗?能说说跟 Vue2 的区别吗?
这是链接,自己看去
Vue3系列
1. Vue3.0的设计目标是什么?做了哪些优化?
2. Vue3.0 性能提升主要是通过哪几方面体现的?
3. Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?
- 检测不到对象属性的添加和删除
- 数组API方法无法监听到
- 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题
Object.defineProperty只能遍历对象属性进行劫持
Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的
4. Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?
通常使用Vue2开发的项目,普遍会存在以下问题:
- 代码的可读性随着组件变大而变差
- 每一种代码复用的方式,都存在缺点
- TypeScript支持有限
以上通过使用Composition Api都能迎刃而解
在 Vue3 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)
即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API
5. 说说Vue 3.0中 Treeshaking 特性?举例说明一下
是什么?
Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到
而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中
Tree shaking无非就是做了两件事:
- 编译阶段利用ES6 Module判断哪些模块已经加载
- 判断那些模块和变量未被使用或者引用,进而删除对应代码
作用
通过Tree shaking,Vue3给我们带来的好处是:
- 减少程序体积(更小)
- 减少程序执行时间(更快)
- 便于将来对程序架构进行优化(更友好)

浙公网安备 33010602011771号