Vue常见面试题
new Vue() 发生了什么
- 内部执行了根实例的初始化过程
options
合并- 属性初始化
- 自定义事件处理
- 数据响应式处理
- 初始化生命周期钩子
Vue 数据响应式原理
- `defineReactive` 把数据定义成响应式的 |
Vue 生命周期
`beforeCreate` |
SPA 优缺点
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。⼀旦⻚⾯加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点
1. 前后端分离
2. 减轻服务器的负担
3. 良好的交互体验 - ajax
缺点
1. 不利于 SEO
2. 首屏渲染时间长
3. 前进、后退难以管理
说说 vue 的事件管理
待更新
vue 如何防止数据被再次 observe
待更新
数据变化,派发更新的时候,做了何优化
将变更放入队列,在下一个
nexttick
之后更新
Vue.$nextTick
原理
在下次 DOM
更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
- 言外之意就是在主线程执行代码完成之后,立刻执行
- 也就是在微任务队列中(也可能是宏任务队列,视运行环境而定)
- 内部实现依次使用
Promise
->MutationObserver
->setImmediate
->setTimeout
做兼容 - Vue 源码
src/core/util/next-tick.js
文件
if (typeof Promise !== 'undefined' && isNative(Promise)) { |
CommonJS 和 esModule 的区别
参考
CommonJS
- module.exports 默认值为 {}
- exports 是 module.exports 的引用(exports 默认指向 module.exports 的内存空间)
- require() 返回的是 module.exports 而不是 exports
//foo.js
exports = {
foo: 'foo',
} // 给 exports 重新赋值,断开了 exports 指向 module.exports 的地址,重新指向一个新地址
//bar.js
const { foo } = require('./foo.js')
//reuqire 返回的是 module.exports 对象, 默认为 {}esModule
- 浏览器加载 ES6 模块,也使用
<script>
标签,但是要加入type="module"
属性。<script type="module" src="./foo.js"></script>
差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的 require() 是同步加载模块,ES6 模块的 import 命令是异步加载,有一个独立的模块依赖的解析阶段。
CommonJS 模块加载 ES6 模块
使用 import() 方法加载
;(async () => {
await import('./foo.mjs')
})()
ES6 模块加载 CommonJS 模块
ES6 模块的 import 命令可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项。
// 正确
import packageMain from 'commonjs-package'
// 报错
import { method } from 'commonjs-package'
父组件监听子组件的生命周期
- 子组件生命周期中
emit
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit('mounted')
} - hook
// Parent.vue
<Child @hook:mounted="doSomething"/>
Vue-Router前端路由
- hash 模式
类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容这种
#
后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化然后我们便可以监听 hashchange 来实现更新页面部分内容的操作window.addEventListener('hashchange', () => {
// do something
}) - history 模式
HTML5 新增的API: pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求但因为没有
#
号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。// 当活动历史记录条目更改时,将触发popstate事件
// 调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)
window.addEventListener('popstate', () => {
// do something
})
history
和 hash
模式区别
hash
模式路径上有#
, 用window.location.hash
读取。而history
路由没有会好看一点hash
路由支持低版本的浏览器,而history
路由是HTML5
新增的API
- 回车刷新操作时,
hash
路由会加载到地址栏对应的页面,而history
路由一般 404 报错history
模式需要后台配置支持。当我们刷新页面或者直接访问路径的时候就会返回 404,那是因为在 history 模式下,只是动态的通过 js 操作 window.history 来改变浏览器地址栏里的路径,并没有发起 http 请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起 http 请求,但是这个目标在服务器上又不存在,所以会返回 404
vue-router 钩子执行顺序
导航钩子分类
- 全局守卫
- 路由独享守卫
- 组件守卫
全局守卫
router.beforeEach((to, from, next) => {
console.log('全局 - beforeEach')
next() // 必须要有调用 next
})
router.afterEach(() => {
console.log('全局 - afterEach')
})
路由独享守卫
{
path: '/home',
name: 'Home',
component: () => import('../views/Home.vue'),
beforeEnter (to, from , next) {
next() // 必须要有调用 next
console.log('路由 - beforeEnter ---> Home')
}
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
beforeEnter (to, from, next) {
next() // 必须要有调用 next
console.log('路由 - beforeEnter ---> About')
}
}
组件守卫
// Home 组件
beforeRouteEnter(to, from, next) {
console.log('组件 - beforeRouteEnter ---> Home')
next() // 必须要有调用 next
},
beforeRouteLeave(to, from, next) {
console.log('组件 - beforeRouteLeave ---> Home')
next() // 必须要有调用 next
},
// About 组件
beforeRouteEnter(to, from, next) {
console.log('组件 - beforeRouteEnter ---> About')
next()
},
beforeRouteLeave(to, from, next) {
next()
console.log('组件 - beforeRouteLeave ---> About')
}
顺序
从Home
组件 跳转到About
组件 依次输出
- 组件 - beforeRouteLeave ---> Home
- 全局 - beforeEach
- 路由 - beforeEnter ---> About
- 组件 - beforeRouteEnter ---> About
- 全局 - afterEach
假设,一打开页面就进入Home
页面,此时的钩子顺序 如下
- 全局 - beforeEach
- 路由 - beforeEnter ---> Home
- 组件 - beforeRouteEnter ---> Home
- 全局 - afterEach
综合上诉两种情况,我们可以得出如下结论
- 如果是第一次进入页面,依次执行全局的进入前置钩子
->路由的进入钩子
->组件的进入钩子
->全局的进入后置钩子
- 如果从组件内离开,将会优先执行组件的beforeRouteLeave
钩子,此后依次执行全局的进入前置钩子
->路由的进入钩子
->组件的进入钩子
->全局的进入后置钩子
- 顺序依次为: 组件离开(可有可无) -> 全局 -> 路由 -> 组件 -> 全局
vuex 判断修改state 是直接修改还是 提交commit
在严格模式下,直接修改 state 会报错,
this.$store.state.xxx = xxx
fetch 和 xhr 有什么区别
- xhr
1.1 浏览器内置 api
1.2 使用起来也比较繁琐,需要设置很多值。
1.3 使用回调函数- fetch
2.1 浏览器内置 api
2.2 Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义
2.3 返回一个 Promise 对象- 差异
- fetch 使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。
- fetch 采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。
- fetch 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。