MVVM
- M: 模型(Model)–对应data中的数据(就是Model Vue实例中的data)
- V:视图(View)–模板(就是DOM html写的东西,这里写双括号的时候,里面的变量可以是vm上的任何属性或方法,这也就是为什么可以拿到data)
- VM:视图模板(ViewModel)–Vue实例对象(整个script中的Vue实例,所以一般都用vm代表Vue实例)
1.模板语法
- 插值语法
<h>你好,</h>
- 指令语法
<a v-bind:href="url">点击去学习</a>
v-bind可以简写为:
JS表达式可以读取到data中的所有一级属性
2.数据绑定
单向,通过
v-bind
,数据只能从data流向页面双向,通过
v-model
,不仅能从data流向页面,还可以从页面流向datav-model
只能应用在表单类元素(输入类元素)上,因为v-model
默认收集的就是value值
3.数据代理
Object.defineProperty
1
2
3
4
5
6
7
8
9
10
11let person = {
name: '张三',
sex: '男'
}
// 这种方式绑定的age属性,是不可枚举(遍历):即Object.keys(person)返回的没有age,for循环也拿不到。
Object.defineProperty(person, 'age', {
value: 18,
// enumerable: true, //控制属性是否可枚举,默认是false
// writable: true, //控制属性是否可被修改,默认是false
// configurable: true, //控制属性是否可被删除,默认是false
})何为数据代理?
通过一个对象代理对另一个对象中属性的操作(读/写)
vue:通过vm对象来代理data对象中的属性操作(读/写)
【原理:通过
Object.definedProperty()
把data对象中所有属性添加到vm上;为每一个添加到vm上的属性,都指定一个getter/setter;在getter/setter内部去操作(读/写)data中对应的属性】比如:用obj2代理obj中的x
1
2
3
4
5
6let obj = {x:100}
let obj2 = {y:200}
Object.defineProperty(obj2,'x',{
get() { return obj.x},
set(value) { obj.x = value}
})只有配置在data中的属性和方法,才可以被数据代理和数据劫持
4.事件处理
使用:
v-on:xxx
或@xxx
@click="demo"
和@click="demo($event)"
效果一致,后者可以传参事件修饰符
prevent、stop、once、capture、self、passive
5.computed
底层用的其实就是Object.defineProperty方法提供的getter和setter
1 | // 当有人读取fullName时,get就会被调用,且返回值作为fullName的值 |
6.watch
当被监视的属性变化时,回调函数自动调用,进行相关操作
1 | watch:{ |
- Vue可以监测对象内部值的改变,但Vue提供的watch默认不可以
- 使用watch时根据数据的具体结构,决定是否采用深度监视
computed能完成的函数,watch都可以完成。优先computed
watch可以进行异步操作,比如定时器等
7.条件渲染
v-if
切换频率较低的场景
- 因为很暴力,不展示的DOM元素直接被移除
v-show
切换频繁的场景
- 不展示的DOM元素未被移除,用样式隐藏
template可以将元素包裹起来,并且不破坏结构,可以和
v-if
搭配使用。
8.列表渲染
v-for="(item, index) in xxx" :key="yyy"
diff对比过程
虚拟DOM
9.Vue监测数据原理
对象
数据响应式:
如果改变了data中的name值,Vue会进行如下操作:
- 改变前,Vue进行数据代理,先加工data,然后把data赋值给
vm._data
- name改变后,Vue会通过
vm._data
中的set name
数据响应式,重新渲染模板 - 渲染模板后生成虚拟DOM,再进行diff对比,name变化了,name这个节点需要使用新的DOM来进行渲染,其他节点不变继续用旧的DOM
模拟数据监测过程(只监测一层):
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
29
30
31
32/**
* suyufan
* 2023/12/29 16:47
* 只能模拟监测一层,不能深层次监测
*/
let data = {
name: 'su',
age: '18'
}
// 创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
function Observer(obj){
// 汇总对象中所有属性 形成一个数组
const keys = Object.keys(obj)
keys.forEach((i)=>{
// 这里的this是function函数,不能往data中添,因为data中本身就有get/set会形成死循环
Object.defineProperty(this,i,{
get(){
return obj[k]
},
set(val){
// 除了进行赋值,还有进行模板渲染 diff比对。原生通过Vue.set(target,key,val)或者vm.$set(target,key,val)实现
obj[k] = val
}
})
})
}
// 准备一个vm实例对象
let vm = {}
vm._data = obs- 改变前,Vue进行数据代理,先加工data,然后把data赋值给
数组
对象是通过set方法响应式监测,数组属性没有set方法,那数组的监测是如何实现的呢???
Vue对数组的监测,有两步
1.调用数组原生的变更方法(
push()、pop()、shift()、unshift()、splice()、sort()、reserve()
)对于不变更原数组的方法,比如
filter()、concat()、slice()
,用新数组替换旧数组的方式监听2.模板渲染
即Vue将被监听的数组的变更方法进行了包裹,所以它们也会触发视图更新
Vue监测数据原理:
如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,可以使用
Vue.set(target,key,val)
或者vm.$set(target,key,val)
如何让监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
注意:Vue.set()和vm.$set()不能给vm或vm的根数据对象 添加属性
10.生命周期
还有3个生命周期钩子
1个是
nextTick
2个是路由组件特有的,在21节路由中介绍(
activated
,deactivated
)
11.ref属性
被用来给元素或子组件注册引用信息(id的替代者)
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
使用方式:
打标识:
<h1 ref="xxx">...</h1>
获取:
this.$refs.xxx
12.props
让组件接收外部传过来的数据。父给子传 很方便使用。
传递数据:
<Demo age="xxx">
接收数据:
只接收:
props:['age']
限制类型
1
2
3props: {
age: Number
}限制类型、限制必要性、指定默认值
1
2
3
4
5
6
7props: {
age: {
type: Number,
required: true, //必要性
default: "老王" //默认值
}
}
props是只读的,Vue底层会监测开发者对props的修改,如果进行修改,会警告。
如果需要需要修改,复制props的内容到data中,修改data
13.事件总线
任意组件间通信
安装
1
2
3
4
5
6new Vue({
...
beforeCreate(){
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
}
})使用
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
1
2
3
4
5
6method(){
demo(data){}
}
mounted() {
this.$bus.$on('xxx', this.demo)
}提供数据:
this.$bus.$emit('xxx',数据)
最好在beforeDestroy钩子中,用$off解绑当前组件所用到的事件
14.消息订阅与发布
需要数据的去订阅消息,发送数据的去发布消息。任意组件间通信
- 订阅消息:
pubsub.subscribe('消息名',function('消息名', 接收到的数据))
- 发布消息:
pubsub.publish('消息名',数据)
- 最好在beforeDestroy钩子中,用
pubsub.unsubscribe(pid)
去取消订阅
15.Vuex
也是一种组件通信方式,由store管理state、actions、mutations
- 共享
- actions:用于响应组件中的动作
- mutations:用于操作加工数据
- state:用于存储数据
vc可以dispatch经action到mutations,还可以直接commit到mutations
- mapState:生成计算属性,从state中读取数据
- 对象写法:
computed:{...mapState({he:'sum',xuexiao:'school'})}
- 数组写法:
computed:{...mapState(['sum','school'])}
- 对象写法:
- mapGetters:生成计算属性,从getters中读取数据
- mapMutations:生成对应方法,方法中调用commit去联系mutations
- 对象写法:
methods:{...mapMutations({he:'sum',xuexiao:'school'})}
- 数组写法:
methods:{...mapMutations(['sum','school'])}
- 对象写法:
- mapActions:生成对应方法,方法中调用dispatch去联系actions
16.插槽
也是一种组件间通信的方式,适用于父组件==》子组件,让父组件可以向子组件指定位置插入html结构
- 默认插槽
- 具名插槽
- 作用域插槽:anger:
17.webStorage
浏览器通过Window.sessionStorage
和Window.localStorage
属性实现本地存储
相关API
xxxStorage.setItem('key','value')
该方法接收一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem('key')
xxxStorage.clear()
18.nextTick
this.$nextTick(回调函数)
- 在下一次DOM更新结束后执行其指定的回调
- 当改变数据后,基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
19.Vue动画
在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名
20.跨域
数据交互的方式有:xhr、jQuery、axios、fetch、vue-resource
vue-resource就是一个插件,现在不怎么用了。
vue-resource使用方式:
- 安装
- main.js中注册引用
vue.use(vueResource)
- 引用之后在vue中this上就能看到
$http
,直接this.$http.get('服务端地址').then()
跨域解决方式:
cors:通过后端人员配置响应头
Access-Control-Allow-Origin
jsonp:通过script的src不受同源策略限制,只能解决get请求的跨域,且需要后端配合
代理,代理服务器的端口号跟前端一致
Nginx反向代理
通过vue-cli脚手架,也就是devServer代理
1
2
3
4
5
6
7
8
9// 在vue.config.js中配置代理
module.exports = {
devServer: {
proxy: 'http://localhost:5000' //服务端地址
}
}
// vue中调接口发请求时
axios.get('http://localhost:8080/student').then()缺点1:只能配置一个端口号的服务器,如果有多个端口不同的服务器,无法配置
缺点2:如果前端的public中有一个名为student的文件,则接口不会发送到后端,会从前端读取文件数据
解决方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 在vue.config.js中配置代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000', //服务端地址
pathRewrite: {'^/api':''}, //由于服务端接口就是/student,如果直接访问/api/student会报错404
ws: true, //用于支持websocket,默认就是true(React中默认是false)
changeOrigin: true //用于控制请求头中的host值,true的时候是真的请求头8080,false的时候是假的5000.默认值为true(React中默认是false)
}
}
}
}
// vue中调接口发请求时
axios.get('http://localhost:8080/api/student').then()
21.路由
- 路由route:一组key-value的对应关系
- 路由器router
SPA应用,局部刷新的原理实现过程:
工具就是vue-router
菜单栏中选中一个菜单项,比如班级管理,浏览器地址栏会在localhost:8080后匹配上一个/class
,Vue中的路由器router
实时监控着地址栏的变化,现在地址栏变成了/class
,router
会根据开发者写的route规则
去匹配,有一条route规则是:/class ==> 班级组件
,能匹配上,内容区展示班级组件。
两个生命周期钩子:(用于捕获路由组件的激活状态)
- activated:路由组件被激活时触发
- deactivated:路由组件失活时触发
全局路由守卫
- 前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{})
- 后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{})
- 前置守卫:初始化时执行、每次路由切换前执行
router两种模式
hash
地址有
/#/
如果将地址通过第三方手机app分享,若app校验严格,地址会被标记为不合法
与服务端兼容性好
SEO差
history
打包到服务端,可能会有404问题
也有解决404的方案:
- node端自己慢慢写正则匹配,哪个是前端的,哪个是服务端的
- 推荐:node端下载
connect-history-api-fallbeck
,专门用来解决history模式404问题
一般来说,后台管理类的项目喜欢用hash模式,求稳;前端类项目,比如淘宝、B站用的是history模式,美观
一些关于路由的细节点:
路由传参(query【/?id=1&name=’s’】、params【/1/s】、props);
传递params参数时,如使用
to
的对象写法,必须使用name配置项,不能用pathrouter-link的两种模式(push、replace);
编程式路由导航(不借助
<router-link>
实现路由跳转,更灵活);缓存路由
<keep-alive>
:让不展示的路由组件保持挂载,不被销毁;除了全局路由守卫,还有独享守卫和组件内守卫
- 独享守卫:
beforeEnter
- 组件内守卫:
beforeRouteEnter
进入守卫:通过路由规则,进入该组件时被调用beforeRouteLeave
离开守卫:通过路由规则,离开该组件时被调用
22.打包部署步骤
npm run build 打成dist包
node+express作后端:npm init
创建项目,之后npm i express
,之后在创建出来的项目文件中右击新建一个server.js
测试服务端是否成功,终端node server
将打包的dist内,所有内容放到node项目的static下
node端的server.js
1 | const express = require('express') |