2022年前端面试必须学会的知识点-VUE篇(持续更新中...)

一、对MVVM的理解

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是view和model层的桥梁,数据会绑定到viewmodel层并自动将数据渲染到页面中,视图变化的通知viewmodel层更新数据

二、双向数据绑定原理

采用数据劫持和发布-订阅模式,通过Object.defineProperty()来劫持对象各个属性的getter/setter,在数据变化时,发布消息给订阅者,触发相应监听回调,

发布-订阅模式是什么?

就是一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新

vue2.x中如何监测数组变化?

使用了函数劫持的方式,重写了数组的方法,vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用API时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组的变化。

const arrayProto = Array.prototype
// arrayMethods 的原型就是数组的原型
export const arrayMethods = Object.create(arrayProto)
// 数组中会修改自身的7个方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
// 如果数组中增加了数据,遍历新增的数据进行响应式处理

const ob = this.__ob__
if (inserted) ob.observeArray(inserted)
// notify change 数组改变后调用 notify方法 通知 Watcher 去更新视图
ob.dep.notify()

这里对__ob__做下解释,__ob__ 会指向一个Observer对象,每个被双向绑定的对象元素(数组也是对象)都会有一个__ob__ ,而且是单例的。

如果 双向绑定的这个obj 是个对象,那么 var childOb 会是一个Observe 对象,否则是一个null。例如当为obj双向绑定的时候,会调用 obj.__ob.__.dep.depend() 添加一个 同样的watcher。

三、vue生命周期

vue的实例从开始创建,初始化数据,模板编译,挂载dom,渲染更新,卸载这一系列过程,会形成一系列事件钩子函数,方便操作

由于vue在初始化实例时对属性执行getter/setter转化,所以属性必须在data对象上才能将vue转换为响应式的,另外提供了$set方法触发视图更新

1.当页面第一次页面加载时

会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数

2. 更新data中的数据

并不是每次更新data中的数据都会触发beforeUpdate钩子函数,因为只有data中的数据使用在template模板中更新时才会触发beforeUpdate钩子函数。同理,在updated钩子函数修改data中的数据,如果数据未在template模板中使用,不会触发beforeUpdate钩子函数,也不会造成死循环的。

3.哪个生命周期里可以操作data数据?

beforecreated:el 和 data 并未初始化 

created :完成了 data 数据的初始化,el没有
beforeMount :完成了 el 和 data 初始化 
mounted  :完成挂载

四、vue组件的参数传递

父子组件:props和$emit;$parent和ref;provide和inject

非父子组件:new Bus(),vuex

vue动态组件?

一是使用<component>元素的 is 的特性,二是使用 v-if 。

缓存
上述讲到的方法虽然能够实现了动态组件的切换,但是每次切换都会把上一个组件销毁,然后渲染下一个组件,对于多次切换而言,显然每次的销毁和重新渲染,很大消耗了我们的性能。所以我们可以通过keep-alive对组件进行缓存,对于不显示的组件不是去销毁他,而是使他处理不激活的状态,当需要显示时再去激活。

<keep-alive> <component :is="currentView"></component> </keep-alive>

五、vue路由钩子函数

全局守卫:router.beforeEach和afterEach

路由独享守卫:页面里beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave

六、VUEX是?

全局状态管理器,有state:数据,mutations:只能通过mutations方法修改state数据,coomit调用,actions:异步操作,通过dispath调用,getter:计算,modules:分模块

七、vue组件data为啥是函数

每个组件都是vue的实例;组件共享data属性,当data的值是引用类型,改变一个会影响其他

八、vue与react的区别?

1.vue组件分全局注册和局部注册,而react都是import...然后模板引用;

2.vue有指令系统,而react只能使用jsx语法;

3.vue核心是双向数据绑定,单文件组件,通过模板引擎来处理,而react函数式编程,单项数据流,通过js来生成html;

九、watch和computed区别

computed:具有缓存性,依赖值改变后,下一次调用computed值才会重新调用对应的getter,适用于比较消耗性能的计算场景

watch:类似数据监听回调,无缓存

1.watch如何监听对象?

设置immediate:true,和deep:true

2.深度监听很消耗性能,如何优化?

使用字符串形式来监听,如‘obj.a’:{handler(new,old){}}

3.深度监听很消耗性能,如何关闭监听?

const unwatch = app.$watch('text')

unwatch()//手动注销

十、为何要用虚拟Dom进行diff算法检查差异?

1.什么是虚拟dom?

    是对真实dom的抽象,以js对象,即vnode节点作为基础的树,用对象的属性来描述节点,最终通过patch操作,将虚拟节点渲染到页面视图中。

2.为啥要用虚拟dom?

    真实dom很庞大,操作dom会带来很大的性能问题

    比如如果一次操作,会更新10个dom节点,浏览器就会做10次更新

    而用了虚拟dom,发生改变时,虚拟dom不会立即更新视图,而是通过diff算法,找出更新点,然后一次性渲染到视图上,避免大量无谓计算

3.虚拟dom优势?

    最大优势是抽象了原本的渲染过程,实现了跨平台的能力,其次是diff算法

    包括浏览器,安卓,ios,小程序

    例如react,react Native,vue,weex

4.diff算法是个啥?

    一种通过同层树节点进行比较的高效算法

1.同级比较,再比较子节点 2.先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除) 3.比较都有子节点的情况(核心diff) 4.递归比较子节点

    特点:同层级比较,不会跨级;在diff比较过程中,先首尾,然后循环往中间比较

    比如新节点没有那就直接触发旧节点的destory,没有旧节点,就直接creatElm

vue2的核心diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比react的diff算法,同样情况下可以减少移动节点次数,减少了不必要的性能损耗,更加的优雅。

vue3.x借鉴了ivi算法和 inferno算法。在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础上再配合核心的diff算法,使得性能上比较vue2.x有了提升。

小知识?

    前端框架有俩种方式侦测变化,一种是pull,一种是push

    pull:代表React,先用setState显式更新,然后react再用虚拟dom diff找出差异,最后将差异patch。

    也就是一开始不知道哪里有变化,只是知道有变化了,再进行暴力的diff查找

    push+pull:代表Vue,程序初始化的时候,对数据data进行依赖收集,一旦发生变化,响应式系统就会立刻知道,

    也就是一开始就知道在哪发生了变化,采用组件级别进行push侦测,先侦测到组件,再对组件内部进行虚拟dom diff操作,而虚拟dom diff操作是pull操作,所以vue是通过push+pull侦测变化的

十一、vue中的key

    key是vue标记节点的唯一标识,可以使diff操作更加精确,diff算法过程中,会用新节点的key与旧节点进行比对,找出差异

由于在浏览器中操作dom是很昂贵的。频繁的操作dom,会产生一定的性能问题,这就是虚拟dom产生的原因。
Virtual DOM本质就是用一个原生的js对象去描述一个dom节点,是对真实dom的一层抽象。Virtual DOM映射到真实dom要经历VNode的create、diff、patch等阶段。
key的作用是尽可能的复用DOM元素
新旧children中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。

十二、nextTick与宏任务和微任务

在下次DOM更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行则采用setTimeout
    定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

    宏任务和微任务?

  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

我的理解是,vue在更新dom时是异步执行的,当数据发生变化,vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成后,再统一进行更新 

十三、你看过VUE的源码吗?

看过一些,不过不是很深入,主要是对vue文件如何运行的比较感兴趣,大概看了一下;

那vue文件如何运行的呢?

vue文件运行过程其实就是vue模板的编译过程,

vue的模板编译原理: 

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)
  • 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
  • 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
  • 将render函数挂载到option中
  • 执行公共的mount函数

解析器:一部分是 截取 字符串,一部分是对截取之后的字符串做 解析

优化器:标记静态节点,

好处:

  1. 每次重新渲染的时候不需要为静态节点创建新节点
  2. 在 Virtual DOM 中 patching 的过程可以被跳过

 代码生成器

<div>
  <p>{{name}}</p>
</div>

 将上述模板字符串生成 render 函数代码字符串后,得到

{
  render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
}

生成后的代码字符串中看到了有几个函数调用 _c_v_s

_c 对应的是 createElement,它的作用是创建一个元素。

  1. 第一个参数是一个HTML标签名
  2. 第二个参数是元素上使用的属性所对应的数据对象,可选项
  3. 第三个参数是 children

首先解析模板,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子函数进行相关的处理。

vue的数据响应式的,但真实模板中并不是所有的数据都是响应式的,有一些数据首次渲染后就不会再变化,对应的DOM也不会变化,那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的对比,对运行的模板起到很大的优化作用。
编译的最后一步是将优化后的AST数转换为可执行的代码。

十四、axios的原理?

Axios 是一个基于 promise 的 HTTP 库,支持promise所有的API

1.promise是什么?

promise是一个Javascript异步编程解决方案,它提供then方法,来为异步提供链式回调函数,

可以解决回调地狱问题。

当然在es7时代,也出现了await/async的异步方案

2.Promise怎么使用?

fn = new Promise(function (resolve, reject) {
    //resolve
    //reject
  },2000)
}).then((res)=>{
  console.log(res)
},(err)=>{
  console.log(err)
})
fn.then((res)=>{
  return new Promise((resolve,reject)=>{})
})
var   p1 = Promise.resolve(1),
      p2 = Promise.reject(2),
Promise.all([p1, p2]).then((res)=>{
    //then方法不会被执行
    console.log(results);
}).catch((err)=>{
    //catch方法将会被执行,输出结果为:2
    console.log(err);
});

 3.Promise的原理

在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected。

(1) promise 对象初始化状态为 pending。

(2) 当调用resolve(成功),会由pending => fulfilled。

(3) 当调用reject(失败),会由pending => rejected。

 Promise相关的面试题

const promise = new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
});
promise.then(() => {
    console.log(3);
});
console.log(4);
输出结果为:1,2,4,3。

fetch和axios的异同?

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,是Promise的实现版本,符合最新的ES规范;

fetch类库实现用的是原生js,对浏览器支持更好;

H5里提供了一个fetch对象,他的诞生,是为了取代ajax的存在而出现,主要目的仅仅只是为了结合ServiceWorkers;

fetch("/users", {  method: "POST",  headers: {    "Accept": "application/json",    "Content-Type": "application/json"  },  body: JSON.stringify({  //这里是post请求的请求体    name: "Hubot",    login: "hubot",  })})

十五、依赖注入provide和inject

主要是为了解决一些循环组件比如tree, menu, list等, 传参困难, 并且难以管理的问题, 主要用于组件封装, 常见于一些ui组件库

通过在父组件中用provide提供的变量,其所有的子组件都可以通过jnject注入去使用

十六,vue指令与自定义指令

指令举例: v-model

v-model本质上就是一个语法糖,可以看成是value+input方法的语法糖。可以通过model属性的prop和event属性进行自定义。原生的v-model会根据标签的不同生成不同的事件和属性。

    Vue.directive("test",{

        inserted (el,binding,vnode, oldVnode){

            console.log(el);

            console.log(binding);

            console.log(vnode);

        },

        bind()//只调用一次

    });

    在初始化过程中,可以通过el.remove()去除,除了el可操作,其余都是只读

    应用场景:防抖,图片懒加载

十七,修饰符

    1.表单修饰符

    lazy:延迟,trim:过滤首空格,number:转数值

    2.事件修饰符

    顺序从前到后

    stop:事件冒泡,相当于原生:e.stopPropagation()    

    pervent:阻止默认事件,e.preventDefault()

    self:自身触发

    once:触发一次

    capture:从元素顶层往下触发

    passive:给passive加了一个lazy

    native:监听元素原生事件

    click.left/middle/right:鼠标

    @onkeyup,@onkeydown.enter/delete...:键盘

    v-bind:xxx.sync对props进行双向数据绑定

十八,Keep-alive

    缓存不活动组件实例,不会销毁

    设置props:include,包含匹配会缓存;exclude,不会缓存

<keep-alive :include="keepAlive" >
      <router-view/>
</keep-alive>   

    多出俩个生命周期钩子,activated与deactiveted

    可以在activated激活或者beforeRouteEnter里操作

注意:使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。

十九,对slot插槽的理解?

    分为默认插槽,

    具名插槽,<template v-slot:content>content插槽</template>

    作用域插槽,<template v-slot:default="slotProps">来自子组件数据{{slotProps}}</template>

    原理?

    组件要渲染到页面上需要经过template -> render function -> vnode -> Dom

    本质上返回Vnode节点的函数

二十,对mixin的理解?

    混入mixin是面向对象设计里的类,其他类可以直接使用其方法,一版用于提取公共代码

    分为:局部混入 import 然后 mixins:[] 和全局混入 vue.mixin()

    混入策略?

    替换?当组件与mixin有相同props,methods,inject,computed时,组件的选项会覆盖mixin的选项

    合并?data

    队列?当有相同生命周期钩子函数和watch时,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子

    叠加?component,directives,filters

二十一,vue中组件和插件?

    组件:.vue文件

    插件:vue-router,添加到vue实例上实现,

    写插件:暴露一个install方法,用xue.use()方式进行注册,需要在new Vue()之前注册

二十二,vue中添加新属性,页面不刷新?

    是vue2中的defineProperty无法响应

    解决方案:

    vue.set() 时间是调用defineReactive实现响应

    Object.assign({},this.someobj,{a:1,b:2}) 直接使用不会刷新,合并可以

    $forceUpdate()

二十三,vue 路由,hash和history的区别,路由切换页面的原理?

形式上:hash模式url里面永远带着#号,开发当中默认使用这个模式。如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合推广宣传;
功能上:比如我们在开发app的时候有分享页面,那么这个分享出去的页面就是用vue或是react做的,咱们把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,但是使用history模式还有一个问题就是,在访问二级页面的时候,做刷新操作,会出现404错误,那么就需要和后端人配合,让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上就ok了

路由的哈希模式其实是利用了window.onhashchange事件,也就是说你的url中的哈希值(#后面的值)如果有变化,就会自动调用hashchange的监听事件,在hashchange的监听事件内可以得到改变后的url,这样能够找到对应页面进行加载

window.addEventListener('hashchange', () => {
   // 把改变后的url地址栏的url赋值给data的响应式数据current,调用router-view去加载对应的页面
   this.data.current = window.location.hash.substr(1)
})

二十四,如何理解vue是一个渐进式的框架?

vue.js的两个核心是数据驱动和组件系统

对于一个Vue项目来说,vue.js只提供了vue-cli生态中最核心的组件系统双向数据绑定

对于其他的vue-router,vuex插件可以选择不用,也可以全用jquery写代码,都没有问题,

比如说,你要使用React,你必须理解:

  • 函数式编程的理念,
  • 需要知道什么是副作用,
  • 什么是纯函数,
  • 如何隔离副作用
  • 它的侵入性看似没有Angular那么强,主要因为它是软性侵入。

也可以在新项目启动初期,有限的使用VUE的功能特性,从而降低上手的成本。 

二十五,build时webpack如何是运作的?

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

如上图: 中间的蓝色块就是webpack. 他会将左边各种文件打包成右侧html能够解析的文件

这里涉及两个概念: 打包模块

1. 什么是模块? 根据依赖关系整合所有用到的模块资源

webpack可以让我们进行模块化开发, 他提供了平台, 并且会帮助我们处理各模块之间的依赖关系.
webpack最终会帮我们将js, css, 图片, json文件等打包为合适的格式, 以供浏览器使用.
在webpack中上述文件类型都可以被当做模块来使用.

2. 什么是打包? 

就是将webpack中各种模块资源进行打包合并成一个或多个包. 并且在打包的过程中, 可以对资源进行处理, 如:压缩图片, 将scss转成css, 将ES6语法转成ES5等可以被html识别的文件类型.

webpack 可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。

比如转化css要用到css-loader,style-loader,less-loader等等

  • css-loader: 只负责加载css文件, style-loader: 负责将样式加载到Dom中,如果用到less,就要用到less-loader
  • 转化图片要用到url-loader
  • 将ES6打包成ES5要用到babel-loader
module.exports = {
  context: path.resolve(__dirname, '../'),
  //用来指定入口, 指定一个路径
  entry: {
    app: './src/main.js'
  },
  //用来指定出口. 需要注意的是: 出口是一个对象, 由两部分组成: path和filename
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  //设置项目文件导入路径
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  node: {
    setImmediate: false,
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty'
  }
}
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值