一、vue的双向数据绑定或响应式原理
(一)数据绑定原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过`Object.defineProperty()`来劫持
各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
(二)Object.defineProperty()
Object.defineProperty(obj,prop, descriptor),该方法接收3个参数
- obj:
要在其上定义属性的对象。 - prop:
要定义或修改的属性的名称。 - descriptor:
给对象的属性添加特性描述,目前提供两种形式:数据描述和存取器描述。 - 返回值:
这个操作的对象
关于descriptor,个人理解为对某个属性的详细配置,分为数据方面的配置,和存取时候的配置
对象属性的描述:对于对象,之前就是直接拿来读或者写它的属性
参考文章·
参考文章二
参考文章三
(三)数据绑定的过程
1.observe
需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
2.实现Compile
compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,
并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新
视图。
3.实现Watcher
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
,则功成身退。
4、实现MVVM
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己
的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile
之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向
绑定效果。
二、MVVM架构
它采用双向绑定,处理接收事件和View更新的工作不再需要开发者来完成,而可以交给框架。
(一)特点
双向绑定——View/Model的变动,自动反映在 ViewModel,反之亦然。
(二)三个部分之间的通信方式:
① View接受用户的指令。
② View将请求传送给ViewModel。
③ ViewModel完成业务逻辑后,要求 Model 改变状态。
④ Model通知ViewModel数据更新。
⑤ ViewModel更新View的数据。
(三)MVVM架构的优点
① 低耦合:View和Model可以相互独立地修改,一个ViewModel可以绑定到不同的View上,View变化时Model可以不变,Model变化时View也可以不变。
② 可重用性:可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
③ 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,生成xml代码。
④ 可测试:测试可以针对ViewModel来写。
三、vue的生命周期
Vue实例从创建到销毁的过程,就是生命周期。
Vue的生命周期包括:开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。
在Vue的整个生命周期中,实例可以调用一些生命周期钩子,这提供了执行自定义逻辑的机会。
Vue提供的生命周期钩子如下:
① beforeCreate
在实例初始化之后,数据观测(data observer,开始监控Data对象数据变化)和初始化事件(init event,Vue内部初始化事件)之前被调用。
② created
在实例已经创建完成之后被调用。实例已完成以下的配置:数据观测(data observer),属性和方法的运算,event事件回调。挂载阶段尚未开始,$el 属性不可见。
③ beforeMount
在挂载开始之前被调用。相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
④ mounted
在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。此时模板中的html渲染到了html页面中,此时一般可以做一些Ajax操作。注意mounted只会执行一次。
⑤ beforeUpdate
在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
⑥ updated
在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
⑦ beforeDestroy
在实例销毁之前调用。实例仍然完全可用。
⑧ destroyed
在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
四、Vue的父子组件之间的传值
(一)父组件向子组件传递
1.使用prop传递
Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。
每次父组件更新时,子组件的所有 prop 都会更新为最新值。
①父组件模板利用v-bind绑定data里边的数据
②子组件要显式地用 props 选项声明预期的数据
<!--父组件模板-->
<template id="m1">
<p>父组件:{{message}}={{num}}</p>
<!--绑定的数据必须是data里边的属性-->
<!--sync表示两个组件之间数据同步变化-->
<my-buycarbtn v-bind:my-message.sync="num"></my-buycarbtn>
</template>
<!--子组件模板-->
<template id="m2">
<!--myMessage是从props里边读取值-->
<p>子组件:{{myMessage}}</p>
<button @click="add">{{message}}</button>
</template>
//子组件,不能在子标签上声明子标签的作用域,必须在父标签上
var BuyCarBtn = Vue.extend({
//这里的myMessage相当于模板中的my-message,因为vue不识别中间带-的写法,所以这里用小驼峰的写法
props:['myMessage'],
template:`#m2`,
methods:{
add:function(){
this.myMessage++
}
}
});
// 父组件
var BuyCar = Vue.extend({
data:function(){
return ({
message:'苹果',
btnVal : '按钮',
num:1
})
},
template:`#m1`,
//子标签的作用域只能作用在父标签里
components:{
'my-buycarbtn':BuyCarBtn
}
});
(二)子组件向父组件传值
由于在vue中子组件不能更改父组件中的内容,所以可以通过子组件触发事件来传值给父组件。
$emit
用法:vm.$emit( event, […args] ),触发当前实例上的事件。 附加参数都会传给监听器
回调。
$emit 返回一个布尔值,取决于父链上的是否存在该事件的监听器以及事件处理程序返回的值
①子组件在methods中用$emit(事件名)传递信号
②父组件:v-on:事件名="自身事件" 监听信号,一旦监听到,立即触发自身事件。在自己的
methods中编写触发事件
子组件:
<button v-on:click="onClickMe">like!</button>
methods: {
onClickMe: function(){
this.$emit('child-say',this.somedata);
}
}
父组件:
<component-a v-on:child-say="listenToMyBoy"></component-a>
<p>Do you like me? {{childWords}}</p>
methods: {
listenToMyBoy: function (somedata){
this.childWords = somedata
}
}
五、Vue的路由关系
(一)安装vue-router
npm install vue-router
(二)引入vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) /*明确的引入vue-router*/
(三)配置路由词典
const router = new VueRouter({
mode:'history',
base:_dirname,
/*注意这里的routers单词书写,这里写主路由*/
routers:[
{
path:'/',
component:Home
},
{
path:'/First',
component:First
},
{
path:'/Second',
component:Second
}
]
})
(四)创建Vue对象,并加重上面创建的路由对象
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
(五)在模板中编写路由跳转链接
<div id="r">
<h1>导航</h1>
<ul>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<li><router-link to="/"></router-link></li>
<li><router-link to="/First"></router-link></li>
<li><router-link to="/Second"></router-link></li>
</ul>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></vue-router>
</div>
六、页面之间的传值跳转
(一)一种是:query方式
this.$route.push({ path: 'register', query: { plan: 'private' }})
// 带查询参数,变成 /register?plan=private
另一页面取得所传的query值
this.$route.query.键值名
(二)一种是:params方式
this.$route.push({ name: 'user', params: { userId: 123 }})
// 变成 /user/123
this.$route.params.键名
七、如何引入Vue组件
组件既可以全局引入,也可以按需引入
import Vue from 'vue';
import Row from './row/src/row';
import Col from './col/src/Col';