Vue2.0 原理篇
- *本文仅对Vue原理作简要概述/总结,具体实现不做详解*
- 创建 Vue实例的两种写法
- el挂载容器
- mount()挂载容器
- 总结
- data的2种写法
- 对象式
- 函数式
- 总结
- 模板语法
- 插值语法
- 指令语法
- 总结
- js表达式
- js语句
- Vue中的MVVM模型
- 总结
- Vue响应式数据原理
- 总结
- 计算属性computed
- 什么是计算属性
- 原理
- 侦听属性watch
- 什么是侦听器
- 什么是深度侦听
- computed与watch区别
- 样式绑定
- class样式
- style内联样式
- 条件渲染
- Vue核心—Differ算法
- Vue核心—虚拟DOM
- Vue工作流程
- 列表渲染/key的选择
- index作key缺点
- id作key优点
- v-model注意事项
- 过滤器
- 自定义指令
- VueComponent构造函数
- Vue原型链
- render函数
- ref属性
- 作用
- 语法
- 注意
- props属性
- 功能
- 语法
- 传递数据
- 接收数据
- 注意
- 应用场景
- mixin混入
- 功能
- 使用方式
- 注意
- 自定义事件
- 绑定自定义事件
- 触发自定义事件
- 解绑自定义事件
- 注意
- 应用场景
- 全局事件总线
- 安装全局事件总线
- 使用全局事件总线
- 提供数据:
- 注意
- 应用场景
- 消息订阅与发布
- 使用步骤
- 注意
- 应用场景
- vuex
- 定义
- 使用步骤
- 应用场景
本文仅对Vue原理作简要概述/总结,具体实现不做详解
创建 Vue实例的两种写法
el挂载容器
代码语言:javascript复制new Vue ({
el: '#root' ===>> document.getElementById('root')
})
mount()挂载容器
代码语言:javascript复制new Vue({...}).mount('#root')===>> document.getElementById('root')
总结
- 通过el 或 mount()来挂载容器,其底层都是通过document.getElementById(‘root’)来操作
- 挂载:即在组件创建完毕后,将DOM结构放入页面的操作
data的2种写法
对象式
代码语言:javascript复制new Vue({
data:{...}
})
注意:对象式只能写在vm中(new Vue中)
函数式
代码语言:javascript复制data:function(){}
可简写为:
data(){
return {...}
}
注意:
- 组件中只能用函数式写法,且有return
- 因为在复用组件的时候,data对象只是被复用了“引用”,一 个组件中的data数据改变,所有组件中的data数据都会改变
总结
- 对象式只能写在new Vue中
- 组件中只能用函数式写法,且有return
- data不能使用箭头函数。因为箭头函数没有this,默认指向其父级函数指向的对象。
- 在这里普通函数的this指向 vm(Vue的实例) ,或 组件的实例对象
模板语法
模板语法分为2大类:插值语法、指令语法
插值语法
语法格式:{{ value }}
功能:用于解析标签体内的内容,即写在标签体内
举例:
代码语言:javascript复制<h1> {{value}}</h1>
指令语法
语法格式:v-xxx
功能:用于解析标签(可解析标签属性、标签体内容、事件绑定…),即写在头标签内
举例:
代码语言:javascript复制<h1 v-xxx:key="value"></h1>
总结
- 都将其value视为JavaScript表达式解析
- 注意区分js表达式和js语句
js表达式
js表达式:即会计算并返回一个值的算数运算
举例:
- a b
- x === y ? ‘a’ : ‘b’
js语句
js语句:即控制代码走向的语句
举例:
- if() { }条件语句
- for() { }循环语句
Vue中的MVVM模型
MVVM是什么,很多文章有介绍,这里就不废话直接总结!
总结
代码语言:javascript复制<div id="root"></div> ===>> view
<script>
new Vue({ ===>> view model
data(){} ==>> model
})
</script>
- data中的数据变化都会被vm侦听到,并响应到root中
- root中的数据变化都会被vm侦听到,并响应到data中
Vue响应式数据原理
由于响应式数据涉及到:数据代理、数据劫持、Object.definepropetry()等操作,这里就不废话,直接总结!
总结
备注:
1). get()方法 ===> getter 2). set()方法 ===> setter 3). 知道这个东西就行不多解释,面试的时候用getter/setter会更专业
总结:
- Vue会为data中的每一个属性都添加一个get()和set()方法
- data中数据的变化,实际是调用了set()方法,修改数据
- 当数据变化会被VM侦听到,自动调用属性的get()方法获取最新的数据,实现响应式数据变化
- v-model的原理也是这样的
计算属性computed
什么是计算属性
- 计算属性就是computed的一个属性。
- 通过计算已有的属性,得到一个返回值。这个返回值就是计算属性的值。
- 已有的属性:vm中存在的属性,常为data中的属性
语法:
代码语言:javascript复制computed:{
计算属性名(){
return {
-- 计算操作 --
}
}
}
原理
备注:
1). get()方法 ===> getter 2). 知道这个东西就行不多解释,面试的时候用getter会更专业 计算属性原理与响应式数据原理相似
原理:
- 当计算属性被调用时,get()就会被调用
- get()拿到vm中的已有属性进行计算
- get()自动返回计算结果,作为计算属性的值
注意:computed不能进行异步操作!eg:计算属性里不能用定时器
侦听属性watch
什么是侦听器
- 监听一个数据,当该数据变化时,侦听器会拿到这个数据的新值与旧值,程序员可以对这两个值进行一些操作
- 即当数据变化时,就立即执行对应的函数,对数据进行操作。
语法:
代码语言:javascript复制watch:{
侦听的数据(参数1,参数2){ //参数1接收新值,参数2接收旧值
-- 对数据进行操作 --
}
}
什么是深度侦听
- Vue中的watch默认只能侦听data中第一层对象的变化
- 深度侦听可以侦听到data中多层结构中所有属性的变化
- 若data中数据又嵌套,则需开启深度侦听
computed与watch区别
- computed可以完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成。eg:watch可以完成异步操作,computed不可以
- watch:只侦听单个数据,无返回值。即不需要return。直接在内部通过this操作data中的数据
- computed:侦听多个数据,返回计算结果。即需要return
样式绑定
class样式
语法: class="xxx"
xxx可以是字符串、对象、数组
字符串:最常用的方式,直接写类名。最常用
对象、数组:可根据数据结构中,数组和对象的优势,按需使用(知道有这2种写法即可,不做详解)
注意:若类名以array或object类型存放在data中,class需用“v-bind”绑定,即
:class="xxx"
style内联样式
语法:
:style="{key:value}"
其中value为动态值,key若为复合词,则用小驼峰:style="[key1,key2]"
其中key为样式对象,很少使用数组形式
条件渲染
- v-show===>> 底层通过
display:none/block
来控制元素显示与隐藏,该元素仍存在与DOM结构中。当显示与隐藏频率高时使用性能最佳 - v-if===>> 直接删除/添加元素。删除后DOM结构中没有该元素。
Vue核心—Differ算法
Differ即different(不同的),即将两个数据进行对比,找出两个数据之间的不同。
Vue核心—虚拟DOM
虚拟DOM也称VDOM,V即virtual(虚拟的)的简写
- Vue会根据vm生成一个虚拟DOM(这个虚拟DOM不会被直接渲染到页面)
- Vue再将虚拟DOM,渲染到页面(el或mount挂载容器,即为该步操作) 详情见下一章节 Vue工作流程
Vue工作流程
备注:
- VDOM ===>> 虚拟DOM
- VNode ===>> 虚拟节点
Vue工作流程:
- 根据初始数据生成VDOM
- 将VDOM 转成 真实DOM
当数据更新
- 根据新的数据生成新的VDOM
- 通过key将新的VDOM与旧的VDOM对比(Differ算法)
- 若旧的Vnode与新的Vnode不一样,则用新的Vnode替换旧的Vnode,渲染到页面
- 若旧的Vnode与新的Vnode一样,则将旧的Vnode复用,不进行任何操作。
注意:key的选择键下一章节
列表渲染/key的选择
- 列表渲染
v-for
的使用就不做多概述了。 key
的作用:节点的唯一标识
index作key缺点
- 数据错乱
若打乱的原始数据的顺序,node的index会改变,会导致在Differ对比时,对比的不是同一Node。(因为Differ将相同index的Node进行对比,而Node的index已经改变。如原来的index=1,而现在index=2。所以对比的不是同一节点。)
- 效率低
由于前后对比的不是同一Node,则Node不能复用,所有的Vnode都需要转成 真实的 Node( 整棵真实DOM数都被替换 )
- DOM结构混乱
若DOM结构中还有输入类的元素,会产生错误的DOM更新 ==>> 界面显示的DOM结构错位( 输入的内容为真实内容,不会出现在Vnode中,Differ在对比时,只能对比标签,标签里没有内容,而标签都是一致的则将标签复用, 因而在Vnode顺序改变,但真实内容还是显示在原来的位置,导致页面显示错位 )
id作key优点
- 效率高,无数据错乱问题
- 不管怎么改变顺序,id值是唯一的,不会改变,真实DOM数中只有部分Node被重写 不写key:Vue默认将 index作为key
key的选择:可为id、手机号、学号、账号…( 大型项目会出现id穷尽的现象 )
v-model注意事项
注意事项:
- text类型表单,则v-model收集的是表单value的值,用户输入的就是value值
- radio类型表单,则v-model收集的是表单value的值,要给表单配置不同的value值
- checkbox类型表单: 1. 未配置input的value值,则v-model收集的是checked(勾选 or 未勾选,是布尔值) 2. 配置了input的value值 * v-model的初始值是非数组,则v-model收集的是checked(勾选 or 未勾选,是布尔值) * v-model的初始值是数组,则收集的就是value组成的数组
注意:
**v-model的3个修饰符
- lazy:失去焦点再收集数据
- number:将输入的字符串转为数字
- trim:过滤输入首位空格
过滤器
过滤器的本质就是一个函数
功能:将要显示的数据,进行一定的格式化后,再显示
注意:没有改变原数据,产生的是新数据
局部过滤器语法: 调用:
- 插值语法调用:
<xx>{{被过滤的对象|过滤器}}</xx>
- 属性语法调用:
<xx:属性="被过滤的对象|过滤器"}></xx>
,属性语法很少用
Vue通过管道符"|",自动将被过滤的对象作为实参传入过滤器,不需要我们手动传参。
声明:
代码语言:javascript复制filters: {//与data同级
过滤器() {//这里要用形参来接收
return --过滤操作--
}
//可以写多个过滤器
}
全局过滤器语法:
代码语言:javascript复制Vue.filter( ' 被过滤的对象 ',function () {
return --过滤数据操作--
}
}) //写在实例化Vue之前
原理:
- 在插值表达式中,将被格式化的对象,作为参数传给过滤器。
- Vue自动调用过滤器,解析完后,自动将插值表达式替换为,解析后的结果
注意:
- 多个过滤器使用 管道符 分割。
{{ 被格式化的对象 | 过滤器1 | 过滤器2 | 过滤器3 }}
- 在调用过滤器时,可以传参,用第二个形参接收传入的参数,第一个形参接收的是 管道符 前的对象,Vue通过管道符自动调用该参数,不需要手动传参
自定义指令
** 定义语法**
- 局部指令:
new vue({
directives:{指令名:配置对象}
})
或
代码语言:javascript复制new vue({
directives:{指令名:回调函数}
})
- 全局指令
Vue.directive(指令名,配置对象)
或
代码语言:javascript复制Vue.directive(指令名,回调函数)
** 注意**
- 定义指令时,不加 v-。使用时要加v-
- 指令名若为复合词,则使用“-”连接,不用使用小驼峰或大驼峰
VueComponent构造函数
作用
生成组件的实例化对象
注意
- 我们创建的组件,本质上就是一个VueComponent构造函数
- 这个构造函数不需要我们去定义,由Vue自动生成
Vue实例化流程
- new Vue 创建Vue的实例vm 若App.vue文件中有我们自定义的组件标签
- Vue自动调用Vue.extend生成VueComponent构造函数
- 自动new VueComponent(options)生成组件的实例化对象,即vc
Vue原型链
注意:本节需要对原型和原型链有一定了解。否则你看不懂!!!
Vue原型链
- 组件的实例vc的__proto__,指向VueComponent的原型对象
2.VueComponent的__proto__,指向Vue的原型对象
- Vue原型对象的__proto__,指向顶级对象Object的原型对象
即VueComponent.prototype.proto === Vue.prototype
render函数
代码语言:javascript复制new Vue({
...
render:h=>h(App)
})
这个你很眼熟吧,让我看看他的前世今生
render函数完整写法
代码语言:javascript复制new Vue({
...
render(createdElement){ // createdElement是一个函数,用于创建元素
return createdElement(App) // 将App作为实参,调用createdElement函数,创建App对应的元素
}
})
render函数简写:因为不需要this,因而可以简写为箭头函数
代码语言:javascript复制new Vue({
...
render:h=>h(App) // h只是一个形参,用其他字母也可
})
ref属性
作用
- ref属性被用来给元素或者组件注册引用信息
- this.$refs.xxx ===>> document.getElnmentById(‘xxx’),二者功能一样,但Vue不建议直接操作DOM,ref相当于id的代替者
语法
- ref绑定在HTML标签上,得到的是真实的DOM元素
<h1 ref="xxx"></h1> // 绑定HTML标签
- ref绑定在组件标签上,得到的是组件实例对象vc
<组件 ref="xxx"></组件> // 绑定组件标签
注意
- this.$refs得到的是相应的真实的DOM元素
- this.$refs.xxx得到的是具体的模板内容
props属性
功能
- 让组件接收外部传来的数据
语法
传递数据
代码语言:javascript复制<组件 name="xxx"></组件>
接收数据
代码语言:javascript复制props:{
name:{
type:String, // 指定数据类型
required:true, // 是否必须属性
default:*** // 指定默认值
}
}
注意
- props是只读的
- 不能直接修改props中的数据
- 若要修改,将props中的数据复制一份到data中,进行相应的操作
- v-model的值不能是props传来的值,因为props是不可修改的
- props传来的若为对象类型的值,可以修改对象中属性的值,但不推荐这样做
应用场景
- 父组件===>>子组件 通信
- 子组件===>>父组件 通信(父组件要先给子组件一个函数)
mixin混入
功能
- 将可复用的js代码封装到一个文件夹中
使用方式
- 在src下创建mixin.js文件
- 定义混入代码,mixin是一个对象
export const xxx={ // 三种导出方式,按需使用即可
data({...}),
methods:{...}
... // 可使用组件中的所有配置属性
}
- 使用混入 a. 全局混入:Vue.mixin(xxx)//慎用 b. 局部混入:mixins:[‘xxx’]
注意
- 若混入的数据,与组件中的语句冲突,则以组件中的数据为准
- 钩子函数冲突,则全部使用
自定义事件
绑定自定义事件
代码语言:javascript复制<组件 @自定义事件="回调函数" ref="xxx"></组件> //法一
代码语言:javascript复制mounted(){ // 法二
this.$refs.xxx.$on('自定义事件',回调)
}
触发自定义事件
代码语言:javascript复制this.$emit('自定义事件',数据) // 法一
解绑自定义事件
代码语言:javascript复制this.$off('自定义事件')
注意
- 若想事件只触发一次,可使用once修饰符,或者$once()方法
- 组件上也可以绑定原生DOM事件,但需要使用native修饰符
- this.refs.xxx.on('自定义事件',回调)绑定自定义事件时,回调函数要么配置在methods中,要么用箭头函数直接定义,否则会出现this指向问题!
应用场景
- 子组件===>>父组件 通信
全局事件总线
安装全局事件总线
代码语言:javascript复制new Vue({
...
beforeCreate(){
Vue.prototype.$bus=this // $bus就是当前应用的vm
}
})
使用全局事件总线
- 接收数据:组件想接收数据,则在组件中给$bus绑定自定义事件,事件的回调留在组件中
mounted(){
this.$bus.$on('事件',回调)
}
提供数据:
- this.bus.emit('事件',数据)
- 将数据作为实参传递给回调函数
- 最好在beforeDestory钩子中,用$off解绑当前组件所使用的所有事件
注意
- 回调函数可以写在methods中,直接写在mounted中记得用箭头函数
- this.bus.on注册事件,在回调中通过形参拿到数据,对数据进行处理
- this.bus.emit触发事件,将第二个参数作为实参(即数据)
- 在new Vue()中创建全局事件总线。
bus可以自定义,建议使用
bus规范
应用场景
- 任意组件之间通信
消息订阅与发布
- 原理和全局事件总线一样,建议使用事件总线,毕竟Vue出品
使用步骤
代码语言:javascript复制1.安装pubsub: npm i pubsub-js 2. 引入pubsub:import pubsub from ‘pubsub-js’ 3. 接收数据:A组件想接收数据,则在A组件中订阅消息,回调留在A组件自身
mounted(){
this.xxx=pubsub.subscribe('事件',回调)
}
4.提供数据:
pubsub.publish('事件',数据)
注意
- 记得在beforeDestory钩子中用
pubsub.unsubscribe(xxx)
取消订阅 回调函数可在methods中,直接写记得用箭头函数 第一个形参为订阅的消息名,第二个形参才是数据。 第一个形参不需要使用,常用_下划线占位
应用场景
- 任意组件间通信
vuex
定义
- 专门在Vue中实现集中式状态(数据)管理的一个插件
使用步骤
- 使用比较复杂,在这里就不做详解
应用场景
- 多个组件之间状态(数据)共享
- 多个组件依赖于同意状态
- 不同组件的行为需要变更同一状态