# Vue

# 1. 实现数据绑定的方式

  • 发布者-订阅者模式:绑定监听 -> 更新数据
  • 脏值检查:定时轮询检测数据变动
  • 数据劫持: vue.js 采用数据劫持 + 发布者-订阅者模式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。
    • Object.defineProperty:该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
    • 通过遍历数组 和递归遍历对象,从而达到利用Object.defineProperty()也能对对象和数组(部分方法的操作)进行监听。

image

双向绑定的实现

  • 监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
  • 解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  • 订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
  • 订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

# 2. MVVM响应式原理

# 2.1 什么是MVVM

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

image

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

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

  • ViewModel 层:ViewModel 是由前端开发人员组织生成和维护的视图数据层。

    • 在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
    • 需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。
    • 这样的封装使得 ViewModel 可以完整地去描述 View 层。
  • MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新-视图。

  • 我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。

  • 这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据。

  • 由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

<!-- View层 -->
<div id="app">
    <p>{{message}}</p>
    <button v-on:click="showMessage()">Click me</button>
</div>
// ViewModel层
var app = new Vue({
    el: '#app',
    data: {  // 用于描述视图状态   
        message: 'Hello Vue!', 
    },
    methods: {  // 用于描述视图行为  
        showMessage(){
            let vm = this;
            alert(vm.message);
        }
    },
    created(){
        let vm = this;
        // Ajax 获取 Model 层的数据
        ajax({
            url: '/your/server/data/api',
            success(res){
                vm.message = res;
            }
        });
    }
})
// Model层
{
    "url": "/your/server/data/api",
    "res": {
        "success": true,
        "name": "IoveC",
        "domain": "www.cnblogs.com"
    }
}

# 3. 零碎知识点

  • v-show / v-if : v-show的元素总会渲染,只是display:none,但是 v-if 只有在true的时候才渲染,适用于不频繁切换条件的场景

  • Vue单向数据流:父组件向子组件通信 prop,但是不能反过来,只能通过 $emit 派发一个自定义事件,父组件收到后,由父组件进行数据的修改操作。

  • computed & watch

    方面 computed watch
    概念 计算属性,类似于过滤器,对绑定到视图的数据进行处理,并监听变化进而执行对应的方法。 一个侦听的动作,用来观察和响应 Vue 实例上的数据变动。当data中的数据(变量)发生改变,触发watch中相应的函数。
    相同点 都起到监听/依赖一个数据,并进行处理的作用 本质上,两者都是基于Vue的Watcher实现的。
    实现机制 当对计算属性依赖的数据做修改的时候,getAndInvoke函数重新计算computed的值,如果新旧值不同,会触发setter过程,this.dep.notify()通知所有订阅它变化的watcher更新,执行watcher.update()方法,这样一来就实现了computed计算属性的更新。 不懂(个人猜测:被监听的数据在修改值的时候会调用set,set调用notify,进而调用update 或者 触发对应的watch监听函数)
    场景 主要用于对同步数据的处理,并且在依赖数据没有变化的时候可以缓存计算的结果。 主要用于观测某个值的变化去完成一段开销较大的复杂业务逻辑
    影响关系 一个数据受多个数据影响 一个数据影响多个数据
    异步 不支持异步 需要执行异步操作的时候的一个推荐选择
  • 不能直接给数组按索引赋值:因为性能问题,可以通过以下方式实现:

    • vm.$set(vm.items, indexOfItem, newValue)
    • vm.items.splice(indexOfItem, 1, newValue)
  • 为什么组件中data是一个函数:

    • 组件是复用的,函数每次都返回新的对象,不同组件之间使用不同的数据,否则共用引用对象会共享数据。
  • v-bind:class与style绑定。

  • v-model:本质上是子父组件通信的语法糖,根据控件类型自动选取正确的方法来更新元素,负责监听用户的输入事件以更新数据。

  • v-for为什么要带上key:

    • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、快速。可以用来标记变化的节点
    • 根据 key 值判断某个node是否已经被修改,未修改则可以复用之前的元素
    • 比如向一个列表 [A B C D E] 中插入节点 F,位置在 B C 之间:
      • 没有 key ,需要把 C 变成 F,D 变成 C, E 变成 D,再插入一个 E
      • 有 key,直接在 B C 中间插入一个 F 即可,有一点哈希的思想在里面
    • 所以,最好也不要使用 index 作为 key
  • 检测数组变化

    • Vue使用函数劫持的方式重写了数组的方法,Vue将data中的数组进行了原型链重写,指向自己定义的数组原型方法,这样当调用数组API时,可以通知依赖更新。
    • 如果数组中包含引用类型,会对数组中的引用类型在进行监控,比如数组中如果有对象,对象是通过之前的 Object.definePeoperty 重新定义。

# 4. vue生命周期:

# 4.1 图

  • image
  • ----------------------原生的图(下)----------------------
  • image

# 4.2 有哪些生命周期:

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

# 4.3 父子组件的生命周期函数钩子函数执行顺序:

  • 执行顺序:
    • 加载渲染过程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
    • 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
    • 父组件更新过程父 beforeUpdate -> 父 updated
    • 销毁过程父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
  • 什么时候才能...?
    • 什么阶段能发起请求?可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
    • 什么时候可以调用DOM?在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。
  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:
    • 一般结合路由和动态组件一起使用,用于缓存组件;
    • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
    • 对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

# 5. 父子组件通信方式

  • props / $emit
  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $emit / $on :这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
    • 比如:
      • this.$bus.$on(eventName, e => {});
      • this.$bus.$emit(eventName, args)
  • $attrs/$listeners
  • provide / inject
  • Vuex: store容器,改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

# 6. Vuex

# 6.1 概念

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

# 6.2 模块

  • 主要包括以下几个模块:
    • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
    • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
    • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
    • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
    • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

# 6.3 原理

  • Vuex的设计思想是将数据存放到全局的store,再将store挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制来进行高效的状态更新。
  • vuex的store是如何挂载注入到组件中呢?
    • 利用vue的插件机制,使用Vue.use(vuex)时,会调用vuex的install方法,装载vuex。
    • install方法中最后一步调用applyMixin方法,使用vue混入机制,在vue的生命周期beforeCreate钩子函数前混入vuexInit方法。
    • vuexInit方法实现了store注入vue组件实例,并注册了vuex store的引用属性$store
  • vuex的state和getters是如何映射到各个组件实例中响应式更新状态呢?
    • 核心方法:resetStoreVM
      • Vuex的state状态是响应式,是借助vue的data是响应式,将state存入vue实例组件的data中;
      • Vuex的getters则是借助vue的计算属性computed实现数据实时监听。
    • Vuex是通过全局注入store对象,来实现组件间的状态共享。在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。假如只是多个组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。

# 7. Vue-router

# 7.1 概念

  • 适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。
  • 传统的页面应用,是用一些超链接来实现页面切换和跳转的。
  • 在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。
  • 原理:
    • 原理核心就是 更新视图但不重新请求页面。
    • 即 第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。
    • JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了。
  • 路由懒加载:Vue 的异步组件 + Webpack 的代码分割功能

# 7.2 三种路由模式

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
  • history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

# 7.2.1 hash模式的实现原理

  • hash即浏览器url中#后面的内容,包含#。hash是URL中的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会加载相应位置的内容,不会重新加载页面。
  • 即# 是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中,不包含#。
  • 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。
  • 所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。
  • 由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。

# 7.2.2 history模式的实现原理

  • HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。
  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

# 7.3 SPA单页面应用:

  • SPA( single-page application ):
    • 仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。
    • 一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;
    • 利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
  • 优点:
    • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
    • 基于上面一点,SPA 相对对服务器压力小;
    • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
  • 缺点:
    • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
    • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
    • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

# 8. Vue SSR

# 8.1 概念

  • SSR,Server-Side Rendering,服务端渲染
  • Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
  • 即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。

# 8.2 服务端渲染的优缺点

(1)服务端渲染的优点:

  • 更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
  • 更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;

(2) 服务端渲染的缺点:

  • 更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
  • 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。

# 15. VUE 3.0

# 15.1 更新要点

首先是通过RFC(Request For Comments)的形式进行的改动,讨论。

  • Performance
  • Tree-shaking support
  • Composition API
  • Fragment, Teleport, Suspense
  • Better TypeScript support
  • Custom Render API

# 15.2 Performance

  • PatchFlag:利用编译器,分析模板,进行优化,性能提升。通过PatchFlag标记动态节点,无论嵌套多深,动态节点都直接与Block根节点绑定,无需再遍历其他静态节点。(重要的思想就是编译时做小的优化,积少成多)
  • hoistStatic:将静态的数据/变量提升到render()函数体之外,渲染的时候只需要进行复用,节约了内存。静态内容解耦
  • cacheHandlers:事件监听缓存,在父组件重新渲染的时候,子组件也会跟着重新渲染,父组件传给子组件的事件处理程序(函数)也需要重新创建一遍,Vue3.0 通过事件监听缓存,在第一次创建的时候生成一个内联函数,等后面再创建的时候直接使用缓存中的事件处理程序(函数)。
  • SSR(server side rendering,服务端渲染):
    • 什么是SSR:服务端收到客户端的初始请求后,把数据填充到模板形成完整的页面,由服务端把渲染的完整的页面返回给客户端。这样减少了一次客户端到服务端的HTTP请求,加快了相应速度,一般用于首屏的性能优化。
    • 在进行SSR的时候,对于静态的内容,合并成一个单独的字符串,直接全部推送进一个buffer中,即使存在动态的内容,也是内嵌在静态的内容中的。

# 15.3 Tree-shaking

  • 什么是Tree-shaking:

    • 可以将无用模块“剪枝”,仅打包需要的。即“按需引入”。
    • ES6的模块引入是静态分析的,故而可以在编译时正确判断到底加载了什么代码。
    • 分析程序流,判断哪些变量未被使用、引用,进而删除此代码。
    • 但是,函数的副作用:除了返回函数的返回值之外,函数还做了其他的事情:修改全局变量、打印输出、获取用户输入、DOM查询、数据库操作等。
  • Vue 3.0的优化:基于函数的 API 每一个函数都可以作为 named ES export 被单独引入,这使得它们对 tree-shaking 非常友好。没有被使用的 API 的相关代码可以在最终打包时被移除,实现DCE(Dead Code Elimination)

    • // Vue 2.x
      import Vue from 'vue'
      
      // Vue 3.0
      import { nextTick, observable } from 'vue'
      
      Vue.nextTick
      Vue.observable
      Vue.version
      Vue.compile (仅在完整构建中)
      Vue.set (仅在2.x版本的兼容性版本中,你很快就会发现原因)
      Vue.delete(同上)
      

# 15.4 Composition API

  • 现有API不受影响

  • 所有的data、methods、computed等都聚在一起,当一个组件中代码量很多的时候且不便于切分的时候,逻辑关注点中相关的代码是分散的,Composition API则是,将每一个逻辑关注点的代码放在一起(所有功能相关的代码放在一起,便于维护),不用在整个文件中跳来跳去。同时,便于复用,组合,更好的灵活性。

  • 基于 proxy 的响应侦测

    • Proxy 的优势如下:

      • Proxy 可以直接监听对象而非属性;
      • Proxy 可以直接监听数组的变化;
      • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
      • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
      • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

      Object.defineProperty 的优势如下:

      • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
  • (以前)基于 getter / setter 的响应侦测

# 15.5 Fragment & Teleport & Suspense

  • Fragment:模板可以是纯文字、多个节点、v-for,会自动变成碎片,一个组件支持多个根元素,不用再都包含在一个节点中了。
  • Teleport:没懂
  • Suspense:实现了一定程度的轻量化的异步调度,在一个组件树渲染到屏幕之前,先在内存中渲染,记录存在异步依赖的组件,当所有嵌套的异步依赖都resolve之后,才会被渲染到屏幕中。async setup()

# 15.6 Better TypeScript Support

更好地支持了TS,自动补全api、参数的提示、静态的检查等,良好的编程体验。

# 15.7 Custom Renderer API

没懂

# 15.8 Vite(法语:快)

  • 一个HTTP服务器/更轻量、更快的面向浏览器的开发工具,没有编译、没有打包、支持热更新(非常快),启动服务器可以直接开始写vue文件,请求了什么才打包、编译什么。
  • 在浏览器端使用 export import 的方式导入和导出模块,在 script 标签里设置 type="module" ,然后使用 ES module。相当于拦截了一下App.vue的请求,让浏览器帮忙处理import的任务,服务器只负责提供请求中所请求的文件。
  • 1、快速的冷启动,不需要等待打包操作; 2、即时的热模块更新,替换性能和模块数量的解耦让更新飞起; 3、真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。