文章目录
- 第 1 章:Vue基础
- 第 2 章:vue 组件化编码
- 第 3 章:vue-ajax
- 第 4 章:vue UI 组件库
- 第 5 章:vue-router
- 第 6 章:vuex
- 第 7 章:vue 源码分析
第 1 章:Vue基础
1.1 Vue简介
1.1.1 官网
- 英文官网: https://vuejs.org/
- 中文官网: https://cn.vuejs.org/
- Vue3.0 + TypeScript: https://24kcs.github.io/vue3_study/
1.1.2 Vue的特点
- 遵循
MVVM
模式 - 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
- 它本身只关注 UI, 可以轻松引入 vue 插件或其它第三库开发项目
1.1.3 与其它前端 JS 框架的关联
- 借鉴
angular
的模板
和数据绑定
技术 - 借鉴
react
的组件化
和虚拟 DOM
技术
1.1.4 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.2 Vue的基本使用
1.2.1 template模板
<template>
<div id="app">
<Main/>
</div>
</template>
<script>
import Header from '@/components/Layout/Header/Header';
import Main from '@/components/Layout/Main/Main';
import Navbar from '@/components/NavBar/NavBar';
import { mapState, mapActions } from 'vuex';
export default {
name: 'App',
components: {
Main
},
data() {
return {
route: this.$route
};
},
computed: {
...mapState({
isH5Page: state => state.setting.isH5Page,
activeNameText: state => state.setting.activeNameText
})
},
created() {
},
mounted() {
const screenWidth = document.body.clientWidth;
screenWidth >= 800 ? this.setPageAction(false) : this.setPageAction(true);
},
methods: {
...mapActions(['setPageAction', 'setActiveNameText'])
}
};
</script>
<style lang="scss">
#app {
height: auto;
min-width: 300px;
background-color: #fff;
}
</style>
1.2.2 理解 Vue 的 MVVM
1.3 模板语法
1.3.1 模板的理解
- 动态的
html
页面 - 包含了一些
JS
语法代码
a. 双大括号表达式{{}}
b. 指令(以 v-开头的自定义标签属性)
1.3.2 双大括号表达式
-
语法:
{{exp}}
-
功能: 向页面输出数据
-
可以调用对象的方法
<span>{{ lang.mustChangePwd }}</span>
1.3.3 指令一: 强制数据绑定
v-bind
功能: 指定变化的属性值- 简洁写法:
:xxx='yyy'
<el-tag
v-for="(item, i) in data1" v-if="show > i"
:class="{wrap:wrap}" :key="item"
:style="{'font-size':fontSize || '13px'}"
>
{{getLabel(item)}}
</el-tag>
1.3.4 指令二: 绑定事件监听
v-on
功能: 绑定指定事件名的回调函数- 完整写法:
v-on:keyup='xxx'
v-on:keyup='xxx(参数)'
v-on:keyup.enter='xxx'
- 简洁写法:
@keyup='xxx'
@keyup.enter='xxx'
<div @click="toLogin">
<i class="iconfont iconcuowu"></i>
</div>
1.4 计算属性和监视
1.4.1 computed 计算属性
-
在
computed
属性对象中定义计算属性的方法 -
在页面中使用
{{方法名}}
来显示计算的结果computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }
-
可以用来获取
vuex
里的状态computed: { ...mapState({ userInfo: state => state.user.userInfo, isH5Page: state => state.setting.isH5Page }), ...mapGetters(["language"]) },
计算属性高级
- 通过
getter/setter
实现对属性数据的显示和监视 - 计算属性存在缓存, 多次读取只执行一次 getter 计算
1.4.2. watch 监视属性
- 通过
vm
对象的$watch()
或watch
配置来监视指定的属性 - 当属性变化时, 回调函数自动调用, 在函数内部进行计算
watch
只在被监听的属性值发生变化时执行.
1.使用immediate: true
选项, 这样它就会在组件创建时立即执行.
watch: {
dog: {
handler(newVal, oldVal) {
console.log(`Dog changed: ${newVal}`);
},
immediate: true
}
}
2.使用deep: true
选项, 开启深度监听
watch: {
obj: {
handler(newVal, oldVal) {
console.log(`obj changed: ${newVal}`);
},
immediate: true,
deep: true
}
}
3.可只监听对象中的某一个key的变化
watch: {
'obj.hello': {
handler(newVal, oldVal) {
console.log(`obj changed: ${newVal}`);
},
immediate: true,
deep: false
}
}
4.computed
和 watch
的差异
computed
是计算一个新的属性,并将该属性挂载到vm
(Vue 实例)上,而watch
是监听已经存在且已挂载到vm
上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)computed
本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问computed
属性,才会计算新的值,而watch
则是当数据发生变化便会调用执行函数- 从使用场景上说,
computed
适用一个数据被多个数据影响,而watch
适用一个数据影响多个数据;
1.5. class 与 style 绑定
使用v-on
属性绑定实现
1.5.1 class 绑定
:class='xxx'
- 表达式是字符串:
'classA'
- 表达式是对象:
{classA:isA, classB: isB}
- 表达式是数组:
['classA', 'classB']
1.5.3 style 绑定
:style="{ color: activeColor, fontSize: fontSize + 'px' }"
- 其中
activeColor/fontSize
是data
属性
1.6 条件渲染
1.6.1 条件渲染指令
v-if
与v-else
v-show
1.6.2 比较 v-if 与 v-show
- 如果需要频繁切换
v-show
较好 - 当条件不成立时,
v-if
的所有子节点不会解析(项目中使用)
1.7. 列表渲染
- 列表显示指令
数组:v-for / index
对象:v-for / key
- 列表的更新显示
删除 item
替换 item - 列表的高级处理
列表过滤
列表排序
<template v-for="(val,index) in data">
<div :key="index" class="in-form" v-if="!val.hasOwnProperty('isShow') || val.isShow"></div>
</template>
1.8 事件处理
1.8.1 绑定监听:
v-on:xxx="fun"
@xxx="fun"
@xxx="fun(参数)"
- 默认事件形参:
event
- 隐含属性对象:
$event
1.8.2 事件修饰符
.prevent
: 阻止事件的默认行为 event.preventDefault().stop
: 停止事件冒泡 event.stopPropagation()
1.8.3 按键修饰符
.keycode
: 操作的是某个 keycode 值的键.keyName
: 操作的某个按键名的键(少部分)
1.9 表单输入绑定
1.9.1 使用 v-model 对表单数据自动收集
- text/textarea
- checkbox
- radio
- select
1.10 Vue 实例生命周期
1.10.1 生命周期流程图
1.10.2 vue 生命周期分析
- 初始化显示
beforeCreate()
created()
beforeMount()
mounted()
- 更新状态:
this.xxx = value
beforeUpdate()
updated()
- 销毁 vue 实例:
vm.$destory()
beforeDestory()
destoryed()
1.10.3 常用的生命周期方法
created()/mounted()
: 发送ajax
请求, 启动定时器等异步任务beforeDestory()
: 做收尾工作, 如: 清除定时器
1.11 过渡&动画
1.11.1 vue 动画的理解
- 操作 css 的
trasition
或animation
- vue 会给目标元素添加/移除特定的 class
- 过渡的相关类名
xxx-enter-active
: 指定显示的 transition
xxx-leave-active
: 指定隐藏的 transition
xxx-enter/xxx-leave-to
: 指定隐藏时的样式
1.11.2 基本过渡动画的编码
- 在目标元素外包裹
<transition name="xxx">
- 定义 class 样式
指定过渡样式:transition
指定隐藏时的样式:opacity/其它
1.12 过滤器
1.12.1 理解过滤器
- 功能: 对要显示的数据进行特定格式化后再显示
- 注意: 并没有改变原本的数据, 可是产生新的对应的数据
1.12.2 定义和使用过滤器
- 定义过滤器
Vue.filter(filterName, function(value[,arg1,arg2,...]){ // 进行一定的数据处理 return newValue })
- 使用过滤器
<div>{{myData | filterName}}</div> <div>{{myData | filterName(arg)}}</div>
1.13 内置指令与自定义指令
1.13.1 常用内置指令
v:text
: 更新元素的 textContentv-html
: 更新元素的 innerHTMLv-if
: 如果为 true, 当前标签才会输出到页面v-else
: 如果为 false, 当前标签才会输出到页面v-show
: 通过控制 display 样式来控制显示/隐藏v-for
: 遍历数组/对象v-on
: 绑定事件监听, 一般简写为@v-bind
: 强制绑定解析表达式, 可以省略 v-bindv-model
: 双向数据绑定ref
: 指定唯一标识, vue 对象通过$refs 属性访问这个元素对象v-cloak
: 防止闪现, 与 css 配合: [v-cloak] { display: none }
1.13.2 自定义指令
- 注册全局指令
Vue.directive('my-directive', function(el, binding){ el.innerHTML = binding.value.toupperCase() })
- 注册局部指令
directives : { 'my-directive' : { bind (el, binding) { el.innerHTML = binding.value.toupperCase() } } }
- 使用指令
v-my-directive='xxx'
第 2 章:vue 组件化编码
2.1. 使用 vue-cli 创建模板项目
2.1.1. 说明
vue-cli
是 vue 官方提供的脚手架工具- github: https://github.com/vuejs/vue-cli
- 作用: 从 https://github.com/vuejs-templates 下载模板项目
2.1.2. 创建 vue 项目
npm install -g
vue-cli vue init webpack vue_demo
cd vue_demo
npm install
npm run dev
访问: http://localhost:8080/
2.1.3. 模板项目的结构
- build : webpack 相关的配置文件夹(基本不需要修改)
- dev-server.js : 通过 express 启动后台服务器
- config: webpack 相关的配置文件夹(基本不需要修改)
- index.js: 指定的后台服务的端口号和静态资源文件夹
- node_modules 依赖包
- src : 源码文件夹
- components: vue 组件及其相关资源文件夹
- App.vue: 应用根主组件
- main.js: 应用入口 js
- static: 静态资源文件夹
- .babelrc: babel 的配置文件
- .eslintignore: eslint 检查忽略的配置
- .eslintrc.js: eslint 检查的配置
- .gitignore: git 版本管制忽略的配置
- index.html: 主页面文件
- package.json: 应用包配置文件
- README.md: 应用描述说明的 readme 文件
2.2. 项目的打包与发布
2.2.1. 打包:
npm run build
2.2.2. 发布 1: 使用静态服务器工具包
npm install -g serve
serve dist
访问: http://localhost:5000
2.2.3. 发布 2: 使用动态 web 服务器(tomcat)
修改配置: webpack.prod.conf.js
output: { publicPath: '/xxx/' //打包文件夹的名称 }
重新打包:
npm run build
修改 dist 文件夹为项目名称: xxx
将 xxx 拷贝到运行的 tomcat 的 webapps 目录下
访问: http://localhost:8080/xxx
2.3. eslint
2.3.1. 说明
- ESLint 是一个代码规范检查工具
- 它定义了很多特定的规则, 一旦你的代码违背了某一规则, eslint会作出非常有用的提示
- 官网: http://eslint.org/
- 基本已替代以前的 JSLint
2.3.2. ESLint 提供以下支持
- ES
- JSX
- style 检查
- 自定义错误和提示
2.3.3. ESLint 提供以下几种校验
- 语法错误校验
- 不重要或丢失的标点符号,如分号
- 没法运行到的代码块(使用过 WebStorm 的童鞋应该了解)
- 未被使用的参数提醒
- 确保样式的统一规则,如 sass 或者 less
- 检查变量的命名
2.3.5. 相关配置文件
- .eslintrc.js : 全局规则配置文件
'rules': { 'no-new': 1 }
- 在 js/vue 文件中修改局部规则
/* eslint-disable no-new */ new Vue({ el: 'body', components: { App } })
- .eslintignore: 指令检查忽略的文件
*.js *.vue
2.4. 组件定义与使用
2.4.1. vue 文件的组成(3 个部分)
- 模板页面
<template> 页面模板 </template>
- JS 模块对象
<script> export default { data() {return {}}, methods: {}, computed: {}, components: {} } </script>
- 样式
<style>样式定义 </style>
2.4.2. 基本使用
-
引入组件
-
映射成标签
-
使用组件标签
<template> <HelloWorld></HelloWorld> <hello-world></hello-world> </template> <script> import HelloWorld from './components/HelloWorld' export default { components: { HelloWorld } } </script>
2.4.3. 关于标签名与标签属性名书写问题
- 写法一: 一模一样
- 写法二: 大写变小写, 并用-连接
2.5. 组件间通信
1.组件间通信基本原则
- 不要在子组件中直接修改父组件的状态数据
- 数据在哪, 更新数据的行为(函数)就应该定义在哪
2.vue 组件间通信方式
props
2)$emit
3) 消息订阅与发布(如:pubsub
库) 4)slot
5)vuex
(后面单独讲)
2.6 props
1.使用组件标签时
<my-component name='tom' :age='3' :set-name='setName'></my-component>
2.定义 MyComponent 时
-
在组件内声明所有的
props
-
方式一: 只指定名称
props: ['name', 'age', 'setName']
-
方式二: 指定名称和类型
props: { name: String, age: Number, setNmae: Function }
-
方式三: 指定名称/类型/必要性/默认值
props: { name: {type: String, required: true, default:xxx}, }
注意:
- 此方式用于父组件向子组件传递数据
- 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
- 问题:
a. 如果需要向非子后代传递数据必须多层逐层传递
b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以
2.7 $emit
1.绑定事件监听
// 方式一: 通过 v-on 绑定
@delete_todo="deleteTodo"
// 方式二: 通过$on()
this.$refs.xxx.$on('delete_todo', function (todo) {
this.deleteTodo(todo)
})
2.触发事件
// 触发事件(只能在父组件中接收)
this.$emit(eventName, data)
注意:
-
此方式只用于子组件向父组件发送消息(数据)
-
问题: 隔代组件或兄弟组件间通信此种方式不合适
2.8 消息订阅与发布(PubSubJS 库)
此方式可实现任意关系组件间通信
(数据)
1.订阅消息
PubSub.subscribe('msg', function(msg, data){})
2.发布消息
PubSub.publish('msg', data)
3. 事件的 2 个重要操作(总结)
-
绑定事件监听 (订阅消息)
目标: 标签元素
事件名(类型): click/focus
回调函数: function(event){} -
触发事件 (发布消息)
DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发
2.9 slot
此方式用于父组件向子组件传递标签数据
1. 子组件: Child.vue
<template>
<div>
<slot name="xxx">不确定的标签结构 1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构 2</slot>
</div>
</template>
2. 父组件: Parent.vue
<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>
第 3 章:vue-ajax
3.1. vue 项目中常用的 2 个 ajax 库
3.1.1. vue-resource
vue 插件, 非官方库, vue1.x 使用广泛
3.1.2. axios
通用的 ajax 请求库, 官方推荐, vue2.x 使用广泛
3.2. vue-resource 的使用
3.2.1. 在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
3.2.2. 下载
npm install vue-resource --save
3.2.3. 编码
// 引入模块
import VueResource from 'vue-resource'
// 使用插件
Vue.use(VueResource)
// 通过 vue/组件对象发送 ajax 请求 this.$http.get('/someUrl').then((response) => {
// success callback
console.log(response.data) //返回结果数据
}, (response) => {
// error callback
console.log(response.statusText) //错误信息
})
3.3. axios 的使用
3.3.2. 在线文档
https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
3.3.3. 在线文档
npm install axios --save
3.3.4. 编码
// 引入模块
import axios from 'axios'
// 发送 ajax 请求
axios.get(url)
.then(response => {
console.log(response.data) // 得到返回结果数据
})
.catch(error => {
console.log(error.message)
})
第 4 章:vue UI 组件库
4.1. 常用
- Mint UI:
a. 主页: http://mint-ui.github.io/#!/zh-cn
b. 说明: 饿了么开源的基于 vue 的移动端 UI 组件库 - Elment
a. 主页: http://element-cn.eleme.io/#/zh-CN
b. 说明: 饿了么开源的基于 vue 的 PC 端 UI 组件库
4.2. 使用 Mint UI
4.2.1. 下载:
npm install --save mint-ui
4.2.2. 实现按需打包
1. 下载
npm install --save-dev babel-plugin-component
2. 修改 babel 配置
"plugins": ["transform-runtime",["component", [
{
"libraryName": "mint-ui",
"style": true
}
]]]
4.2.3. mint-ui 组件分类
- 标签组件
- 非标签组件
4.2.4. 使用 mint-ui 的组件
-
main.js
import {Button} from 'mint-ui' Vue.component(Button.name, Button)
-
App.vue
<template> <mt-button @click="handleClick" type="primary" style="width: 100%">Test</mt-button> </template> <script> import {Toast} from 'mint-ui' export default { methods: { handleClick () { Toast('点击了测试'); } } } </script>
第 5 章:vue-router
5.1. 理解
5.1.1. 说明
- 官方提供的用来实现 SPA 的 vue 插件
- github: https://github.com/vuejs/vue-router
- 中文文档: http://router.vuejs.org/zh-cn/
- 下载: npm install vue-router --save
5.1.2. 相关 API 说明
-
VueRouter(): 用于创建路由器的构建函数
new VueRouter({ // 多个配置项 })
-
路由配置
routes: [ { // 一般路由 path: '/about', component: About }, { // 自动跳转路由 path: '/', redirect: '/about' } ]
-
注册路由器
import router from './router' new Vue({ router })
-
使用路由组件标签
1. <router-link>: 用来生成路由链接 <router-link to="/xxx">Go to XXX</router-link> 2. <router-view>: 用来显示当前路由组件界面 <router-view></router-view>
5.2. 基本路由
5.2.2. 路由组件
Home.vue
About.vue
5.2.3. 应用组件: App.vue
<div>
<!--路由链接-->
<router-link to="/about">About</router-link>
<router-link to="/home">Home</router-link>
<!--用于渲染当前路由组件-->
<router-view></router-view>
</div>
5.2.4. 路由器模块: src/router/index.js
export default new VueRouter({
routes: [
{
path: '/',
redirect: '/about'
},
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
}
]
})
5.2.5. 注册路由器: main.js
import Vue from 'vue'
import router from './router'
// 创建 vue 配置路由器
new Vue({
el: '#app',
router,
render: h => h(app)
})
5.2.6. 优化路由器配置
linkActiveClass: ‘active’, // 指定选中的路由链接的 class
5.2.7. 总结: 编写使用路由的 3 步
- 定义路由组件
- 注册路由
- 使用路由
<router-link>
<router-view>
5.3. 嵌套路由
5.3.2. 子路由组件
News.vue
Message.vue
5.3.3. 配置嵌套路由: router.js
path: '/home',
component: home,
children: [
{
path: 'news',
component: News
},
{
path: 'message',
component: Message
}
]
5.3.4. 路由链接: Home.vue
<router-link to="/home/news">News</router-link>
<router-link to="/home/message">Message</router-link>
<router-view></route-view>
5.4. 向路由组件传递数据
5.4.2. 方式 1: 路由路径携带参数(param/query)
-
配置路由
children: [ { path: 'mdetail/:id', component: MessageDetail } ]
-
路由路径
<router-link :to="'/home/message/mdetail/'+m.id">{{m.title}}</router-link>
-
路由组件中读取请求参数
this.$route.params.id
5.4.3. 方式 2: 属性携带数据
<router-view :msg="msg"></router-view>
5.5. 缓存路由组件对象
5.5.1. 理解
- 默认情况下, 被切换的路由组件对象会死亡释放, 再次回来时是重新创建的
- 如果可以缓存路由组件对象, 可以提高用户体验
5.5.2. 编码实现
<keep-alive>
<router-view></router-view>
</keep-alive>
5.6.2. 相关 API
- this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
- this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
- this.$router.back(): 请求(返回)上一个记录路由
- this. r o u t e r . g o ( − 1 ) : 请求 ( 返回 ) 上一个记录路由 5 ) t h i s . router.go(-1): 请求(返回)上一个记录路由 5) this. router.go(−1):请求(返回)上一个记录路由5)this.router.go(1): 请求下一个记录路由
第 6 章:vuex
6.1. vuex 理解
6.1.1. vuex 是什么
- github 站点: https://github.com/vuejs/vuex
- 在线文档: https://vuex.vuejs.org/zh-cn/
- 简单来说: 对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
6.1.2. 状态自管理应用
- state: 驱动应用的数据源
- view: 以声明方式将 state 映射到视图
- actions: 响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
6.1.3. 多组件共享状态的问题
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
- 以前的解决办法
a. 将数据以及操作数据的行为都定义在父组件
b. 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递) - vuex 就是用来解决这个问题的
6.2. vuex 核心概念和 API
6.2.1. state
-
vuex 管理的状态对象
-
它应该是唯一的
const state = { xxx: initValue }
6.2.2. mutations
- 包含多个直接更新 state 的方法(回调函数)的对象
- 谁来触发: action 中的 commit(‘mutation 名称’)
- 只能包含同步的代码, 不能写异步代码
const mutations = {
yyy (state, {data1}) {
// 更新 state 的某个属性
}
}
6.2.3. actions
- 包含多个事件回调函数的对象
- 通过执行:
commit()
来触发mutation
的调用, 间接更新state
- 谁来触发: 组件中:
$store.dispatch('action 名称', data1) // 'zzz'
- 可以包含异步代码(定时器, ajax)
const actions = {
zzz ({commit, state}, data1) {
commit('yyy', {data1})
}
}
6.2.4. getters
- 包含多个计算属性(get)的对象
- 谁来读取: 组件中:
$store.getters.xxx
const getters = {
mmm (state) {
return ...
}
}
6.2.5. modules
- 包含多个 module
- 一个 module 是一个 store 的配置对象
- 与一个组件(包含有共享数据)对应
6.2.6. 向外暴露 store 对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
6.2.7. 组件中
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
computed: {
...mapState(['xxx']),
...mapGetters(['mmm']), }
methods: mapActions(['zzz']) }
{{xxx}} {{mmm}} @click="zzz(data)"
6.2.8. 映射 store
import store from './store'
new Vue({ store })
6.2.9. store 对象
- 所有用 vuex 管理的组件中都多了一个属性$store, 它就是一个 store 对象
- 属性:
state: 注册的 state 对象
getters: 注册的 getters 对象 - 方法:
dispatch(actionName, data): 分发调用 action
6.5. vuex 结构分析
第 7 章:vue 源码分析
7.1. 说明
- 分析 vue 作为一个
MVVM
框架的基本实现原理- 数据代理
- 模板解析
- 数据绑定
- 剖析 github 上某基友仿 vue 实现的 mvvm 库
- 地址: https://github.com/DMQ/mvvm
7.2. 准备知识
- [].slice.call(lis): 将伪数组转换为真数组
- node.nodeType: 得到节点类型
- Object.defineProperty(obj, propName, {}): 给对象添加/修改属性(指定描述符)
configurable: true/false 是否可以重新 define
enumerable: true/false 是否可以枚举(for…in / keys())
value: 指定初始值
writable: true/false value 是否可以修改
get: 回调函数, 用来得到当前属性值
set: 回调函数, 用来监视当前属性值的变化 - Object.keys(obj): 得到对象自身可枚举的属性名的数组
- DocumentFragment: 文档碎片(高效批量更新多个节点)
- obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性
7.3. 数据代理
- 数据代理: 通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)
- vue 数据代理: 通过
vm
对象来代理data
对象中所有属性的操作 - 好处: 更方便的操作 data 中的数据
- 基本实现流程
a. 通过Object.defineProperty()
给vm
添加与data
对象的属性对应的属性描述符
b. 所有添加的属性都包含getter/setter
c.getter/setter
内部去操作data
中对应的属性数据
7.4. 模板解析
7.4.1. 模板解析的基本流程
- 将
el
的所有子节点取出, 添加到一个新建的文档fragment
对象中 - 对
fragment
中的所有层次子节点递归进行编译解析处理- 对大括号表达式文本节点进行解析
- 对元素节点的指令属性进行解析
- 事件指令解析
- 一般指令解析
- 将解析后的
fragment
添加到 el 中显示
7.4.2. 模板解析(1): 大括号表达式解析
- 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1 name
- 从
data
中取出表达式对应的属性值 - 将属性值设置为文本节点的
textContent
7.4.3. 模板解析(2): 事件指令解析
- 从指令名中取出事件名
- 根据指令的值(表达式)从
methods
中得到对应的事件处理函数对象 - 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
- 指令解析完后, 移除此指令属性
7.4.4. 模板解析(3): 一般指令解析
- 得到指令名和指令值(表达式)
text/html/class
msg/myClass
- 从
data
中根据表达式得到对应的值 - 根据指令名确定需要操作元素节点的什么属性
v-text
—textContent
属性v-html
—innerHTML
属性v-class
–className
属性
- 将得到的表达式的值设置到对应的属性上
- 移除元素的指令属性
7.5. 数据绑定
7.5.1. 数据绑定
一旦更新了 data
中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会 更新
7.5.2. 数据劫持
- 数据劫持是
vue
中用来实现数据绑定的一种技术 - 基本思想: 通过
defineProperty()
来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
7.5.3. 四个重要对象
7.5.3.1 Observer
a. 用来对 data
所有属性数据进行劫持的构造函数
b. 给 data
中所有属性重新定义属性描述(get/set
)
c. 为 data
中的每个属性创建对应的 dep
对象
7.5.3.2 Dep(Depend)
a. data
中的每个属性(所有层次)都对应一个 dep
对象
b. 创建的时机:
- 在初始化 define data 中各个属性时创建对应的
dep
对象 - 在
data
中的某个属性值被设置为新的对象时
c. 对象的结构
{
id, // 每个 dep 都有一个唯一的 id
subs //包含 n 个对应 watcher 的数组(subscribes 的简写)
}
d. subs 属性说明
- 当
watcher
被创建时, 内部将当前watcher
对象添加到对应的dep
对象的subs
中 - 当此
data
属性的值发生改变时,subs
中所有的watcher
都会收到更新的通知,从而最终更新对应的界面
7.5.3.3 Compiler
a. 用来解析模板页面的对象的构造函数(一个实例)
b. 利用 compile
对象解析模板页面
c. 每解析一个表达式(非事件指令)都会创建一个对应的 watcher
对象, 并建立 watcher
与 dep
的关系
d. complie
与 watcher
关系: 一对多的关系
7.5.3.4 Watcher
a. 模板中每个非事件指令或表达式都对应一个 watcher
对象
b. 监视当前表达式数据的变化
c. 创建的时机: 在初始化编译模板时
d. 对象的组成
{
vm, //vm 对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds //表达式中各级属性所对应的 dep 对象的集合对象 //属性名为 dep 的 id, 属性值为 dep
}
7.5.3.5 总结: dep 与 watcher 的关系: 多对多
a. data
中的一个属性对应一个 dep
, 一个 dep
中可能包含多个 watcher
(模板中有几个表达式使用到了同一个属性)
b. 模板中一个非事件表达式对应一个 watcher
, 一个 watcher
中可能包含多个 dep
(表达式是多层: a.b)
c. 数据绑定使用到 2 个核心技术
defineProperty()
消息订阅与发布
7.6. MVVM 原理图分析
7.7. 双向数据绑定
- 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
- 双向数据绑定的实现流程:
a. 在解析v-model
指令时, 给当前元素添加input
监听
b. 当 input 的value
发生改变时, 将最新的值赋值给当前表达式所对应的data
属性