提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 2.2双向绑定的原理是什么
- vue框架采用的是数据双向绑定的方式,由三个重要部分构成
- 2.2.1.ViewModel
- 2.2.2 双向绑定
- 2.2.3.1.编译`Compile`
- 2.2.3.2.依赖收集
- 3.组件通信方式
- 3.1概念
- 3.2解决了什么
- 3.3分类
- 3.4方案
- 3.4.1.props
- 3.4.2.$emit 触发自定义事件
- 3.4.3.ref
- 3.4.4.EventBus
- 3.4.5.provide和inject
- 3.4.6.vuex
- 3.5.小结
- 4.data属性是一个函数和是一个对象的区别?
- 4.1.实例和组件定义data的区别
- 4.2.区别
- 4.3.结论
- 5.动态给vue的data添加一个新的属性时会发生什么?怎样解决
- 5.1.直接添加属性
- 5.2分析
- 5.3.解决方法
- 5.3.1.Vue.set()
- 5.3.2.Object.assign()
- 5.3.3.$forceUpdate
- 5.4.小结
- 6.v-if和v-for的优先级
- 6.1.作用
- 6.2.优先级
- 6.2.1.示例
- 6.3.注意事项
- 7.v-show和v-if区别?使用场景
- 7.1.共同点
- 7.2.区别
- 7.3.原理分析
- 7.3.1.v-show
- 7.3.2.v-if
- 7.4.使用场景
- 8.vue中的key
- 8.1.1.背后的事情
- 8.2.设置key与不设置key区别
- 8.2.1.设置key值一定能提高diff效率吗
- 9.常用的修饰符
- 9.1.定义
- 9.2.作用
- 9.2.1.表单修饰符
- 9.2.1.1.lazy
- 9.2.1.2.trim
- 9.2.1.3.number
- 9.2.2.事件修饰符
- 9.2.2.1.stop
- 9.2.2.2.prevent
- 9.2.2.3.self
- 9.2.2.4.once
- 9.2.2.5.capture
- 9.2.2.6.passive
- 9.2.2.7.native
- 10.2.3.鼠标按钮修饰符
- 10.2.4.键盘修饰符
- 10.2.5.v-bind修饰符
- 10.2.5.1.async
- 10.2.5.2.props
2.2双向绑定的原理是什么
vue框架采用的是数据双向绑定的方式,由三个重要部分构成
- 数据层(Model):应用的数据及业务逻辑
- 视图层(View):应用的展示效果,ui组件
- 业务处理层(ViewModel):框架封装的核心,负责将数据与视图关联起来,这个分层的方案,有一个专业术语
MVVM
这里控制的核心功能便是数据双向绑定
。
2.2.1.ViewModel
主要职责:
- 🍕🍕🍕数据变化后更新视图
- 视图变化后更新数据
还有两个主要部分 - 监听器(Observer):对所有数据的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
2.2.2 双向绑定
vue中的双向绑定的流程:
- 🚜🚜🚜new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observe中
- 对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,发生在Compile中
- 定义一个更新函数和Watcher,将来对应数据变化时
Watcher
会调用更新函数 - 由于
data
的某个key在一个视图中可能出现多次,所有每个key都需要一个管家Dep来管理多个Watcher
- 将来data中数据一旦发生变化,会首先找到对应的
Dep
,通知所有Watcher
执行更新函数
http://cmsadm.caidao8.com/electricityTax/showInstruction.hx?uuid=5c81c7c0e8ba4a078d4c32e811fab932
2.2.3.1.编译Compile
- 🤣🤣🤣对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
2.2.3.2.依赖收集
data
中的某个key
,同一个key
可能出现多次,每次都需要收集出来用一个watcher
来维护他们,此过程称为依赖收集,多个watcher
需要一个Dep
来管理,需要更新时由Dep
统一通知
3.组件通信方式
3.1概念
- 🧨🧨🧨组件
- 通信
组件是框架最强大的功能之一,每一个.vue结尾的文件都可以称为一个组件。
通信指的是发送者通过某种格式来传递信息到接收者以达到某个目的。
广义上,任何信息的传递都是组件间通信。通过一种方式来传递信息以达到某个目的。
3.2解决了什么
🚕🚕🚕通信的本质是信息同步共享,框架中,每个组件之间都有独自的作用域,组件之间的数据是无法共享的。但由于开发中需要让组件之间共享数据,这样通过组件通信就可以实现同步。
3.3分类
- 父子组件之间
- 兄弟组件之间
- 祖孙与后代
- 非关系组件之间
3.4方案
🍕🍕🍕常规通信方案
- props
- $emit触发自定义事件
- ref
- EventBus
- Provide与Inject
- Vuex
3.4.1.props
- 适用场景:父->子
- 子组件设置
props
属性,用来接收父组件传递的参数 - 父组件在使用子组件标签中通过字面量来传递值
Children.vue
props:{//字符串形式name:String,age:{type:Number,default:19,require:true }
}
Father.vue
<Children name='' age = 19 />
3.4.2.$emit 触发自定义事件
- 适用场景:子组件传递给父组件
- 子组件通过$emit触发自定义事件,第二个参数为传递的数据
- 父组件绑定监听器获取到子组件传递过来的数据
Children.vue
methods:{sendData(){this.$emit('add',info) }
}
Father.vue
<Children @add="children($event)" />
3.4.3.ref
- 🌮🌮🌮父组件在使用子组件的时候设置
ref
- 父组件通过设置子组件
ref
来获取数据
父组件
<Children ref="foo" />
//获取子组件实例方法,就能获取到对应数据
this.$refs.foo
3.4.4.EventBus
- 🚜🚜🚜兄弟组件传值
- 创建一个中央事件总线EventBus
- 兄弟组件通过
$emit
触发自定义事件,$emit
第二个参数为传递的数值 - 另一个兄弟组件通过
$on
监听自定义事件
//main.js
Vue.prototype.$bus = new Vue()
Children1.vue
this.$bus.$emit('foo')
Children2.vue
this.$bus.$on('foo',this.nameData)
3.4.5.provide和inject
- 在祖先组件定义
provide
属性,返回传递的值 - 在后代组件通过
inject
接收组件传递过来的值
祖先组件
provide(){return{foo:'foo' }
}
后代组件
inject:['foo'] //获取到祖先组件传递过来的值
3.4.6.vuex
- 🥟🥟🥟复杂关系的组件数据传递
- vuex相当于一个用来存储共享变量的容器
state
用来存放共享变量getter
,可以增加一个getter
派生状态,(相当于store
中的计算属性),用来获得共享变量的值mutations
用来存放修改state
的方法actions
也是用来存放修改state
的方法,不过action
是在mutations
的基础上进行。常用来做一些异步操作
3.5.小结
- 🌭🌭🌭父子关系选择
props
或ref
- 🥟🥟🥟兄弟关系选择
$bus
- 🌮🌮🌮祖先与后代选择
Provide
与Inject
- 🍕🍕🍕复杂关系选择
vuex
存放共享的数据变量
4.data属性是一个函数和是一个对象的区别?
4.1.实例和组件定义data的区别
vue
实例的时候定义data
属性即可以是一个对象,也可以是一个函数
const app = new Vue({el:"#app",//对象格式data:{foo:"foo" },//函数格式data(){return{foo:"foo" } }
})
组件中定义data
属性,只能是一个函数
如果定义为一个对象
Vue.component('component1',{template:`<div>组件</div>`,data:{foo:'foo' }
})
则会得到警告信息,返回的data
应该是一个函数
4.2.区别
function Component(){}
Component.prototype.data = {count:0
}
创建两个组件实例
const componentA = new Component()
const componentB = new Component()
//修改`componentA`组件`data`属性的值,`componentB`中的值也发生了变化
console.log(componentB.data.count)//0
componentA.data.count = 1
console.log(componentB.data.count)//1
//因为共用了一个内存地址
采用函数的形式,则不会有影响(函数返回的对象内存地址并不相同)
function Component(){this.data = this.data()
}
Component.prototype.data = function(){return {count:0 }
}
//修改`componentA`组件`data`属性的值,`componentB`中的值不受影响
console.log(componentB.data.count)//0
componentA.data.count = 1
console.log(componentB.data.count)//1
4.3.结论
- 🍔🍔🍔根实例对象
data
可以是对象也可以是函数(根实例时单例),不会产生数据污染情况。 - 组件实例对象
data
必须是函数,目的是为防止多个组件实例对象之间共用一个data
,会产生数据污染。采用函数的形式,initData
时会将其作为工厂函数都会返回全新的data
对象
5.动态给vue的data添加一个新的属性时会发生什么?怎样解决
5.1.直接添加属性
从一个例子开始,定义一个p标签,通过v-for遍历
然后,给button绑定点击事件,预期点击按钮时,数据新增一个属性,界面新增一行
<p v-for="(value,key) in item" :key="key">{{value}}</p>
<button @click="addProperty">动态添加新属性</button>
实例化一个实例,定义data属性和methods方法
const app = new Vue({el:"#app",data:()=>{item:{oldProperty:"旧属性" } },methods:{addProperty(){this.item.newProperty = "新属性" //添加console.log(this.item);//输出 }}
})
触发按钮,发现数据更新了,但页面没有更新
5.2分析
vue2
是用Object.defineProperty
实现的数据响应式
const obj = {};
Object.defineProperty(obj,'foo',{get(){console.log(`get foo:${val}`)return val;},set(newVal){if(newVal !== val){console.log(`set foo:${newVal}`)val = newVal }}
})
🍕🍕🍕当访问foo
属性或者设置foo
值的时候都能够触发setter
和getter
obj.foo
obj.foo = 'new'
🍔🍔🍔当为obj
添加新属性时,却无法触发事件属性的拦截
obj.newPro = '新属性'
🍟🍟🍟一开始obj
的foo
属性被设定成响应式数据,而newPro
是后面新增的属性,并没有通过Object.defineProperty
设置成响应式数据
5.3.解决方法
🍳Vue
不允许在已经创建的实例上动态添加新的响应式属性,若想实现数据与视图同步更新,以下三种方式
- Vue.set()
- Object.assign()
- $forceUpdated()
5.3.1.Vue.set()
🧀Vue.set(target,propertyName/index,value)
- {Object | Array} target
- { string | number } propertyName/index
- { any } value
🧨返回值:设置的值
🌮通过Vue.set
向响应式对象中添加一个property
,并确定这个新property
同样是响应式的,且触发视图更新。其内部是调用defineReactive
方法,通过Object.difineProperty
实现属性拦截
5.3.2.Object.assign()
🍿直接使用Object.assign()
添加到对象的新属性不会触发更新,应创建一个新的对象,合并原对象和混入对象的属性
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2})
5.3.3.$forceUpdate
🚓如果需要在vue中做一次强制更新,99.9%的情况,是在某个地方做了错误的写法,迫使实例重新渲染。仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
5.4.小结
- 如果添加少量的新属性,可以采用Vue.set()
- 如果添加大量的新属性,采用
Object.assign()
创建新对象 - (不建议)比较粗暴的方式
$forceUpdate()
强制刷新
🌭vue3
用proxy
实现的数据响应式,添加新属性时可以实现数据响应式
6.v-if和v-for的优先级
6.1.作用
🐱🏍v-if
指令用于条件性地渲染一块内容。指令表达式返回true
时被渲染
v-for
指令基于一个数组来渲染一个列表。需要使用item in data
形式的特殊语法,其中data
是源数据,而item
是被迭代的数组元素的别名
🚗在v-for
时,建议设置key
值,并且保证每个key
值是独一无二的,这便于diff
算法进行优化
6.2.优先级
🍖v-if
与v-for
都是vue
模板系统中的一个指令
在模板编译时,会将指令系统转化成可执行的render
函数
6.2.1.示例
<div id="app"><p v-if="isShow" v-for="item in items">{{item.title}}</p>
</div>
🍟创建vue
实例,存放isShow
与items
数据
const app = new Vue({el:"#app",data(){return{items:[{title:"foo"},{title:"更多"} ] } },computed:{isShow(){return this.items && this.items.length > 0 } }
})
🧇模板指令的代码都会生成在render
函数中,通过app.$options.render
就能得到渲染函数
f anonymous(){with(this){return _c('div',{attrs:{"id":"app"}},_l((items),function(item){return (isShow) ? _c("p",[_v("\n" + _s(item.title) + "\n")]) : _e() }),0)}
}
_l是vue
的列表渲染函数,函数内部都会进行一次if
判断
初步得到结论:v-for
优先级是比v-if
高
🍖再将v-for
与v-if
放到不同标签上
<div id="app"><template v-if="isShow"><p v-for="item in listData" :key="item.id">{{}}</p> </template>
</div>
再输出render
函数
f anonymous(){with(this){return _c('div',{attrs:{"id":"app"}},[(isShow)?[_v("\n"),_l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
}
当作用在不同标签上时,是先进行判断,再进行列表的渲染。
在进行if
判断时,v-for
是比v-if
先进行判断
最终结论:v-for
优先级高于v-if
6.3.注意事项
🥞两个指令不要放在同一个元素上,带来性能方面的浪费。
外层嵌套template
(页面渲染不生成dom
节点),进行判断,在内部v-for
循环
如果条件出现在内部,通过计算属性computed
提前过滤掉不需要显式的项
computed:{listdata:function(){return this.list.filter(function(item){return item.isShow }) }
}
7.v-show和v-if区别?使用场景
7.1.共同点
🌮都能控制元素是否显式
7.2.区别
- 控制手段不同
- 编译过程不同
- 编译条件不同
🍿控制手段不同:v-show
隐藏是为元素添加样式隐藏display:none
,dom元素依旧还在。v-if
显式隐藏是将dom
整个添加或删除
🧇编译过程不同:v-if
切换有有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件。v-show
只是简单的基于css切换
🧀编译条件:v-if
是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染。
🌭性能消耗:v-if
有更高的切换消耗;v-show
有更高的初始渲染消耗;
7.3.原理分析
大概过程
- 将模板
template
转为ast
结构的js
对象 - 用
ast
得到的js
对象拼装render
和staticRenderFns
函数 render
和staticRenderFns
函数被调用后生成虚拟VNODE
节点,该节点包含创建DOM
节点所需信息vm.patch
函数通过虚拟DOM
算法利用VNODE
节点创建真实DOM
7.3.1.v-show
🚓不管初始条件是什么,元素总是会被渲染
7.3.2.v-if
🥙v-if
在实现上比v-show
要复杂,因为还有else
else-if
等条件需要处理
7.4.使用场景
v-if
相比v-show
开销更大,如果需要频繁切换,使用v-show
较合适,如果在运行时条件很少改变,使用v-if
较合适
8.vue中的key
🚓在使用v-for
时,需要给单元加上key
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确,更快的找到对应的vnode节点
8.1.1.背后的事情
- 🎉如果不用key,Vue会采用就地原则,最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse
- 🍟如果使用key,Vue会根据keys的顺序记录element,曾经拥有了key的element 如果不再出现的话,会被直接remove或destoryed
- 用+new Date()生成的时间戳作为key,手动强制触发重新渲染
- 当拥有新值的
render
作为key时,拥有了新key的comp出现了,那么旧key Comp会被移除,新 key Comp触发渲染
8.2.设置key与不设置key区别
🧨创建一个实例,2秒后往items
插入数据
<body><div id="app"><p v-for="item in items" :key="item">{{item}}</p> </div><script>const app = new Vue({el:'#app',data:{items:['a','b','c','d']},mounted(){setTimeout(()=>{this.itmes.splice(2,0,'f')},2000) } }) </script>
</body>
🥪在不使用key
时,
8.2.1.设置key值一定能提高diff效率吗
- 🧀文档明确表示
当用v-for
正在更新已渲染过的元素列表时,它默认用"就地复用"策略。如果数据项的顺序被改变,vue将不会移动dom元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已渲染过的每个元素。 - 🍗此默认的模式是高效的,只适用于不依赖子组件状态或临时dom状态(例如:表单输入值)的列表渲染输出
- 🌭
9.常用的修饰符
9.1.定义
- 🌮是用于限定类型以及类型成员的声明的一种符号
处理了许多DOM
事件的细节,不再需要花时间去处理零碎的事情,而能有更多的精力专注于程序的逻辑处理。
分为以下五种: - 表单修饰符
- 事件修饰符
- 鼠标按键修饰符
- 键值修饰符
- v-bind修饰符
9.2.作用
9.2.1.表单修饰符
🌭在填写表单时用的最多的是input标签,指令用的最多的是v-model
,关于表单的修饰符有
- lazy
- trim
- number
9.2.1.1.lazy
🍿在填写完信息后,光标离开标签时,才会将值赋予给value
,也就是在change
事件之后之后再进行信息同步
<input type="text" v-model.lazy="value" />
<p>{{value}}</p>
9.2.1.2.trim
🧀自动过滤用户输入的首空格字符,而中间的空格不会过滤
<input type="text" v-model.trim="value" />
9.2.1.3.number
🍗自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat
解析,则会返回原来的值
<input v-model.number="age" type="number">
9.2.2.事件修饰符
🥪是对事件捕获以及目标进行了处理,有如下
- stop
- prevent
- self
- once
- capture
- passive
- native
9.2.2.1.stop
🧨阻止事件冒泡,相当于调用event.stopPropagation
方法
<div @click="shout(2)"><button @click.stop="shout(1)">ok</button>
</div>
9.2.2.2.prevent
🚗阻止默认行为,相当于event.preventDefault
方法
<form v-on:submit.prevent="onSubmit"></form>
9.2.2.3.self
🎉只当在event.target
是当前元素自身时触发处理函数
<div v-on:click.self="">...</div>
🍕顺序很重要;相应的代码会以同样的顺序产生。用v-on:click.prevent.self
会阻止所有的点击。而v-on:click.self.prevent
只会阻止对元素自身的点击
9.2.2.4.once
🚓绑定了事件,只能触发一次,第二次就不会触发
<button @click.once="add(1)">ok</button>
9.2.2.5.capture
🍚使事件触发从包含这个元素的顶层开始往下触发
<div @click.capture="add(1)">
obj1
<div @click.capture="add(2)">
obj2
<div @click="add(3)">
obj3
<div @click="add(4)">
obj4</div>
</div>
</div>
</div>
9.2.2.6.passive
🍔在移动端,当监听元素滚动事件时,会一直触发onscroll
事件会让网页变卡,因此使用时,相当于给onscroll
事件加了一个.lazy
修饰符
//滚动事件的默认行为将会立即触发,而不会等待`onScroll`完成,其中包括`event.preventDefault()`的情况
<div v-on:scroll.passive="onScroll"></div>
🥫 .passive
和prevent
不能一起使用,因为.prevent
将会被忽略,同时浏览器会展示一个警告
9.2.2.7.native
🥞让组件变成像html
内置标签那样监听根元素的原生事件,否则组件上使用v-on
只会监听自定义事件
<chrildren-component v-on:click.native="add" />
注意:使用native
修饰符来操作普通html标签是会令事件失效。
10.2.3.鼠标按钮修饰符
🍔鼠标按钮修饰符针对的就是左键、右键、中键点击
- left左键
- middle中键
- right右键
<button @click.left="add(1)">ok</button>
<button @click.right="add(2)"></button>
10.2.4.键盘修饰符
🍚键盘事件(onkeyup,onkeydown
),keyCode
有很多,但vue
为我们提供了很多别名,分为以下两种
- 普通键(
enter、tab、delete、space、esc、
) - 系统修饰键(
ctrl、alt、meta、shift...
)
//只有按键为keyCode的时候才触发
<input type="text" @keyup.keyCode="add()">
还可以通过以下方式自定义一些全局的键盘吗别名
Vue.config.keyCodes.f2 = 113
10.2.5.v-bind修饰符
🚓为属性进行操作,
- async
- prop
- camel
10.2.5.1.async
🥪能对props
进行一个双向绑定
//父组件
<component :myMessage.sync="bar"></component>
//子组件
this.$emit('update:myMessage',params)
以上方式相当于以下的简写
<component :myMessage="bar" @update:myMessage="add"></component>
<script>
add(e){this.bar = e
}
</script>
<!--子组件-->
<script>
func(){this.$emit('update:myMessage',data)
}
</script>
🍞使用sync
需要注意以下:
- 使用
async
的时候,子组件传递的事件名称格式必须为update:value
,其中value
必须与子组件中props
中声明的名称完全一致 - 注意带有
.async
修饰符的v-bind
不能和表达式一起使用 - 将
v-bind.sync
用在一个字面量的对象上,例如v-bind.sync="{title:doc.title}"
,是无法正常工作的。
10.2.5.2.props
🍿设置自定义标签属性,避免暴露数据,防止污染HTML结构
<input id="uid" title="title1" value="1" :index.prop="index">