本篇文章给大家分享一些常见的前端 vue 面试题. 有一定的参考价值, 有需要的朋友可以参考一下, 希望对大家有所帮助.
对于前端来说, 尽管 CSS,html,JS 是主要的基础知识, 但是随着技术的不断发展, 出现了很多优秀的 mv * 框架以及小程序框架. 因此, 对于前端开发者而言, 需要对一些前端框架进行熟练掌握. 这篇文章我们一起来聊一聊 VUE 及全家桶的常见面试问题.
1, 请讲述下 VUE 的 MVVM 的理解?
MVVM 是 Model-View-ViewModel 的缩写, 即将数据模型与数据表现层通过数据驱动进行分离, 从而只需要关系数据模型的开发, 而不需要考虑页面的表现, 具体说来如下:
Model 代表数据模型: 主要用于定义数据和操作的业务逻辑.
View 代表页面展示组件(即 dom 展现形式): 负责将数据模型转化成 UI 展现出来.
ViewModel 为 model 和 view 之间的桥梁: 监听模型数据的改变和控制视图行为, 处理用户交互. 通过双向数据绑定把 View 层和 Model 层连接了起来, 而 View 和 Model 之间的同步工作完全是自动的, 无需人为干涉
在 MVVM 架构下, View 和 Model 之间并没有直接的联系, 而是通过 ViewModel 进行交互, Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中, 而 Model 数据的变化也会立即反应到 View 上.
2,VUE 的生命周期及理解?
答: 总共分为 8 个阶段, 具体为: 创建前 / 后, 载入前 / 后, 更新前 / 后, 销毁前 / 后.
创建前 / 后: 在 beforeCreated 阶段: ue 实例的挂载元素 $el 和数据对象 data 都为 undefined, 还未初始化; 在 created 阶段, vue 实例的数据对象 data 有了,$el 还没有.
载入前 / 后: 在 beforeMount 阶段, vue 实例的 $el 和 data 都初始化了, 但还是挂载之前为虚拟的 dom 节点, data.message 还未替换; 在 mounted 阶段, vue 实例挂载完成, data.message 成功渲染.
更新前 / 后: 当 data 变化时, 会触发 beforeUpdate 和 updated 方法.
销毁前 / 后: 在执行 destroy 方法后, 对 data 的改变不会再触发周期函数, 说明此时 vue 实例已经解除了事件监听以及和 dom 的绑定, 但是 dom 结构依然存在.
具体讲解及应用
beforeCreate: 在 new 一个 vue 实例后, 只有一些默认的生命周期钩子和默认事件, 其他的东西都还没创建, data 和 methods 中的数据都还没有初始化. 不能在这个阶段使用 data 中的数据和 methods 中的方法
create:data 和 methods 都已经被初始化好了, 如果要调用 methods 中的方法, 或者操作 data 中的数据, 最早可以在这个阶段中操作
beforeMount: 执行到这个钩子的时候, 在内存中已经编译好了模板了, 但是还没有挂载到页面中, 此时, 页面还是旧的, 不能直接操作页面的 dom 和获取 dom 对象
mounted: 执行到这个钩子的时候, 就表示 Vue 实例已经初始化完成了. 此时组件脱离了创建阶段, 进入到了运行阶段. 如果我们想要通过插件操作页面上的 DOM 节点, 最早可以在和这个阶段中进行
beforeUpdate: 当执行这个钩子时, 页面中的显示的数据还是旧的, data 中的数据是更新后的, 页面还没有和最新的数据保持同步
updated: 页面显示的数据和 data 中的数据已经保持同步了, 都是最新的
beforeDestory:Vue 实例从运行阶段进入到了销毁阶段, 这个时候上所有的 data 和 methods, 指令, 过滤器 ...... 都是处于可用状态. 还没有真正被销毁
destroyed: 这个时候上所有的 data 和 methods, 指令, 过滤器 ...... 都是处于不可用状态. 组件已经被销毁了.
3,v-if 和 v-show 的区别?
共同点: 都能控制元素的显示和隐藏;
不同点: 实现本质方法不同, v-show 本质就是通过控制 CSS 中的 display 设置为 none, 控制隐藏, 只会编译一次; v-if 是动态的向 DOM 树内添加或者删除 DOM 元素, 若初始值为 false, 就不会编译了. 而且 v-if 不停的销毁和创建比较消耗性能.
如果要频繁切换某节点, 使用 v-show(切换开销比较小, 初始开销较大). 如果不需要频繁切换某节点使用 v-if(初始渲染开销较小, 切换开销比较大).
4,v-if 和 v-for 同时使用在同一个标签上的表现?
当 v-if 与 v-for 一起使用时, v-for 具有比 v-if 更高的优先级, 这意味着 v-if 将分别重复运行于每个 v-for 循环中.
所以, 不推荐 v-if 和 v-for 同时使用. 如果 v-if 和 v-for 一起用的话, vue 中的的会自动提示 v-if 应该放到外层去
5,v-for 中的 key 的理解?
需要使用 key 来给每个节点做一个唯一标识, Diff 算法就可以正确的识别此节点. 主要是为了高效的更新虚拟 DOM.
6,vue 中 transition 的理解?
1)定义 transition 时需要设置对应的 name, 具体语法为:<transition name="fade">需要动画的内容或者组件或者页面</transition>
2)过渡动画主要包含 6 个 class, 分别为:
v-enter: 定义元素进入过渡的初始状态, 在元素插入前生效, 插入后一帧删除,
v-enter-active: 在元素插入前生效, 在动画完成后删除,
v-enter-to: 在元素插入后一帧生效, 在动画完成后删除,
v-leave: 离开过渡的初始状态, 在元素离开时生效, 下一帧删除
v-leave-active: 在离开过渡时生效, 在动画完成后删除
v-leave-to: 离开过渡结束状态, 在离开过渡下一帧生效, 在动画完成后删除
:v 会转化为对应的 transition 的 name 值
3)当然我们也可以自定义这六个 class 可以直接在 transition 中设置对应的属性为对应的 class 名称, 属性有: enter-class,enter-active-class,enter-to-class,leave-class,leave-active-class,leave-to-class
4)在同时使用过渡和 CSS 动画的时候 可以设置 type 属性来制定 vue 内部机制监听 transitioned 或者 animationed 事件来完成过渡还是动画的监听
5)如果需要设置对应的过渡时间, 可以直接设置属性 duration, 可以直接接收一个数字(单位为毫秒), 也可以接收一个对象{enter:1000,leave:300}
6)也可以设置过渡的钩子函数, 具体有: before-enter,enter,after-enter,enter-cancelled,before-leave,leave,after-leave,leave-cancelled
7,vue 的自定义指令?
自定义指令分为全局指令和组件指令, 其中全局指令需要使用 directive 来进行定义, 组件指令需要使用 directives 来进行定义, 具体定义方法同过滤器 filter 或者其他生命周期, 具体使用方法如下:
全局自定义指令 directive(name,{}), 其中 name 表示定义的指令名称(定义指令的时候不需要带 v-, 但是在调用的时候需要哦带 v-), 第二个参数是一个对象, 对象中包括五个自定义组件的钩子函数, 具体包括:
bind 函数: 只调用一次, 指令第一次绑定在元素上调用, 即初始化调用一次,
inserted 函数: 并绑定元素插入父级元素 (即 new vue 中 el 绑定的元素) 时调用(此时父级元素不一定转化为了 dom)
update 函数: 在元素发生更新时就会调用, 可以通过比较新旧的值来进行逻辑处理
componentUpdated 函数: 元素更新完成后触发一次
unbind 函数: 在元素所在的模板删除的时候就触发一次
钩子函数对应的参数 el,binding,vnode,oldnode, 具体参数讲解如下:
a,el 指令所绑定的元素 可以直接操组 dom 元素
b,binding 一个对象, 具体包括以下属性:
1)name: 定义的指令名称 不包括 v-
2)value: 指令的绑定值, 如果绑定的是一个计算式, value 为对应计算结果
3)oldvalue: 指令绑定元素的前一个值, 只对 update 和 componentUpdated 钩子函数有值
4)expression: 指令绑定的原始值 不对值进行任何加工
5)arg: 传递给指令的参数
6)modifiers: 指令修饰符, 如: v-focus.show.async 则接收的 modifiers 为{show:true,async:true}
c,vnode:vue 编译生成的虚拟 dom
d,oldVnode: 上一个 vnode, 只在 update 和 componentUpdated 钩子函数中有效
: 如果不需要其他钩子函数, 可以直接简写为: directive("focus",function(el,binding){})
8,vue 的实现原理?
vue.js 是采用数据劫持结合发布者 - 订阅者模式的方式, 通过 Object.defineProperty()来劫持各个属性的 setter,getter, 在数据变动时发布消息给订阅者, 触发相应的监听回调.
具体步骤:
第一步: 需要 observe 的数据对象进行递归遍历, 包括子属性对象的属性, 都加上 setter 和 getter
这样的话, 给这个对象的某个值赋值, 就会触发 setter, 那么就能监听到了数据变化
第二步: compile 解析模板指令, 将模板中的变量替换成数据, 然后初始化渲染页面视图, 并将每个指令对应的节点绑定更新函数, 添加监听数据的订阅者, 一旦数据有变动, 收到通知, 更新视图
第三步: Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁, 主要做的事情是:
1, 在自身实例化时往属性订阅器 (dep) 里面添加自己
2, 自身必须有一个 update()方法
3, 待属性变动 dep.notice()通知时, 能调用自身的 update()方法, 并触发 Compile 中绑定的回调, 则功成身退.
第四步: MVVM 作为数据绑定的入口, 整合 Observer,Compile 和 Watcher 三者, 通过 Observer 来监听自己的 model 数据变化, 通过 Compile 来解析编译模板指令, 最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁, 达到数据变化 -> 视图更新; 视图交互变化(input) -> 数据 model 变更的双向绑定效果.
9,vue 的 diff 算法理解?
1)diff 算法的作用: 用来修改 dom 的一小段, 不会引起 dom 树的重绘
2)diff 算法的实现原理: diff 算法将 virtual dom 的某个节点数据改变后生成的新的 vnode 与旧节点进行比较, 并替换为新的节点, 具体过程就是调用 patch 方法, 比较新旧节点, 一边比较一边给真实的 dom 打补丁进行替换
3)具体过程详解:
a, 在采用 diff 算法进行新旧节点进行比较的时候, 比较是按照在同级进行比较的, 不会进行跨级比较:
b, 当数据发生改变的时候, set 方法会调用 dep.notify 通知所有的订阅者 watcher, 订阅者会调用 patch 函数给响应的 dom 进行打补丁, 从而更新真实的视图
c,patch 函数接受两个参数, 第一个是旧节点, 第二个是新节点, 首先判断两个节点是否值得比较, 值得比较则执行 patchVnode 函数, 不值得比较则直接将旧节点替换为新节点. 如果两个节点一样就直接检查对应的子节点, 如果子节点不一样就说明整个子节点全部改变不再往下对比直接进行新旧节点的整体替换
d,patchVnode 函数: 找到真实的 dom 元素; 判断新旧节点是否指向同一个对象, 如果是就直接返回; 如果新旧节点都有文本节点, 那么直接将新的文本节点赋值给 dom 元素并且更新旧的节点为新的节点; 如果旧节点有子节点而新节点没有, 则直接删除 dom 元素中的子节点; 如果旧节点没有子节点, 新节点有子节点, 那么直接将新节点中的子节点更新到 dom 中; 如果两者都有子节点, 那么继续调用函数 updateChildren
e,updateChildren 函数: 抽离出新旧节点的所有子节点, 并且设置新旧节点的开始指针和结束指针, 然后进行两辆比较, 从而更新 dom(调整顺序或者插入新的内容 结束后删掉多余的内容)
10,vue 组件的通信(父子组件和非父子组件)?
父子组件通信
传递参数可以使用 props, 传递函数可以直接在调用子组件的时候传递自定义事件, 并使用 $emit 来调用, 例如:
- // 父组件
- <div classs="parent">
- <child @getinfo="myname" :userinfo="usermessage"></child>
- <div>
- export default {
- data(){
- return {
- usermessage:'我是父亲'
- }
- },
- methods:{
- myname(name){
- console.log('我的名字叫'+name)
- }
- }
- }
- // 子组件
- <div classs="child">
- <button @click="getname">显示我的名字</button>
- <div>
- export default {
- props:['userinfo'],
- methods:{
- getname(){
- this.$emit('getinfo','bilibili')
- }
- }
- }
- import Vue from 'vue'
- export default new Vue()
- <template>
- <div>
- <span>A 组件 ->{{msg}}</span>
- <input type="button" value="把 a 组件数据传给 b" @click ="send">
- </div>
- </template>
- <script>
- import vmson from "../../../util/emptyVue"
- export default {
- data(){
- return {
- msg:{
- a:'111',
- b:'222'
- }
- }
- },
- methods:{
- send:function(){
- vmson.$emit("aevent",this.msg)
- }
- }
- }
- </script>
- <template>
- <div>
- <span>b 组件, a 传的的数据为 ->{{msg}}</span>
- </div>
- </template>
- <script>
- import vmson from "../../../util/emptyVue"
- export default {
- data(){
- return {
- msg:""
- }
- },
- mounted(){
- vmson.$on("aevent",(val)=>{// 监听事件 aevent, 回调函数要使用箭头函数;
- console.log(val);// 打印结果: 我是 a 组件的数据
- this.msg = val;
- })
- }
- }
- </script>
- // 使用钩子函数对路由进行权限跳转
- router.beforeEach((to, from, next) => {
- const role = localStorage.getItem('ms_username');
- if(!role && to.path !== '/login'){
- next('/login');
- }else if(to.meta.permission){
- // 如果是管理员权限则可进入, 这里只是简单的模拟管理员权限而已
- role === 'admin' ? next() : next('/403');
- }else{
- // 简单的判断 IE10 及以下不进入富文本编辑器, 该组件不兼容
- if(navigator.userAgent.indexOf('MSIE')> -1 && to.path === '/editor'){
- confirmButtonText: '确定'
- });
- }else{
- next();
- }
- }
- })
- beforeRouteEnter(to, from, next) {
- next(vm => {
- if (
- vm.$route.meta.hasOwnProperty('auth_key') &&
- vm.$route.meta.auth_key != ''
- ) {
- if (!vm.hasPermission(vm.$route.meta.auth_key)) {
- vm.$router.replace('/admin/noPermission')
- }
- }
- })
- }
- Vue.filter('testfilter', function (value,text) { // 返回处理后的值
- return value+text
- })
- filters: {
- changemsg:(val,text)\=>{ return val + text
- }
- },
- <h3 :title="test|changemsg(1234)">
- {{test|changemsg(4567)}}
- </h3>
- // 多个过滤器也可以串行使用
- <h2>
- {{name|filter1|filter2|filter3}}
- </h2>
- //1. 创建一个单独的文件定义并暴露函数对象
- const filter1 = function (val) {
- return val + '--1'
- }
- const filter2 = function (val) {
- return val + '--2'
- }
- const filter3 = function (val) {
- return val + '--3'
- }
- export default {
- filter1,
- filter2,
- filter3
- }
- //2. 导入 main.JS(在 vue 实例之前)
- import filters from './filter/filter.js'
- //3. 循环注册过滤器
- Object.keys(filters).forEach(key=>{
- Vue.filter(key,filters[key])
- })
- <!-- 逗号分隔字符串, 只有组件 a 与 b 被缓存. -->
- <keep-alive include="a,b">
- <component></component>
- </keep-alive>
- <!-- 正则表达式 (需要使用 v-bind, 符合匹配规则的都会被缓存) -->
- <keep-alive :include="/a|b/">
- <component></component>
- </keep-alive>
- <!-- Array (需要使用 v-bind, 被包含的都会被缓存) -->
- <keep-alive :include="['a','b']">
- <component></component>
- </keep-alive>
- plugins: [
- new webpack.optimize.UglifyJsPlugin({ // 添加 - 删除 console.log
- compress: {
- warnings: false,
- drop_debugger: true,
- drop_console: true
- },
- sourceMap: true
- }),
- http { // 在 http 中配置如下代码,
- gzip on;
- gzip_disable "msie6";
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 8; #压缩级别
- gzip_buffers 16 8k;
- #gzip_http_version 1.1;
- gzip_min_length 100; #不压缩临界值
- gzip_types text/plain application/JavaScript application/x-JavaScript text/CSS
- application/xml text/JavaScript application/x-httpd-PHP image/jpeg image/gif image/PNG;
- }
- // 字符串
- this.$router.push('home')
- // 对象
- this.$router.push({ path: 'home' })
- // 命名的路由
- this.$router.push({ name: 'user', params: { userId: 123 }})
- // 带查询参数, 变成 /register?plan=123
- this.$router.push({ path: 'register', query: { plan: '123' }})
- // 页面路由跳转 前进或者后退
- this.$router.go(-1) // 后退
- /* vue 异步组件技术 */
- {
- path: '/home',
- name: 'home',
- component: resolve => require(['@/components/home'],resolve)
- },{
- path: '/index',
- name: 'Index',
- component: resolve => require(['@/components/index'],resolve)
- },{
- path: '/about',
- name: 'about',
- component: resolve => require(['@/components/about'],resolve)
- }
- // 下面 2 行代码, 没有指定 webpackChunkName, 每个组件打包成一个 JS 文件.
- /* const Home = () => import('@/components/home')
- const Index = () => import('@/components/index')
- const About = () => import('@/components/about') */
- // 下面 2 行代码, 指定了相同的 webpackChunkName, 会合并打包成一个 JS 文件. 把组件按组分块
- const Home = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
- const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
- const About = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about')
- {
- path: '/about',
- component: About
- }, {
- path: '/index',
- component: Index
- }, {
- path: '/home',
- component: Home
- }
- /* 组件懒加载方案三: webpack 提供的 require.ensure() */
- {
- path: '/home',
- name: 'home',
- component: r => require.ensure([], () => r(require('@/components/home')), 'demo')
- }, {
- path: '/index',
- name: 'Index',
- component: r => require.ensure([], () => r(require('@/components/index')), 'demo')
- }, {
- path: '/about',
- name: 'about',
- component: r => require.ensure([], () => r(require('@/components/about')), 'demo-01')
- }
- [v-cloak] {
- display: none;
- }
来源: http://www.css88.com/interview/17164.html