1.MVC和MVVM的区别
MVC:M-model模型,状态管理,V-view视图展示,C-controller控制器,负责将数据展示到页面
MVVM:VM-view-model,实现了数据的双向绑定,将数据展示到页面(数据绑定),监听视图更新数据(DOM事件监听)
vue为什么没有遵循MVVM思想,是因为MVVM要求M与V不能直接通信,而vue提供了$ref导致model可以直接操作view
2.vue中data为什么是一个函数
组件中数据以data函数的返回值返回,相当于给组件创建了私有数据空间,让每个组件维护各自私有的数据。如果data是对象的话,会导致复用的组件共用一份数据,当一个组件的数据修改后,所有组件的数据都发生了改变
3.vue组件通讯有哪几种方式
1.父子组件props和$emit
2.provide和inject使用于父子和子孙,组件库很常见
3.$attrs和$listeners
4.$children和$parent
5.$ref获取组件实例
6.eventBus
7.vuex
4.vue生命周期
creat、mount、update、destroy、active
想要获取DOM需要在mounted后
获取数据:created后
发送请求可以:created 推荐created后,减少loading时间
5.v-if和v-show区别
v-if:有条件的新增和删除dom节点,使用于按条件渲染,切换不频繁的场景
v-show:修改display属性值,初始渲染耗时,使用于频繁隐藏和显示节点的场景
6.display:none、visibility:hidden 和 opacity:0 之间的区别
7.说说vue内置指令
内置指令是官方提供的使用频率高的操作语法糖,v-model,v-bind,v-on,v-if,v-show,v-for,v-once,v-cloak,v-html,v-text,v-pre
8.怎么理解vue的单向数据流
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能通过$emit请求父组件修改数据。这样会防止子组件修改父组件的状态,导致你的应用数据流难以理解
实在想要修改父组件的值可以将其赋值给子组件的新变量
9.computed和watch的区别和应用场景
computed:会将计算结果缓存,只有当依赖发生改变时才会从新计算并缓存
watch:监听依赖的改变,不会缓存结果;使用于有异步的情况
10.v-for与v-if为什么不建议一起使用
因为v-for的优先级大于v-if,如果遇到需要同时使用可以考虑现将数据源用computed处理后再使用。
vue3已经优化了这个问题
11.vue2.0响应式数据处理的原理
vue2.0:数据劫持+观察者模式,使用Object.defineProperty来对属性进行劫持,当页面使用对应属性时,每个属性都拥有自己的dep属性,里面存放所依赖的watcher(依赖收集器),当属性发生改变后会通知自己对应的watcher去更新(派发更新)
class Observer {
// 观测值
constructor(value) {
this.walk(value);
}
walk(data) {
// 对象上的所有属性依次进行观测
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {
observe(value); // 递归关键
// --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止
// 思考?如果Vue数据嵌套层级过深 >>性能会受影响
Object.defineProperty(data, key, {
get() {
console.log("获取值");
//需要做依赖收集过程 这里代码没写出来
return value;
},
set(newValue) {
if (newValue === value) return;
console.log("设置值");
//需要做派发更新过程 这里代码没写出来
value = newValue;
},
});
}
export function observe(value) {
// 如果传过来的是对象或者数组 进行属性劫持
if (
Object.prototype.toString.call(value) === "[object Object]" ||
Array.isArray(value)
) {
return new Observer(value);
}
}
12.vue如何检测数组的变化
考虑到性能,没有使用defineProperty对数组每一项进行拦截,而是采取数组的(push,shift,pop,splice,unshfit,sort,reverse)方法进行重写,所以vue中修改数组的索引和长度是无法监控的,通过上面7中方法才能够触发数组对应的watcher进行更新
// src/obserber/array.js
// 先保留数组原型
const arrayProto = Array.prototype;
// 然后将arrayMethods继承自数组原型
// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能
export const arrayMethods = Object.create(arrayProto);
let methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"reverse",
"sort",
];
methodsToPatch.forEach((method) => {
arrayMethods[method] = function (...args) {
// 这里保留原型方法的执行结果
const result = arrayProto[method].apply(this, args);
// 这句话是关键
// this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4) this就是a ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
const ob = this.__ob__;
// 这里的标志就是代表数组有新增操作
let inserted;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
default:
break;
}
// 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测
if (inserted) ob.observeArray(inserted);
// 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓
return result;
};
});
13.vue3.0用过吗?了解多少?
vue3.0相比vue2.0做了较大的更新,主要从下面几个方面
1、响应式原理改变:使用proxy替代defineProerty,对数据进行劫持
2、composition API
3.teltport
4.支持多片段
5.setup语法糖
6.全局和内部API支持tree-shake
7.v-model升级
8.v-for和v-if优先级变更
9.组件事件需要在emits中声明....
14.vue3.0与vu2.0响应式原理的区别
vue3.0使用Proxy替代Object.defineProerty,因为Proxy可以直接监听对象和数组的变化,多达13中拦截方法
import { mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
import { isObject } from "./util"; // 工具方法
export function reactive(target) {
// 根据不同参数创建不同响应式对象
return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {
if (!isObject(target)) {
return target;
}
const observed = new Proxy(target, baseHandler);
return observed;
}
const get = createGetter();
const set = createSetter();
function createGetter() {
return function get(target, key, receiver) {
// 对获取的值进行放射
const res = Reflect.get(target, key, receiver);
console.log("属性获取", key);
if (isObject(res)) {
// 如果获取的值是对象类型,则返回当前对象的代理对象
return reactive(res);
}
return res;
};
}
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
console.log("属性新增", key, value);
} else if (hasChanged(value, oldValue)) {
console.log("属性值被修改", key, value);
}
return result;
};
}
export const mutableHandlers = {
get, // 当获取属性时调用此方法
set, // 当修改属性时调用此方法
};
15.vue父子组件生命周期钩子函数执行顺序
1.加载渲染过程
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted
2.子组件更新过程
父 beforeUpdate->子 beforeUpdate->子 updated->父 updated
3.父组件更新过程
父 beforeUpdate->父 updated
4.销毁过程
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
16.虚拟DOM是什么?有什么优缺点?
虚拟DOM是为了提高更新DOM的速度而生的虚拟DOM树,当数据发生改变时,先修改虚拟DOM树,再将其与真实的DOM进行对比。避免了频繁操作DOM
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM,会比innerHtml插入慢
17.v-model原理
<input :value="val" @input="val=$event.target.value" />
text和textarea:value和input radio和checkbox:checked和change select:value和change 在组件上
<currency-input v-model="price"></currentcy-input>
<!--上行代码是下行的语法糖
<currency-input :value="price" @input="price = arguments[0]"></currency-input>
-->
<!-- 子组件定义 -->
Vue.component('currency-input', {
template: `
<span>
<input
ref="input"
:value="value"
@input="$emit('input', $event.target.value)"
>
</span>
`,
props: ['value'],
})
18.v-for为什么要加key
vue为了提高更新效率,vue采用了就地更新原则。为了保证不了列表的正确更新,需要增加唯一标识key
19.vue事件绑定原理
vue绑定事件是利用$on实现的
$on、$eimt是基于发布订阅者模式,维护一个事件中心,$on将事件按照名称存储在事件中心里面,称之为订阅者。$emit将对应的事件进行发布,去执行事件中心里面的监听器
20.vue-router路由钩子函数是什么?执行顺序是什么?
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫(2.2+)。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。
21.vue-router动态路由是什么 有什么问题
当我们需要根据不同参数展示相同的组件时,我们需要动态参数来达到这个效果
但是当用户从 `/users/johnny` 导航到 `/users/jolyne` 时,相同的组件实例将被重复使用,会导致组件的生命周期不会被调用,我们可以使用watch监听$route.params的改变或者使用beforeRouteUpdate导航守卫
22.谈谈vuex
vuex是专门为vue提供的全局状态管理系统,用于多个组件数据共享和缓存
-
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
-
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
-
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
-
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
-
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
23.vuex页面刷新数据丢失怎么办
将mutation赋值的时候将值备份到本地,取值的时候store中没有就去本地的
推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
24.vuex为什么要分模块病假命名空间
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间:默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
25.使用过vue ssr吗?
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端。
优点: SSR 有着更好的 SEO、并且首屏加载速度更快
缺点: 开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。
服务器会有更大的负载需求
26.vue中使用了哪些设计模式
1.工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装饰模式: (@装饰器的用法)
6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
27.你做过哪些vue的性能优化
- 首屏优化 包的大小,使用CDN加载依赖
- v-for\v-if 使用computed处理数据源
- 请求接口反应慢 将请求接口的方法放在created里面
- 优化内存空间 beforedestory里面清除定时器...
- vuex数据持久化处理
- 对象层级不要过深,否则性能就会差
- 不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
- 大数据列表和表格性能优化-虚拟列表/虚拟表格
- 防止内部泄漏,组件销毁后把全局变量和事件销毁
- 图片懒加载
- 路由懒加载
- 第三方插件的按需引入
- 适当采用 keep-alive 缓存组件
- 防抖、节流运用
- 服务端渲染 SSR or 预渲染
28.vue.mixin的使用场景和原理
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
29. nextTick的使用场景
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
30.keep-alive 使用场景和原理
keep-alive 是 Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
- 常用的两个属性 include/exclude,允许组件有条件的进行缓存。
- 两个生命周期 activated/deactivated,用来得知当前组件是否处于活跃状态。
- keep-alive 的中还运用了 LRU(最近最少使用) 算法,选择最近最久未使用的组件予以淘汰。
31.vue.set方法原理
了解 Vue 响应式原理的同学都知道在两种情况下修改数据 Vue 是不会触发视图更新的
1.在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
2.直接更改数组下标来修改数组的值
Vue.set 或者说是$set 原理如下
因为响应式数据 我们给对象和数组本身都增加了__ob__属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象__ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组
32.vue.extend作用和原理
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
33.自定义指令
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
-
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
-
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
-
update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
-
componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
-
unbind:只调用一次,指令与元素解绑时调用。
原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
34.Vue修饰符有哪些
事件修饰符
-
.stop 阻止事件继续传播
-
.prevent 阻止标签默认行为
-
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
-
.self 只当在 event.target 是当前元素自身时触发处理函数
-
.once 事件将只会触发一次
-
.passive 告诉浏览器你不想阻止事件的默认行为 v-model 的修饰符
-
.lazy 通过这个修饰符,转变为在 change 事件再同步
-
.number 自动将用户的输入值转化为数值类型
-
.trim 自动过滤用户输入的首尾空格 键盘事件的修饰符
-
.enter
-
.tab
-
.delete (捕获“删除”和“退格”键)
-
.esc
-
.space
-
.up
-
.down
-
.left
-
.right
系统修饰键
- .ctrl
- .alt
- .shift
- .meta
鼠标按钮修饰符
- .left
- .right
- .middle
35.模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
36.生命周期钩子是如何实现的
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
37.函数式组件使用场景和原理
函数式组件与普通组件的区别
1.函数式组件需要在声明组件是指定 functional:true
2.不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
3.没有生命周期钩子函数,不能使用计算属性,watch
4.不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰
使用场景:
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
38.能说下 vue-router 中常用的路由模式实现原理吗
hash 模式
1. location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
2. 可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录使用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点:兼容性好但是不美观
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
特点:虽然美观,但是刷新会出现 404 需要后端进行配置
39.diiff算法
40.说说你对SPA单页面应用的理解,它的优缺点是什么?
SPA仅在web页面初始化时加载相应的HTML、js、css资源。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;而是使用路由机制实现HTML内容的改变,ui与用户的交互,避免页面的重新加载
优点 用户体验好、快,内容的改变不需要重新加载整个页面,避免不必要的跳转和重复渲染 减少服务器的压力 前后端分离,架构清晰。前端负责交互逻辑,后端负责数据处理 缺点 初次加载耗时多 不能使用浏览器的前进后退功能,需要使用路由实现 SEO难度大
41.v-show与v-if有什么区别
v-show和v-if都是vue提供的可以隐藏元素的指令 v-show使用的是css display:none来隐藏元素 v-if是有条件的删除或新增元素,一般与v-else、v-else-if搭配使用 如果元素切换频繁,建议使用v-show。按条件展示建议使用v-if
42.直接给数组赋值,vue能检测到变化吗
由于js限制,vue不能检测到一下变化
当你利用索引直接设置数组项时
可以使用$set,splice
当你直接修改数组长度时
可以使用splice
43.父组件可以监听到子组件的生命周期吗
1.手动监听 在子组件生命周期钩子函数里面手动触发父组件定义的事件 2.利用@hook 来监听子组件生命周期
44.vue怎么使用$set解决对象新增属性不能响应的问题
由于vue无法检测到对象属性的添加后删除,以及数组索引赋值、直接修改长度。vue提供了$set
如果是数组,会直接调用splice去触发更新
如果是对象,会盘对key是否存在,如果存在就直接赋值,不存在需要将对应的属性进行响应式处理(defineReactive)
44.# Vue3.0里为什么要用 Proxy API 替代 defineProperty API
defineproperty
- 检测不到对象属性的添加和删除
- 数组
API方法无法监听到 - 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题 proxy是对整个对象进行劫持,而且多达13种拦截方式;也可以监听到数组的改变;对于属性是对象的话,也需要深层监听 proxy
Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty不具备的
正因为defineProperty自身的缺陷,导致Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外set、delete方法)
Proxy 不兼容IE,也没有 polyfill, defineProperty 能支持到IE9