🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来Vue篇专栏内容:vue常用特性
目录
[1.1 自定义指令](#1.1 自定义指令)
[5.2 计算属性 - computed](#5.2 计算属性 - computed)
[5.3 监听器 - watch](#5.3 监听器 - watch)
[5.5 过滤器 - filters](#5.5 过滤器 - filters)
[5.6 混入 - mixins](#5.6 混入 - mixins)
[5.7 生命周期](#5.7 生命周期)
[5.8 虚拟DOM与diff算法](#5.8 虚拟DOM与diff算法)
1.1 自定义指令
除了核心功能默认内置的指令,Vue也允许注册自定义指令。有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作。
自定义指令分为: 全局 指令和 局部 指令,当全局指令和局部指令同名时以局部指令为准。
自定义指令常用钩子函数有:
-
bind:在指令第一次绑定到元素时调用
-
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
-
update:数据更新时调用
请注意:不管在定义全局还是局部自定义指令时,所提及的指令名均是不带 v- 前缀的名称
全局自定义指令定义
javascript
// 无参(v-once/v-cloak)
Vue.directive('指令名',{
钩子函数名: function(el[,....]){
// 业务逻辑
// el参数是挂载到元素的DOM对象
}
}
// 传参(v-text/v-html/v-model) v-model="username"
Vue.directive('指令名',{
钩子函数名: function(el,binding[,....]){
let param = binding.value // param 其实就时 username 的值
// 业务逻辑
},
....
}
全局自定义指令(后续的知识点也是的)不能写在Vue实例中(或者某个组件中)
自动获取焦点的指令
javascript
<body>
<div id="app">
<input type="text" v-focus>
</div>
</body>
<script src="lib/vue.js"></script>
<script>
// 全局自定义指令 -- new Vue之前
// Vue.directive('focus', {
// inserted (element) {
// element.focus()
// }
// });
new Vue({
el: '#app',
data: {},
directives: { // 局部自定义指令
'focus': {
inserted (element) {
element.focus()
}
}
}
})
</script>
局部自定义指令定义
可以在 new Vue 的时候添加 directives 以注册局部自定义指令,局部自定义指令只能在当前组件中使用:
javascript
directives: {
指令名: {
// 指令的定义
钩子函数名: function (el,binding) {
// 业务逻辑
}
}
}
函数简写(重点)
在很多时候,我们可能想在 bind 和 update 时触发相同行为(如果只是其一,则还是单独分开声明),而不关心其它的钩子。那么这样写:
javascript
// 全局
Vue.directive('指令名', function (el,binding) {
// 业务逻辑
})
// 局部
directives: {
指令名: function (el,binding) {
// 业务逻辑
}
}
在自定义指令的方法中,不能像以前的 methods 中的方法一样使用关键词 this ,此时 this关键词指向的是 Window 对象。
案例:使用自定义指令实现以下效果
-
使用全局指令定义自定义的 v-red(不传参) 和 v-color(传参)
-
使用局部自定义指令实现 v-mobile(不传参) 验证用户输入的是否是合法的手机号
javascript
<body>
<div id="app">
<div v-red>哈哈哈哈</div>
<div v-color="'green'">嘻嘻</div>
<input type="text" v-mobile v-model="phone">
</div>
</body>
<script src="lib/vue.js"></script>
<script>
// 使用全局指令定义自定义的 v-red(不传参) 和 v-color(传参)
Vue.directive('red', {
inserted (el) {
el.style.color = 'red'
}
})
Vue.directive('color', {
inserted (el, binding) {
el.style.color = binding.value // binding.value 传递过来的值
}
})
new Vue({
el: '#app',
data: {
phone: ''
},
// 使用局部自定义指令实现 v-mobile(不传参) 验证用户输入的是否是合法的手机号
directives: {
mobile: {
update (el) {
console.log(el.value)
if (!(/^1[3-9]\d{9}$/).test(el.value)) {
el.style.color = "red"
} else {
el.style.color = "green"
}
}
}
}
})
</script>
1.2 计算属性 - computed
模板中放入太多的逻辑会让模板过重且难以维护,使用计算属性可以让模板变得简洁易于维护。计算属性是基于它们的响应式依赖进行缓存的,计算属性比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。
计算属性定义在Vue对象中,通过关键词 computed 属性对象中定义一个个函数,并返回一个值,使用计算属性时和 data 中的数据使用方式一致。
任何复杂的业务逻辑,我们都应当使用计算属性 - 计算属性具有依赖性,只有依赖的值发生改变,才会重新计算
示例
javascript
<body>
<div id="app">
<h1>事件</h1>
{{ addNum() }}
{{ addNum() }}
<h1>计算属性</h1>
{{ numcom }}
{{ numcom }}
{{ numcom }}
</div>
</body>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
num: 0
},
computed: {
numcom () {
console.log('计算属性')
return this.num + 100
}
},
methods: {
addNum () {
console.log('加1')
}
}
})
</script>
<div id="app">
<!-- 当多次调用 cfn计算属性时只要里面的 num值不改变,它会把第一次计算的结果直接返回直 到data中的num值改变 计算属性才会重新发生计算 -->
<div>{{ cfn }}</div>
<div>{{ cfn }}</div>
<!-- 调用methods中的方法的时候 他每次会重新调用 -->
<div>{{ fn() }}</div>
<div>{{ fn() }}</div>
</div>
<script src="lib/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: "#app",
data: {
num: 10
},
// 方法
methods: {
fn() {
console.log("methods");
return this.num;
}
},
// 计算属性
computed: {
cfn() {
console.log("computed");
return this.num;
}
}
})
</script>
**注意:**只要依赖的数据源不发生改变,计算属性里的对应方法就只被调用1次,其它时候被调用时则使用缓存。
1.3 监听器 - watch
使用watch来侦听data中数据的变化,watch中的属性一定是data 中已经存在的数据。
**使用场景:**数据变化时执行异步或开销比较大的操作。
典型应用: 在线汉字转拼音
参考代码:
html
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> =
{{ fullName }}
</div>
</body>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
firstName: '',
lastName: '',
fullName: ''
},
watch: {
firstName (newVal, oldVal) {
this.fullName = newVal + this.lastName
},
lastName (newVal, oldVal) {
this.fullName = this.firstName + newVal
}
}
})
</script>
<body>
<div id="app">
<input type="text" v-model="firstName"> +
<input type="text" v-model="lastName"> =
{{ fullName }}
</div>
</body>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
firstName: '',
lastName: ''
},
computed: { // 计算属性优于侦听属性
fullName () {
return this.firstName + this.lastName
}
}
})
</script>
注意点:
声明监听器,使用的关键词是 watch
每个监听器的方法,可以接受2个参数,第一个参数是新的值,第二个参数是之前的值
**注意:**当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听。
使用对象的数据形式改写上述案例参考代码:
html
<body>
<div id="app">
<input type="text" v-model="userInfo.firstName"> +
<input type="text" v-model="userInfo.lastName"> =
{{ userInfo.fullName }}
</div>
</body>
<script src="lib/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
userInfo: {
firstName: '',
lastName: '',
fullName: ''
}
},
watch: {
// 为什么要 oldVal,路由 /home -> /detail/1,监听路由变化
// 通过对象属性侦听
// 'userInfo.firstName': function (newVal, oldVal) {
// this.userInfo.fullName = newVal + this.userInfo.lastName
// },
// 'userInfo.lastName': function (newVal, oldVal) {
// this.userInfo.fullName = this.userInfo.firstName + newVal
// }
userInfo: {
handler (val) {
this.userInfo.fullName = val.firstName + val.lastName
},
deep: true
}
}
})
</script>
1.4 过滤器 - filters
**作用:**格式化数据,比如将字符串格式化为首字母大写、将日期格式化为指定的格式等。
-
过滤器可以定义成全局过滤器和局部过滤器。
-
过滤器的本质就是一个方法,使用过滤器实际上就相当于方法调用,仅是书写形式上的差异(使用的时候需要用"|",其也可以被称之为 管道 或 变量/数据修饰符 )
声明语法:
javascript
// 全局过滤器
Vue.filter('过滤器名称',function(value[,arg1,arg2...]){
//过滤器业务逻辑
return ....
})
// 局部过滤器
el: '#app',
data: {},
filters: {
过滤器名称: function(value[,arg1,arg2...]){
return something
},
....
}
使用语法:
javascript
<!-- 过滤器使用 -->
<div>{{msg | upper}}</div>
<!-- 过滤器允许连续使用,"前 → 后"按顺序执行 -->
<div>{{msg | upper | lower}}</div>
<!-- 过滤器支持在v-bind中使用 -->
<div v-bind:id='id | formatId'></div>
<!-- 过滤器支持传参 -->
<div>{{msg | mysub(1,2)}}</div>
案例:声明转字母为大写的全局过滤器和转字母为小写的局部过滤器并使用
javascript
<body>
<div id="app">
<div>{{ msg | toUpper }}</div>
<div>{{ msg | toLower }}</div>
<div>{{ sex | sexFilter }}</div>
</div>
</body>
<script src="lib/vue.js"></script>
<script>
// 全局大写过滤器
Vue.filter('toUpper', (val) => {
return val.toUpperCase()
})
Vue.filter('sexFilter', (val) => {
return val === 1 ? '男' : '女'
})
new Vue({
el: '#app',
data: {
msg: 'Hello World',
sex: 1
},
// 局部小写过滤器
filters: {
'toLower': (val) => {
return val.toLowerCase()
}
}
})
</script>
1.5 混入 - mixins
混入(mixins)是一种分发Vue组件中可复用 功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象(加的水)的选项将被混入该组件本身的选项(锅底)。
混入分为全局混入和局部混入。
示例:
- 局部混入(按需混入)
javascript
<script src="lib/vue.js"></script>
<script type="text/javascript">
// 定义一个混入对象(局部混入)
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log("hello from mixin!")
}
},
};
// Vue实例
const vm = new Vue({
mixins: [myMixin]
});
</script>
- 全局混入(强制混入)
javascript
<script src="lib/vue.js"></script>
<script type="text/javascript">
// 全局混入
Vue.mixin({
created: function () {
var myOption = this.myOption;
if (myOption) {
console.log(myOption)
}
}
});
new Vue({
data: {
myOption: "hello!"
}
})
</script>
<body>
<div id="app">
<button @click="test">测试</button>{{ reverseMsg }}
</div>
</body>
<script src="lib/vue.js"></script>
<script>
// 全局混入 --- 强制加载
Vue.mixin({
methods: {
test () {
console.log(this.msg)
}
}
});
var myMixins = {
data: {
msg: '456'
},
computed: {
reverseMsg () {
return this.msg.split('').reverse().join('')
}
},
mounted () {// --- 生命周期的钩子函数
console.log('mixin')
}
}
new Vue({
el: '#app',
data: {
msg: '123'
},
mounted () {
console.log('com')
},
// 局部混入 - 按需加载
mixins: [myMixins]
})
</script>
注意事项
-
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行"合并",合并策略:
-
data 数据对象发生冲突时以组件数据优先
-
同名钩子函数将合并为一个数组,都将被调用,并且混入对象的钩子将在组件自身钩子之前调用
-
值为对象的选项,例如 methods 、 components 和 directives ,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
-
-
全局注册使用时需要格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例
1.6 生命周期
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会。
Vue生命周期的主要阶段:4个before, 4个ed,创建,装载,更新,销毁
-
挂载(初始化相关属性)
-
beforeCreate ---- 备孕
注意点:在此时不能获取data中的数据,也就是说 this.msg 得到的是
-
created ---- 怀上了
-
beforeMount ---- 怀胎十月
-
mounted【页面加载完毕的时候就是此时】 ---- 生下来了
注意点:默认情况下,在组件的生命周期中只会触发一次
-
-
更新(元素或组件的变更操作)
-
beforeUpdate
-
updated
注意点:可以重复触发的
-
-
销毁(销毁相关属性)
-
beforeDestroy --- game over前
-
destroyed --- game over
-
销毁(手动)使用 this.$destroy()
关于8个生命周期涉及到的方法,可以参考Vue官网API:
1.7 虚拟DOM与diff算法
什么是虚拟DOM?
什么是diff(different)算法?
差异比较算法的一种,把树形结构按照层级分解,只比较同级元素。不同层级的节点只有创建和删除操作
虚拟DOM+diff算法 的方式与 传统DOM操作 相比,有什么好处?
传统DOM操作:在一次操作中,往往会伴随多个DOM节点更新,浏览器收到第一个DOM请求后并不知道还有若干次更新操作,因此会马上执行流程,最终执行若干次。在后续找DOM坐标的时候,可能因为前期更新DOM导致了后续需要寻找的DOM坐标发生了变化。而操作DOM频繁还会出现页面卡顿,影响用户体验。
虚拟 DOM+diff算法**:若一次操作中有若干次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这若干次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象**一次性放到DOM树上,再进行后续操作,避免大量无谓的计算量。