new Vue() 发生了什么

  1. 内部执行了根实例的初始化过程
  2. options 合并
  3. 属性初始化
  4. 自定义事件处理
  5. 数据响应式处理
  6. 初始化生命周期钩子

Vue 数据响应式原理

- `defineReactive` 把数据定义成响应式的
- 给属性增加一个 `dep`,用来收集对应的那些 `watcher`
- 等数据变化进行更新

Vue 生命周期

`beforeCreate`

这是第一个生命周期函数,此时,组件的 `data``methods` 以及页面 `DOM` 结构都还没有初始化,所以这个阶段,什么都做不了

`created`

这是组件创建阶段第二个生命周期函数,此时,组件的 `data``methods` 已经可以用了,但是页面还没有渲染出来;在这个生命周期函数中,我们经常会发起 `Ajax` 请求

`beforeMount`

当模板在内存中编译完成,会立即执行实例创建阶段的第三个生命周期;此时内存中的模板结构,还没有真正渲染到页面上,此时页面上看不到真实的数据( 用户看到的只是一个模板页面 )

`mounted`

这个是组件创建阶段最后一个生命周期函数,此时,页面已经真正的渲染好了,用户已经可以看到真实的页面数据了;当这个生命周期函数执行完,组件就离开了创建阶段,进入到了运行中的阶段

- 如果用到了第三方的 `UI` 插件,而且这个插件还需要被初始化,那么,必须在 `mounted` 及之后来初始化插件(echarts 图表)

`beforeUpdate`

当执行 `beforeUpdate` 生命周期函数的时候, 数据肯定时最新的,但是页面上呈现的数据,还是旧的

`updated`

页面已经完成了更新,此时,`data` 数据是最新的,同时,页面上呈现的数据,也是最新的

`beforeDestory`

当执行 `beforeDestory` 的时候,组件即将被销毁,但是还没有真正开始销毁,此时组件还是正常可用的,`data``methods` 等数据或方法,依然可以被正常访问

`destoryed`

组件已经完成了销毁,此时组件已经废了,`data``methods` 都不可用了

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)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (
!isIE &&
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
// Use MutationObserver where native Promise is not available,
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true,
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
作用: 视图更新之后,获取最新的 `dom` 节点,`data` 数据修改后,立即拿到最新数据

CommonJS 和 esModule 的区别

参考
CommonJS

  1. module.exports 默认值为 {}
  2. exports 是 module.exports 的引用(exports 默认指向 module.exports 的内存空间)
  3. require() 返回的是 module.exports 而不是 exports
    //foo.js
    exports = {
    foo: 'foo',
    } // 给 exports 重新赋值,断开了 exports 指向 module.exports 的地址,重新指向一个新地址

    //bar.js
    const { foo } = require('./foo.js')
    //reuqire 返回的是 module.exports 对象, 默认为 {}

    esModule

  4. 浏览器加载 ES6 模块,也使用 <script> 标签,但是要加入 type="module" 属性。
    <script type="module" src="./foo.js"></script>

    差异

  5. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  6. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  7. 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'

父组件监听子组件的生命周期

  1. 子组件生命周期中 emit
    // Parent.vue
    <Child @mounted="doSomething"/>

    // Child.vue
    mounted() {
    this.$emit('mounted')
    }
  2. hook
    // Parent.vue
    <Child @hook:mounted="doSomething"/>

Vue-Router前端路由

  1. hash 模式

    类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容这种 # 后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化然后我们便可以监听 hashchange 来实现更新页面部分内容的操作

    window.addEventListener('hashchange', () => {
    // do something
    })
  2. history 模式

    HTML5 新增的API: pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求但因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面

    // 当活动历史记录条目更改时,将触发popstate事件
    // 调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)
    window.addEventListener('popstate', () => {
    // do something
    })

historyhash 模式区别

  1. hash 模式路径上有 #, 用 window.location.hash 读取。而 history 路由没有会好看一点
  2. hash 路由支持低版本的浏览器,而 history 路由是 HTML5 新增的 API
  3. 回车刷新操作时,hash 路由会加载到地址栏对应的页面,而 history 路由一般 404 报错
  4. history 模式需要后台配置支持。当我们刷新页面或者直接访问路径的时候就会返回 404,那是因为在 history 模式下,只是动态的通过 js 操作 window.history 来改变浏览器地址栏里的路径,并没有发起 http 请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起 http 请求,但是这个目标在服务器上又不存在,所以会返回 404

vue-router 钩子执行顺序

导航钩子分类

  1. 全局守卫
  2. 路由独享守卫
  3. 组件守卫

全局守卫

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 有什么区别

  1. xhr

    1.1 浏览器内置 api
    1.2 使用起来也比较繁琐,需要设置很多值。
    1.3 使用回调函数

  2. fetch

    2.1 浏览器内置 api
    2.2 Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义
    2.3 返回一个 Promise 对象

  3. 差异
    1. fetch 使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。
    2. fetch 采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。
    3. fetch 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。