vuex
vuex是一套专门为vue.js应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件状态并以相应的规则保证状态以一种可预测的方式发生变化。
为什么要用vuex
- 单纯的使用兄弟组件间的状态传递,无法看出来数据是从哪里来的
- 把组件的共享状态抽取出来,以一个全局单例模式管理
- 解决了多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
vuex的属性
- state 存储数据和状态,在根实例中注册了store以后,用this.$store.state来访问,来对应vue里面的data,存放数据方式为响应式,并且vue组件从store中读取数据,如果数据发生变化,组件也会对应的更新
- getter state的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变的时候才会被重新计算
- mutation 更改vuex中的store的状态的方法,用来改变状态
- action 包含异步操作,通过提交mutation更改状态
- module
将store分割成模块,每个模块都具有state,mutation,acton,getter,甚至嵌套子模块
数据流方向action-->mutation-->state
- 用户的交互行为、组件或后端api触发actions
- action提交mutations
- mutations修改state
- 组件根据state或者getter来渲染页面
挂载
vue与react的区别
相同点
- 都是javaScript的Ui框架,专注于创造前端的应用
- 都鼓励、支持组件化应用,将应用拆分成一个个功能明确的模块,每个模块都可以通过合适的方式互相联系,都是数据驱动视图
- 都使用了虚拟dom技术
- 提供了响应式
- props,允许父组件往子组件传送数据
- 都提供官方构建工具(create react app、vue-cli),得到一个根据最佳实践设置的项目模板
- 提供chrome的开发工具
- 配套插件,两个框架都专注于ui层,其他的功能如路由、状态管理等等都是交由同伴框架进行处理都把注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库
vue+vue-router+vuex
react+react-router+redux - 均是单向数据流传递,父组件传递数据到子组件中去
不同点
-
监听数据变化的实现原理不同 vue是响应式的(双向绑定),react是手动setState
react中当某个租金啊的状态发生变化的时候,它会以组件为根,重新渲染整个组件子树,如果要避免不必要的子组件的重渲染,需要去手动实现
vue应用中的依赖是在渲染过程中自动追踪的,所以系统能够精确的知晓哪个组件确实需要被重新渲染,开发者不需要考虑组件是否需要重新渲染之类的优化 -
数据流的不同
-
组件通信的区别
-
模板渲染方式的不同 vue使用模板,react是使用jsx(一切都是javascript),JSX 是使用 XML 语法编写 JavaScript 的一种语法糖。
-
框架本质的不同 vue本质上是mvvm框架,由mvc发展而来
react是前端组件化框架,由后端组件化发展而来
v-if和v-show
v-if
真正的条件渲染,直到真正为true的时候才开始渲染,即document文档流里面不存在此dom元素,通过控制dom节点的存在与否来控制元素的显隐,切换过程中是完整的销毁和重新创建内部的事件监听和子组件
有更高的切换消耗(因为要更改dom元素的结构),适用于不怎么切换页面的时候使用
v-show
不管初始条件是什么,页面一开始就会开始渲染,只是简单的基于css的display属性进行切换(有更高的初始渲染消耗),适用于频繁切换消耗
v-bind
v-bind用于绑定数据和元素属性
即通过绑定属性可以访问data中的数据 简写成 :属性名=“”
用法
- 动态绑定html class
- 直接绑定一个样式对象
- 对象方法
- 数组方法
- 行内内联绑定
computed、watch、methods
watch和computed都是以vue的依赖追踪机制为基础:当一个依赖数据发生变化的时候,所有依赖这个数据的相关数据自动发生变化,自动调用相关的函数去实现数据的变动
- methods是用来定义函数的,需要手动调用,每当重新渲染的时候会再次执行
- computed是计算属性,事实上和data对象里面的数据属性是同一类的(在使用方式上),计算属性基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变的时候才会重新求值,擅长场景:一个数据受多个数据影响(多对一)
computed是缓存的只要是依赖的数据不更改他就不会重新计算更新,而methods里面的方法是每次刷新都会去执行的
computed作为一个对象的时候,含有get和set两个选项,不能像methods一样接收参数,并且可以依赖其他computed和组件里面的data - watch:类似于监听机制+事件机制(擅长场景:一个数据影响多个数据(一对多))当需要数据变化执行异步或者开销操作较大的时候,此方式是最有用的,watch允许我们执行异步操作,本质上是监听某一个值的变化执行对应的方法
用法
只是需要动态值,当有一些数据需要随着另外一些数据进行变化的时候,那就使用计算属性computed
需要知道值的改变以后执行业务逻辑(复杂异步任务)的,才使用watch
事件修饰符
- .stop 阻止事件冒泡(阻止事件向父级dom元素传递)
- .prevent 阻止默认事件发生(例如超链接会进行页面的跳转,点击表单提交按钮会重新加载页面)
- .capture 捕获冒泡,即有冒泡发生的时候,有该修饰符的dom元素会先执行,如果有多个,则从外到内依次执行,然后再按自然顺序执行触发的事件
- .self 将事件绑定到自身,只有点击当前自身才能触发,通常用于避免冒泡事件的影响
- .once 设置事件只能触发一次,比如按钮的点击
- .passive 用于对DOM的默认事件进行性能优化(比如超过最大范围的滚动条滚动,主要用于移动端的scroll事件,提升移动端的性能)
- .native 在父组件中给子组件绑定一个原生的事件,就将子组件编程了普通的HTML标签,不加native是无法触发的(可以理解为该修饰符的作用就是把一个vue组件转化成一个普通的HTML标签,并且该修饰符对普通HTML标签是没有任何作用的)
- 键盘修饰符、鼠标修饰符、修饰键、自定义按键修饰符别名
组件中 data 为什么是函数
因为组件是可复用的vue实例,一个组件被创建好以后,就可能被用在各个地方,并且组件中的data数据是相互隔离且互不影响的。组件每复用一次,data数据就应该被复制一次,之后当某一处的地方组件内的data数据被改变的时候,其他复用地方组件的data数据不受影响。
所以一个组件的data选项必须是一个函数,每一个实例的data属性都是独立的不会互相影响,因此每个实例可以维护一份被返回对象的独立拷贝
keep-alive
是vue的内置组件,当它包裹动态组件的时候,会缓存不活动的组件实例,而不是销毁它们。
作用
在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间以及性能消耗,提高用户体验性
原理
在created函数调用的时候将需要缓存的虚拟dom节点保存在this.cache中
在render(页面渲染)的时候,如果vnode的name符合缓存条件,则会从this.cache中取出之前缓存的vnode实例进行渲染
用法
- 缓存所有页面
- 根据条件缓存页面
- 结合router,缓存部分页面
v-model
本质上是语法糖,利用v-model绑定数据以后,又添加了一个input事件监听
- v-bind绑定一个value属性
- v-on指令给当前元素绑定input事件
单向数据流
父级prop的更新将会向下流动到子组件中,每次父级组件发生更新的时候,子组件中所有的prop都将会刷新为最新的值(反之则不行)
不能直接修改传入的prop值,最好定义一个本地的 data 属性并将这个 prop 用作其初始值
如果是对prop值的转换,可以使用计算属性
单向数据流子组件数据同步到父组件
即修改子组件的prop,同步到父组件
- 使用.sync修饰符,配合this.$emit("update:xxx",this.xxxx)语法
- 将要操作的队形封装成一个对象再操作,所以就可以进行双向数据绑定了
vue生命周期
vue每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁,这就是一个组件所谓的生命周期
beforeCreate(创建前)
在实例初始化以后,数据观测和时间配置之前被调用,此时组件的选项对象还未创建,el和data并未初始化,因此无法访问methos、data、computed等方法和数据
created(创建后)
此时 实例已经创建完成之后被调用,并完成数据观测、属性、事件回调等,完成data数据的初始化,通常我们可以在这里对实例进行预处理,然而挂载阶段还没有开始,$el属性目前不可见
beforeMount(挂载前)
挂载开始之前被调用,相关的render函数首次被调用(虚拟dom),实例已开始以下的配置:编译模板,并把data里面的数据和模板生成了html,完成了el和data的初始化
el
用于指明vue实例的挂载目标
mounted(挂载时)
挂载完成,也就是模板中的html渲染到html页面当中去,此时vue实例挂载完成,data成功渲染 ,此时一般可以做一些ajax操作,mounted只会执行一次
beforeUpdate(更新前)
在数据更新之前被调用,发生在虚拟dom重新渲染和打补丁之前,可以在该钩子中进一步的更改状态。
updated(更新后)
调用时组件dom已经更新,所以可以执行依赖于dom的操作,然而在大多数情况下应该避免在此期间更改状态,因为这可能会导致更新无限循环,该钩子在服务器端渲染期间不被调用
beforeDestroy(销毁前)
在实例销毁之前调用,此时实例仍然完全 可用
- 这一步可以用this获取实例
- 一般在这一步做一些重置的操作
removeEventListener(清除组件中的定时器和监听的dom事件)
destroy(销毁后)
调用以后所有的事件监听器会被移出,所有的子实例也会被销毁,该钩子在服务器端渲染期间不被调用
组件之间的通信
props/$emit
- 父组件向子组件传值
- 子组件向父组件传值(通过事件形式)
on
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
vuex
路由跳转方式
router-linkthis.$router.push()跳转到指定url路径,并想history栈中添加一个记录,点击后退会返回到上一个页面this.$router.replace()
跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
mvc和mvvm
this.nextTick()
this.nextTick()将回调延迟到下次DOM更新循环之后执行,在修改数据之后立即使用它,然后等待dom的更新
它跟全局方法VUE.nextTick一样,不同的是回调的this自动绑定到调用它的实例上面。
异步更新队列(vue异步更新DOM)
vue在观察到数据变化的时候并不是直接更新DOM,而是开启一个队列,并缓冲在同一件事件循环中发生的所有数据改变
vue的原理
vue的模式使用mvvm模式,即(model-view-viewModel),通过viewModel作为中间层(即vm的实例),进心双向数据的绑定与变化
- 通过家里虚拟DOM树
document.createDocumentFragment()方法创建虚拟DOM树 - 一旦被创建的数据发生改变,会通过Object.defineProperty定义的数据拦截,截取到数据的变化
- 截取到的数据变化,从而通过订阅-发布者模式,触发Watcher(观察者),从而改变虚拟DOM的中的具体数据
- 最后通过更新虚拟Dom的元素值,从而改变最后渲染DOM树的值,完成双向绑定。
render函数
vue虚拟dom,diff算法
虚拟dom
虚拟dom其实就是一棵以vnode节点作为基础的树,用对象属性来描述节点,实际上只是一层对真实dom树的抽象。
虚拟dom是通过js语法在内存中维护一个通过数据解构描述出来的一个模拟DOM树,当数据发生改变的时候,会先对虚拟dom进行模拟修改,然后再通过新的虚拟dom树与旧的dom树来进行对比,进行局部更新
简单来说可以把虚拟dom理解为一个简单的js对象,最少包含标签名(tag)、属性(attrs)和子元素对象(children)三个属性
模板-渲染函数-虚拟dom树-真实dom
虚拟dom的最终目标是将虚拟节点渲染到视图上面去,但是如果直接使用虚拟节点去覆盖旧结点的话,会有很多不必要的dom操作。真实情况是虚拟节点与旧虚拟节点进行对比,然后找出真正需要更新的节点来进行dom操作,从而避免操作其他无需改动的dom。
优点
- 跨平台:虚拟dom是由js对象基础而不依赖真实平台环境,所有具备跨平台的能力(本质上只是一个js对象,而不是dom对象)
- 手动操作dom速度慢,所以可以将dom对比操作放在js层提高效率,运用patching算法来计算出真正需要更新的节点,最大限度减少dom操作,提高显示性能。(可以理解为虚拟dom的本质是在js和dom之间做了一层缓存)
- 提高渲染性能:虚拟dom优势不在于单次操作,而是大量、频繁的数据更新,减少DOM操作的次数和范围
patch核心-diff算法
- 用js对象结构表示dom树结构,然后用这个对象结构构建一个真正的dom树,插到文档中(初次渲染)
- 当状态、数据变更的时候,重新构造一棵新的对象树,然后用新旧两棵树进行比较记录两棵树的差异(再次渲染)
- 把所记录的差异应用到构建的真正的dom树中,即更新了视图
首先进行根元素的对比,如果根元素发生改变直接整个替换。再对下一层进行对比,执行删除、替换、添加等操作
对比的过程中通过key值来判断元素是否发生改变,判断元素是仅仅位置发生改变还是整个替换删除
如果不是元素进行改变的话,再对内容进行对比,直接进行内容的修改
(本质上就是进行逐层的对比,通过不同的对比执行不同的操作)
diff 是通过JS层面的计算,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染
- 传统虚拟dom算法(O(n^3))
- vue仅在同级别的虚拟vnode做diff,递归进行同级别的diff操作,最终实现整个dom树的更新。因为跨层级的操作非常少,所以时间复杂度从O(n^3)--> O(n)
总结
vue.js通过编译奖模板转换成渲染函数,执行渲染函数就可以得到一个虚拟节点树,虚拟节点树提供虚拟节点vnode和新旧两个vnode进行对比并根据比对结果进行dom操作来更新视图,达到减少对dom操作的目的,从而减少浏览器的开销,提高渲染速度,改善用户体验
vue响应式原理(数据绑定)
改变数据的时候,视图跟着更新。
想要完成这个过程我们需要:
- 侦测数据的变化 (数据劫持/数据代理)
- 收集视图依赖了哪些数据(依赖收集)
- 数据变化时,自动通知需要更新的视图部分,并进行更新(发布-订阅模式)
proxy(vue3)
let p = new Proxy(target, handler);
proxy就是代理,可以帮助我们完成很多事情,顾名思义就是在我们访问对象前添加了一层拦截可以过滤很多操作
proxy的代理是针对整个对象的,而不是对象的某个属性,因此不同于object.defineProperty的必须遍历对象的每个属性,proxy只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层次结构,递归还是需要进行的。
- target:需要使用proxy包装的目标对象
- handler:一个对象,属性是当执行一个操作时定义代理行为的函数(触发器)
Object.defineProperty(vue2)
vue使用的是Object.defineProperty的方法里面的setter与getter方法的观察者模式来实现的。
vue2使用defineProperty,不能检测数组的变化,不能检测对象属性的添加/删除,并且不会对每个元素都监听,提升了性能。
vue通过设定对象属性的setter/getter方法来监听数据的变化,通过getter进行依赖收集,而每一个setter就是一个收集者,在数据变更的时候通知订阅者更新视图。
这个方法就是在一个对象上定义一个新的属性,或者改变一个对象现有的属性,并且返回这个对象。
<input type="text" id="txt" />
<span id="sp"></span>
<script>
var txt = document.getElementById('txt'),
sp = document.getElementById('sp'),
obj = {}
// 给对象obj添加msg属性,并设置setter访问器
Object.defineProperty(obj, 'msg', {
// 设置 obj.msg 当obj.msg反生改变时set方法将会被调用
set: function (newVal) {
// 当obj.msg被赋值时 同时设置给 input/span
txt.value = newVal
sp.innerText = newVal
}
})
// 监听文本框的改变 当文本框输入内容时 改变obj.msg
txt.addEventListener('keyup', function (event) {
obj.msg = event.target.value
})
</script>
事件发布-订阅模式
- 订阅者Dep:用来收集依赖、删除依赖和向依赖发送信息(用来存放watcher观察者对象)
- 观察者watcher:数据发生变化的时候通知他,然后他再通知其他地方
观察者模式
- 注册
- 发布 Dep的简单实现
class Dep {
constructor () {
/* 用来存放Watcher对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象(依赖收集) */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher对象更新视图 (派发更新)*/
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
Watcher的简单实现
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图
this.cb(this.value)
}
}
收集依赖
在getter中收集依赖,在setter中触发依赖
总结
- vue的响应式原理核心是通过object.defindeProperty中的get和set方法,也就是加上setter/getter函数,来对数据进行追踪变化,当被设置的对象被读取的时候会执行getter函数,而在当被赋值的时候执行setter函数。
- 当检测到数据的变化的时候,会通知观察者watcher,观察者watcher自动触发重新render当前组件(子组件不会重新渲染),生成新的虚拟dom树,vue框架会遍历并对比新旧虚拟dom树中每个节点的区别,并记录下来最后加载操作,将所有记录的不同点局部修改到真实的dom树上面。