既然Vue通过数据劫持能够精准探测数据变动,为什么还须要虚构DOM进行diff检测差别
- 响应式数据变动,
Vue
的确能够在数据变动时,响应式零碎能够立即得悉。然而如果给每个属性都增加watcher
用于更新的话,会产生大量的watcher
从而升高性能 - 而且粒度过细也得导致更新不精确的问题,所以
vue
采纳了组件级的watcher
配合diff
来检测差别
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。
3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。
4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。
5. unbind:只调用一次,指令与元素解绑时调用。
vue 中应用了哪些设计模式
1.工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2.单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3.公布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装璜模式: (@装璜器的用法)
6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略
…其余模式欢送补充
生命周期钩子是如何实现的
Vue 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)
相干代码如下
export function callHook(vm, hook) {
// 顺次执行生命周期对应的办法
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i].call(vm); //生命周期外面的this指向以后实例
}
}
}
// 调用的时候
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = mergeOptions(vm.constructor.options, options);
callHook(vm, "beforeCreate"); //初始化数据之前
// 初始化状态
initState(vm);
callHook(vm, "created"); //初始化数据之后
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
对SSR的了解
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端实现,而后再把html间接返回给客户端
SSR的劣势:
- 更好的SEO
- 首屏加载速度更快
SSR的毛病:
- 开发条件会受到限制,服务器端渲染只反对beforeCreate和created两个钩子;
- 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于Node.js的运行环境;
- 更多的服务端负载。
vue 中应用了哪些设计模式
1.工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2.单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3.公布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装璜模式: (@装璜器的用法)
6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略
Vue模版编译原理晓得吗,能简略说一下吗?
简略说,Vue的编译过程就是将template
转化为render
函数的过程。会经验以下阶段:
- 生成AST树
- 优化
- codegen
首先解析模版,生成AST语法树
(一种用JavaScript对象的模式来形容整个模板)。 应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM也不会变动。那么优化过程就是深度遍历AST树,依照相干条件对树节点进行标记。这些被标记的节点(动态节点)咱们就能够跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是将优化后的AST树转换为可执行的代码
。
为什么在 Vue3.0 采纳了 Proxy,摈弃了 Object.defineProperty?
Object.defineProperty 自身有肯定的监控到数组下标变动的能力,然而在 Vue 中,从性能/体验的性价比思考,尤大大就弃用了这个个性。为了解决这个问题,通过 vue 外部解决后能够应用以下几种办法来监听数组
push();
pop();
shift();
unshift();
splice();
sort();
reverse();
因为只针对了以上 7 种办法进行了 hack 解决,所以其余数组的属性也是检测不到的,还是具备肯定的局限性。
Object.defineProperty 只能劫持对象的属性,因而咱们须要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么须要深度遍历,显然如果能劫持一个残缺的对象是才是更好的抉择。
Proxy 能够劫持整个对象,并返回一个新的对象。Proxy 不仅能够代理对象,还能够代理数组。还能够代理动静减少的属性。
MVVM、MVC、MVP的区别
MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,次要通过拆散关注点的形式来组织代码构造,优化开发效率。
在开发单页面利用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简略我的项目时,可能看不出什么问题,如果我的项目变得复杂,那么整个文件就会变得简短、凌乱,这样对我的项目开发和前期的我的项目保护是十分不利的。
(1)MVC
MVC 通过拆散 Model、View 和 Controller 的形式来组织代码构造。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 利用了观察者模式,当 Model 层产生扭转的时候它会告诉无关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它次要负责用户与利用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来实现对 Model 的批改,而后 Model 层再去告诉 View 层更新。
(2)MVVM
MVVM 分为 Model、View、ViewModel:
- Model代表数据模型,数据和业务逻辑都在Model层中定义;
- View代表UI视图,负责数据的展现;
- ViewModel负责监听Model中数据的扭转并且管制视图的更新,解决用户交互操作;
Model和View并无间接关联,而是通过ViewModel来进行分割的,Model和ViewModel之间有着双向数据绑定的分割。因而当Model中的数据扭转时会触发View层的刷新,View中因为用户交互操作而扭转的数据也会在Model中同步。
这种模式实现了 Model和View的数据主动同步,因而开发者只须要专一于数据的保护操作即可,而不须要本人操作DOM。
(3)MVP
MVP 模式与 MVC 惟一不同的在于 Presenter 和 Controller。在 MVC 模式中应用观察者模式,来实现当 Model 层数据发生变化的时候,告诉 View 层的更新。这样 View 层和 Model 层耦合在一起,当我的项目逻辑变得复杂的时候,可能会造成代码的凌乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过应用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只晓得 Model 的接口,因而它没有方法管制 View 层的更新,MVP 模式中,View 层的接口裸露给了 Presenter 因而能够在 Presenter 中将 Model 的变动和 View 的变动绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还蕴含了其余的响应逻辑。参考前端vue面试题具体解答
说说Vue的生命周期吧
什么时候被调用?
- beforeCreate :实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event
事件回调。无$el
. - beforeMount:在挂载之前调用,相干
render
函数首次被调用 - mounted:了被新创建的
vm.$el
替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些DOM操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于DOM的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax放在哪个生命周期?:个别放在 mounted
中,保障逻辑统一性,因为生命周期是同步执行的, ajax
是异步执行的。复数服务端渲染 ssr
同一放在 created
中,因为服务端渲染不反对 mounted
办法。 什么时候应用beforeDestroy?:以后页面应用 $on
,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove
。
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref 与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent / $children
:拜访父 / 子实例
(3)EventBus ($emit / $on)
实用于 父子、隔代、兄弟组件通信 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( class 和 style 除外 )。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 ( class 和 style 除外 ),并且能够通过v-bind="$attrs"
传入外部组件。通常配合inheritAttrs
选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的)v-on
事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信 先人组件中通过 provider
来提供变量,而后在子孙组件中通过 inject
来注入变量。 provide / inject API
次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。 (6)Vuex
实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
双向数据绑定的原理
Vue.js 是采纳数据劫持联合发布者-订阅者模式的形式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:
- 须要observe的数据对象进行递归遍历,包含子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变动
- compile解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
- Watcher订阅者是Observer和Compile之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器(dep)外面增加本人 ②本身必须有一个update()办法 ③待属性变动dep.notice()告诉时,能调用本身的update()办法,并触发Compile中绑定的回调,则功成身退。
- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听本人的model数据变动,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据model变更的双向绑定成果。
为什么Vue采纳异步渲染呢?
Vue
是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue
会在本轮数据更新后,在异步更新视图。核心思想 nextTick
。
dep.notify()
告诉 watcher进行更新, subs[i].update
顺次调用 watcher 的 update
, queueWatcher
将watcher 去重放入队列, nextTick( flushSchedulerQueue
)在下一tick中刷新watcher队列(异步)。
v-model 的原理?
咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:
- text 和 textarea 元素应用 value 属性和 input 事件;
- checkbox 和 radio 应用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
vue是如何实现响应式数据的呢?(响应式数据原理)
Vue2: Object.defineProperty
从新定义 data
中所有的属性, Object.defineProperty
能够使数据的获取与设置减少一个拦挡的性能,拦挡属性的获取,进行依赖收集。拦挡属性的更新操作,进行告诉。
具体的过程:首先Vue应用 initData
初始化用户传入的参数,而后应用 new Observer
对数据进行观测,如果数据是一个对象类型就会调用 this.walk(value)
对对象进行解决,外部应用 defineeReactive
循环对象属性定义响应式变动,外围就是应用 Object.defineProperty
从新定义数据。
虚构 DOM 实现原理?
虚构 DOM 的实现原理次要包含以下 3 局部:
- 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
- diff 算法 — 比拟两棵虚构 DOM 树的差别;
- pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。
谈谈你对MVVM的了解
为什么要有这些模式,目标:职责划分、分层(将Model
层、View
层进行分类)借鉴后端思维,对于前端而已,就是如何将数据同步到页面上
MVC模式 代表:Backbone
+ underscore
+ jquery
- 传统的
MVC
指的是,用户操作会申请服务端路由,路由会调用对应的控制器来解决,控制器会获取数据。将后果返回给前端,页面从新渲染 MVVM
:传统的前端会将数据手动渲染到页面上,MVVM
模式不须要用户收到操作dom
元素,将数据绑定到viewModel
层上,会主动将数据渲染到页面中,视图变动会告诉viewModel
层 更新数据。ViewModel
就是咱们MVVM
模式中的桥梁
MVVM模式 映射关系的简化,暗藏了controller
MVVM
是Model-View-ViewModel
缩写,也就是把MVC
中的Controller
演变成ViewModel
。Model
层代表数据模型,View
代表UI组件,ViewModel
是View
和Model
层的桥梁,数据会绑定到viewModel
层并主动将数据渲染到页面中,视图变动的时候会告诉viewModel
层更新数据。
Model
: 代表数据模型,也能够在Model
中定义数据批改和操作的业务逻辑。咱们能够把Model
称为数据层,因为它仅仅关注数据自身,不关怀任何行为View
: 用户操作界面。当ViewModel
对Model
进行更新的时候,会通过数据绑定更新到View
ViewModel
: 业务逻辑层,View
须要什么数据,ViewModel
要提供这个数据;View
有某些操作,ViewModel
就要响应这些操作,所以能够说它是Model for View
.
总结 : MVVM
模式简化了界面与业务的依赖,解决了数据频繁更新。MVVM
在应用当中,利用双向绑定技术,使得 Model
变动时,ViewModel
会自动更新,而 ViewModel
变动时,View
也会主动变动。
咱们以下通过一个 Vue
实例来阐明 MVVM
的具体实现
<!-- View 层 -->
<div id="app">
<p>{{message}}</p>
<button v-on:click="showMessage()">Click me</button>
</div>
// ViewModel 层
var app = new Vue({
el: '#app',
data: { // 用于形容视图状态
message: 'Hello Vue!',
},
methods: { // 用于形容视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: '/your/server/data/api',
success(res){
vm.message = res;
}
});
}
})
// Model 层
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "test",
"domain": "www.baidu.com"
}
}
delete和Vue.delete删除数组的区别
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
v-show 与 v-if 有什么区别?
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的 “display” 属性进行切换。
所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。
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
思路剖析
这个题目很有难度,首先思考vuex
解决的问题:存储用户全局状态并提供治理状态API。
vuex
需要剖析- 如何实现这些需要
答复范例
- 官网说
vuex
是一个状态管理模式和库,并确保这些状态以可预期的形式变更。可见要实现一个vuex
- 要实现一个
Store
存储全局状态 - 要提供批改状态所需API:
commit(type, payload), dispatch(type, payload)
- 实现
Store
时,能够定义Store
类,构造函数接管选项options
,设置属性state
对外裸露状态,提供commit
和dispatch
批改属性state
。这里须要设置state
为响应式对象,同时将Store
定义为一个Vue
插件 commit(type, payload)
办法中能够获取用户传入mutations
并执行它,这样能够按用户提供的办法批改状态。dispatch(type, payload)
相似,但须要留神它可能是异步的,须要返回一个Promise
给用户以解决异步后果
实际
Store
的实现:
class Store {
constructor(options) {
this.state = reactive(options.state)
this.options = options
}
commit(type, payload) {
this.options.mutations[type].call(this, this.state, payload)
}
}
vuex简易版
/**
* 1 实现插件,挂载$store
* 2 实现store
*/
let Vue;
class Store {
constructor(options) {
// state响应式解决
// 内部拜访: this.$store.state.***
// 第一种写法
// this.state = new Vue({
// data: options.state
// })
// 第二种写法:避免外界间接接触外部vue实例,避免内部强行变更
this._vm = new Vue({
data: {
$$state: options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.getters = {}
options.getters && this.handleGetters(options.getters)
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
get state () {
return this._vm._data.$$state
}
set state (val) {
return new Error('Please use replaceState to reset state')
}
handleGetters (getters) {
Object.keys(getters).map(key => {
Object.defineProperty(this.getters, key, {
get: () => getters[key](this.state)
})
})
}
commit (type, payload) {
let entry = this._mutations[type]
if (!entry) {
return new Error(`${type} is not defined`)
}
entry(this.state, payload)
}
dispatch (type, payload) {
let entry = this._actions[type]
if (!entry) {
return new Error(`${type} is not defined`)
}
entry(this, payload)
}
}
const install = (_Vue) => {
Vue = _Vue
Vue.mixin({
beforeCreate () {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
},
})
}
export default { Store, install }
验证形式
import Vue from 'vue'
import Vuex from './vuex'
// this.$store
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
// state从哪里来的
add (state) {
state.counter++
}
},
getters: {
doubleCounter (state) {
return state.counter * 2
}
},
actions: {
add ({ commit }) {
setTimeout(() => {
commit('add')
}, 1000)
}
},
modules: {
}
})
vue的长处
轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十kb;
简略易学:国人开发,中文文档,不存在语言障碍 ,易于了解和学习;
双向数据绑定:保留了angular的特点,在数据操作方面更为简略;
组件化:保留了react的长处,实现了html的封装和重用,在构建单页面利用方面有着独特的劣势;
视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
虚构DOM:dom操作是十分消耗性能的,不再应用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种形式;
运行速度更快:相比拟与react而言,同样是操作虚构dom,就性能而言,vue存在很大的劣势。
理解nextTick吗?
异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用nextTick
会将办法存入队列,通过异步办法清空以后队列。
子组件能够间接扭转父组件的数据吗?
子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。
Vue提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。
只能通过 $emit
派发一个自定义事件,父组件接管到后,由父组件批改。
前端vue面试题具体解答
说说Vue的生命周期吧
什么时候被调用?
- beforeCreate :实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event
事件回调。无$el
. - beforeMount:在挂载之前调用,相干
render
函数首次被调用 - mounted:了被新创建的
vm.$el
替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些DOM操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于DOM的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax放在哪个生命周期?:个别放在 mounted
中,保障逻辑统一性,因为生命周期是同步执行的, ajax
是异步执行的。复数服务端渲染 ssr
同一放在 created
中,因为服务端渲染不反对 mounted
办法。 什么时候应用beforeDestroy?:以后页面应用 $on
,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove
。
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。
3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。
4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。
5. unbind:只调用一次,指令与元素解绑时调用。
谈一下对 vuex 的集体了解
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无奈长久化、外部外围原理是通过发明一个全局实例 new Vue)
次要包含以下几个模块:
- State:定义了利用状态的数据结构,能够在这里设置默认的初始状态。
- Getter:容许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到部分计算属性。
- Mutation:是惟一更改 store 中状态的办法,且必须是同步函数。
- Action:用于提交 mutation,而不是间接变更状态,能够蕴含任意异步操作。
- Module:容许将繁多的 Store 拆分为多个 store 且同时保留在繁多的状态树中。
如何从实在DOM到虚构DOM
波及到Vue中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的JS语法(将实在DOM转换成虚构DOM) - 优化树
- 将
ast
树生成代码
虚构 DOM 的优缺点?
长处:
- 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
- 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
- 跨平台: 虚构 DOM 实质上是 JavaScript 对象,而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。
毛病:
- 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。
computed和watch有什么区别?
computed:
computed
是计算属性,也就是计算值,它更多用于计算值的场景computed
具备缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值扭转之后,下一次获取computed的值时才会从新调用对应的getter来计算computed
实用于计算比拟耗费性能的计算场景
watch:
- 更多的是「察看」的作用,相似于某些数据的监听回调,用于察看
props
$emit
或者本组件的值,当数据变动时来执行回调进行后续操作 - 无缓存性,页面从新渲染时值不变动也会执行
小结:
- 当咱们要进行数值计算,而且依赖于其余数据,那么把这个数据设计为computed
- 如果你须要在某个数据变动时做一些事件,应用watch来察看这个数据变动
Vue 初始化页面闪动问题如何解决?
呈现该问题是因为在 Vue 代码尚未被解析之前,尚无法控制页面中 DOM 的显示,所以会看见模板字符串等代码。
解决方案是,在 css 代码中增加 v-cloak 规定,同时在待编译的标签上增加 v-cloak 属性:
[v-cloak] { display: none; }
<div v-cloak>
{{ message }}
</div>
虚构 DOM 实现原理?
虚构 DOM 的实现原理次要包含以下 3 局部:
- 用 JavaScript 对象模仿实在 DOM 树,对实在 DOM 进行形象;
- diff 算法 — 比拟两棵虚构 DOM 树的差别;
- pach 算法 — 将两个虚构 DOM 对象的差别利用到真正的 DOM 树。
vue 中应用了哪些设计模式
1.工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2.单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3.公布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装璜模式: (@装璜器的用法)
6.策略模式 策略模式指对象有某个行为,然而在不同的场景中,该行为有不同的实现计划-比方选项的合并策略
Vue性能优化
编码优化:
- 事件代理
keep-alive
- 拆分组件
key
保障唯一性- 路由懒加载、异步组件
- 防抖节流
Vue加载性能优化
- 第三方模块按需导入(
babel-plugin-component
) - 图片懒加载
用户体验
app-skeleton
骨架屏shellap
p壳pwa
SEO优化
- 预渲染
路由的hash和history模式的区别
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
1. hash模式
简介: hash模式是开发中默认的模式,它的URL带着一个#
特点:hash值会呈现在URL外面,然而不会呈现在HTTP申请中,对后端齐全没有影响。所以扭转hash值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的IE浏览器也反对这种模式。hash路由被称为是前端路由,曾经成为SPA(单页面利用)的标配。
原理: hash模式的次要原理就是onhashchange()事件:
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
应用onhashchange()事件的益处就是,在页面的hash值发生变化时,无需向后端发动申请,window就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash值变动对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的hash值和对应的URL关联起来了。
2. history模式
简介: history模式的URL中没有#,它应用的是传统的路由散发模式,即用户在输出一个URL时,服务器会接管这个申请,并解析这个URL,而后做出相应的逻辑解决。 特点: 相比hash模式更加难看。然而,history模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回404。 API: history api能够分为两大部分,切换历史状态和批改历史状态:
- 批改历史状态:包含了 HTML5 History Interface 中新增的
pushState()
和replaceState()
办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了url,但浏览器不会立刻向后端发送申请。如果要做到扭转url但又不刷新页面的成果,就须要前端用上这两个API。 - 切换历史状态: 包含
forward()
、back()
、go()
三个办法,对应浏览器的后退,后退,跳转操作。
尽管history模式抛弃了俊俏的#。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。
如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({
mode: 'history',
routes: [...]
})
3. 两种模式比照
调用 history.pushState() 相比于间接批改 hash,存在以下劣势:
- pushState() 设置的新 URL 能够是与以后 URL 同源的任意 URL;而 hash 只可批改 # 前面的局部,因而只能设置与以后 URL 同文档的 URL;
- pushState() 设置的新 URL 能够与以后 URL 截然不同,这样也会把记录增加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录增加到栈中;
- pushState() 通过 stateObject 参数能够增加任意类型的数据到记录中;而 hash 只可增加短字符串;
- pushState() 可额定设置 title 属性供后续应用。
- hash模式下,仅hash符号之前的url会被蕴含在申请中,后端如果没有做到对路由的全笼罩,也不会返回404谬误;history模式下,前端的url必须和理论向后端发动申请的url统一,如果没有对用的路由解决,将返回404谬误。
hash模式和history模式都有各自的劣势和缺点,还是要依据理论状况选择性的应用。
对 React 和 Vue 的了解,它们的异同
相似之处:
- 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;
- 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;
- 都应用了Virtual DOM(虚构DOM)进步重绘性能;
- 都有props的概念,容许组件间的数据传递;
- 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。
不同之处 :
1)数据流
Vue默认反对数据双向绑定,而React始终提倡单向数据流
2)虚构DOM
Vue2.x开始引入”Virtual DOM”,打消了和React在这方面的差别,然而在具体的细节还是有各自的特点。
- Vue声称能够更快地计算出Virtual DOM的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
- 对于React而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate这个生命周期办法来进行管制,但Vue将此视为默认的优化。
3)组件化
React与Vue最大的不同是模板的编写。
- Vue激励写近似惯例HTML的模板。写起来很靠近规范 HTML元素,只是多了一些属性。
- React举荐你所有的模板通用JavaScript的语法扩大——JSX书写。
具体来讲:React中render函数是反对闭包个性的,所以import的组件在render中能够间接调用。然而在Vue中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。 4)监听数据变动的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
- React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的从新渲染。这是因为 Vue 应用的是可变数据,而React更强调数据的不可变。
5)高阶组件
react能够通过高阶组件(HOC)来扩大,而Vue须要通过mixins来扩大。
高阶组件就是高阶函数,而React的组件自身就是纯正的函数,所以高阶函数对React来说大海捞针。相同Vue.js应用HTML模板创立视图组件,这时模板无奈无效的编译,因而Vue不能采纳HOC来实现。
6)构建工具
两者都有本人的构建工具:
- React ==> Create React APP
- Vue ==> vue-cli
7)跨平台
- React ==> React Native
- Vue ==> Weex
Vue3.x 响应式数据原理
Vue3.x
改用Proxy
代替Object.defineProperty
。因为Proxy
能够间接监听对象和数组的变动,并且有多达13
种拦挡办法。并且作为新规范将受到浏览器厂商重点继续的性能优化。
proxy根本用法
// proxy默认只会代理第一层对象,只有取值再次是对象的时候再次代理,不是一上来就代理,进步性能。不像vue2.x递归遍历每个对象属性
let handler = {
set(target, key, value) {
return Reflect.set(target, key, value);
},
get(target, key) {
if (typeof target[key] == 'object' && target[key] !== null) {
return new Proxy(target[key], handler); // 懒代理,只有取值再次是对象的时候再次代理,进步性能
}
return Reflect.get(target, key);
}
}
let obj = { school: { name: 'poetry', age: 20 } };
let proxy = new Proxy(obj, handler);
// 返回对象的代理
proxy.school
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题
- Vue应用了Object.defineProperty实现双向数据绑定
- 在初始化实例时对属性执行 getter/setter 转化
- 属性必须在data对象上存在能力让Vue将它转换为响应式的(这也就造成了Vue无奈检测到对象属性的增加或删除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下来咱们看看框架自身是如何实现的呢?
Vue 源码地位:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 批改数组的长度, 防止索引>数组长度导致splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的splice变异办法触发响应式
target.splice(key, 1, val)
return val
}
// key 曾经存在,间接批改属性值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// target 自身就不是响应式数据, 间接赋值
if (!ob) {
target[key] = val
return val
}
// 对属性进行响应式解决
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
- 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法
Vuex 和 localStorage 的区别
(1)最重要的区别
- vuex存储在内存中
- localstorage 则以文件的形式存储在本地,只能存储字符串类型的数据,存储对象须要 JSON的stringify和parse办法进行解决。 读取内存比读取硬盘速度要快
(2)利用场景
- Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。vuex用于组件之间的传值。
- localstorage是本地存储,是将数据存储到浏览器的办法,个别是在跨页面传递数据时应用 。
- Vuex能做到数据的响应式,localstorage不能
(3)永久性
刷新页面时vuex存储的值会失落,localstorage不会。
留神: 对于不变的数据的确能够用localstorage能够代替vuex,然而当两个组件共用一个数据源(对象或数组)时,如果其中一个组件扭转了该数据源,心愿另一个组件响应该变动时,localstorage无奈做到,起因就是区别1。
Vue-router 路由钩子在生命周期的体现
一、Vue-Router导航守卫
有的时候,须要通过路由来进行一些操作,比方最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就勾销跳转,并跳到登录页面让其登录。
为此有很多种办法能够植入路由的导航过程:全局的,单个路由独享的,或者组件级的
- 全局路由钩子
vue-router全局有三个路由钩子;
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用
- router.afterEach 全局后置钩子 进入路由之后
具体应用∶
- beforeEach(判断是否登录了,没登录就跳转到登录页)
router.beforeEach((to, from, next) => {
let ifInfo = Vue.prototype.$common.getSession('userData'); // 判断是否登录的存储信息
if (!ifInfo) {
// sessionStorage里没有贮存user信息
if (to.path == '/') {
//如果是登录页面门路,就间接next()
next();
} else {
//不然就跳转到登录
Message.warning("请从新登录!");
window.location.href = Vue.prototype.$loginUrl;
}
} else {
return next();
}
})
- afterEach (跳转之后滚动条回到顶部)
router.afterEach((to, from) => {
// 跳转之后滚动条回到顶部
window.scrollTo(0,0);
});
- 单个路由独享钩子
beforeEnter 如果不想全局配置守卫的话,能够为某些路由独自配置守卫,有三个参数∶ to、from、next
export default [
{
path: '/',
name: 'login',
component: login,
beforeEnter: (to, from, next) => {
console.log('行将进入登录页面')
next()
}
}
]
- 组件内钩子
beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave
这三个钩子都有三个参数∶to、from、next
- beforeRouteEnter∶ 进入组件前触发
- beforeRouteUpdate∶ 以后地址扭转并且改选件被复用时触发,举例来说,带有动静参数的门路foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,因为会渲染同样的foa组件,这个钩子在这种状况下就会被调用
- beforeRouteLeave∶ 来到组件被调用
留神点,beforeRouteEnter组件内还拜访不到this,因为该守卫执行前组件实例还没有被创立,须要传一个回调给 next来拜访,例如:
beforeRouteEnter(to, from, next) {
next(target => {
if (from.path == '/classProcess') {
target.isFromProcess = true
}
})
}
二、Vue路由钩子在生命周期函数的体现
- 残缺的路由导航解析流程(不包含其余生命周期)
- 触发进入其余路由。
- 调用要来到路由的组件守卫beforeRouteLeave
- 调用局前置守卫∶ beforeEach
- 在重用的组件里调用 beforeRouteUpdate
- 调用路由独享守卫 beforeEnter。
- 解析异步路由组件。
- 在将要进入的路由组件中调用 beforeRouteEnter
- 调用全局解析守卫 beforeResolve
- 导航被确认。
- 调用全局后置钩子的 afterEach 钩子。
- 触发DOM更新(mounted)。
- 执行beforeRouteEnter 守卫中传给 next 的回调函数
- 触发钩子的残缺程序
路由导航、keep-alive、和组件生命周期钩子联合起来的,触发程序,假如是从a组件来到,第一次进入b组件∶
- beforeRouteLeave:路由组件的组件来到路由前钩子,可勾销路由来到。
- beforeEach:路由全局前置守卫,可用于登录验证、全局路由loading等。
- beforeEnter:路由独享守卫
- beforeRouteEnter:路由组件的组件进入路由前钩子。
- beforeResolve:路由全局解析守卫
- afterEach:路由全局后置钩子
- beforeCreate:组件生命周期,不能拜访tAis。
- created;组件生命周期,能够拜访tAis,不能拜访dom。
- beforeMount:组件生命周期
- deactivated:来到缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
- mounted:拜访/操作dom。
- activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
- 执行beforeRouteEnter回调函数next。
- 导航行为被触发到导航实现的整个过程
- 导航行为被触发,此时导航未被确认。
- 在失活的组件里调用来到守卫 beforeRouteLeave。
- 调用全局的 beforeEach守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnteY。
- 解析异步路由组件(如果有)。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+),标示解析阶段实现。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 非重用组件,开始组件实例的生命周期:beforeCreate&created、beforeMount&mounted
- 触发 DOM 更新。
- 用创立好的实例调用 beforeRouteEnter守卫中传给 next 的回调函数。
- 导航实现
发表回复