vue 2.0-3.0 设计原理

vue面试题

什么是MVVM

1.MVVM是一种设计思想,Model层代表数据模型,也可以在model中定义数据修改和操作的业务逻辑;view代表UI组件,它负责将
数据模型转化成UI展现出来,viewModel是一个同步view和model的对象
2.在MVVM架构下,view和Model之间没有直接的联系,Model和viewModel之间的交互是双向的,因此View数据的变化会同步到Model中,而Model数据的变化也会立即反应到View上。

响应式数据的原理是什么

1.核心是通过 ES2015 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上

2.检测属性为对象类型
只需再一次将当前对象下的所有普通类型的监听变化即可 如果该对象下还有对象属性,使用递归 继续监听就可以了

3.检测属性为数组对象类型
//arr.pop 删除最后一个元素 改变数组长度
//arr.shift 删除第一个元素 改变数组长度 unshift是新增
//arr.sort 字母排序 reverse 倒序
只有push、length、pop一些特殊的方法确实不能触发setter,这跟方法的内部实现与Object.defineProperty的setter钩子的触发实现有关系,是语言层面的原因
直接修改原型上的push,pop,shift,unshift,splice, sort,reverse七个方法,
Vue2.x中并没有实现将已存在的数组元素做监听,而是去监听造成数组变化的方法,触发这个方法的同时去调用挂载好的响应页面方法,达到页面响应式的效果。

4.Vue3.0的数据变化监听
1.defineProperty 只能监视对象的属性读写
2.Proxy 可以监听delete操作 对象方法调用等等
3.Proxy 可以更好的支持数组对象的监视
4.proxy 是以非侵入的方式监管了对象的读写
不需要对对象本身进行任何的操作 而defineProperty需要特定的方式单独定义 需要被监视的属性 需要做很多额外操作

5.observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持;
  dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;
  watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。

mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?

1.mvc和mvvm其实区别并不大。都是一种设计思想。主要就是mvc中controller演变成mvvm中的viewModel。mvvm主要
解决lmvc中大量DOM操作是页面渲染性能降低,加载速度变慢,影响用户体验
2.区别:vue数据驱动,通过数据来显示视图层而不是节点操作
3.场景:跨端、跨平台

为何Vue采用异步渲染

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以考虑性能问题,Vue会在本轮数据更新之后,再去异步更新视图
原理:

首先调用dep.notify()通知watcher进行更新
调用watcher的subs.update()方法
将watcher去重之后放到队列当中

双向绑定实现

<input type="text" id="text">
<p id="show"></p>
<script>
    var obj = {};
    Object.defineProperty(obj,"name",{
        get:function(){
            return name;
        },
        set:function(value){
            document.getElementById("text").value = value;
            document.getElementById("show").innerHTML = value;
        }
    });
    var input = document.getElementById("text");
    input.addEventListener("input",function(event){
        var text = event.target.value;
        obj.name = text;
    });
</script>

nextTick实现原理

我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行

vue中ajax请求放在哪个生命周期中

等到异步渲染开启的时候 可能调用 N 多次
在created的时候,视图中的dom并没有被渲染出来,所以此时如果直接去操作dom节点,无法找到相关元素。
在mounted中,由于此时的dom元素已经渲染出来了,所以可以直接使用dom节点。
一般情况下,都放在mounted中,保证逻辑的统一性。因为生命周期是同步执行的,ajax是异步执行的。

vue 生命周期 beforeDestroy 和 destroyed 调用

当前路由不使用 缓存,离开当前路由会直接调用 beforeDestroy 和 beforeDestroy 销毁
定时器销毁 解除事件的绑定 scroll mousemove

vue中computed和watch区别

1.computed不能再data中定义 是计算值 数据变化时就会调用 props和$emit 页面重新渲染 不会再次执行 可以缓存
1.watch要在data中定义 页面重新渲染 会再次执行 不可以缓存

Vue事件绑定原理

Vue中通过v-on或其语法糖@指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST,生成render函数后并执行得到VNode,VNode生成真实DOM节点或者组件时候使用addEventListener方法进行事件绑定。

Vue中v-html的问题

1.V-html更新的是元素的 innerHTML 不会作为 Vue 模板进行编译
2.scoped 的样式不会应用在 v-html 内部

为什么v-for和v-if不能连用

1.v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
2.如果连用的话会把 v-if 给每个元素都添加一下,会造成性能问题。
3、要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
4、如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

Vue的data为什么是一个函数

组件是可复用的vue实例,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了多少次,组件中的data数据都应该是相互隔离,互不影响的,基于这一理念,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,

vue中什么是作用域插槽

父组件调用子组件的时候,给子组件传了一个插槽,这个插槽叫做作用域插槽,作用域插槽必须是template开头和结尾的一个内容,同时这个插槽要声明(slot-scope)我要接收的数据都放在哪?都放在props里,还要告诉子组件一个模板的信息,

用vnode来描述一个dom结构

1.把DOM标签,属性,内容,子级变成对象的属性
2.兼容性强 不受执行环境的影响 不管node和浏览器
3.不需要频繁操作DOM 提高性能
4.可以做大量性跨平台应用 适合频繁的数据操作和大型项目 维护性高

Vue中diff算法的理解

diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结构进行重绘与回流,而diff算法能够使得操作过程中只更新修改的那部分DOM结构而不更新整个DOM,这样能够最小化操作DOM结构,能够最大程度上减少浏览器重绘与回流的规模。

vue模板编译原理

1.将模板字符串转换成element ASTs(解析器)
2.对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
3.使用element ASTs生成render函数代码字符串(代码生成器)
详细解析

Vue3.0相对2.x的一些改进

1.优化了virtual dom
2.x:在运行时会对所有节点生成一个虚拟节点树,当页面数据发生变更好,会遍历判断virtual dom所有节点(包括一些不会变化的节点)有没有发生变化;
3.0:在模板编译时就会进行一些优化来减少运行时的开销,在编译时会对模板进行分析,判断哪些节点时静态不会变化,哪些时动态会随着model发生变化的,运行时数据变更只会去判断这些动态的dom节点;
2.数据监听系统
vue2.0:使用ES5的Object.defineProperty的getter、setter实现
vue3.0:基于proxy实现了全语言(属性新增删除,数组长度变化等)支持和更好新能的提升,避免了再初始化时对对象的每个属性进行代理
4.更好的多端渲染
vue2.0:比如在小程序中使用vue的时候需要去引用一些第三方的框架,他们本质上时对vue的源码进行了部分重构,以支持对不同端的渲染。而且开发者需要随时关注vue的更新以适配vue可能的更新。
vue3.0:引入了一个Custom Render API,框架开发者只需要从一个平台无关的runtime-core(包含了vue的各种组件,virtual dom的相关算法,但是不包含dom的操作)引入createRenderer来将vue的组件渲染到原生

5.更好的typescript支持
vue2.0:可能需要用到一些第三方的插件,例如vue-property-decorator,就是依赖vue-class-component来进行class形式的代码编写,并通过装饰器进行类型约束
vue3.0:更好的typescript支持包括原生的Class API和TSX

vue和react的区别

1.react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变。
2.而vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
3.总之,react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。
4.类式的组件写法和声明式的写法

1、vue 基于 html 的模板语法,react 是 jsx 语法,可以 html 和 js 混写;
2、状态管理 vue 是 data,react 是 state 但是不能直接修改 state,需要 setState 方法;
3、vue 使用 solt 插槽进行嵌套传递,react 使用 props.children 传递;
4、vue 使用指令渲染,react 逻辑运算符;
5、父子组件传值,vue 使用 props 和 $emit,react 使用 props 和子组件触发父组件的方法,父组件通过setState修改;
6、路由,vue 路由组件都会渲染到 router-view 里面,react 是全局组件的方式,子组件作为 children 传递到父组件;
7、vue 实现双向绑定,react 没有;
8、vue 父组件更新子组件不会动,react 父更新子必更新,需要手动设置;

vue的优点是什么

低耦合:视图可以独立于model变化和修改,一个ViewModel可以绑定到不同的view上,
当Model变化的时候View也可以不变
可重用性:你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用逻辑
独立开发:开发人员可以专注于业务逻辑和数据的开发(viewmodel)

require和import区别

require:运行时加载
require:模块就是对象,输入时必须查找对象属性

import:编译时加载(效率更高)【由于是编译时加载,所以import命令会提升到整个模块的头部】
import:ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入(这也导致了没法引用 ES6 模块本身,因为它不是对象)。由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

vue如何实现按需加载配合webpack设置

webpack中提供了require.ensure()来实现按需加载。以前引入路由是通过import 这样的方式引入,改为const定义的方式进行引入。

不进行页面按需加载引入方式:import home from ‘…/…/common/home.vue’

进行页面按需加载的引入方式:const home = r => require.ensure( [], () => r (require(’…/…/common/home.vue’)))

vuex面试相关

1.有五种,分别是 State、 Getter、Mutation 、Action、 Module
2.用法
// store/index.js

mutations: {
  changeCount(state){
   state.count=3000;
  },//欢迎加入前端全栈开发交流圈一起学习交流:864305860
 },
 actions: {
  changeCount3000s({commit}){
   setTimeout(()=>{
    commit('changeCount')
   },3000)
  }
 }

// Index组件

  computed:{
   ...mapState(['count']), // count(){return this.$store.state.count}
   ...mapGetters(['filterNumbers']), // filterNumbers(){return this.$store.getters.filterNumbers}
  }
  methods:{
    ...mapMutations(['add']), // add(){this.$store.commit('add',10)}
    ...mapActions(['changeCount3000s']) // changeCount3000s({this.$store.dispatch('changeCount3000s')}
  }

3.getters 可以对State进行计算操作,它就是Store的计算属性
getters 可以在多组件之间复用
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。

keep-alive的作用是什么?

包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
原理:

1、在 vue 实例创建的时候,created 钩子会创建一个 cache 对象,用来作为缓存容器,保存 vdom 节点。
2、destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
3、在mounted钩子的 render 函数中会先获取 keep-alive 里面组件的名称,然后根据 include 和 exclude 来匹配看看是否要缓存,如果不匹配直接直接返回 vdom ;否则会根据 key 在 this.cache 缓存器里面查找,如果存在则说明之前已经缓存了,直接返回缓存的 vdom 覆盖当前的 vdom ;否则就会将当前的 vdom 存储在 cache 中。
4、最后将该组件实例的 keepAlive 设置为 true。

vue的生命周期

beforeCreate:实例初始化之后;数据观测和事件配置之前调用,组件的选项对象还没有创建,el挂载和data都没有初始化,无法访问方法和数据。
created:实例创建完成之后调用;已经完成了数据观测,属性方法的运算,watch/event 事件回调,data数据初始化已经完成,el挂载还没有开始。
beforeMount:挂载之前调用;el初始化已经完成,vdom已经完成data和模板的结合生成html,但是还没有挂载到html页面里。
mounted:挂载完成之后调用;模板中的html渲染到html页面里。
beforeUpdate:数据更新之前调用;发生在vdom重新渲染和打补丁之前,可以进一步更改状态,不会触发重新渲染。
updated:数据更新,dom渲染之后调用;要避免在这里更改状态,防止更新导致的无线循环。
beforeDestory:实例销毁之前调用;还可以获取到this,一般用于清除定时器和监听事件等。
destoryed:实例销毁之后调用;所有监听事件被清除,子实例被销毁。
activated:(keep-alive的生命周期)页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
deactivated:(keep-alive的生命周期)页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated

vue创建组件的时候data中为什么会被return出一个对象?

可以保证组件的每一次调用都是创建一个新对象,组件之间不会产生影响;

vue路由的几种模式

两种:hash(默认) history 可以用model属性切换路由模式

vue中的事件修饰符

.prevent() 阻止默认事件;
.once() 只执行一次;
.stop() 阻止冒泡;

vue中内置组件

1.component
2.keep-live
3.slot
4.transition
5.transition-group

vue如何优化页面加载

1.使用cdn,不要打包一些公共的文件和组件库
2.图片压缩 懒加载
3.组件提取 方法封装

vue中key的作用

diff算法:
1.当页面的数据发生变化时,Diff算法只会比较同一层级的节点
如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新

2.key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,

3.没有的话
如:B和C之间加一个F
C更新成F,D更新成C,E更新成D,最后再插入E,效率极低
在相同元素中添加会使状态错乱

vue有哪些指令和自定指令

1.v-text、v-html、v-cloak、v-once、v-if、v-else、v-show、v-for、
2.v-bind
v-bind用来动态的绑定一个或者多个特性。没有参数时,可以绑定到一个包含键值对的对象。常用于动 态绑定class和style。以及href等。
简写为一个冒号【 :】

//进行类切换的例子
<div id="app">
    <!--当data里面定义的isActive等于true时,is-active这个类才会被添加起作用-->
    <!--当data里面定义的hasError等于true时,text-danger这个类才会被添加起作用-->
    <div :class="{'is-active':isActive, 'text-danger':hasError}"></div>
</div>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            isActive: true,  
            hasError: false
        }
    })
</script>

3.v-model

修饰符: .lazy、.number、 .trim

4.v-on

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

5.自定义指令

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '
'
+ 'value: ' + s(binding.value) + '
'
+ 'expression: ' + s(binding.expression) + '
'
+ 'argument: ' + s(binding.arg) + '
'
+ 'modifiers: ' + s(binding.modifiers) + '
'
+ 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#hook-arguments-example', data: { message: 'hello!' } })

computed 和 watch 区别,以及 computed 如何实现缓存和更新的

computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm
上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)
computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而
watch 则是当数据发生变化便会调用执行函数
从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据;

watch的参数:
deep:深度监听
immediate :组件加载立即触发回调函数执行

computed缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;

vue初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。

hash 和 history 区别 ,分别是怎么实现的

区别:

1、 url 展示上,hash 有“#”,history 没有
2、hash 可以支持低版本浏览器和 IE8 ,history 只兼容到 IE10
3、history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致404错误	

原理:
hash:通过 onhashchange 事件监听 hash 变化,然后根据 hash 的变化更新页面部分内容(hash变化不会触发浏览器请求,但是会触发 hashchange 事件)。
history:主要是 H5 新增的两个 API(pushState、replaceState);他们可以改变url,但是不会发送请求,这样就可以通过 onpopstate 监听 url 变化来实现页面部分内容更新。

solt 插槽种类以及原理

1、父组件先解析,把插槽当作子组件的子元素处理;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,拿到第一步解析得到的插槽节点,并返回;
//子组件
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>
//父组件
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

父子组件加载顺序

1)、加载过程:父beforeCreate->父cerated->父beforeMount->子beforeCreate->子cerated->子beforeMount>子mounted->父mounted
2)、子组件更新:父beforeUpdate->子beforeUpdate->子updated->父updated
3)、父组件更新:父beforeUpdate->父updated
4)、销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

vue路由的钩子函数(导航守卫)

全局的路由钩子函数:beforeEach、afterEach(一般用于全局进行权限跳转)
单个的路由钩子函数:beforeEnter、beforeLeave(路由内部钩子,一般在路由表里)
组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate

beforeEach:每一次路由该变的之后页面加载之前执行,三个参数(to 将要进入的路由对象、from 即将离开的路由对象、next 跳转方法),next 必须调用
afterEach:每一次路由该变的之后页面加载之后执行;两个参数(to 将要进入的路由对象、from 即将离开的路由对象)
beforeEnter:进入指定路由跳转时需要执行的逻辑
beforeLeave:离开指定路由跳转时需要执行的逻辑
beforeRouteEnter、beforeRouteLeave、 beforeRouteUpdate都是写在组件里面,也有三个参数(to、from、next)

Vue. extend 能做什么

// 创建构造器
var Profile = Vue.extend({
  template: '

{{firstName}} {{lastName}} aka {{alias}}

'
, data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point')

Vue.mixin 使用场景和原理

Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,在使用 mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用;当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。

关于$emit(‘input’,false)

子组件在传值的时候,选用input,如this. e m i t ( ‘ i n p u t ’ , v a l ) , 在 父 组 件 直 接 用 v − m o d e l 绑 定 , 就 可 以 获 取 到 了 , 而 子 组 件 也 可 以 通 过 emit(‘input’,val),在父组件直接用v-model绑定,就可以获取到了,而子组件也可以通过 emit(input,val)vmodelemit(‘input’,false),去改变父组件中v-model 和 子组件中 value 的值 。

<template>
  <div
    v-portal="'body'"
    :class="cls.Popup"
    :style="rootStyle"
  >
    <transition
      name="transition-mask"
      appear
    >
      <div
        v-if="value && mask"
        :class="cls.mask"
        :style="maskStyle"
        @click="handleClickMask"
      />
    </transition>
    <transition
      :name="contentTransitionName"
      appear
      @afterLeave="$emit('after-close')"
      @afterEnter="$emit('after-open')"
    >
      <div
        v-if="value"
        :class="contentClasses"
        :style="contentStyle"
      >
        <slot />
      </div>
    </transition>
  </div>
</template>

你可能感兴趣的:(vue,面试题,vue,vue.js)