Vue相关
我的技术栈是vue,所以面试被问到vue的只是相对较多,发现其实问的很多文档里都有,但是平常很容易忽略,就“搬”出来备用。
vue的生命周期以及对应的钩子函数、各个周期通常做什么事情
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要 设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM元素等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
经典官方文档图:
new Vue({})实力初始化
-
beforeCreate: 在实例初始化之后,数据观测 (data observer) 和event/watcher事件配置之前被调用。 -
created: 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event事件回调,(此时watch的immeditae: true的函数也会先于created执行)。然而,挂载阶段还没开始,$el 属性目前尚不可用。 -
beforeMount: 在挂载开始之前被调用:相关的render函数首次被调用。该钩子在服务器端渲染期间不被调用。 -
mounted: 实例被挂载后调用,这时el被新创建的vm.$el替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时vm.$el也在文档内。
注意mounted不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在mounted内部使用vm.$nextTick:mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }该钩子在服务器端渲染期间不被调用。
-
beforeUpdate: 数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。
-
updated: 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于
DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。注意
updated不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在updated里使用vm.$nextTick该钩子在服务器端渲染期间不被调用。
-
beforeDestroy: 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 在这一步可以进行一些清除操作。比如定时器的清除:
beforeDestroy: { //清除加载的定时器 cleartInterval(this.timer) }这样的写法存在两个问题:
- 需要在这个组件实例中保存这个 定时器,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
- 我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化地清理我们建立的所有东西。
所以vue文档给了更加“高级”的用法:
mounted: function () { var picker = new Pikaday({ field: this.$refs.input, format: 'YYYY-MM-DD' }) this.$once('hook:beforeDestroy', function () { picker.destroy() }) } -
destroyed: 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。该钩子在服务器端渲染期间不被调用。
vue的响应式实现原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
Object.defineProperty(obj, prop, descriptor)
obj要定义属性的对象。prop要定义或修改的属性的名称或 Symbol 。descriptor要定义或修改的属性描述符。- 返回值 被传递给函数的对象。
Object.defineProperty(o, "b", {
// 使用了方法名称缩写(ES2015 特性)
// 下面两个缩写等价于:
// get : function() { return bValue; },
// set : function(newValue) { bValue = newValue; },
get() { return bValue; }, //获取o['b']时的回调函数
set(newValue) { bValue = newValue; }, //设置o['b']时的回调函数
enumerable : true,
configurable : true
});
通过在 Object.defineProperty 中自定义get 和 set 函数,并在 get 中进行依赖收集,在 set 中派发更新。
但是这个方法需要指明对象键值,所以对于一些对象利用下标新增属性的操作无法做到实时监听,数组直接赋值同理,vue对数组的slice、push等能够触发更新是因为重写了数组的这些对应方法。
Vue3.0最新的实现方式,用Proxy和Reflect来替代Object.definePropertypry的方式。
proxy
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
new Proxy(target, handler)
参数
target要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
et validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// The default behavior to store the value
obj[prop] = value;
// 表示成功
return true;
},
get (obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age);
proxy与Object.defineProperty的直观区别就是 前者少了对象的key参数,
也正是由于参数key的未知性限制了后者对对象下标新增属性的setter/getter的处理,前者是可以做到对对象的基本定义行为进行拦截,而不用不受限于参数key。
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
-
描述 与大多数全局对象不同,Reflect不是一个构造函数。你不能将其与一个new运算符一起使用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。
-
方法
Reflect对象提供以下静态函数,它们具有与处理器对象方法相同的名称。这些方法中的一些与 Object 上的对应方法相同。如Reflect的has、apply、defineProperty等。
data增加响应式属性
data
-
类型:
Object|Function -
限制:组件的定义只接受
function。
详细:
Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的属性会被忽略。大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
一旦观察过,你就无法在根数据对象上添加响应式属性。因此推荐在创建实例之前,就声明所有的根级响应式属性。
如果需要,可以通过将vm.$data 传入 JSON.parse(JSON.stringify(...)) 得到深拷贝的原始数据对象。
对于新增的对象属性要做到响应式的方法:
Vue.set( target, propertyName/index, value )
vm.$set( target, propertyName/index, value ) //别名
参数:
- {
Object | Array}target - {
string | number}propertyName/index - {
any}value返回值:设置的值。
用法:
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi')
注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
对应的删除属性触发更新的方法 vm.$delete、Vue.delete
为什么组件内的data要以函数形式返回
当一个组件被定义,
data必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果data仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供data函数,每次创建一个新实例后,我们能够调用data函数,从而返回初始数据的一个全新副本数据对象。
vue-router的实现原理
基本原理:视图渲染而不刷新,两种模式hash和history:hash模式利用监听浏览器锚点变化获取相应视图,不触发其他请求,缺点是只可修改#后面的部分,故只可设置与当前同文档的url;history模式则借助浏览器html5的historyAPI实现,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面,但它可将url修改的就和正常请求后端的url一样(history不带#),需要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。vue-router自身会对环境做校验,如果发现没有浏览器的 API,vue-router会自动强制进入abstract模式,所以 在使用vue-router时只要不写mode配置即可,默认会在浏览器环境中使用hash模式,在移动端原生环境中使用abstract模式。 (当然,你也可以明确指定在所有情况下都使用abstract模式)
vue-router的路由守卫
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫
beforeRouteLeave。 - 调用全局的
beforeEach全局前置守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫 (2.2+)。 - 在路由配置里调用
beforeEnter独享守卫。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve全局解析守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach全局后置守卫钩子。 - 触发
DOM更新。 - 用创建好的实例调用
beforeRouteEnter守卫中传给 next 的回调函数。
vue-router传参
$route.params
类型: Object
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
$route.query
类型: Object
一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
编程式导航:
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }}) //通过$route.query获取
注意:如果提供了
path,params会被忽略,上述例子中的query并不属于这种情况。你需要提供路由的name或手写完整的带有参数的path
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
上述例子获取传参时都要使用$route.query/params,这样在组件中使用 $route会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
vue官网推荐使用 props 将组件和路由解耦,即在路由声明组件中声明props,从而是参数以props的形式传入组件内:
props?: boolean | Object | Function
- 如果
props被设置为true,route.params将会被设置为组件属性。
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
//这里规定参数以props的形式传入组件 组件内获取以props获取
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
props如果是一个对象,它会被按原样设置为组件属性(props接收)。当props是静态的时候有用。跟上述例子获取方法一样。
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
<!--组件内以props: ['newsletterPopup']获取-->
- 可以创建一个函数返回
props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
URL /search?q=vue 会将 {query: 'vue'} 作为属性props传递给 SearchUser 组件,组件内以props: ['query']获取。
请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。
$route与$router的区别
$route只读
一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。
路由对象是不可变 (immutable) 的,每次成功的导航后都会产生一个新的对象。
路由对象出现在多个地方:
-
在组件内,即
this.$route -
在
$route观察者回调内 -
router.match(location)的返回值 -
导航守卫的参数:
router.beforeEach((to, from, next) => { // `to` 和 `from` 都是路由对象 }) -
scrollBehavior方法的参数:const router = new VueRouter({ scrollBehavior(to, from, savedPosition) { // `to` 和 `from` 都是路由对象 } })
常用api: .path .name .hash .params等
$router
router 实例。常用方法:go push pop replace等
vue的组件间数据通信的方式(祖父子孙)
-
父传子:
props -
子传父:
this.$emit([EVENT_NAME], params)父组件监听@[EVENT_NAME] -
兄弟之间:
new Vue通过这个实例 结合$emit()$on() -
获取父(祖)
$parent(.$parent)的方法属性都可以 -
获取子(孙)
$children(.$children)的方法属性都可以 -
provide/inject(需要注意数据并非响应的,按照文档所说如果是一个可监听对象还是可以的,建议在provide提供更新方法,inject页面注入这个实例,并调用这个刷新状态值,vue.js组件精讲小册给的思路) -
vuex管理一个全局state对象各页面统一数据获取和修改入口