Vue高级面试题汇总(一)
说说你对SPA单页面的理解,它的优缺点分别是什么?是一种只需要将单个页面加载到服务器之中的web应用程序。当浏览器向服务器发出第一个请求时,服务器会返回一个index.html文件,它所需的js,css等会在显示时统一加载,部分页面按需加载。url地址变化时不会向服务器在请求页面,通过路由才实现页面切换。优点:良好的交互体验,用户不需要重新刷新页面,获取数据也是通过Ajax异步获取,页面显示流畅;
说说你对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的核心是什么?
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实例挂载的过程是什么?
在初始化的最后,如果检测到选项有
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算法在很大程度上依赖于每个子组件总是只有一个根元素。
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
。
你有使用过JSX吗?说说你对JSX的理解?
JSX就是Javascript和XML结合的一种格式。React发明了JSX,利用HTML语法来创建虚拟DOM。当遇到
<
,JSX就当HTML解析,遇到{
就当JavaScript解析。
如果想扩展某个现有的Vue组件时,怎么做呢?
- 用mixins混入
- 用extends,比mixins先触发
- 用高阶组件HOC封装
Vue渲染大量数据时应该怎么优化?说下你的思路!
懒加载和分页
vue-loader是什么?它有什么作用?
vue-loader是一个webpack的loader,是一个模块转换器,用于把模块原内容按照需求转换成新内容。
它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。可以解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的loader去处理。
更多推荐
所有评论(0)