web前端知识点(VUE篇)
vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
vue是一个MVVM框架,MVVM是一个MVC框架的改进版,由model-view-viewModel三块组成,由viewModel来是实现数据(model)和视图(view)之间的通信。
生命周期
生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
beforeCreate、created、beforeMounted、mounted、beforeUpdate、updated、beforeDestroy、destroyed。
底层实现原理
vue是一个MVVM的单向数据流,数据双向绑定的框架,2.0的双向数据绑定是由Object.defineProperty来实现,3.0是由Object.proxy来实现。
具体实现流程:
- Observer:核心是通过Obeject.defineProperty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter,进行数据劫持,然后通知订阅者,订阅者就是Watcher。
-
Watcher:订阅者作为Observer和Compile之间通信的桥梁。在收到属性变化的通知后,调用自身的更新函数,触发Compile中绑定的回调函数。
-
Compile:主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
虚拟 DOM
虚拟DOM本质上就是一个普通的JavaScript对象,里面能很好的表示DOM元素需要记录的信息:节点名称、属性,文本,子节点等。比如下:
{ // 节点类型 type: 'ul', // 节点的属性,包括dom原生属性和自定义属性 props: { class: 'list', style: 'color:red;' }, // 子节点数组 // 子对象结构也是一样,包含了type,props,children,没有子节点的话就是普通文本 // 子对象拥有子节点时,继续往下扩展就行 children: [ {type: 'li',props: {class: 'list'},children: ['利群']}, {type: 'li',props: {class: 'list'},children: ['玉溪']}, {type: 'li',props: {class: 'list'},children: ['黄鹤楼']} ] }
虚拟DOM的必要性:
virtual-dom
(后文简称vdom
)的概念大规模的推广还是得益于react
出现,virtual-dom
也是react
这个框架的非常重要的特性之一。相比于频繁的手动去操作dom
而带来性能问题,vdom
很好的将dom
做了一层映射关系,进而将在我们本需要直接进行dom
的一系列操作,映射到了操作vdom
,而vdom
上定义了关于真实dom
的一些关键的信息,vdom
完全是用js
去实现,和宿主浏览器没有任何联系,此外得益于js
的执行速度,将原本需要在真实dom
进行的创建节点
,删除节点
,添加节点
等一系列复杂的dom
操作全部放到vdom
中进行,这样就通过操作vdom
来提高直接操作的dom
的效率和性能。
Virtual DOM 算法实现的大致逻辑:
- 用JavaScript对象结构DOM树的结构,然后用这个结构构建成一个真正的DOM树,并渲染到文档中去。
- 当JavaScript对象发生变化,则重新构建一颗新的对象树,然后比较两个对象树,记录两棵树之间的差别。
- 把记录的差别应用到步骤1所构建的DOM树上,完成更新。
数据响应式设计的必要性
所谓数据响应式就是用户对数据层做的更改能够触发视图层做出的更新响应的机制。
mvvm 框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要实现一套响应式机制,这样一旦数据发生变化就可以立即做出更新处理。
在 vue 中,通过数据响应式加上虚拟 DOM 和 patch 算法,可以使我们只需要操作数据,完全不用接触繁琐的 DOM 操作,从而大大提升开发效率,降低开发难度。
vue2 中的数据响应式会根据数据类型来做不同处理,如果是对象则采用 Object.defineProperty() 的方式定义数据拦截,当数据被访问或发生变化时,框架感知并作出相应;如果是数组则通过覆盖该数组原型的方法,扩展它的7个变更方法,使这些方法可以额外的做出更新通知,从而作出相应。这种机制很好的解决了数据响应化的问题,但也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除时需要用户使用 Vue.set/delete 这样特殊的 api 才能生效;对于 es6 中新产生的 Map、Set 等数据结构不支持等问题。
基于这些问题,vue3 重新编写了这一部分的实现:利用 es6 的 Proxy 机制代理要相应化的数据,达到变成体验一致,不需要使用特殊 api ,初始化新能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的 reactivity 包,我们可以更灵活的使用它,甚至不要 vue 的引入。
watcher与computed的区别
watcher:一个值影响多个值;监听已有属性;允许异步操作
computed:多个值影响一个值;生成一个新的属性;不允许异步操作;必须有依赖型数据;当依赖数据发生变化才会重新计算,否则用缓存;内部有getter和setter方法。
created与mounted的区别
主要的区别在于是否有渲染DOM树。
- beforeCreate:DOM节点、data、methods都不能获取。
- created:DOM节点不能获取,data、methods可以获取。
- beforeMounted:DOM节点不能获取,data、methods可以获取。
- mounted:DOM节点、data、methods都可以获取。
v-for与v-if为什么避免一起使用
v-for的优先级高于v-if,所以v-if会执行在每一个v-for的子元素中,从而降低性能。
解决方法:
- 将v-if置于v-for的上层
- 将需要循环的属性通过computed进行过滤,然后v-for循环computed属性。
循环指令中,key值的作用
主要作用是用来提高虚拟DOM更新的效率,是在diff算法判断中,用来判断两个节点是否相同的一个很重要的标准。
组件间的通信
- 父传子props,v-model,子传父emit
- 全局vuex,eventBus(Vue.prototype.bus = new Vue())
vue.use的用途及原理
use方法是用来扩展插件的。源码如下(src/core/global-api/use.js):
export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // 如果当前plugin已经被use调用过了,则直接返回 if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters const args = toArray(arguments, 1) args.unshift(this) // 如果plugin对象中有install方法,则执行 if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) // 如果plugin本身就是方法,则执行 } else if (typeof plugin === 'function') { plugin.apply(null, args) } // 最后再将plugin缓存起来 installedPlugins.push(plugin) return this } }
vue.$nextTick方法的用途及原理
nextTick 是 vue 提供的一个全局 api ,由于 vue 的异步更新策略导致我们对数据的修改不会立刻体现在 DOM 变化上,如果想要立即获取更新后的 DOM 状态, 就需要使用这个方法。
vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,vue 就会开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会推入到队列中一次。这种在缓存时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick 方法会在队列中加入一个回调函数,确保该函数在前面的 DOM 操作完成后才调用。
以下为vue源码(src/core/util/next-tick.js):
let timerFunc // 如果有Promise且是原生API,则选用Promise(微任务) if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) if (isIOS) setTimeout(noop) } isUsingMicroTask = true // 如果支持MutationObserver,则用MutationObserver(MutationObserver是用来监听DOM变化的API,为微任务) } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { 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 // 如果支持setTmmediate,则用setTmmediate(可以看成setTimeout,兼容性不好,仅IE10+及nodejs支持,为宏任务) } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. // 最后的应急方案,setTimeout方法(宏任务) timerFunc = () => { setTimeout(flushCallbacks, 0) } }
上面代码分析得出,在vue被实例化执行的时候,首先判断微任务(Promise > MutationObserver)是否支持,然后再判断宏任务(setImmediate)是否支持,最后采用setTimeout方法。具体之间的区别可以查下JavaScript的事件循环,微任务与宏任务的区别。
mixin
Mixins 使我们能够为 Vue 组件编写可插拔和可重用的功能。如果我们希望在多个组件间重用一组选项,比如data、computed、methods等,那么我们可以将其写成mixin,并在需要的地方引用它,然后通过mixin合并到当前组件。如果一个组件中有引入mixin,那么mixin中的生命周期hook将优先组件自己内的hook。
vuex
Vuex 是一个专为 Vue.js 应用程序开发的响应式状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
5个核心概念:
- state:单一的状态树对象,包含了全部的应用层级状态。可以简单的理解为整个状态管理的仓库地址,可以设置内部所有属性的初始值,并且整个store状态是响应式的,每当state内的属性值改变,监视的vue组件也会同步更新。
- mutation:提交 mutation是更改 Vuex 的 store 中的状态的唯一方法,它是一个同步函数。
- action:类似与mutation,不同的是action不能直接更改state,要通过提交mutation来更改。在action内可以执行任何异步任务,也可以通过dispath调用另一个action任务。
- module:当项目很复杂的时候,我们可以使用module来进行模块划分。每个module内部都有自己的state,mutation,action,getter、对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象、action内部使用的根节点为context.rootState、getter使用的根节点为第三个参数。默认情况下,模块内部的state是注册在全局命名空间的,如果有使用namespaced为true,那么在引用的时候,需要在前面加上模块名称,如(this.$store.dispath('moduleName/actionFunction()'))。
- getter:可以认为为store的计算属性,通过它可以对state状态的值进行重新计算缓存并返回。
数据传输流程:
- 在实例化Vuex.Store()的时候,创建一个全局仓库state,并将内部属性进行数据劫持。
- state内部数据需要修改的时候需要遵循单项数据流,在组件中通过dispath方法调用action方法。
- action内部方法处理完成后,然后再调用commit方法通知mutation。
- mutation内部方法用来修改state中的数据。
- state内部数据更改完成,因其数据为响应式,则组件中依赖的值也会相应变化
v-model原理
v-model 是 vueJs 用来实现数据双向绑定的功能,其实质是一个语法糖,既绑定了数据,又添加了一个 input 时间监听:
// 正常写法 <Children v-model="number" /> // 等同于 <Children :value="number" @input="number = $event" /> // Children.vue <template> <div @click="$emit('input', value + 1)"></div> </template> <script> export default { props: ['value'] } </script>
未完待续......