都是本人面试过程中所经历过得问题,然后不断积累,整理如下,希望能帮到正在找工作的你,good luck
vue的两个核心
vue实现数据双向绑定主要是:采用 数据劫持结合发布者-订阅者模式 的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回掉。当把一个普通 JavaScript 对象传给Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用Object.defineProperty 将他们转为 getter/setter。用户看不到getter / setter ,但是在内部它们让vue追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Complie 和 Watcher三者,通过Observer来监听自己的model的数据变化,通过Complie来解析编译模板指令(vue中是用来解析 {{ }} ),最终利用watcher搭起observer和complie之间的通信桥梁,达到数据变化 ==》 视图更新,视图交互变化(input)==》 数据model变更双向绑定效果。
实现步骤:
1.实现一个监听者Oberver来劫持并监听所有的属性,一旦有属性发生变化就通知订阅者
2.实现一个订阅者watcher来接受属性变化的通知并执行相应的方法,从而更新视图
3.实现一个解析器compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相对应的订阅者
第一步:需要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变更的双向绑定效果。
1、数据驱动,也叫双向数据绑定
vue.js数据观察原理在技术实现上,利用的是ES5的object.defineproperty和存储器属性:getter和setter(只兼容IE9以上的版本),可称为基准依赖收集的观测模式。核心是VM,即ViewModel,保证数据和视图的一致性。
观察者模式:发布-订阅
2、组件系统。
vue组件的核心选项:
模板、初始数据、接受的外部参数、方法、生命周期的钩子函数、私有资源
Vue.set() 响应式新增与修改数据
什么情况下使用$set?
由于 Vue 会在初始化实例时进行双向数据绑定,使用Object.defineProperty()对属性遍历添加 getter/setter 方法,所以属性必须在 data 对象上存在时才能进行上述过程 ,这样才能让它是响应的。如果要给对象添加新的属性,此时新属性没有进行过上述过程,不是响应式的,所以会出想数据变化,页面不变的情况。此时需要用到$set。
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性
(比如 this.myObject.newProperty = 'hi')(官方示例) 我自己的理解就是,在vue中对一个对象内部进行一些修改时,vue没有监听到变化无法触发视图的更新,此时来使用$set来触发更新,使视图更新为最新的数据。
由于 JavaScript 的限制,Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
VUE自定义指令
局部指令、全局指令
1.创建局部指令
var app = new Vue({
el: '#app',
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// 指令中第一个参数是当前使用指令的DOM
console.log(el);
console.log(arguments);
// 对DOM进行操作
el.style.width = '200px';
el.style.height = '200px';
el.style.background = '#000';
}
}
}
})
2.全局指令
Vue.directive('dir2', {
inserted(el) {
console.log(el);
}
})
3.指令的使用
<div id="app">
<div v-dir1></div>
<div v-dir2></div>
</div>
vue请求数据放在created好还是mounted里好
建议放在created里
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
如果在mounted钩子函数中请求数据可能导致页面闪屏问题
其实就是加载时机问题,放在created里会比mounted触发早一点,如果在页面挂载完之前请求完成的话就不会看到闪屏了
vue组件中data为什么必须是函数
因为一个组件是可以共享的,但他们的data是私有的,所以每个组件都要return一个新的data对象,返回一个唯一的对象。
hash和history两种模式的区别
- hash —— 即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
- hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
- history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
- history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。 Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
路由传参的几种方式
一、通过调用$router对象的push()方法,向push()方法传递一个路由配置对象,通过params来传递参数
需要注意的是使用params必须和name属性一起使用,否则要跳转的目标路由页面无法通过params获取到传递过来的参数。
this.$route.params.userName;//User.vue组件中就可以通过$route的params获取到
二、通过调用$router对象的push()方法,向push()方法传递一个路由配置对象,需要通过query来传递参数
需要注意的是使用query的时候,可以通过path属性也可以通过name属性来指定目标路由。
这种情况下,query传递的参数会显示在url后面?userName=?&,如:
http://localhost:8082/about?userName=lhb
三、通过路由配置,配置动态路由参数
this.$router.push({path:`/user/${userName}`});
指令v-el的作用是什么?
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例,
为什么在vue中使用axios
1.axios:
- 从 node.js 创建 http 请求
- 支持 Promise API
- 客户端支持防止CSRF
- 提供了一些并发请求的接口(重要,方便了很多的操作)
2.jQuery ajax:
- 本身是针对MVC的编程,不符合现在前端MVVM
- 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
- JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
vue中nextTick的作用
$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。(官网解释)
解决的问题:有些时候在改变数据后立即要对dom进行操作,此时获取到的dom仍是获取到的是数据刷新前的dom,无法满足需要,这个时候就用到了$nextTick。
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})
vue中minxin的作用
vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用。
vue相关生命周期
1、vue生命周期和生命周期钩子函数:
vue的生命周期:vue实例从创建到销毁,也就是从开始创建、初始化数据、编译模板、挂在DOM->渲染、更新->渲染、卸载等一系列过程
在这个过程中也会运行一些叫做生命周期钩子函数,在不同阶段添加自己代码的机会。
生命周期钩子函数(11个)
beforeCreate 在实例初始化之后,数据观测(dataobserver)和event/watcher事件配置之前被调用。
created 在实例创建完成后被立即调用,在这一部,实例已经完成了以下的配置,数据感测,属性和方法运算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见。 http请求建议在该生命周期发出。
beforeMount 在挂载开始之前被调用:相关的render函数首次被调用。
mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用改钩子。如果root实例挂载一个文档内原色,当mounted被调用时,vm.$el也在文档内
beforeUpdate 数据更新时被调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移出已添加的事件监听器。该钩子在服务器渲染期间不被调用,因为只有初次渲染会在服务器端进行。
updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
activated keep-alive组件激活时调用。该钩子在服务器端渲染期间不被调用。
deactivted keep-alive组件停用时调用。该钩子在服务器端渲染期间不被调用。
beforeDestory 实例销毁之前调用,在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
destoryed vue实例销毁后调用,调用后,vue实例指示的所有东西多会解绑定,所有的时间监听会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
errorCaptured 2.5新增,(err:Error,vm:Component,info:string)=>?boolean 当捕获一个子孙组件的错误时被调用。
vue中对于对象的更改检测
由于javascript的限制,vue不能检测对象属性的添加和删除
对于已经创建的实例,vue不能动态添加根界别的响应式属性。但是可以使用Vue.set(object,key,value)方法向嵌套对象添加响应式属性。该钩子在服务器端渲染期间不被调用。
vue计算属性和缓存和方法调用有什么区别
1、计算属性必须返回结果
2、计算属性是基于它的依赖缓存。一个计算属性所依赖的数据发生变化时,它才会重新取值。
3、使用计算属性还是methods取决于是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性。
4、计算属性是根据依赖自动执行的,method需要事件调用。
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值
对于 method ,只要发生重新渲染,method 调用总会执行该函数
v-model实现机制
我们先简单说下v-model的机制:v-model会把它关联的响应式数据(如info.message),动态地绑定到表单元素的value属性上,然后监听表单元素的input事件:当v-model绑定的响应数据发生变化时,表单元素的value值也会同步变化;当表单元素接受用户的输入时,input事件会触发,input的回调逻辑会把表单元素value最新值同步赋值给v-model绑定的响应式数据。
computed中的getter和setter
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
watch监听对象
如果只是监听obj内的某一个属性变化,可以直接obj.key进行监听。
watch: {
'obj.question': function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
}
如果对整个obj深层监听
watch: {
obj: {
handler: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
},
deep: true,
immediate: true
}
}
immediate的作用:当值第一次进行绑定的时候并不会触发watch监听,使用immediate则可以在最初绑定的时候执行。
完整的vue-router导航解析流程
导航被触发-->在失活的组件里调用离开守卫-->调用全局beforeEach守卫-->在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)-->在路由配置里调用beforeEnter-->解析异步路由组件-->在被激活的组件里调用beforeRouteEnter-->调用全局的beforeResole守卫(2.5+)-->导航被确认-->调用全局afterEach --> 触发DOM更新 --> 用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数
$route和$router的区别
$route 是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数
$router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。
vue中key值得作用
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。
key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度
key具有唯一性
vue中循环需加 :key=“唯一标识” ,唯一标识可以使item里面id index 等,因为vue组件高度复用增加key可以标识组件的唯一性,为了更好地区别各个组件key的作用主要是为了高效的更新虚拟DOM
不带有key,并且使用简单的模板,基于这个前提下,可以更有效的复用节点,diff速度来看也是不带key更加快速的,因为带key在增删节点上有耗时。这就是vue文档所说的默认模式。但是这个并不是key作用,而是没有key的情况下可以对节点就地复用,提高性能。这种模式会带来一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。VUE文档也说明了。还有就是key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度,但是这个含有争议。
<keep-alive></keep-alive>的作用是什么?
<keep-alive></keep-alive> 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
大白话: 比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用<keep-alive></keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
第一步:在components目录新建你的组件文件(smithButton.vue),script一定要export default {
第二步:在需要用的页面(组件)中导入:import smithButton from ‘../components/smithButton.vue’
第三步:注入到vue的子组件的components属性上面,components:{smithButton}
第四步:在template视图view中使用,<smith-button> </smith-button>
问题有:smithButton命名,使用的时候则smith-button。
vue-loader是一个webpack的加载器,允许你将一个组件的所有HTML、JavaScript和CSS代码编写到同一个文件中
什么是vuex
vuex是一个专为vue.js应用程序开发的状态管理器,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
解决的问题:
有了vue-router,可以将一个组件视为一个页面来使用。由于组件只维护自身的状态(data),组件创建时或者说进入路由时它们被初始化,切换至其他的组件页时当前页自然要被销毁,从而导致data也随之销毁。
页面与页面之间总会产生各种需要的共享变量,如果通过$router.param或者$router.meta来传递是远远不够的,很多情况下不得不采用window来保存一些全局的共享变量。
这样出现的问题:vue是不会维护window的这些共享变量的。对于组件来讲,这些变量都存在于组件作用域以外,组件并不会自动维护,这样就违背了js编程规范或者风格规范的一条基本准则:全局变量是毒瘤,是具有极高副作用的。
当我们将window内的对象绑定到不同的自定义组件内,一旦要对window内的变量进行修改,会发现所有以对象方式绑定的自定义组件,当对象内的某个属性发生改变时将不会执行自动刷新,所有的计算属性也同时失效!更诡异的是这种情况并不是绝对出现的,当页面元素相对简单的时候一切都显得很正常,一旦页面元素增多,对应的交互操作增多时,这种奇怪的现象就会发送。
vuex就是专门解决页面与页面之间需要的共享变量的创建、维护、变更问题的。
vuex的核心概念
vuex的应用核心就是store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)
vuex和单纯的全局对象有以下两点不同
1、vuex的状态存储是响应式的,当vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应的得到高效更新。
2、你不能直接改变store的状态。改变store中的状态的唯一途径就是显示提交(commit)mutation。这样使得我们可以方便的跟踪每一个状态的变化。
vuex的核心概念
1、state -- vuex store 实例的根状态对象,用于定义共享的状态变量
2、action -- 动作,向store发出调用通知,执行本地或者远端的某一个操作(可以理解为store的methods)
3、Mutations -- 修改器,它只用于修改state中定义的状态变量
4、geeter -- 读取器,外部程序通过它获取变量的具体值,或者在取值前做一些计算(可以认为是store的计算属性)
vue 等单页面应用的优缺点
优点:
- 良好的交互体验
- 良好的前后端工作分离模式
- 减轻服务器压力
缺点:
- SEO难度较高
- 前进、后退管理
- 初次加载耗时多
1.与AngularJs的区别
相同点:都支持指令,内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定,都不支持低端浏览器;
不同点:AngularJs的学习成本高,比如Dependency、Injecttion特性,而vue.js本身提供的API都比较简单、直观;在性能上,AngularJs依赖对数据做脏检查,所以watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所以的数据都是独立触发的。
2. 与React的区别
相同点:
React采用独特的JSX语法,Vue.js 在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心实现相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化底去处理需求,都不内置列数Ajax,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性了;
不同点:
React采用Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷的操作Virtual DOM。