SPA单页面的优缺点

SPA(single-page application)

仅在web页面初始化时加载相应的HTML、JavaScript、css。
页面加载完成后,spa不会因用户的操作而进行页面的重新加载或跳转。利用路由机制实现HTML内容的变换。

优点

  • 体验好,内容改变无需重新加载整个页面。
  • 服务器压力相对较小,避免了不必要的跳转和重复渲染。
  • 便于前后端分离。架构清晰。

    缺点

  • 首屏加载慢。加载页面时,js、css统一加载。部分页面按需加载。

  • 不利于seo。所有内容都在一个页面中动态替换显示。

什么是MVVM?

MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。

(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。

(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。

(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

Vue响应式原理

实现原理

Object.defineProperty()

对象

无法检测:

对象 property 的添加和移除。

解决:

所以添加新属性时,可以使用 vm.$set ,创建 property。
或者直接替换整个对象的引用地址。

  1. var vm = new Vue({
  2. data:{
  3. user: {
  4. name: ''
  5. }
  6. }
  7. })
  8. // `vm.user.name` 是响应式的
  9. vm.user.name = '小明'
  10. // `vm.user.age` 是非响应式的
  11. vm.user.age = 18

数组

无法检测:

  • 直接利用索引设置一个数组项时,如:vm.arr[index] = newValue
  • 修改数组长度时,如:vm.arr.length = newLength

    解决:

  • vm.$set(vm.arr, index, newValue)

  • vm.arr.splice(index, 1, newValue)

使用数组的变异方法可以触发,如:pop、shift、splice、sort、reverse 等。
非变异方法,可以用返回的新数组替换旧数组,如 vm.arr = vm.arr.filter(item => item.id === 1)

声明响应式 property

Vue不允许动态添加根级响应式property(data下第一层)。必须在初始化实例前就在data中声明所有根级的响应式property,哪怕只是一个空值。

异步更新队列,nextTick使用

Vue在更新DOM时,是 异步执行 的。
侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个watcher被多次触发,只会被推入到队列中一次。
在一个事件循环“tick”中,Vue刷新队列并执行实际工作。
异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate
如果环境不支持,则会采用 setTimeout(fn, 0) 代替。

若要等待dom更新完成,可以在数据变更后立即使用nextTick。

代码示例:

  1. Vue.component('example', {
  2. template: '<span>{{ message }}</span>',
  3. data: function () {
  4. return {
  5. message: '未更新'
  6. }
  7. },
  8. methods: {
  9. update: function () {
  10. this.message = '已更新'
  11. console.log(this.$el.textContent) // => '未更新'
  12. this.$nextTick(function () {
  13. console.log(this.$el.textContent) // => '已更新'
  14. })
  15. },
  16. // $nextTick() 返回一个 Promise 对象,所以可以使用 async/await
  17. updateAsync: async function () {
  18. this.message = '已更新'
  19. console.log(this.$el.textContent) // => '未更新'
  20. await this.$nextTick()
  21. console.log(this.$el.textContent) // => '已更新'
  22. }
  23. }
  24. })

虚拟DOM

实现原理

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

    优缺点

    优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;

  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

  • 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

Vue实例生命周期

概念

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

生命周期钩子

生命周期 描述
beforeCreate 组件实例被创建之初,组件的属性生效之前。
created 组件实例已经完全创建,属性也绑定,但真实 dom 还没有生成,$el 还不可用。
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用
mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
update 组件数据更新之后
activited keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
beforeDestory 组件销毁前调用
destoryed 组件销毁后调用

什么阶段进行异步请求,操作DOM?

异步请求

在created之后进行调用,因为此时data已经创建。
推荐在created或mounted中调用。

created:能更快获取到服务端数据。ssr不支持mounted,放created中有助于一致性
mounted:想要在请求完成后操作dom

操作DOM

在 mounted 之后。

生命周期示意图

Vue生命周期.jpg

keep-alive

是Vue内置的一个组件,可以缓存组件的状态,避免重新渲染。其有以下特性:

  • 结合路由与动态组件一起使用
  • 提供include和exclude。exclude优先级高于include
  • 对应两个钩子函数 activated、deactivated

Vue组件间通信方式

1. props$emit

适用:父子组件间的通信

2. ref$parent$children

适用:父子组件间的通信

ref,绑定在普通DOM上,引用就指向DOM元素。
绑定在子组件上,引用就指向组件实例
$parent 访问父实例,$children 访问子实例

3. EventBus - $emit$on

适用:父子、隔代、兄弟组件通信

通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

4. $attrs$listeners

适用:隔代组件通信

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

5. provide / inject

适用:隔代组件通信,高阶插件、组件库使用

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

6. Vuex

适用:各种情况

Vuex包含的模块

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

模块

  • State:定义了应用状态的数据结构,类似于data
  • Getter:类似于computed
  • Mutation:是唯一更改store中状态的方法,且必须是同步函数。 ```javascript // 定义 mutations: { increment (state, payload) { state.count += payload.amount } }

// 调用 store.commit(‘increment’, { amount: 10 })

  1. - ActionAction提交的是mutation,而不是直接更改状态,Action可以包含任意异步操作
  2. ```javascript
  3. // 定义
  4. mutations: {
  5. increment (state) {
  6. state.count++
  7. }
  8. },
  9. actions: {
  10. increment (context) {
  11. context.commit('increment')
  12. }
  13. }
  14. // 使用
  15. store.dispatch('increment')
  • Modules ```javascript const moduleA = { state: () => ({ … }), mutations: { … }, actions: { … }, getters: { … } }

const moduleB = { state: () => ({ … }), mutations: { … }, actions: { … } }

const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } })

store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态

  1. <a name="qhWtI"></a>
  2. # Vue-Router的几种模式及原理
  3. <a name="hBIHR"></a>
  4. ## hash模式
  5. - URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  6. - hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
  7. - 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
  8. - 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
  9. <a name="0WbUx"></a>
  10. ## history模式
  11. - pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
  12. - 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
  13. - history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
  14. 如何监听 `pushState` 和 `replaceState` 的变化呢?
  15. ```javascript
  16. var _wr = function(type) {
  17. var orig = history[type];
  18. return function() {
  19. var rv = orig.apply(this, arguments);
  20. var e = new Event(type);
  21. e.arguments = arguments;
  22. window.dispatchEvent(e);
  23. return rv;
  24. };
  25. };
  26. history.pushState = _wr('pushState');
  27. history.replaceState = _wr('replaceState');
  28. window.addEventListener('replaceState', function(e) {
  29. console.log('THEY DID IT AGAIN! replaceState 111111');
  30. });
  31. window.addEventListener('pushState', function(e) {
  32. console.log('THEY DID IT AGAIN! pushState 2222222');
  33. });

abstract模式

支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

对Vue项目进行过哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 减少 ES6 转为 ES5 的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈