1. 在vue模板渲染中,当为某个元素绑定方法时,那么模板每次渲染的时候,这个方法都会被执行一次,因为method没有缓存。不同的是,computed计算属性具缓存功能,即当你为元素绑定的是计算属性的时候,由于计算属性具有缓存功能,因此模板每次渲染的时候,不会重新去执行这个计算属性,而是找到这个计算属性的缓存之,当且计算属性依赖的属性发生变化的时候,才会重新去执行这个属性。
2.由于计算属性的返回值是通过已有属性计算并return的,因此因为这个特别性质,计算属性是不可以进行一些异步操作的。如下:
3. computed能完成的,watch都可以完成
4. watch能完成的,computed不一定能完成,例如,watch能进行异步操作
每一个计算属性都具有两个方法,get()与set(),详细写法如下:
computed: { fullName: { get(){ console.log('get被调用了') return this.firstName + '-' + this.lastName; }, set(value){ console.log('set被调用了'); const str=value; str=value.split('-') this.firstName=str[0]; this.lastName=str[1] } } }
当你所用到的计算数据只考虑读取,不考虑修改的情况,那么可以使用简写方式:
computed:{ fullName(){ console.log('get被调用了') return this.firstName + '-' + this.lastName; } }
那么,在计算属性中,get与set分别在什么时候开始调用呢?
(1)初次读取数据时会执行一次(因为具有缓存)
(2)当依赖的数据发生变化时会被再次调用
4. 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试更加方便
5.备注:
(1)计算属性最终会出现在vm上,直接读取即可
(2)如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起计算时依赖的数据发生变化
(1)new Vue时传入的watch配置
(2)通过vm.$watch监视
watch:{ isHot:{ immediate:true, //handler什么时候调用?即是isHot发生改变的时候 handler(newValue,oldValue){ //这里通常可以根据新旧值之间的变化而执行有些操作 console.log('isHot被修改了,新值为:'+newvalue+'旧值为:'+oldvalue) } } }
ps:
深度监听是可以监听对象内部值的变化
备注:Vue自身是可以监听对象内部值的改变,但是Vue提供的watch默认不可以
使用watch时,根据数据的具体结构,决定是否采用深度监听
注意 :watch的监听建立在已经存在的属性上,注意下面错误示范
错误方式①:
data: { numbers: { a: 1, b: 2 } } watch: { a: { handler(){ console.log('a被改变了') } } }
错误方式②
因此,正确的方式是,为该属性开启深度监听
还要要注意一个问题是,当使用简写的方式进行属性监听的时候,只有该属性不需要配置其他属性时,比如深度监听或者初始化时监听
首先按官网介绍的,可以主要用在Vue的虚拟Dom算法,在新旧节点对比时辨识VNodes。使用key时,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。
有相同父元素的子元素必须有独特的key,重复的key会造成渲染错误。
vue中循环列表时,绑定:key的值往往有两种方式,一是将循环列表的索引值index作为key,而是根据返回数据中的唯一标识作为key。不管以什么方式绑定key,key的值在列表中必须是唯一的,可以使用index的原因是索引值必然是唯一的。、
以下举例子说明以index或者唯一标识符绑定key的时的执行流程
1.比如有初始数据如下:
{id:'001',name:'张三',age:'18'}, {id:'002',name:'李四',age:'19'}, {id:'003',name:'王五',age:'20'}
2. 在模板使用v-for循坏该数据。以列表li的形式显示
<ul> <li v-for="(index,item) in data" :key="index"> {item.name} - {item.age} <input type="text"> </li> </ul>
3. 那么vue会根据数据生成虚拟DOM,生成的虚拟DOM形式如下:
<li key="0">张三 - 18 <input type="text"></li> <li key="1">李四 - 19 <input type="text"></li> <li key="2">王五 - 20 <input type="text"></li>
4.将虚拟DOM转成真实的DOM
---------------------分割:以上是vue从绑定数据且从虚拟DOM转成真实DOM的过程----------------------
5.接下来说明数据发现变化时,key绑定值位index时数据的改变,改变数据如下
{id:'004',name:'老刘',age:'20'}, {id:'001',name:'张三',age:'18'}, {id:'002',name:'李四',age:'19'}, {id:'003',name:'王五',age:'30'}
6.此时由于数据变化了,那么vue就会重复数据渲染过程,此时根据新数据生成的虚拟DOM如下
<li key="0">老刘 - 30 <input type="text"></li> <li key="1">张三 - 18 <input type="text"></li> <li key="2">李四 - 19 <input type="text"></li> <li key="3">王五 - 20 <input type="text"></li>
7.将虚拟DOM转成真实DOM
8.接下来解释为什么会出现以上的情况?
由于在vue中key时虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DON】与【旧虚拟DON】的差异比较,其中的比较规则如下:
(1)在旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 旧虚拟DOM中内容没有变化的时候,则直接使用之前的真实DOM(比如上面的input的内容)
- 若虚拟DOM中的内容发生变化了,则直接生成新的DOM,随后替换掉页面中之前的真实DOM
(2)旧虚拟DOM中未找到与新虚拟DOM相同的key
- 直接创建新的真实DOM,随后渲染到页面上
(3) 由于以上例子并没有绑定key值,那么每次循环的时候key值都是自上而下从0-n,而自上而下input值没有发生改变,因此直接复用
下面先同个例子来简单说明一下vue中的双向数据绑定
比如我想通过点击按钮来直接该百年数组persons中某一项的数据:this.persons[0]={id:'001',name:'马老师',age:'50',sex:'男'}
却发现页面没有跟新:
那么我们会发现,直接对persons数组中某一项进行更改是无效的,那么根据Vue双向数据绑定的原理,怎么样才能有效更改呢?
以下是有效的更改方式:(即是对数组项的指定属性进行修改)
this.persons[0].name='马老师'; this.persons[0].age=50; this.persons[0].sex="男"
当你想给vue添加信新的属性时,也不能直接使用this.student.sex='男'进行增加,因此这样新增加的新属性是不具备数据的双向绑定的:
那么问题来了,应该怎么样添加新的属性才可以达到响应式呢?根据vue官网提示,当你不是一开始在data中定义的数据,直接添加是不具备响应式的,如果想又不是直接在data中添加,而是后期想用的时候在某个地方添加的时候,可以通过API实现,如下:
Vue.set(target,propertyName/index,value)
1. 参数:
2. 返回值:设置的值
3. 用法:
向响应式随想中添加一个property,并确保这个新peopety同样是响应式的,且出发视图更新。它必须用于相映式对象上添加新property,因为Vue无法探测普通的新增property(比如this.myObject.newProperty='hi')
注意:对象不能是Vue实例,或者Vue实例的根数据对象
实例:
methods: { addSex() { Vue.set(this.student, 'sex', '男') } }
通过setter实现监视,且要在new Vue时就传入要检测的数据
(1)对象中后追加的属性。Vue默认不做响应式处理
(2)如需要给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(arget,propertyName/index,value)
3. 如何检测数组中的数据?
通过包裹数组跟新元素的方法实现,本质上就是做了两件事:(Vue中对操作数组的相关方法进行包装,可以达到数据双向绑定)
(1)调用原生对应的方法对数组进行更新
(2)重新解析模板,进而更新页面。
4. 在Vue修改数组中的某个元素一定要用如下方法:
(1)使用这个API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()、
(2)Vue.set() 或者 vm.$set()
特别注意:Vue.set()和vm.$set()不能给vm或者vm的根数据(vm._data)对象添加属性
核心是通过v-model指令和给表单元素添加value属性
注意,在使用v-model指令实现数据双向绑定时,v-model实质上接收到的是表单元素的value值。由于对于输入框比如“input、textarea”用户输入的值即为value值,因此不用额外给input绑定value值。而对于其他非输入框的表单元素(比如:radio、checkbox等),想要使用v-model实现双向绑定时,通常需为其添加value属性,
例子如下:
(1)没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)配置input的value属性:
- v-model的初始值是非数字,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- v-model的初始值是数组,那么收集的就是value组成的数组
4.v-model的三个修饰符
(1)lazy:失去焦点再收集数据
(2)number:输入字符串转为有效的数字
(3)trim:过滤首尾空格
(1)v-html会替换掉节点中所有的内容,{{xx}}则不会
(2)v-html可以识别html结构
3.严重注意:v-html有安全性问题!!
(1)在网站上动态渲染任意html是非常危险的,容易倒是xss攻击
(2)一定要在可信内容中使用v-html,永远不要在用户提交的内容上!(比如一些恶意人会通过其获取你的cookie等)
Vue的生命周期按执行顺序有:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestory、destoryed。
vue在beforeCeate钩子函数中,进行了初始化、因为此时数据监测和数据代理还没开始,因此还无法通过vm访问到data中的数据、methods中的方法。
此时,已经完成数据检测、数据代理,因此可以在这里通过vm访问到data中的数据、methods中配置的方法。
注意:警告created之后,由于Vue并没有开始编译了,需要经过下面的beforeMount、mounted阶段才完成模板解析。
此阶段vue已经开始解析模板、生成虚拟dom(内存中),页面还不能显示解析好的内容。即此时页面呈现的是未经Vue编译的DOM结构。所有对DOM的操作,最终都不奏效。
经过上面的beforeMount之后,此时vue已经将内存中的虚拟DOM转成真实的DOM插入到页面中了。
经过上面的周期函数之后,页面已经渲染完毕,也可以相应数据。
在这里,如果数据发生变化,那么在这里数据新的,只不过页面是旧的,也就是说页面尚未和数据保持同步更新。
这里根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面的更新,即完成了Model->View的更新。此时,数据是新的,页面也是新的。即保持数据和页面同步。
总结图
组件是可以复用的Vue实例,且带有一个名字。
关于VueComponent:
2.new Vue()配置中
1.data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】
5.VueComponent的实例对象,可以简称为vc(组件实例对象)
Vue实例对象,简称vm
function Demo(){ this.a=1, this.b=2 } //创建一个Demo的实例对象 const d = new Demo() console.log(Demo.prototype)//显示原型属性 console.log(d.__proto__)//隐式原型属性
1.每个对象(或者构造函数)都有一个原型属性prototype
2.每个对象的实例都有一个隐形原型属性
3. 构造函数的原型属性和实例的原型属性均指向原型对象
Demo.prototype === d.__proto__
4.以下验证`Demo.prototype === d.__proto__`
//通过显示原型属性操作原型对象,追加一个x属性,值为99 Demo.prototype.x=99; console.log('@',d.__proto__.x) //或者直接输出d.x(因为找不到时也会往原型链上查找) console.log(d.x)
注意:实例的隐形原型属性,一定是指向自己构造这的原型对象,那么显而易见
Vue的实例对象vm的隐形属性指向Vue的原型对象
Vue的原型对象的原型属性又指向Object的原型对象
思考:那么Vue和VueComponent是如何联系起来的呢?
Vue内部将VueConponent的原型对象指向了Vue的原型对象(目的:使得不管是Vue实例还是VueConponent实例均能公用Vue的方法以及实例)
1.
比如我要在vc上找x属性,当vc上没有x属性时,会沿着__proto__上去找(这个proto指向VueCompontent的原型对象),当VueCompontent的原型对象上没有找到x时,则往proto上找x,此时,vue中将VueCompontent的原型对象的原型属性proto(指向了Vue原型对象),此时在Vue原型对象上找x,此可以找到,注意,如果在Vue的原型对象中也没有找到的时候,则继续往Vue原型对象的proto上找,即找到Object
在入口文件main.js文件中,默认引入的vue不是完整版的,是vue开发者在vue完整版基础上改动的(这里称之为“残缺”版本),且这个引入的vue是不具备编译模板功能的。因此,当你在main.js入口文件,在创建Vue实例的时候,试图同时实现模板编译时,如下,是会出错的
报错如下:
因此,vue团队引入render函数,实现template模板编译,render函数的本质作用是生成节点
//完整版的rander函数 render(createElement){ return createElement('h1','你好啊') //render函数需要返回值 } //简写 render:(q)=>{ return q('h1','你好啊') } //精简简写 render: q=>q(App)//这里的App是import引入的App.vue组件 // new Vue({ el:'#app', //下面这行代码,完成了将App组件放入容器中 render:h=>h(App) })
思考:既然是要进行模板编译的,那为什么vue团队一开始不引入完整的,直接实现模板编译功能呢?原因是当你功能实现了之后,你的代码是需要通过webpack或者其他打包工具进行打包的,那么在进行打包之后,此时一些vue文件已经被编译成了浏览器能够识别的html、js、css等文件,那么在线上即是也不需要这个编译功能的。避免多此一举又占内存,因此vue团队一开始就是引入不带编译功能的vue。
疑问:如果引入的“残缺”版本的vue,那么在其他组件中的<template>标签是如何编译的呢?
答:vue中专门引入了一个包,用来解析组件中的额<template>标签
总结:关于不同版本的vue:
1. vue.js与vue.runtime.xxx.js的区别:
(1)vue.js是完整版本的Vue,包含:核心功能+模板解析器
(2)vue.runtime.xxx.js是运行版本的Vue,只包含:核心功能,没有模板解析器
2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的ceateElement函数去指定具体内容。
1.使用vue inspect > output.js可以查看Vue脚手架的默认配置
2.新建一个vue.config.js可以对脚手架进行个性化定制,详见:配置参考 | Vue CLI
3. 脚手架的文件结构
打标识:<h1 ref="xxx">.....</h1>或者<School ref="xxx"></School>
获取:this.$refs.xxx
prop属性在实现父子组件之间的通信中起到重要作用。
使用方式如下:
父组件中: 在引入的子组件中直接:<子组件 变量名1="值1“ 变量名2="值2” :变量名3=“值3”/>
子组件中:
使用props属性进行接收父组件传过来的数据(使用props接收过来的数据,类似在data中定义的数据,直接挂在到vm上,因此在模板上可以直接使用,但是不能直接更改props中从父组件接接收过来的数据,可以间接改变,下面会讲到)
ps:当你想改变由父组件传递过来的数据时,不要企图直接修改,可以重新在data中定义一个变量来接收这个数据,然后通过更改data中这个新定义的变量去改变。
在父组件向子组件传递数据的时候,需要使用v-bind(简写:)指令告诉vue其中传入的时js表达式,而不是字符串。当你不适用v-bind指令的时候,那么vue就会默认你传入的数据时字符串类型。
功能:让组件接收外部传过来的数据
(1)传递数据:
<Demo name="xxx"/>
(2)接收数据:
第一种方式(只接收)
props:['name']
第二种方式(限制类型)
props:{ name:String }
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String,//类型 required:ture,//必要性 default:'老王'//默认值 } }
混入提供了一种非常灵活的方式,来分发Vue组件中可复用的功能。一个混入对象可以包含任意组件的选项。当组件使用混入对象时,所有混入对象的选项将被“混入”进入该组件本身的选项。
例子:
1.定义一个混合对象
2.在组件中引入混合对象
3.总结
功能:可以把多个组件公用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{ data(){......}, methods:{...} }
第二步使用混入,例如:
(1)全局混入:Vue.mxiin(xxxx)
(2)局部混入:mixins:['xxxx']
插件通常用来为Vue添加全局功能,插件的功能范围没有严格的限制---一般有下面几种:
使用全局方法Vue.use()适应插件,它需要在你调用new Vue()启动应用之前完成:
// 调用 `MyPlugin.install(Vue)` Vue.use(MyPlugin) new Vue({ // ...组件选项 })
功能:用来做增强Vue
本质:包含install方法的一个对象,install的第一个参数是vue,第二个以后的参数是插件使用者传递的数据。
注意:在来引入插件之后,记得Vue.use()来使用插件(全局可使用)
为了避免组件之间类名的重复而导致样式之间的冲突,因此在Vue中,给<style>标签提供了scope属性,加了scope之后,组件之间的样式互不影响。作用原理是每次渲染的时候,会给标签加上随机的属性,从而避免组件之间的样式冲突。
1.一种组件通信的方式,使用于:子组件==>父组件
2.使用场景:A是父组件,B是字组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中,即在那里接受数据,就在那里设定事件的回调)
3.绑定自定义事件:
①第一种方式,在父组件中
<Demo @XXX="test"/> 或者 <Demo v-on:XXX="text"/>
⑤ 第二种方式,在组件中:
<Demo ref="demo"/> ...... mounted(){ //这个this.test是定义在methods中的函数,可以避免this指向的问题 this.$refs.xxx.$on('xxx',this.test) }
③.若想自定义事件只能出发一次,可以使用once修饰符,或者$once方法
4.触发自定义事件:this.$emit('xxx',数据)
5.解绑自定义事件:this.off('xxx')
6.组件上也可以绑定原生DOM事件,需要使用.native修饰符
7.注意:通过this.$ref.xxx.$on('xxx',回调函数)绑定自定义事件时,回调要么配置在methods中,要么使用箭头函数,否者this指向会出现问题!
1.一种组件通信的方式,使用于任意组件间通信
2.安装全局事件总线:
new Vue({ ..... beforeCreate(){ Vue.prototype.$bus=this } })
3.使用事件总线:
1.接受数据:A组件想接受数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){ .... } } mounted(){ this.$bus.$on('xxx',this.demo) }
2.提供数据:this.$bus.$emit('xxx',数据)
4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
提供数据的是发布消息方
需要数据的是订阅消息方
npm i pubsub-js
mouuted(){ pubsub.subscribe('hello',function(){ console.log('有人发布了hello消息,这里hello消息的回调执行了') } }
3.在消息发布方
methods(){ sendStudentName(){ pubsub.publish('hello',666) } }
4.取消订阅
1.一种组件通信的方式,使用于任意组件间通信
2.使用步骤
1.安装pubsub:npm i pubsub-js
2.引入:import pubsub from ‘pubsub-js’
3.接受数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods(){ demo(data){ .... } } mounted(){ this.pid=pubsub.subscribe('xxx',this.demo)//订阅消息 }
4.提供数据:pubsub.publish('xxx',数据)
5.取消订阅,最好在beforeDestory钩子中,用PubSub.unsubscribr(pid)去取消订阅
比如在统一事件中,上一步你刚数据变化更改了试图,下一步又想获取DOM的时候,这时候你立即执行下一步,那么可能获取到的不是罪行的DOM,因此会影响页面效果,此时你使用this.nextTick()便可以得到解决。