计算属性和methods的区别
计算属性的特性就是,只要其依赖值不变,那么就不会重新计算,因为它会有一个缓存,第一次调用结束后,它会将结果进行一次缓存,当再次调用时,会直接取缓存中的值。- 而
methods中的方法每调用一次就会执行一次。
computed:{
reverseMsg:function(){
return this.message.split("").reverse().join("")
}
}
methods:{
reverseMessage:function(){
return this.message.split("").reverse().join("")
}
}
//第一种,js表达式,会计算三次
<p>{{message.split("").reverse().join("")}}<p>
<p>{{message.split("").reverse().join("")}}<p>
<p>{{message.split("").reverse().join("")}}<p>
//第二种,计算属性,计算一次
<p>{{reverseMsg}}<p>
<p>{{reverseMsg}}<p>
<p>{{reverseMsg}}<p>
//第三种,methods中的方法,计算三次
<p>{{reverseMessage()}}<p>
<p>{{reverseMessage()}}<p>
<p>{{reverseMessage()}}<p>
v-show和v-if的区别
v-show始终会被渲染并保存在DOM中,它只是简单的切换元素的display属性。v-if当后面为false,相应的元素及子元素不会被渲染,控制DOM元素的创建和销毁。v-show适合频繁切换状态时使用,而v-if适合运行时很少使用的时候使用v-if设置在div标签时,会显示一个div空标签,而子元素不会被显示,当设置在template上时,template不会显示。v-show不支持设置在template上。
v-for为什么要加key
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素。
- 首先我们先创建一个列表和一个按钮,按钮用于增加列表项,注意此时我们没有添加
key属性。
- 在页面中显示是这样的,点击后也成功在列表项前添加成功。
- 此时我们先将某一项选中后,再次点击增加,此时添加之后出现了一个问题,就是原本选中的是李四,而增加后选中的变为了张三,是因为它找不到它对应的列表项。
-
key的作用就是添加一个唯一的标识,这时就应该提到一下diff算法,虚拟DOM的diff算法会对比操作之前和操作之后的同一层的节点。当在同一层有很多相似的节点时,例如li标签,此时diff算法会进行如下操作: -
我们此时想在
B和C之间插入F。
diff算法会将C的位置更新成F,D的位置更新成C,E的位置更新成D,然后在末尾添加一个E。
- 但是这种方法很没有效率并且很消耗性能,此时我们就需要
key值给它们添加一个唯一标识,也可以理解为起一个名字,有key值后,就如第二个图一样,a对a,b对b,在c和d中间添加一个z。所以添加上key值方便我们快速找到节点,减少渲染次数,提升渲染性能。
v-model原理
v-model本质上是两个操作,第一个是通过v-bind绑定一个value属性,第二个是通过v-on给当前元素添加一个input事件。
- 首先我们先给input标签添加一个动态的value属性,此时可以实现修改msg时输入框的内容上改变。
<input type="text" :value="msg" >
- 其次我们给input标签绑定一个input事件,使得输入框内容改变时,修改msg的值,这样就实现了数据的双向绑定。
<input type="text" :value="msg" @input="changeMsg" >
组件中data为什么一定是一个函数,并且要返回一个对象
我们知道,函数是会有局部作用域的,它每一次执行这个函数返回出的对象都是一个全新的对象,而我们这个组件,又是个可复用的,也就是说复用多次的话,如果共享的是同一个数据的话,就会造成数据污染。
- 首先创建好一个content组件,组件里data内设置一个msg。
- 我们在另一个组件里调用三次,会显示如下结果。
- 在content组件中设置一个修改msg的点击事件,点击第一个按钮,显示如下结果。
- 可以看到只改变了第一个组件的msg值,因为每次调用content组建返回出的对象都是一个新的对象互不干扰。我们修改一下父组件,使其直接返回一个对象。
- 可以看到所有组件的msg值都发生了改变,因为我们直接返回了一个对象,三次调用组件调用的都是同一个对象,所以其中一个改变,所有的值都会改变。所以这就是data要是一个函数并且要返回一个对象的原因。
$router与$route
$router是一个全局的路由容器,里面包含着很多的路由对象;$route是$router容器中状态是active的那个路由对象。$route是当前活跃的路由对象,可以获取当前页面的path、params、query、name等信息。$router可以调用push、forward、go等方法,对页面进行操作。
vite/vue-cli通过proxy解决跨域问题
例如当从localhost:3000访问https://i.maoyan.com/api/mmdb/movie/v3/list/hot.json?ct=%E5%8C%97%E4%BA%AC&ci=1&channelId=4时,由于违反了同源策略造成跨域问题,所以我们可以配置一个代理服务器来请求到数据后返回。
vite
具体操作方法在vite.config.js中的export default中添加server字段。
export default defineConfig({
server:{//中转服务器
proxy:{//通过代理解决跨域
//https://i.maoyan.com
'/path':{
target:'https://i.maoyan.com',//替换的服务端地址
changeOrigin:true,//开启代理,允许跨域
rewrite:path=>path.replace(/^\/path/,'')//设置重写的路径
}
}
}
})
此时只需要访问时修改下路径即可。
axios.get('/path/api/mmdb/movie/v3/list/hot.json?ct=%E5%8C%97%E4%BA%AC&ci=1&channelId=4').then((res)=>{
console.log(res)
})
vue-cli
实现方法与vite类似,只不过某些字段名不同。
module.exports = {
devServer: {
// 代理配置
proxy: {
// 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
// localhost:8888/api/abc => 代理给另一个服务器
// 本地的前端 =》 本地的后端 =》 代理我们向另一个服务器发请求 (行得通)
// 本地的前端 =》 另外一个服务器发请求 (跨域 行不通)
'/api': {
target: 'www.baidu.com', // 我们要代理的地址
changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求
// 路径重写
pathRewrite: {
// 重新路由 localhost:8888/api/login => www.baidu.com/api/login
'^/api','' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做
}
},
}
}
}
v-for和v-if为什么不建议在一起使用
- 在
vue2中,v-for的优先级是高于v-if,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费; - 在
vue3中则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,就会导致异常。 - 建议如果有这种需求时,先使用计算属性对数据进行过滤,再使用
v-for进行循环遍历。
vue中如何使用自定义指令?
- 指令:
v-bind、v-if、v-for、v-model...不同的指令实现不同的功能。 - 局部注册:直接在对应的组件下的
options下定义属性directives,与data同级。
<script>
export default {
data(){
return {}
}
directives(){// 自定义指令,不需要写v-。
focus:{
inserted:function(el){// inserted表示被绑定元素插入父节点是调用,bind(第一次绑定时调用)、update...
// el表示指令所绑定的元素
// binding:对象,属性name:指令名,value:指令绑定值...
el.focus()// 获取焦点
}
}
}
}
</script>
<input type="text" v-focus><!-- 使用时需要写v-。 --!>
- 全局注册:在
main.js中定义Vue.directive
// Vue.directive("指令的名字", "对象数据也可以是一个指令函数")
Vue.directive("focus", {
inserted:function(el){// inserted表示被绑定元素插入父节点是调用,bind(第一次绑定时调用)、update...
// el表示指令所绑定的元素
// binding:对象,属性name:指令名,value:指令绑定值...
el.focus()// 获取焦点
}
})
vue中什么是$nextTick?
理解:是将回调函数延迟在下一次dom更新数据之后调用
<template>
<div>
<div ref="msg">{{ msg }}</div>
<p>{{ msg1 }}</p>
<button @click="changeMsg">改变msg</button>
</div>
</template>
<script>
export default {
data() {
msg:"helloworld",
msg1:""
}
methods: {
changeMsg: function(){
this.msg = "你好世界"
this.msg1 = this.$refs.msg.innerHTML
}
}
}
</script>
如果直接向上方这么写的话,在点击按钮后,msg1显示的仍是helloworld,因为vue是异步渲染的框架数据更新之后,dom是不会立刻渲染的。
此时有两种解决方式,一种是使用setTimeOut,另一种是使用this.$nextTick。$nextTick会在dom渲染之后被触发,用来获取最新的dom节点。
methods: {
changeMsg: function(){
this.msg = "你好世界"
setTimeOut(() => {
this.msg1 = this.$refs.msg.innerHTML
})
}
}
methods: {
changeMsg: function(){
this.msg = "你好世界"
this.$nextTick(() => {
this.msg1 = this.$refs.msg.innerHTML
})
}
}
可以看到此时msg1显示的是你好世界。
使用场景:
- 在生命周期钩子created中进行dom操作,一定要放到$nextTick函数中执行。
- 在数据变化后要执行某个操作,而这个操作需要使用随数据变化而变化的dom结构时,这个操作需要放到$nextTick中。