序
中国最火的一套构建用户界面的渐进式框架
Vue基本认识
Vue特点
- 遵循MVVM模式
- 编码简洁,体积小,运行效率高,适合移动/PC端开发
- 它本身只关注UI,可以轻松引入vue插件或其它第三库开发项目
Vue扩展插件
- vue-cli:vue脚手架
- vue-resource(axios):ajax请求
- vue-router:路由
- vuex:状态管理
- vue-lazyload:图片懒加载
- vue-scroller:页面滑动相关
- mint-ui:基于vue的UI组件库(移动端)
- element-ui:基于vue的UI组件库(PC端)
基本使用
1 | 1. 引入vue.js |
MVVM
- model: 模型,数据对象data
- view:视图,模板页面
- viewmodel:实体模型,vue的实例
vue指令
v-text/v-html: 指定标签体
- v-text : 当作纯文本,默认没有闪现属性
- v-html : 将value作为html标签来解析
v-if v-else v-show: 显示/隐藏元素
- v-if : 如果vlaue为true, 当前标签会输出在页面中
- v-else : 与v-if一起使用, 如果value为false, 将当前标签输出到页面中
- v-show: 就会在标签中添加display样式, 如果vlaue为true, display=block, 否则是none
v-for : 遍历
- 遍历数组 : v-for=”(person, index) in persons”
- 遍历对象 : v-for=”value in person” $key
- 变异方法:vue对原生js中数组的方法进行了重新的定义,vue中的数组方法加上了监听,所以会触发视图的更新
v-on : 绑定事件监听
- v-on:事件名, 可以缩写为: @事件名
- 监视具体的按键: @keyup.keyCode @keyup.enter只有少数的按键才有
- 停止事件的冒泡和阻止事件默认行为: @click.stop @click.prevent
- 隐含对象: $event就代表事件对象,可以通过event.target获取对象属性
v-bind : 强制绑定解析表达式
- html标签属性是不支持表达式的, 就可以使用v-bind
- 可以缩写为: :id=’name’
- :class
- :class=”a”字符串
- :class=”{classA : isA, classB : isB}”对象
- :class=”[classA, classB]”数组
- :style
- :style=”{color : color}”
v-model
- 双向数据绑定
- 自动收集用户输入数据
ref : 标识某个标签
- ref=’xxx’
- 读取得到标签对象: this.$refs.xxx
v-cloak
- 防止闪现(即在加载时会短暂的出现HTML代码)
- 与css配合,在解析之前匹配一个标签,解析后就不存在了
- 在需要匹配的标签内加v-cloak,并且对v-cloak添加样式{display:none}
自定义指令
- 注册全局指令
1
2
3
4Vue.directive('my-directive',function(el,binding){
//指令名v-省略,el:指令属性所在的标签对象,binding:包含指令相关信息数据的对象
el.innerHTML=binding.value.toupperCase()
}) - 注册局部指令
1
2
3
4
5
6
7directives:{
'my-directive':{
bind(el,binding){
el.innerHTML=binding.value.toupperCase()
}
}
} - 使用指令
v-my-directive=’xxx
自定义插件
- Vue插件是一个包含install方法的对象
- 通过install方法给Vue或Vue实例添加方法,定义全局指令等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28举例:
(function (window) {
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
console.log('Vue函数对象的myGlobalMethod()')
}
// 2. 添加全局资源
Vue.directive('my-directive',function (el, binding) {
el.textContent = 'my-directive----'+binding.value
})
// 3. 添加实例方法
Vue.prototype.$myMethod = function () {
console.log('vm $myMethod()')
}
}
// 4. 向外暴露
window.MyPlugin = MyPlugin
})(window)
调用:
<script type="text/javascript" src="vue-myPlugin.js"></script>
// 声明使用插件(安装插件: 调用插件的install())
Vue.use(MyPlugin) // 内部会调用插件对象的install()
Vue对象
el
- 指定dom标签容器的选择器
- Vue就会管理对应的标签及其子标签
data
- 对象或函数类型
- 指定初始化状态属性数据的对象
- vm也会自动拥有data中所有属性
- 页面中可以直接访问使用
- 数据代理: 由vm对象来代理对data中所有属性的操作(读/写)
methods
- 包含多个方法的对象
- 供页面中的事件指令来绑定回调
- 回调函数默认有event参数, 但也可以指定自己的参数
- 所有的方法由vue对象来调用,访问data中的属性直接使用this.xxx
computed
- 包含多个方法的对象
- 对状态属性进行计算返回一个新的数据, 供页面获取显示
- 一般情况下是相当于是一个只读的属性
- 利用get/set方法来实现属性数据的计算读取, 同时监视属性数据的变化
- 如何给对象定义get/set属性
- 在创建对象时指定: get name () {return xxx} / set name (value) {}
- 对象创建之后指定: Object.defineProperty(obj, age, {get(){}, set(value){}})
watch
- 包含多个属性监视的对象
- 分为一般监视和深度监视
1
2
3
4
5'xxx' : {
deep : true,
handler : fun(value)
}
另一种添加监视方式: vm.$watch('xxx', funn)
Vue实例的生命周期
- 大致分为三个阶段:
- 初始化显示
- beforeCreate()
- created()
- beforeMount()
- mounted()
2) 更新状态:this.xxx=value - beforeUpdate()
- updated()
3) 销毁vue实例:vm.$destory() - beforeDestory()
- destoryed()
- 初始化显示
- 主要的生命周期函数(钩子)
- created() / mounted(): 启动异步任务(启动定时器,发送ajax请求, 绑定监听)
- beforeDestroy(): 做一些收尾的工作,如:清除定时器
过渡&动画
- 利用vue去操控css的transition/animation动画
- 模板: 使用
包含带动画的标签 - css样式
- .fade-enter-active: 进入过程, 指定进入的transition
- .fade-leave-active: 离开过程, 指定离开的transition
- .xxx-enter, .xxx-leave-to: 指定隐藏的样式
1
2
3
4
5
6
7
8
9
10
11
12
13编码例子
//显示隐藏时的过渡效果
.xxx-enter-active, .xxx-leave-active {
transition: opacity .5s
}
//隐藏时的样式
.xxx-enter, .xxx-leave-to {
opacity: 0
}
<transition name="xxx">
<p v-if="show">hello</p>
</transition>
- css样式
过滤器
- 功能:对要显示的数据进行特定格式化后再显示
- 注意:并没有改变原本的数据,可是产生新的对应的数据
- 定义过滤器
1
2
3
4Vue.filter(filterName,function(value[,arg1,arg2,...]){
//进行一定的数据处理
return newValue
}) - 使用过滤器 vue init webpack-simple vue_demo –offline
1
2<div>{{myData|filterName}}</div>
<div>{{myData|filterName(arg)}}</div>
vue组件化编码
创建vue项目
1 | npm init webpack vue_demo |
模板项目的结构
1 | |--build: webpack相关的配置文件夹(基本不需要修改) |
项目的打包
- npm run build
- 发布:
- 使用静态服务器工具包
- npm install -g serve
- serve dist
- 访问:localhost:5000
- 使用动态web服务器(tomcat)
- 配置webpack.prod.conf.js
1
2
3output:{
publicPath:'打包文件夹名称'
} - 重新打包npm run build
- 修改dist文件夹名称
- 将dist文件夹复制到tomcat的webpack下
- 启动tomcat–>startup
- 访问:localhost:8080
- 配置webpack.prod.conf.js
- 使用静态服务器工具包
eslint
1) ESLint 是一个代码规范检查工具
2) 它定义了很多特定的规则, 一旦你的代码违背了某一规则, eslint 会作出非常有用的提示
3) 官网: http://eslint.org/
4) 基本已替代以前的 JSLint
ESLint 提供以下支持
1) ES
2) JSX
3) style 检查
4) 自定义错误和提示
ESLint 提供以下几种校验
1) 语法错误校验
2) 不重要或丢失的标点符号,如分号
3) 没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
4) 未被使用的参数提醒
5) 确保样式的统一规则,如 sass 或者 less
6) 检查变量的命名
组件的定义和使用
组成部分
1 | 页面模板 |
基本使用
1 | <template> |
组件间通信
1) 不要在子组件中直接修改父组件的状态数据
2) 数据在哪, 更新数据的行为(函数)就应该定义在哪
props
- 父组件使用组件标签
1
2<my-component name='tom' :age='3' :set-name='setName'></my-component>
需要动态的数据的时候,可以用属性绑定的形式设置,此时数据来自父组件data中的数据,传的值可以是数字、对象、数组等等 - 子组件定义component1) 此方式用于父组件向子组件传递数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15在组件内声明所有的 props
方式一: 只指定名称
props: ['name', 'age', 'setName']
方式二: 指定名称和类型
props: {
name: String, age: Number,
setNmae: Function
}
方式三: 指定名称/类型/必要性/默认值
props: {
name: {type: String, required: true, default:xxx},
}
2) 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
3) 问题:
- 如果需要向非子后代传递数据必须多层逐层传递
- 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以
vue自定义事件
- 绑定事件监听
1
2
3
4
5
6
7// 方式一: 通过 v-on 绑定
@delete_todo="deleteTodo"
// 方 式 二 : 通 过 $on()
this.$refs.xxx.$on('delete_todo', function (todo) {
this.deleteTodo(todo)
}) - 触发事件
1
2// 触发事件(只能在父组件中接收)
this.$emit(eventName, data)
此方式只用于子组件向父组件发送消息(数据)
问题: 隔代组件或兄弟组件间通信此种方式不合适
消息订阅与发布(PubSubJS 库)
- 订阅消息
1
2
3
4
5
6绑定事件监听 (订阅消息)
目标: 标签元素 <button>
事件名(类型): click/focus
回调函数: function(event){}
PubSub.subscribe('msg', function(msg, data){}) - 发布消息
1
2
3
4
5触发事件 (发布消息)
DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发
PubSub.publish('msg', data)
- 优点: 此方式可实现任意关系组件间通信(数据)
slot(插槽)
- 此方式用于父组件向子组件传递
标签数据
- 子组件
1
2
3
4
5
6
7<template>
<div>
<slot name="xxx">不确定的标签结构 1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构 2</slot>
</div>
</template> - 父组件
1
2
3
4<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>Vue-ajax
vue-resource
- vue 插件, 非官方库, vue1.x 使用广泛
- 在线文档
- 下载
- npm install vue-resource –save
- 使用
1
2
3
4
5
6
7
8
9
10
11// 引入模块
import VueResource from 'vue-resource'
// 使用插件
Vue.use(VueResource)
// 通过 vue/组件对象发送 ajax 请求
this.$http.get('/someUrl').then((response) => {
// success callback console.log(response.data) //返回结果数据
}, (response) => {
console.log(response.statusText) //错误信息
})
axios
- 通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛
- 在线文档
下载
- npm install axios –save
使用
1
2
3
4
5
6
7
8
9
10// 引入模块
import axios from 'axios'
// 发送 ajax 请求
axios.get(url)
.then(response => {
console.log(response.data) // 得到返回结果数据
})
.catch(error => { console.log(error.message)
})
Vue-router
- 路由的本质就是一种对应关系,比如说url地址和资源之间的对应关系
SPA
- 单页面应用程序(single page application):整个网站只有一个页面,内容的变化通过Ajax局部更新实现,同时支持浏览器地址栏前进后退操作
- SPA实现原理之一:基于URl地址的hash(hash的变化会导致浏览器记录访问历史的变化,但是hash的变化不会触发新的url请求)
- 在SPA的实现过程中,最核心的技术点就是前端路由
前端路由
- 概念:根据不同的用户事件,显示不同的页面内容
- 本质:用户事件与事件处理函数之间的对应关系
router基本使用步骤
先创建路由器模块(定义路由组件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 路由器模块
import Vue from 'vue'
import VueRouter from 'vue-router'
import About from './components/About.vue'
import Home from './components/Home.vue'
//挂载属性
Vue.use(VueResource)
//创建路由对象并配置路由规则
export default new VueRouter({ routes: [
{
path: '/', redirect: '/about'
},
{
path: '/about', component: About
},
{
path: '/home', component: Home
}
]
})在main.js引入路由模块
1
2
3
4
5
6
7
8import Vue from 'vue'
import router from'./router' //引入路由器模块
// 创建 vue 配置路由器
new Vue({
el: '#app',
router:router,
})使用路由
1
2
3
4
5
6
7<div>
<!--路由链接-->
<router-link to="/about">About</router-link>
<router-link to="/home">Home</router-link>
<!--用于渲染当前路由组件-->
<router-view></router-view>
</div>
路由重定向redirect
1 | //routes是路由规则数组 |
嵌套路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
实现步骤:
A.更改现有的路由模板:(在模板中添加子级路由链接以及占位符)
var Login = { template: `<div>
<router-link to="/login/account">账号密码登录</router-link>
<router-link to="/login/phone">扫码登录</router-link>
<!-- 子路由组件将会在router-view中显示 -->
<router-view></router-view>
</div>` }
B.使用children添加子级路由规则:
var myRouter = new VueRouter({
routes: [
{ path: "/login", component: Login,
//通过children属性为/login添加子路由规则
children:[
{ path: "/login/account", component: account },
{ path: "/login/phone", component: phone },
]
}
]
})
向路由组件传参数
方式一:路由路径携带参数(param/query)
1
2
3
4
5
6
7
8
9$route.params
配置路由组件
children: [
{path: 'mdetail/:id', component: MessageDetail}
]
配置路由链接
<router-link :to="'/home/message/mdetail/${message.id}'">{{m.title}}</router-link>方式二:通过props获取参数
动态参数
A.我们可以将props属性设置为true来传递参数,route.params将会被设置为组件属性,那么组件可以通过props接收route.params
1
2
3
4
5
6routes:[
{path: 'mdetail/:id', component: MessageDetail,props:true }
]
props:['id']
<router-link :to="'/home/message/mdetail/message.id'">{{m.title}}</router-link>静态参数
B.可以将props属性设置为一个对象,那么组件可以通过props接收对象的数据
1
2
3
4
5
6routes:[
{path: 'mdetail/:id', component: MessageDetail, props:{name : 'xxx' ,age:12}}
]
props:['name','age'],
<div>{{name+age}}</div>动静结合
C. 还可以将props属性设置为一个函数,那么就可以同时将参数数据以及对象数据传递给组件
1
2
3
4
5
6routes:[
{path: 'mdetail/:id', component: MessageDetail, props:{name : 'xxx' ,age:12, id = router.params.id}}
]
props:['name','age','id'],
<div>{{name+age+id}}</div>
- 方式三:< router-view >属性携带数据
1
router-view :msg="msg"></router-view>
缓存路由组件对象
1) 默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的
2) 如果可以缓存路由组件对象, 可以提高用户体验
1
2
3<keep-alive>
<router-view></router-view>
</keep-alive>
命名路由
- 可以使用name属性给路由设置别名
编程式路由导航
- 声明式导航:通过点击链接的方式实现的导航< router-link >
- 编程式导航:调用js的api方法实现导航
1
2
3
4
5
6
7
8
9this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
this.$router.back(): 请求(返回)上一个记录路由
this.$router.go(-1): 请求(返回)上一个记录路由
this.$router.go(1): 请求下一个记录路由
vuex
vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源
- view,以声明方式将 state 映射到视图
- actions,响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
核心思想(工作流程):
使用vuex的好处:
- 能够在vuex中集中管理共享的数据,易于开发和后期维护
- 能够高效的实现组件之间的数据共享,提高开发效率
- 存储在vuex中的数据是响应式的,能够实时保持数据和页面的同步
什么样的数据适合存储在vuex中
- 一般情况下,只有组件之间共享的数据,才有必要存储在vuex中;对于组件中的私有数据,依旧存储在组件自身中的data中即可
vuex的基本使用
安装vuex依赖包
- npm install vuex –save
导入vuex包(在store组件中)
1
2
3
4
5
6import Vuex from 'vuex'
vue.use(Vuex)
const store = new Vuex.Store({
state:{ count:0 }
})将store对象挂载到vue实例中
1
2
3
4
5
6
7
8
9
10import Vue from 'vue'
import router from'./router' //引入路由器模块
import store from'./store'
// 创建 vue 配置路由器
new Vue({
el: '#app',
router:router,
store
})vuex的核心概念
State
State提供唯一的公共数据源,所有的共享数据都要统一放到Store的State中进行存储
1
2
3
4
5
6export default new Vuex.Store({
state:{
//共享数据
count:0
}
})如何访问state中数据:
Mutation用于变更store中的数据
只能通过mutation变更store的数据,不可以直接操作store中的数据
通过这种方式虽然操作起来稍微繁琐一些,但是可以集中监控所有数据的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20export default new Vuex.Store({
state:{
//共享数据
count:0
},
// 定义mutation
mutations:{
add(state){
//变更状态
state.count++
},
// 可以在触发mutations时传递参数
addn(state,step){
//变更状态
state.count+=step
}
}
})触发mutation的方式:
- 方式一
1
2
3
4
5
6
7
8methods:{
handel(){
this.$store.commit('add')
},
handel2(){
this.$store.commit('addn',3)
},
} - 方式二
1
2
3
4
5
6
7//从vuex中按需导入mapState函数
import {mapMutations} from 'vuex'
//通过mapMutations函数将需要的mutation函数映射为当前组件的methods方法
methods:{
...mapMutations(['add','addn'])
}
- 方式一
Action
action用于处理异步任务
如果通过异步操作变更数据,必须通过action而不是mutation,但是在action中还是要通过触发mutation的方式简介变更数据
actions异步任务(可以携带参数):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//定义action
export default new Vuex.Store({
state:{
//共享数据
count:0
},
// 定义mutation
mutations:{
add(state){
//变更状态
state.count++
},
addn(state,step){
//变更状态
state.count+=step
}
},
actions:{
addnAsync(context,step){
setTimeout(()=>{
context.commit('addn',5)
},1000)
}
}
})触发action的方式:
- 方式一
1
2
3
4
5methods:{
handle3{
this.$store.dispatch('addnAsync',step)
}
} - 方式二
1
2
3
4
5
6
7//从vuex中按需导入mapActions函数
import {mapActions} from 'vuex'
//通过mapActions函数将需要的mapActions函数映射为当前组件的methods方法
methods:{
...mapActions(['addnAsync'])
}
- 方式一
Getter
- Getter用于对store中的数据进行加工处理形成新数据
- Getter不会修改state中的数据,他只起到包装的作用
- Getter可以对store中已有的数据进行加工处理之后形成新的数据,类似于vue的计算属性
- store中数据发生变化,Getter的数据也会跟着变化
1
2
3
4
5
6
7
8
9
10
11
12//定义Getter
export default new Vuex.Store({
state:{
count:0
},
getters:{
shownum:state=>{
return state.count
}
}
}) - 使用getters
- 方式一
1
this.$store.getters.名称
- 方式二
1
2
3
4
5import {mapGetters} from 'vuex'
computed:{
...mapGetters(['shownum'])
}
- 方式一
vue源码分析
- 源码:GitHub上一个仿vue实现的mvvm库:https://github.com/DMQ/mvvm
数据代理
1) 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)
2) vue 数据代理: 通过 vm 对象来代理 data 对象中所有属性的操作
3) 好处: 更方便的操作 data 中的数据
4) 基本实现流程
1. 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
2. 所有添加的属性都包含 getter/setter
3. getter/setter 内部去操作 data 中对应的属性数据
1 | /* |
模板解析
模板解析的基本流程
1) 将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中
2) 对 fragment 中的所有层次子节点递归进行编译解析处理
* 对大括号表达式文本节点进行解析
* 对元素节点的指令属性进行解析
* 事件指令解析
* 一般指令解析
3) 将解析后的 fragment 添加到 el 中显示
大括号表达式解析
1) 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
2) 从 data 中取出表达式对应的属性值
3) 将属性值设置为文本节点的 textContent
事件指令解析
1) 从指令名中取出事件名
2) 根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
3) 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
4) 指令解析完后, 移除此指令属性
一般指令解析
1) 得到指令名和指令值(表达式) text/html/class msg/myClass
2) 从 data 中根据表达式得到对应的值
3) 根据指令名确定需要操作元素节点的什么属性
* v-text—textContent 属性
* v-html—innerHTML 属性
* v-class–className 属性
4) 将得到的表达式的值设置到对应的属性上
5) 移除元素的指令属性
数据绑定
- 一旦更新了 data 中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新
数据劫持
1) 数据劫持是 vue 中用来实现数据绑定的一种技术
2) 基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
四个重要对象
1) Observer
- 用来对 data 所有属性数据进行劫持的构造函数
- 给 data 中所有属性重新定义属性描述(get/set)
- 为 data 中的每个属性创建对应的 dep 对象
2) Dep(Depend)
- data 中的每个属性(所有层次)都对应一个 dep 对象
- 创建的时机:
* 在初始化 define data 中各个属性时创建对应的 dep 对象
* 在 data 中的某个属性值被设置为新的对象时
- 对象的结构
1
2
3
4{
id, // 每个 dep 都有一个唯一的 id
subs //包含 n 个对应 watcher 的数组(subscribes 的简写)
}
- subs 属性说明
* 当 watcher 被创建时, 内部将当前 watcher 对象添加到对应的 dep 对象的 subs 中
* 当此 data 属性的值发生改变时, subs 中所有的 watcher 都会收到更新的通知,从而最终更新对应的界面
3) Compiler
- 用来解析模板页面的对象的构造函数(一个实例)
- 利用 compile 对象解析模板页面
- 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher与 dep 的关系
- complie 与 watcher 关系: 一对多的关系
4) Watcher
- 模板中每个非事件指令或表达式都对应一个 watcher 对象
- 监视当前表达式数据的变化
- 创建的时机: 在初始化编译模板时
- 对象的组成
1
2
3
4
5
6
7{
vm, //vm 对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数value, //表达式当前的值
depIds //表达式中各级属性所对应的 dep 对象的集合对象
//属性名为 dep 的 id, 属性值为 dep
}
5) 总结: dep 与 watcher 的关系: 多对多
- data 中的一个属性对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了同一个属性)
- 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表达式是多层: a.b)
- 数据绑定使用到 2 个核心技术
* defineProperty()
* 消息订阅与发布
双向数据绑定
1) 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
2) 双向数据绑定的实现流程:
- 在解析 v-model 指令时, 给当前元素添加 input 监听
- 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性
最后这点源码的解析属实给我看的有点晕,讲的时候能明白,自己再看就又想不通了/(ㄒoㄒ)/~~,还是理解的不够深入
other
- vue.set
- 向对象添加一个新的属性