五年面试,三年模拟(框架篇)

vue 相关

v-if 与 v-for可以一起使用吗?

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中,所以不推荐 v-if 和 v-for 同时使用

1
2
3
4
5
6
7
8
9
10
11
// 推荐写法
<ul v-if="shouldShowUsers">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>

// 并不是任何时候都不推荐,想渲染特定节点时,如下代码渲染未完成的todolist
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

watch 和 computed 区别

computed 会创建新的响应式数据。并且具有可缓存,可依赖多个属性等特点。
watch 是响应式数据的自定义侦听器,用于监听响应数据的变化。

ps:

  1. 如果一个数据依赖于其他数据,那么把这个数据设计为computed的
  2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

watch deep属性实现原理

vue会一层层遍历,给这个对象的所有属性都加上这个监听器。但是这样性能开销会比较大,修改任何一个属性,都会出发这个监听器里的handler.

keep-alive刷新前一个页面

使用keep-alive时,再次进入了缓存页面会走以下生命周期:beforeRouteEnter –>activated –> deactivated,
所以如果需要在进入页面时刷新数据就在activated 里面做操作

vue按钮级别鉴权

  1. 储存权限数据
  2. 自定义指令传入当前权限的参数
    1
    2
    // 示例代码
    <el-button type="primary" v-has="'line_add'">新增</el-button>
  3. 遍历对比权限数据中字段是否与标签中的自定义参数line_add相等,匹配成功则表明有权限,失败没有权限,返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function permissionJudge(value) {
let list = store.getters.getMenuBtnList;
for (let item of list) {
if (item.permission === value) {
return true;
}
}
return false;
}

// 注册一个全局自定义指令 `v-has`
Vue.directive('has', {
// 当被绑定的元素插入到 DOM 中时触发bind钩子
bind: function (el, binding) {
if (permissionJudge(binding.value)) {
el.parentNode.removeChild(el);
}
}
});

vuex多个模块之间action如何调用

1
2
3
// 用dispatch调用,总共三个参数,每个都必须写,第一个就是action名,第二个是需要传递的参数,第三个代表的是非本模块内的action

dispatch(“tagsView/delAllViews”, {}, { root: true });

vue中data为什么是函数

因为组件是用来复用的,且js里对象是引用关系,如果组件中data是一个对象,那么作用域没有隔离,组件中的data属性值会相互影响。
组件中data是函数的话,每个实例可以维护一份独立的拷贝,组件实例之间的data属性值不会互相影响;
new Vue的实例,是不会被复用的,因此不存在引用对象的问题。

vue二次封装组件,参数如何传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<el-select v-bind="$props" v-on="$listeners"></el-select>
<div>{{myProps}}</div>
</div>

</template>
<script>
import {Select} from 'element-ui'

export default {
name: 'my-select',
props: {
...Select.props, // 这里继承内在组件的props
myProps: String // 新增的props
}
}
</script>

vue2和vue3的区别

  1. 双向数据绑定原理不同,vue2是使用Object.definePropert(),vue3是使用ES6的Proxy API对数据代理。
  2. 语法不同,vue2使用选项类型api,选项型api在代码里分割了不同的属性:data,computed,methods等。vue3使用组合式api,相比于旧的api使用属性来分组,这样代码会更加简便和整洁。
  3. 定义数据变量和方法不同,vue2是把数据放入data中。vue3使用ref或者reactive,在setup()方法来返回我们的反应性数据
  4. 生命周期钩子函数不同,vue3删除了beforeCreate、created取代的是setup

什么是数据双向绑定

vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相当于:value和@input。

Vue3为什么要用 Proxy API 替代 defineProperty API ?

object.defineProperty有下面几个缺陷

  1. 检测不到对象属性的添加和删除
  2. 数组API方法无法监听到
  3. 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

v-for中key的作用以及实现原理

vue组件高度复用,增加Key可以标识组件的唯一性,可以更高效的更新虚拟DOM

原理:
key是vue在对比过程中判断两个节点是否是同一结点的必要条件。如果不设置key则a.key === b.key一直为true,vue内部会认为比较的两个节点是相同的节点,只能去做更新操作,这造成了大量的dom更新操作。

1
2
3
4
5
6
7
8
9
10
11
function sameVnode(a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory &&
((a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)) ||
(isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
)
}

vue路由获取参数

1
2
3
4
5
6
7
8
9
10
<router-link to="/about/123">About</router-link>

// 获取参数
this.$route.params

<router-link :to="{path:'/about',query:{num:123}}">About</router-link>

// 获取参数
this.$route.query.num

diff原理

diff就是调用patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

  1. 首先看有没有新旧节点,没有新节点,直接触发旧节点的destory钩子;没有旧节点,直接用新节点生成dom元素
  2. 如果有新旧节点,判断两节点是否一样,一样执行patchVnode方法;否则直接销毁旧节点,根据新节点生成dom元素

patchVnode部分

  1. 新节点是否是文本节点,如果是,则直接更新dom的文本内容为新节点的文本内容
  2. 新节点和旧节点如果都有子节点,则处理比较更新子节点
  3. 只有新节点有子节点,旧节点没有,那么不用比较了,所有节点都是全新的,所以直接全部新建就好了,新建是指创建出所有新DOM,并且添加进父节点
  4. 只有旧节点有子节点而新节点没有,说明更新后的页面,旧节点全部都不见了,那么要做的,就是把所有的旧节点删除,也就是直接把DOM 删除

nextTick原理和作用

如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick()

vue中scope和slot-scope区别

这两个其实都是被废除的标签了,vue官方推荐使用 v-slot

这是用来在指定的位置输出我们的子元素的标签

vue模版data属性里面一个对象,给该对象新增属性,视图没有更新,如何解决

1
2
// 使用this.$set, 第一个参数是需要新增属性的对象,第二个参数是新增的属性的key,第三个参数是属性对应的value
this.$set(this.obj, 'a', '呆呆');

实现一个双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<body>
<span></span>
<input type="text">
</body>
<script>
const span = document.querySelector('span');
const input = document.querySelector('input');

let obj = {};
// 数据劫持
// 第一个参数是需要在其上定义或修改属性的对象
// 第二个参数是要定义或修改的属性的名称
Object.defineProperty(obj, 'inputText', {
enumerable: true, // 是否可在for in 和Object.keys() 中被枚举
configurable: true, // 是否可以被删除
get() {
console.log('被读取了')
},
set(newValue) {
input.value = newValue;
span.innerHTML = newValue;
console.log('数据更新了')
}
})

// 监听input的输入事件
input.addEventListener('keyup', function (e) {
obj.inputText = e.target.value;
})
</script>

vue的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
beforeCreate() {
// 实例创建前
// 可以在此时加一些loading效果,在created时进行移除
},
created() {
// 实例创建
// vue对象的属性有值了,但是DOM还没有生成
},
beforeMount() {
// 渲染DOM前
// data 和 $el 均已存在,但 DOM 为虚拟DOM 仍未完全加载
},
mounted() {
// 渲染DOM后
// 一般在这个生命周期进行ajax请求
},
beforeupdate() {
// 更新数据前
},
updated() {
// 更新数据后
// data属性中的数据每次更新都会触发这个生命周期
},
beforeDestroy() {
// 卸载组件前
},
destroyed() {
// 更新数据后
},

vue第一次加载页面,会触发哪几个生命周期

beforeCreate、created、beforeMount、mounted

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key 的特殊属性主要用在 Vue/React 的虚拟DOM算法,在新旧nodes对比时辨识VNodes

但是注意,不要用遍历时的index做key,容易引发奇怪的bug

说说你对 SPA 单页面的理解,它的优缺点分别是什么?

spa但页面应用是指:在页面初始化时加载相应的html、js、css,页面加载完成后,不会因为用户的操作而对页面进行跳转或重新加载

优点:用户体验好、前后端分离职责清晰

缺点:首次加载耗时长、seo难度大

如何seo:ssr服务器渲染

操作dom与操作数据优缺点(vue和jquery优缺点)

1.dom操作过多会影响页面性能

2.数据双向绑定在处理表单时更方便

3.组件化的开发模式更有利于项目维护

vue2.0 $router和$route的区别

1.$router是Vue的一个实例,里面有路由的很多关键的对象和属性(例如:this.$router.push、this.$router.go等)

2.$route对象表示当前的路由信息,包含当前的路径,参数,query对象等(例如:this.$route.query.id等)

$route.params和$route.query区别

1.params传参不会拼接在url上,query会

2.params刷新会丢失传参,query不会

vue路由传参的几种方式

第一种 直接传

1
2
3
4
5
6
7
8
9
10
11
12
// 如果是多个参数   this.$router.push(`/uploadFileDetail/${row.id}${row.age}`)
this.$router.push(`/uploadFileDetail/${row.id}`);

// 这种形式路由文件需要配置一下,如果是多个参数 path: '/uploadFileDetail/:id:age'
{
path: '/uploadFileDetail/:id',
component: resolve => require(['@/pages/details/uploadFileDetail'], resolve),
name: '文件详情'
}

// 取参数,this.$route.params是一个对象,参数以键值对形式储存
this.$route.params.id

第二种 query传参(传递的参数会拼接在url上)

1
2
3
4
5
6
7
8
9
10
11
this.$router.push({
path: "/uploadFileDetail",
query: {
id: 1,
name: "呆呆的",
age: 18
}
});

// 取参
this.$route.query.id

第三种 params传参(params传参不会拼接在url上,刷新会丢失传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 这种形式就不需要path字段了,需要路由的name字段
this.$router.push({
name: "文件详情",
params: {
id: 1,
name: "呆呆的",
age: 18
}
});

// 路由配置
{
path: '/uploadFileDetail',
component: resolve => require(['@/pages/details/uploadFileDetail'], resolve),
name: '文件详情'
}

// 取参
this.$route.params

history路由和hash路由区别

对比点 hash history
url显示 有#,类似:http://oursite.com/#/id=123 无#,类似:http://oursite.com/user/id
回车刷新 可以加载到hash值对应页面 跳转到404
支持版本 支持低版本浏览器和IE浏览器 HTML5新推出的API
底层原理 利用了onhashchange 事件 history api

vuex的使用场景

1.解决页面之间复杂数据传输的问题

vue是双向数据流吗?为什么页面会实时更新?

Vue是单向数据流,不是双向绑定,Vue的双向绑定不过是语法糖,Object.definePropert是用来做响应式更新的

1
2
3
// 类似于
<input v-model=“phoneInfo.phone”/>
<input :value="PhoneInfo.phone" @input="val => { PhoneInfo.phone = val }"

描述下vuex的几个属性

state:vuex的基本数据,用来存储变量

getters:从基本数据(state)派生的数据,相当于state的计算属性

mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数)

action:和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作

modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

说说你对slot的理解有多少?slot使用场景有哪些?

通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理。

slot有三类,默认插槽、具名插槽、作用域插槽

默认插槽

1
2
3
4
5
6
7
8
<FancyButton>
<!-- 插槽内容 -->
</FancyButton>

// FancyButton组件内部
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>

具名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>

<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>

// BaseLayout组件内部
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

作用域插槽,可以通过子组件绑定数据传递给父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

// 组件内部
<script setup>
const greetingMessage = 'hello'
</script>

<template>
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
</template>

Vue中的自定义指令是如何工作的?描述一下Vue中自定义指令的注册和使用过程

在Vue中,可以通过Vue.directive方法来创建自定义指令。自定义指令可以用于扩展Vue的功能,实现特定的DOM操作或交互行为。

1
2
3
4
5
6
7
// 自定义指令的生命周期钩子

bind:在指令绑定到元素时调用,只调用一次。
inserted:在被绑定元素插入到父节点时调用。
update:在组件更新时调用,可能会触发多次。
componentUpdated:在组件及其子组件更新完成后调用。
unbind:在指令从元素上解绑时调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 在Vue实例中定义自定义指令
Vue.directive('my-directive', {
// 指令生命周期钩子
bind(el, binding, vnode) {
// 在指令绑定到元素时调用,只调用一次
// el:指令所绑定的元素
// binding:指令的绑定信息,包括值、修饰符等
// vnode:Vue编译生成的虚拟节点

// 指令绑定时的初始化操作
el.textContent = 'Initialized!';
},

inserted(el, binding, vnode) {
// 在被绑定元素插入到父节点时调用
// 只保证父节点存在,但不一定已经插入到文档中

// 执行插入后的操作
el.textContent = 'Inserted!';
},

update(el, binding, vnode, oldVnode) {
// 在组件更新时调用,可能会触发多次

// 执行更新操作
el.textContent = 'Updated!';
},

componentUpdated(el, binding, vnode, oldVnode) {
// 在组件及其子组件更新完成后调用

// 执行更新完成后的操作
el.textContent = 'Component Updated!';
},

unbind(el, binding, vnode) {
// 在指令从元素上解绑时调用

// 执行解绑操作
el.textContent = 'Unbind!';
}
});

五年面试,三年模拟(框架篇)
https://xypecho.github.io/2021/03/18/五年面试,三年模拟-框架篇/
作者
很青的青蛙
发布于
2021年3月18日
许可协议