自定义指令directives
这是局部的,全局的需要Vue.
directives:{color:{ //自定义指令名bind:function(el,binding,vnode){ //bind是钩子函数el.style.color=binging.value...el:是当前元素,默认传进来的binding:是自定义指令的参数,一般在function中使用binding.value;可有可没有vnode:虚拟dom节点}
}<div v-color='red'>abc</color>
1. 钩子函数
- bind: 当每次将该自定义指令绑定时自动调用;且只调用一次。
- unbind:当元素与该指令解绑时
- inserted:当该元素被渲染到(添加到)html页面的Dom中时
- update:所有组件的vnode更新时 (vnode就是虚拟dom节点)
- componentUpdated:该组件的vnode及子vnode全部全部更新后调用
2.钩子函数的一些参数
el
:指令所绑定的元素,可以用来直接操作 DOM 。binding
:一个对象,包含以下属性:name
:指令名,不包括v-
前缀。binding.namevalue
:指令的绑定值,例如:<p v-my-directive="1 + 1">中,绑定值为2。binding.value。同时也可以<p v-my-directive="{id:1,text:'abc'}"> 这样的话传入的是一个对象,那么就是binding.value.text。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数(:),可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符(.)的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
3. 钩子函数能够动态定义指令的参数:arg
<p v-my-directives:[arg]='value'> example 注意引号是否存在 </p>data:{arg:'left',value:'200px'
}
4. 简写方法
上面的那个自定义指令例子是全写,简写指的是不注明钩子函数,此时默认绑定bind和update
directive:{ //指令:v-colorcolor:function(...){....} //默认绑定bind和update
}
Keep-alive
https://www.jianshu.com/p/9523bb439950
定义
在开发中经常有从列表跳到详情页,然后返回详情页的时候需要缓存列表页的状态(比如滚动位置信息),这个时候就需要保存状态,要缓存状态。
keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。总的来说,keep-alive用于保存组件的渲染状态。
<keep-alive><com1> 或者 <router-view>
</keep-alive>
属性 props
- include:定义缓存白名单
- exclude:定义缓存黑名单
- max:最多能缓存多少组件
- activated 和 deactivate 生命周期钩子
实现步骤
1.a,b,c会被keep-alive
<keep-alive include='a,b,c'><router-view>
</keep-alive>2.
<keep-alive><router-view v-if='this.$router.meta.keepAlive'></router-view>
</keep-alive>
同时给router设置meta:
export default{path:'',component:'',meta:{keepAlive:true}
}
- 第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;
- 第二步:根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;
- 第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;
- 第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key);
- 第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为true。
生命周期过程
设置了keep-alive的组件:
第一次进入: beforeRouteEnter => created => ... => activated => ... => deactivated
后续进入:beforeRouteEnter => activated => deactivated,
只有第一次进入该组件时,才会走created钩子,需要缓存的组件中activated时每次都会走的钩子函数
页面滚动问题
https://juejin.im/post/5b2ce07ce51d45588a7dbf76
在router实例里面添加scrollBehavior方法
const router=new VueRouter({routes:[{path:"/",component:Home}],scrollBehavior(to,form,savedPosition){if(savedPosition){点击按钮时返回的页面会滚动过到之前按钮return savedPosition}else{设置返回值来指定页面的滚动位置,表示在用户切换路由时让是所有页面都返回到顶部位置return {x:0,y:0}}}
})
如果是通过vue路由进行的页面切换。例如a前往b,首先判断a是不是通过keep-alive缓存的组件,如果是,则在a路由的meta中添加一个savedPosition字段,并且值为a的滚动位置。最后return的是页面需要回滚的位置。如此一来,如果打开一个页面,该页面的组件路由中meta.savedPosition为undefined的话,则页面滚动到(0,0)的位置。那么如果打开一个页面,它的路由的meta.savedPosition有值的话,则滚动到上次浏览的位置,因为meta.savedPosition保存的就是上次浏览的位置。
Vue实现双向绑定
1.首先背下来什么是MVVM
2.实现步骤
首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
并且实现一个订阅者管理器。这个管理器中有两个方法:添加订阅者方法和通知订阅者方法。还有一个target属性和一个subs数组。target是当前的订阅者。subs数组是订阅者数组。
下面的代码最好背下来。
function defineReactive(data, key, val) {observe(val); var dep = new Dep(); // 背下来:定义一个订阅器Object.defineProperty(data, key, {enumerable: true, // 背下来configurable: true, // 背下来get: function() { // 背下来if (dep.target) { // 背下来dep.addSub(dep.target); // // 背下来:在这里添加一个订阅者}return val;// 背下来},set: function(newVal) {if (val === newVal) {return;}val = newVal;dep.notify(); // // 背下来:如果数据变化,通知所有订阅者}});
}function Dep () {this.subs = [];
}
Dep.prototype = {addSub: function(sub) { // 背下来:添加订阅者this.subs.push(sub);},notify: function() {this.subs.forEach(function(sub) { // 背下来:通知更新订阅者sub.update();});}
};
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
$refs和ref
ref可以加到dom上也可以加到组件上,ref相当于name的作用。
<com ref='com'></com>
<div ref='div1'>div test</div>this.$refs.com
this.$refs.div1
- $refs是当前组件的ref的集合。但是this.$refs. 不能写道模板里<template>,因为他们不是响应式的
- 在created及beforecreated之前,$refs获取不到。
三大作用:
1. 父组件的vue js代码中获得子组件的data、method、computed
子组件.vue
data:{msg:'son'
},
methods:{test_son:function(){ ... }
}
父组件.vue
<com ref='com'></com>methods:{test_father:function(){this.$refs.com.test_son()console.log(this.$refs.com.msg)}
}
2.子组件获得父组件的方法:通过this.$emit('触发父组件methods的v-on名')
点击button
父组件.vue
<com ref='com' @test2='test_father'></com>
<button @test1='get_son'>click</button>methods:{get_son:function(){this.$refs.com.test_son()},test_father:function(){console.log('output father')}
}
子组件.vue
methods:{test_son:function(){this.$emit('test2')}
}
$ref 和 v-for一起使用:(ref=变量,前别忘记加:)
$refs 和 v-for一起用的时候比较特殊,$refs 会受到 v-if、v-for、v-show 这些语句的影响
<body>
<div id="app"><ul ><li v-for='item in array' :name='item.name' :ref='item.name'>{{item.name}}</li></ul><button @click='c1'>click</button>
</div><script>
var vm=new Vue({el:'#app',data:{array:[{key:1,name:'one'},{key:2,name:'two'}]},methods:{c1:function(){console.log(this.$refs.one)}}
})</script>
</body>
点击按钮之后输出:这个li莫名是个数组,获得不到每个li的name,所以这种写法不对
(这里注意一下,如果是:ref='item.key',下面就是undefined了。因为$refs只接收字符串)
下面这种写法,跟上面比就改了两句话
<body>
<div id="app"><ul ><li v-for='item in array' :name='item.name' :ref='item.tag'>{{item.name}}</li></ul><button @click='c1'>click</button>
</div><script>
var vm=new Vue({el:'#app',data:{array:[{key:1,name:'one',tag:'array'},{key:2,name:'two',tag:'array'}]},methods:{c1:function(){console.log(this.$refs.array[0])var q=this.$refs.array[0];console.log(q.getAttribute('name'))}}
})</script>
</body>
(这里有用模板匹配的方法,我没看:https://blog.csdn.net/LzzMandy/article/details/91372406)
$nextTick()
(和前面的event loop一起食用更加)
nextTick(function(){...}),是将其内部的回调函数function延迟在下一次dom更新数据后调用。同一事件循环DOM 更新会引起nextTick回调函数触发
<template><div class="hello"><div><button id="firstBtn" @click="testClick()" ref="aa">{{testMsg}}</button></div></div>
</template><script>
export default {name: 'HelloWorld',data () {return {testMsg:"原始值",}},methods:{testClick:function(){this.testMsg="修改后的值";this.$nextTick(function(){console.log(that.$refs.aa.innerText); //输出:“修改后的值”});//这里正常应该输出“原始值”}}
}
</script>
nextTick()的用处:
- Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳。
- 当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中
- 一些插件
nextTick()原理:
Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个event loo的数据变化的推送进这个队列。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你修改 vm.msg = '新值',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。
简单的流程:同步代码执行 -> 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1] ->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2]...
主线程完成同步环境执行,查询任务队列,提取队首的任务,放入主线程中执行;执行完毕,再重复该操作,该过程称为事件循环。而主线程的每次读取任务队列操作,是一个事件循环的开始。