《深入浅出vue.js》随读小记

187 阅读16分钟

NO.1 变化侦测

Unit 1

Jquery是命令式操作DOM,但程序复杂后难以维护;

Vue.js 是声明式操作DOM,便于维护。  

vue.js是一款高性能的渐进式JavaScript框架; 渐进式框就是框架分层,(核心:视图层渲染;组件机制;路由机制;状态管理;最外层是构建工具),分层也就是你可以只用最核心的视图渲染功能快速开发一些需求,也可以使用所有的全家桶开发。

vue.js 2.0 引入了虚拟DOM,使渲染过程变快了;支持JSX和TypeScript。

vue特点是响应式系统。 响应式系统赋予框架重新渲染的能力(变化侦测),变化侦测也是响应式核心。

 

Unit 2 Object的变化侦测

  • 什么是变化侦测

运行时内部状态不断变化,需要不停重新渲染,如何确定状态中发生了变化?------变化侦测  

变化侦测分为两种类型:“推”(push);“拉”(pull);

Angular使用“拉”,当状态发生变化时,它不知道哪个状态变了,只知道状态可能变了,会发信号告诉框架,框架内部收到后会进行暴力比对来找出那些DOM节点需要重新渲染。这也就是Angular 的脏检查流程。

React使用“拉”,使用的是虚拟DOM。

Vue 的变化侦测属于“推”,当状态发生变化就立刻知道。

 

  • 如何追踪变化

在JavaScript中,两种方式侦测一个对象的变化:Object.defineProperty 和ES6的Proxy。

image.png 封装后只需传递data、key、val

每当从data的key中读取数据时, get函数被触发; 每当往data的key中设置数据时,set函数被触发。

  • 如何收集依赖

当数据的属性发生变化时,可以通知那些曾经使用了该数据的地方。

<h1>{{ name }}</h1>

先收集依赖,把用到数据name的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍。

总结: 在getter中收集依赖,在setter中触发依赖。

  • 依赖收集在哪儿

每个key都有一个数组,用来存储当前key的依赖,假设依赖是一个函数,保存在window.target 上。

新增数组dep,用来存储收集的依赖。

vue里面有个依赖管理器叫Dep类。 src/core/observer/dep.js

image.png

 

  • 递归侦测所有的key

定义一个Observer类,把一个正常的object转换成被侦测的object(即将数据类所有属性包括子属性转换成getter/setter的形式)

image.png

walk会将每一个属性转换成getter/setter的形式来侦测变化。

  image.png

 

  • 关于Object的问题

vue通过Object.defineProperty只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。

vue提供两个api去解决------vm.setvm.set 和 vm.delete 。

 

Unit 3 Array的变化侦测

    • Array原型中可以改变数组自身内容的方法有7个,分别是:push、pop、shift、unshift、splice、sort、reverse。
    • Array通过设置一个拦截器覆盖Array.prototype ,每当使用Array原型上方法操作数组时,其实执行的是拦截器中提供的方法,然后再拦截器中使用原生Array的原型方法去操作数组。

 

 

Unit 4 变化侦测相关API实现原理

平时在Vue中使用的this默认指代的是vue实例,该实例包含data内的数据,methods中的函数等等,以及vue内置的原生API。如this.setthis.set、this.watch、

1.vm.$watch

用于观察一个表达式或者computed函数在Vue.js实例上的变化。 回调函数调用时,会从参数得到新数据和旧数据。

vm.$watch( expOrFn, callBack, [option])

option ---- deep(发现对象内部值的变化, deep:true) 和 immediate(将立即以表达式的当前值触发回调, immediate:true)

2.vm.$set

vm.$set( target, key, value)

target---- 需要修改的数据本身(为object或array); key --- object的key或 array的下标; value --- 修改后的值;

(注: target 不能是Vue.js 实例或Vue.js 实例的根数据对象)

用于避开Vue.js 不能侦测属性被添加的限制。

3.vm.$delete

vm.$delete( target, key)

target---- 需要修改的数据本身(为object或array); key --- object的key或 array的下标

(注: target 不能是Vue.js 实例或Vue.js 实例的根数据对象)

用于避开Vue.js 不能侦测属性被(数据使用delete关键字删除)的限制。

 

 

NO.2 虚拟DOM

 

Unit 5 虚拟DOM的简介

1.什么是虚拟DOM

程序运行时,状态会不断发生变化(用户点击按钮,某个ajax请求等)。

状态变化通常只有有限的几个dom节点需要重新渲染,为了性能,我们不仅需要找出哪里需要更新,还需尽可能少的访问dom。

解决方式有很多,react和vue就是使用的虚拟DOM;

虚拟DOM本质就是解决方案中的一种,可以用但不一定必须用;

 

虚拟DOM的解决方式是: 通过状态生成一个虚拟节点树,然后使用虚拟节点进行渲染,在渲染钱,会使用新生成的虚拟节点树和上一次的虚拟节点树进行对比,只渲染不同的部分。

Unit 6 VNode

1.什么是VNode

在Vue.js中存在一个VNode类,使用它可以实例化不同类型的vnode实例,而不同类型的vnode实例各自代表不同类型的DOM元素。。

vnode只是一个名字,本质是JavaScript中一个普通的对象。 简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真是的DOM节点。

Unit 7 patch

1.什么是patch

虚拟DOM最核心的部分就是patch,它可以将vnode渲染成真实的DOM。

patch的实际作用是在现有的DOM上进行修改,来实现更新视图的目的。

patch对现有DOM进行修改需要做三件事:

创建新增的节点;

删除已经废弃的节点;

修好需要更新的节点。

2.新增节点

场景: 首次渲染时,即oldVnode不存在,而vnode存在时,就需要使vnode生产真实的DOM元素并将其插入到视图中去。

3.删除节点

在DOM中需要使用vnode创建的新节点替换oldVnode所对应的旧节点,而替换过程就是将新创建的DOM节点插入到旧节点的旁边,然后再删除旧节点,从而完成替换过程。

4.更新节点

当新旧两节点是相同的节点时,需对两节点进行细致对比,然后对oldVnode在视图中所对应的真实节点进行更新。

 

 

NO.3 模板编译原理

 

Unit 8 模板编译

模板编译所介绍的内容是如何让虚拟DOM拿到vnode。

image.png

 

1.概念

模板编译的主要目的就是生成渲染函数。

而渲染函数的作用就是每次执行它,就会根据当前最新的状态生成一份新的vnode,使用这个vnode进行渲染。

2.将模板编译成渲染函数

大体上模板编译分三部分:

(1)解析器: 将模板解析为AST(抽象语法树);

(2)优化器:遍历AST标记静态节点;

(3)代码生成器:使用AST生成渲染函数。 ----- 将AST转换成渲染函数中的内容,这个内容可以称为‘代码字符串’。

一个简单的模板:

image.png

生成的代码字符串:

image.png

如何将代码字符串放到函数里:

image.png

 

 

 

 

 

Unit 9 解析器

1.作用

将模板解析成AST。

例: 模板如下 转换成AST后:

image.png

image.png  

2.解析器内部运行原理

解析器内部也分好几个子解析器:如HTML解析器、文本解析器、过滤解析器。

HTML解析器:

在解析HTML的过程中会不断触发各种钩子函数。(开始标签钩子函数、结束标签、文本以及注释钩子函数四种。)

 

伪代码如下:

image.png

例:

image.png

被HTML解析器解析时,所触发的钩子函数依次是: start、start、chars、end和end。

 

 

Unit 10 优化器

1.作用

在AST中找出静态子树(指永远不会发生变化的节点)并打上标记。

标记静态子树好处:

(1) 每次重新渲染时,不需要为静态子树创建新的节点;

(2) 在虚拟DOM中打补丁(patching)的过程可以跳过。

优化步骤:

(1) 在AST中找出所有静态节点并打上标记;

(2) 在AST中找出所有静态根节点并打上标记;

静态节点:static:true ;

静态根节点:staticRoot:true ;

 

 

Unit 11 代码生成器

1.作用

将AST转换成渲染函数中的内容,即代码字符串。

渲染函数被执行后,可以生成一份VNode,而虚拟DOM可以通过这个VNode来渲染视图。

image.png

=>解析成AST并优化

  image.png

 

=>将AST生成代码字符串

image.png

 

_c 元素节点 createElement

_v 文本节点 createTextVNode

_e 注释节点 createEmptyVNode

 

 

NO.4 整体流程

 

Unit 12 架构设计与项目结构

 

Unit 13 实例方法与全局API的实现原理

1.数据相关的实例方法

有3个:vm.watchvm.watch 、vm.set 、vm.$delete.

 

2.事件相关的实例方法

有4个:vm.onvm.on 、vm.once 、vm.offvm.off 、vm.emit.

1、vm.$on

监听当前实例上自定义事件,由vm.$emit触发。

回调函数会接受所传入事件所触发的函数的额外参数。

image.png  

2、vm.$off

移除自定义事件监听器。

3、vm.$once

监听一个自定义事件,但只触发一次,在第一次触发后移除监听器。

  4、vm.$emit

触发当前实例上的事件,附加参数都会传给监听器回调。

vm.$emit的作用是触发事件。

 

3.生命周期相关的实例方法

有4个:vm.mountvm.mount、vm.forceUpdate、vm.nextTickvm.nextTick 、vm.destroy.

1、vm.$forceUpdate

作用是迫使Vue.js实例重新渲染。

注意:它仅仅影响实例本身以及插入插槽内容的子组件,而不是所有子组件。

 

2、vm.$destroy(不常用)

作用是完全销毁一个实例,它会清理该实例与其他实例的连接,并解绑其全部指令及监听器,同时会触发beforeDestroy 和 destroyed 的钩子函数。

3、vm.$nextTick

nextTick接收一个回调函数作为参数。

作用是将回调函数延迟到下次DOM更新周期之后执行。

(场景: 当更新了状态(数据)后,需要对新DOM做一些操作,但是这时我们其实获取不到更新后的DOM,因为还没有重新渲染,这时就需使用nextTick)

 

*什么是下次DOM更新周期*

在Vue.js中,状态发生变化时,watcher会得到通知,然后触发虚拟DOM的渲染流程。而watcher触发渲染这个操作不是同步而是异步。 Vue.js中有一个队列,每当渲染时,会将watcher推送到这个队列中,在下一次时间循环中触发渲染的流程。

 

*为什么Vue.js使用异步更新队列*

Vue.js从2.0开始使用虚拟DOM,变化侦测只发送到组件,组件内用到的所有状态的变化都会通知到同一个watcher,然后虚拟DOM会对整个组件进行比对,并更新DOM。

如果再同一轮事件循环中有两个数据发生了变化,那么组件watcher会受到两份通知,进行两次渲染。事实上不需要两次渲染,虚拟DOM会对整个组件进行渲染,只需要等所有状态都修改完毕后,一次性将整个组件的DOM更新即可。

 

*异步任务:微任务和宏任务*

不同类型的任务会被分配到不同的任务队列中。

当执行栈中所有任务 都执行完毕后,会依次执行微任务队列中事件对应的回调,直至为空。然后去宏任务队列中取出一个事件,把对应的回调加入当前执行栈,执行栈中所有任务执行完毕后,检查微任务队列中是否有时间,无限重复此过程,称为事件循环。

 

微任务的事件有:

Promise.then 、 MutationObserver 、 Object.observer 、 process.nextTick 等;

 

宏任务的事件有:

setTimeout 、 setInterval、 setImmediate、 MessageChannel、 requestAnimationFrame、 I/O、 UI交互事件 等 ( I/O指node.js的非阻塞I/O,即 Input/output 指一个系统的输入和输出)

 

*执行栈*

当我们执行一个方法时,JavaScript会生成一个与这个方法对应的执行环境(执行上下文)。

这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数、私有作用域中定义的变量、this指向。

这个执行环境会被添加到一个栈中,这个栈就是执行栈。

4、vm.$mount

触发当前实例上的事件,附加参数都会传给监听器回调。

vm.$emit的作用是触发事件。

 

4.全局API的实现原理

1、Vue.extend

2、Vue.nextTick

3、Vue.set

4、Vue.delete

5、Vue.directive

6、Vue.filter

7、Vue.component

8、Vue.use

9、Vue.mixin

10、Vue.compile

11、Vue.version

 

 

 

Unit 14 生命周期

1.四个阶段

初始化阶段: beforeCreate、 created ---在Vue.js实例上初始化一些属性、事件及响应式数据,如data、props、methods、computed、watch等;

模板编译阶段: 在 created 与 beforeMount ---将模板编译为渲染函数;

挂载阶段: beforeMount、 mounted ---将实例挂载到DOM元素上,Vue.js会开启watcher来持续追踪依赖变化,每当状态变化,watcher会通知虚拟DOM重新渲染视图;

beforeUpdate 当状态发生变化时

updated 使用虚拟DOM重新渲染

卸载阶段: beforeDestroy、 destroyed ---将自身从父组件删除,取消所有依赖的追踪并移除事件监听器;

 

2.初始化状态

用户在实例化Vue.js 时使用了哪些状态,哪些状态就需要被初始化,没有用到的状态则不用初始化;

初始化状态分为5个子项(分顺序):

初始化props、

初始化methods、

初始化data(data中可使用props中数据)、

初始化computed、

初始化watch(watch即可观察props,也可观察data)。

 

1、初始化 props

对于props,父组件提供数据,子组件通过props字段选择自己需要哪些内容,Vue.js 内部通过子组件的props 选项将需要的数据筛选出来后添加到子组件的上下文中;

props对象中的值可以是一个基础的类型函数:

image.png  

也可能是一个数组:

image.png

还可能是一个对象类型的高级选项

image.png

注: 在Vue.js 内部,数组格式的props 将被规格化成对象格式。

 

2、初始化 methods

初始化methods 时,只需要循环选项中的methods对象,并将每个属性依次挂载到vm上即可:

  3、初始化 data

  4、初始化 computed

  5、初始化 watch

 

 

Unit 15 指令的奥秘

指令是Vue.js 提供的带有v- 前缀的特殊特性。

指令的职责是当表达式的值改变时,将其产生的连带影响响应式地作用于DOM。

Vue.directive 全局API 可以创建自定义指令并获取全局指令。

 

Unit 16 过滤器的奥秘

过滤器主要用来格式化文本,可用在两个地方: 双花括号、v-bind 表达式。

image.png

  此外,过滤器可以串联使用。

且过滤器是JavaScript 函数,可接受参数。

image.png

 

 

Unit 17 最佳实践及风格规范

1.为列表渲染v-for设置属性key

key这个特殊属性主要用在虚拟DOM算法中,在比对新旧虚拟节点时辨识虚拟节点;在更新子节点时,设置了key会使查找速度快很多。

image.png

 

2.在v-if、v-if-else、v-else 中使用 key

key可以有效避免 本不同的元素被识别为相同,出现意料之外的副作用。

image.png

 

3.路由切换,组件没变化

典型问题: 当页面切换到同一个路由但不同参数的地址时,组件的生命周期钩子函数并不会重新触发。

image.png

原因: vue-router 会识别出两个路由使用的是同一个组件从而进行复用,并不会重新创建组件,因此组件的生命周期钩子函数不会被触发。

方法 一: 路由导航守卫 beforeRouteUpdate

该守卫在当前路由改变且组件被复用时调用。

只需要把每次切换路由时需要执行的逻辑放到beforeRouteUpdate 守卫中即可。

例如: 在守卫中发送请求拉取数据,更新状态并重新渲染视图。

 

方法 二: 观察$route 对象的变化

通过watch 可以监听到路由对象发生的变化,从而对路由变化作出相应。(但是会带来依赖追踪的内存开销)

  注意: 最好是只观察自己需要的query。

场景: 一个页面,分两部分,上面是个人的描述信息,下面是带翻页的列表,路由/user?id=4&page=1,即用户ID是4,列表是第一页。

image.png image.png  

方法 三:为router-view 组件添加属性key

本质是利用虚拟DOM 在渲染时通过key 来比对两个节点是否相同的原理。

image.png

优点: 简单粗暴,改动小。

缺点: 每次切换路由组件都会被销毁且重建,浪费性能。

 

4.为所有路由统一添加query

场景: <https://berwin.me/a?referer=hao360>cn 和 [https://berwin.me/b?referer=hao360](https://berwin.me/a?referer=hao360)cn。

1.使用全局守卫beforeEach

2.使用函数劫持router.history.transition

 

5.区分Vuex 和 props 的使用边界

通常,业务组件会使用Vuex 维护状态,使用不要组件统一操作Vuex 中的状态。 ---这样不论父子组件还是兄弟组件通信都很容易。

对于通用组件,使用props以及事件进行父子组件通信(通用组件不需要兄弟组件通信),-----这样说是因为通用组件会拿到各个业务组件中使用,需与业务解耦,所以props获取状态。

 

 

6.避免v-if 和 v-for 一起使用

官方强烈建议不要把v-if 和 v-for 同时用在同一个元素上。

v-for 比 v-if 优先级更高。

解决方法: 使用计算属性遍历 并 过滤掉不需要渲染的数据。

场景一:

image.png  

image.png

 

场景二:

image.png  

image.png

 

 

7.为组件样式设置作用域

防止一个组件中的样式影响到其他的组件样式。

方法: 使用scoped 或者 CSS Modules 。

 

 

8.避免在scoped 中使用元素选择器

在scoped 样式中,类选择器比元素选择器更好,大量使用元素选择器是很慢的。

 

 

9.为避免隐性的父子组件通信

优先通过prop 和事件进行父子组件间通信,而不是使用 this.$parent 或者改变prop 。

最理想的是 “ prop 向下传递,事件向上传递”。

 

 

10.单文件组件如何命名

一: 单文件组件的文件名的大小写

单词首字母大写( PascalCase)或者 始终是横线连接的( kebab-case );

 

二: 基础组件名

应用特定样式 和 约定的基础组件 应该全部以一个特定的前缀开头;

image.png => image.png

 

三: 单例组件名

单例组件永远不接受任何 prop ,因为它们是为你的应用定制的,而不是应用中的上下文;

只拥有单个活跃实例的组件以The 前缀命名,以示其唯一性。

但这不是指该组件只可用于一个单页面,二是每个页面只使用一次。

image.png

 

四: 紧密耦合的组件名

和父组件紧密耦合的子组件应以父组件名作为前缀命名;

image.png

 

五: 组件名中单词的顺序

以高级别(通常是一般化描述的)单词开头,以描述性的修饰词结尾;

比如有多个针对于搜索的组件:

image.png

 

注:Vue.js 官方推荐非常大型的应用下才考虑 换成多级目录的方式,即把所有的搜索组件放到search 目录下。

 

六: 完整单词的组件名

组件名应该倾向于完整单词而不是缩写;

 

七: 组件名应为多个单词

组件名应该始终由多个单词组成,避免与现有的以及未来的HTML 元素冲突。

 

八: 模板中的组件名大小写

在单文件组件和字符串模板中的组件名应该总是单词首字母大写,但在DOM模板中总是横线连接的;

image.png  

 

九: JS /JSX 中的组件名大小写

JS / JSX 中的组件名应该始终是单词首字母大写的;

image.png

 

 

11.自闭合组件

在单文件组件、字符串模板和 JSX 中,没有内容的组件应该是自闭合的,但是在DOM模板中则不是。

image.png

 

12.prop 名的大小写

在申明 prop 的时候,命名应该始终使用驼峰式命名规则, 而在模板和 JSX中应该使用横线连接的方式。

image.png

   

13.多个特性的元素

多个特性的元素应该多行撰写,每个特性一行,更易读。

image.png  

 

14.模板中简单的表达式

在组件模板中应只包含简单的表达式,复杂的表达式应重构为计算属性或方法。

image.png  

 

15.简单的计算属性

把复杂的计算属性分割为尽可能多的 更简单的属性。 易于测试、易于阅读、更加的灵活。

image.png

 

 

16.指令缩写

指令缩写(用:表示v-bind: 、 @表示 v-on:),要保持统一,同一元素中不混用。

 

17.良好的代码顺序

代码顺序指的是组件/实例的选项的顺序、元素特性的顺序以及单文件组件的顶级元素的顺序。

  一: 组件/ 实例的选项的顺序

  二: 元素特性的顺序

三: 元素特性的顺序