Vue高级面试题汇总

19,873 阅读15分钟

说说你对SPA单页面的理解,它的优缺点分别是什么?

参考答案

是一种只需要将单个页面加载到服务器之中的web应用程序。当浏览器向服务器发出第一个请求时,服务器会返回一个index.html文件,它所需的js,css等会在显示时统一加载,部分页面按需加载。url地址变化时不会向服务器在请求页面,通过路由才实现页面切换。

优点:

  • 良好的交互体验,用户不需要重新刷新页面,获取数据也是通过Ajax异步获取,页面显示流畅;
  • 良好的前后端工作分离模式。

缺点:

  • SEO难度较高,由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势。
  • 首屏加载过慢(初次加载耗时多)

SPA单页面的实现方式有哪些?

参考答案
  • 在hash模式中,在window上监听hashchange事件(地址栏中hash变化触发)驱动界面变化;
  • 在history模式中,在window上监听popstate事件(浏览器的前进或后退按钮的点击触发)驱动界面变化,监听a链接点击事件用history.pushState、history.replaceState方法驱动界面变化;
  • 直接在界面用显示隐藏事件驱动界面变化。

说说你对MVC、MVP、MVVM模式的理解

参考答案

在以往开发程序过程中,界面布局代码、界面交互逻辑代码、业务逻辑代码三者代码都是混在一起的。随着业务需求越来越大,代码越来越复杂,不仅导致开发困难,更是导致维护代码更困难,特别是维护别人的代码。

所以就出现MVC模式来解决这个问题,其中M代表Model,专门来处理、存储数据。V代表View,专门来处理页面的展示。C代表Controller专门处理业务逻辑。

用户操作View,View发送指令到Control,完成业务逻辑处理后,要求Model处理相应的数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

当然用户也可以直接下发指令到Control,完成对应业务逻辑处理后,要求Model处理相应的数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

也可以通过View直接要求Moder处理数据,将处理好的数据发送到View,要求View把这些数据展示给用户。

然而在MVC模式中,Model、Control、View三者相互依赖,修改起来要兼顾其他两者,还是非常困难。

所以又出现了MVP模式来解决这个问题,在MVP模式中P代表Presenter替代原来的Control。

当用户操作View,View发送指令到Presenter,完成业务逻辑处理后,要求Model处理相应的数据,将处理好的数据返回到Presenter中,Presenter将数据发送到View中,要求View把这些数据展示给用户。

MVP模式中,Presenter将View和Model完全隔离开,Presenter和View相互依赖,Presenter和Model相互依赖,View和Model不再相互依赖,使代码耦合降低。

因为Presenter和View相互依赖,这样Presenter就没办法单独做单元测试,非得等到View做好以后才行。所以对View分割一部分叫做View接口,Presenter只依赖View接口,这样Presenter不用依赖View就可以测试了,并且也增加了复用性,只要View实现了View接口部分,Presenter就可以大发神威。

然而在MVP模式中,因为让Presenter发送数据到View,让View展示,仍然需要大量的、烦人的代码,这实在是一件不舒服的事情。 那么可不可以让View在Model变化时自动更新。

所以出现了MVVM模式来实现这个设想,其中VM代表ViewModel负责视图显示逻辑和监听视图变化,M代表Model变成处理业务逻辑和数据。

当用户操作View时,ViewModel监听到View的变化,会通知Model中对应的方法进行业务逻辑和数据处理,处理完毕后,ViewModel会监听到自动让View做出相应的更新。ViewModel可以对应多个View,具有很强的复用性。

在Vue项目中。new Vue()就是一个ViewModel,View就是template模板。Model就是Vue的选项如data、methods等。在开发过程我们只关注View怎么展示,Model怎么处理业务逻辑和数据。不要去管处理业务逻辑和数据后怎么让View更新,View上有操作,怎么让Model处理这个操作,这些通通交给ViewModel来实现,大大降低了开发成本。

说说你对Object.defineProperty的理解

参考答案
  • Object.defineProperty(obj,prop,descriptor)方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
    • obj:要在其上定义属性的对象。
    • prop:要定义或修改的属性的名称。
    • descriptor:将被定义或修改的属性描述符。
  • descriptor属性描述符主要有两种形式:数据描述符和存取描述符。描述符必须是这两种形式之一;不能同时是两者。
    • 数据描述符和存取描述符共同拥有
      • configurable:特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。默认为false。
      • enumerable:当该属性的enumerable为true时,该属性才可以在for...in循环和Object.keys()中被枚举。默认为false。
    • 数据描述符
      • value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined。
      • writable:当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为false。
    • 存取描述符
      • get:一个给属性提供 getter的方法,如果没有getter则为undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。默认为undefined。
      • set:一个给属性提供 setter的方法,如果没有setter则为undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为undefined。
  • 定义descriptor时,最好先把这些属性都定义清楚,防止被继承和继承时出错。
function Archiver() {
    var temperature = null;
    var archive = [];
    Object.defineProperty(this, 'temperature', {
        get: function() {
          console.log('get!');
          return temperature;
        },
        set: function(value) {
          temperature = value;
          archive.push({ val: temperature });
        }
    });
    this.getArchive = function() { return archive; };
}
var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

说说你对Proxy的理解

参考答案 官方定义:proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

通俗来说是在对目标对象的操作之前提供了拦截,对外界的操作进行过滤和修改某些操作的默认行为,可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象。

let proxy = new Proxy(target, handler)

  • target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理);
  • handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。

handle可以为{},但是不能为null,否则会报错

Proxy 目前提供了 13 种可代理操作,比较常用的

  • handler.get(target,property,receiver)获取值拦截
  • handler.set(target,property,value,receiver)设置值拦截
  • handler.has(target,prop)in 操作符拦截
let obj = {
	a : 1,
	b : 2
}
let test = new Proxy(obj,{
    get : function (target,property) {
        return property in target ? target[property] : 0
    },
    set : function (target,property,value) {
        target[property] = 6;
    },
    has: function (target,prop){
        if(prop == 'b'){
            target[prop] = 6;
        }
        return prop in target;
    },
})

console.log(test.a);        // 1
console.log(test.c);        // 0

test.a = 3;
console.log(test.a)         // 6

if('b' in test){
    console.log(test)       // Proxy {a: 6, b: 6}
}

Object.defineProperty和Proxy的区别

参考答案
  • Object.defineProperty
    • 不能监听到数组length属性的变化;
    • 不能监听对象的添加;
    • 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
  • Proxy
    • 可以监听数组length属性的变化;
    • 可以监听对象的添加;
    • 可代理整个对象,不需要对对象进行遍历,极大提高性能;
    • 多达13种的拦截远超Object.defineProperty只有get和set两种拦截。

Vue的模板语法用的是哪个web模板引擎的吗?说说你对这模板引擎的理解?

参考答案

采用的是Mustache的web模板引擎mustache.js

<script type="text/javascript" src="./mustache.js"></script>
<script type="text/javascript">
    var data = {
        "company": "Apple",
    }

    var tpl = '<h1>Hello {{company}}</h1>';
    var html = Mustache.render(tpl, data);

    console.log(html);
</script>

你认为Vue的核心是什么?

参考答案

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。

以上是官方原话,从中可以得知Vue的核心是模板语法和数据渲染。

说说你对单向数据流和双向数据流的理解

参考答案

单向数据流是指数据只能从父级向子级传递数据,子级不能改变父级向子级传递的数据。

双向数据流是指数据从父级向子级传递数据,子级可以通过一些手段改变父级向子级传递的数据。

比如用v-model.sync来实现双向数据流。

什么是双向绑定?原理是什么?

参考答案

双向绑定是指数据模型(Module)和视图(View)之间的双向绑定。

其原理是采用数据劫持结合发布者-订阅者模式的方式来实现。

Vue中先遍历data选项中所有的属性(发布者)用Object.defineProperty劫持这些属性将其转为getter/setter。读取数据时候会触发getter。修改数据时会触发setter。

然后给每个属性对应new Dep(),Dep是专门收集依赖、删除依赖、向依赖发送消息的。先让每个依赖设置在Dep.target上,在Dep中创建一个依赖数组,先判断Dep.target是否已经在依赖中存在,不存在的话添加到依赖数组中完成依赖收集,随后将Dep.target置为上一个依赖。

组件在挂载过程中都会new一个Watcher实例。这个实例就是依赖(订阅者)。Watcher第二参数式一个函数,此函数作用是更新且渲染节点。在首次渲染过程,会自动调用Dep方法来收集依赖,收集完成后组件中每个数据都绑定上该依赖。当数据变化时就会在seeter中通知对应的依赖进行更新。在更新过程中要先读取数据,就会触发Wacther的第二个函数参数。一触发就再次再次自动调用Dep方法收集依赖,同时在此函数中运行patch(diff运算)来更新对应的DOM节点,完成了双向绑定。

什么是虚拟DOM?

参考答案 虚拟DOM是将状态映射成视图的众多解决方案中的一种,其是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染生成真实DOM,在渲染之前,会使用新生成的虚拟节点树和上一次虚拟节点树进行对比,只渲染不同的部分。

Vue中如何实现一个虚拟DOM?说说你的思路

参考答案 首先要构建一个VNode的类,DOM元素上的所有属性在VNode类实例化出来的对象上都存在对应的属性。例如tag表示一个元素节点的名称,text表示一个文本节点的文本,chlidren表示子节点等。将VNode类实例化出来的对象进行分类,例如注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点。

然后通过编译将模板转成渲染函数render,执行渲染函数render,在其中创建不同类型的VNode类,最后整合就可以得到一个虚拟DOM(vnode)。

最后通过patch将vnode和oldVnode进行比较后,生成真实DOM。

你了解Vue的diff算法吗?

参考答案

你知道nextTick的原理吗?

参考答案

说说你对Vue的template编译的理解?

参考答案

Vue实例挂载的过程是什么?

参考答案

在初始化的最后,如果检测到选项有el属性,则调用vm.$mount方法挂载vm,挂载的目标就是把模板渲染成最终的DOM

  • 第一步:确保vm.$options有render函数。

因为在不同构建版本上的挂载过程都不一样,所以要对Vue原型上的$mount方法进行函数劫持。

首先创建一个变量mount将Vue原型上的$mount方法保存到这个变量上。然后Vue原型上的$mount方法被一个新的方法覆盖。在这个新方法中调用mount这个原始方法。

通过el属性进行获取DOM元素。如果el是字符串,则使用document.querySelector获取DOM元素并赋值给el。如果获取不到,则创建一个空的div元素并赋值给el。如果el不是字符串,默认el是DOM元素,不进行处理。

判断el是不是html元素或body元素,如果是则给出警告退出程序。

因为挂载后续过程中需要render函数生成vnode,故要判断$options选项中是否有render函数这个属性,如果有直接调用原始的$mount方法。

如果没有,则判断template是否存在。若不存在则将el的outerHTML赋值给template。若存在,如果template是字符串且以#开头,通过选择符获取DOM元素获取innerHTML赋值给template,如果template已经是DOM元素类型直接获取innerHTML赋值给template。

然后将template编译成代码字符串并将代码字符串转成render函数,并赋值到vm.$options的render属性上。

最后调用原始的$mount方法。

  • 第二步: 在原始的$mount方法,先触发beforeMount钩子函数,然后创建一个Watcher实例,在第二参数传入一个函数vm._update

该函数是首次渲染和更新渲染作用,参数为render函数(vnode),如果vm._vnode不存在则进行首次渲染。

同时vnode中被劫持的数据自动收集依赖。当vnode中被劫持的数据变化时候触发对应的依赖,从而触发vm._update进行更新渲染。

最后触发mounted钩子函数。

说下你对指令的理解?

参考答案

Vue为什么要求组件模板只能有一个根元素?

参考答案 当前的virtualDOM差异和diff算法在很大程度上依赖于每个子组件总是只有一个根元素。

你有用过事件总线(EventBus)吗?说说你的理解

参考答案

使用Vue后怎么针对搜索引擎做SEO优化?

参考答案

SSR解决了什么问题?有做过SSR吗?你是怎么做的?

参考答案

axios是什么?怎样使用它?怎么解决跨域的问题?

参考答案

axios 是一个基于 promise 的 HTTP 库,先封装在使用。

使用proxyTable配置解决跨域问题。

比如你要调用http://172.16.13.205:9011/getList这个接口

先在axios.create()配置baseURL增加标志

const service = axios.create({
  baseURL: '/api',
});

service.get(getList, {params:data});

然后在config/index.js文件中配置

dev:{
    proxyTable: {
        '/api': {
            target: 'http://172.16.13.205:9011', // 设置你调用的接口域名和端口号
            secure: false,
            changeOrigin: true,// 跨域
            pathRewrite: {
                '^/api': '' // 去掉标志
            }
        }
    },
}

配置后要重新npm run dev

F12中看到请求是http://localhost:8080/api/getList,实际上请求是http://172.16.13.205:9011/getList

ajax、fetch、axios这三都有什么区别?

参考答案

为何官方推荐使用axios而不用vue-resource?

参考答案

你有封装过axios吗?主要是封装哪方面的?

参考答案

Vue开发过程你是怎么做接口管理的?

参考答案

如何中断axios的请求?

参考答案

如果将axios异步请求同步化处理?

参考答案

你了解axios的原理吗?有看过它的源码吗?

参考答案

你有写过自定义组件吗?

参考答案

说说你对Vue组件的设计原则的理解

参考答案

写出多种定义组件模板的方法

参考答案

你有使用过render函数吗?有什么好处?

参考答案

你了解什么是函数式组件吗?

参考答案

函数式组件的应用

你有使用过JSX吗?说说你对JSX的理解?

参考答案

JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。

如果想扩展某个现有的Vue组件时,怎么做呢?

参考答案
  • 用mixins混入
  • 用extends,比mixins先触发
  • 用高阶组件HOC封装

你了解什么是高阶组件吗?可否举个例子说明下?

参考答案

怎么在Vue中使用插件?

参考答案

组件和插件有什么区别?

参考答案

为什么Vue使用异步更新组件?

参考答案

你有看过Vue推荐的风格指南吗?列举出你知道的几条

参考答案

如何优化首页的加载速度?

参考答案

Vue渲染大量数据时应该怎么优化?说下你的思路!

参考答案 懒加载和分页

你有使用过babel-polyfill模块吗?主要是用来做什么的?

参考答案

为什么我们写组件的时候可以写在.vue里呢?可以是别的文件名后缀吗?

参考答案

vue-loader是什么?它有什么作用?

参考答案 vue-loader是一个webpack的loader,是一个模块转换器,用于把模块原内容按照需求转换成新内容。

它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。可以解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的loader去处理。

分析下Vue项目本地开发完成后部署到服务器后报404是什么原因呢?

参考答案

Vue首页白屏是什么问题引起的?如何解决呢?

参考答案

vue打包成最终的文件有哪些?

参考答案

如何解决vue打包vendor过大的问题?

参考答案

webpack打包vue速度太慢怎么办?

参考答案

vue部署上线前需要做哪些准备工作?

参考答案

说说你觉得认为的Vue开发规范有哪些?

参考答案

你有使用过Vue开发多语言项目吗?说说你的做法?

参考答案

用vue怎么实现一个换肤的功能?

参考答案

对于即将到来的vue3.0特性你有什么了解的吗?

参考答案

其他Vue系列面试题