前端笔记--Vue
Vue基本使用
指令
- {{ msg }} 、v-text(会覆盖子文本)
- v-html 会有xss风险,会覆盖子组件
- 设置动态属性 v-bind: (简写:)
<button v-bind:disabled="isButtonDisabled">Button</button>
- 表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
- v-on:(简写@)监听DOM事件
- 修饰符 . 用于指出一个指令应该以特殊方式绑定
<form v-on:submit.prevent="onSubmit">...</form>
<input @keyup.enter="onCheck" />
- v-if
- v-show
- v-if和v-show的区别
1.v-if是通过控制dom节点的存在与否来控制元素的显隐;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
2.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗
3.使用场景:如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。 - v-for
v-for和v-if不能一起使用 - v-model
计算属性和监听属性
- computed
对于任何复杂逻辑,你都应当使用计算属性。computed有缓存,data不变不会重新计算。
- watch
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
1.当需要监听引用类型时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听。
2.watch监听引用类型,拿不到oldVal,
组件通讯
父子组件传值
任何组件的通讯,包括父子,兄弟,跨级
组件通讯
通过自定义事件实现兄弟组件通讯,在beforeDestroy时及时进行销毁,否则可能造成内存泄漏
1.需要创建一个空白vue实例作为兄弟间的桥梁(event.js)
import Vue from 'vue'
export default new Vue()
2.要传送数据的组件使用$emit触发接收数据组件的自定义事件并把数据传过去
import event from '../utils/event.js'
export default {
data () {
return {
msg: 'A组件数据'
}
},
methods: {
send: function () {
event.$emit('receive', this.msg) // 使用 $emit 自定义事件把数据带过去
}
}
}
3.接收数据的组件使用$on监听自定义事件接收数据
import event from '../utils/event.js' // vue的空白实例(兄弟间的桥梁)
export default {
data () {
return {
msg: ''
}
},
mounted () {
event.$on('receive', (val) => { // 监听事件receive,回调函数要使用箭头函数;
console.log(val)
this.msg = val
})
},
beforeDestroy () {
event.$off('receive')
},
}
Vue生命周期
1.created和mounted区别
created完成了vue实例的初始化,页面还没开始渲染,mounted完成了页面的渲染
2.父子组件生命周期调用顺序
p created
s created
s mounted
p mounted
p beforeUdate
s beforeUdate
s updated
p updated
p beforeDestroy
s beforeDestroy
s destroyed
p destroyed
Vue高级特性
手动实现一个v-model
父组件
<template>
<div id="app">
<p>{{name}}</p>
<HelloWorld v-model="name"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
HelloWorld
},
data(){
return {
name: 'vue'
}
}
}
</script>
子组件
<template>
<div>
<input type="text" :value = "val" @input="$emit('change',$event.target.value)">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
model: {
prop: 'val',
event: 'change'
},
props: {
val: {
type: String,
default(){
return ''
}
}
},
}
</script>
补充:
<input type="text" v-model="name">
实则等于<input type="text" :value = "name" @input = "name=$event.target.value">
,也就是说v-model就是绑定的了一个名为value的prop和一个input事件
2.model属性,允许一个自定义组件在使用v-model时定制prop和event。避免与一些表单类型的value产生冲突。
$nextTick
定义:在下次DOM更新循环结束之后执行延时回调。在修改数据后立即使用该方法,获取更新后的DOM。
- Vue是异步渲染的
- data改变过后,DOM不会立即渲染
- $nextTick会在DOM渲染完成后被触发,以获取最新的DOM节点
什么时候需要用$nextTick
1.在created钩子函数中进行的DOM操作一定要放在Vue.nextTick()回调函数中
2.在改变数据后,基于新DOM进行的一系列js操作都需要放到Vue.nextTick()回调函数中
Vue.nextTick(callback)使用原理
Vue是异步渲染DOM的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环(event loop)中观察到数据变化的watcher推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效地去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
为了在数据变化之后等待 Vue 完成渲染 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) ,这样回调函数在 DOM 渲染完成后就会调用。
slot
具名插槽
slot元素有一个特殊的 attribute:name。一个不带 name 的 slot 出口会带有隐含的名字“default”。任何没有被包裹在带有 v-slot 的 template中的内容都会被视为默认插槽的内容。
注意:v-slot只能添加在template上
作用域插槽
让插槽内容能够访问子组件中的数据
绑定在
动态组件
需要根据数据动态渲染的场景,即组件不确定
异步组件
只在需要的时候才从服务器加载,提高性能
keep-alive
缓存组件,频繁切换,不需要重复渲染,提高性能
mixin
多个组件有相同的逻辑,抽离出来
mixin的问题:
1.变量来源不明确,不利于阅读
2.多mixin可能会造成命名冲突
3.mixin和组件可能出现多对多关系,复杂度较高
vuex
专为Vue.js开发的状态管理模式
基本使用
vuex核心概念
1.state单一状态树
2.mutations
同步操作方法
vuex的store状态的唯一更新方式:mutation
- mutation传递参数
多个参数以对象的形式传递
mutation另一种提交风格
mutation响应式规则
使用vue.set()和vue.delete()
3.actions
异步操作方法
4.getters
类似于单个组件中的计算属性,当state需要进行一系列变化再展示时使用
5.modules
当应用变得复杂时,store对象可能变得相当臃肿,可以将store进行模块划分,而每个模块也有自己的state,mutations,actions,getters等
uploading-image-269448.png
vuex推荐的项目结构
vue-router
什么是前端渲染,什么是后端渲染
后端渲染:服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示
前端渲染:浏览器中显示的页面中的大部分内容都是由前端编写的js代码在浏览器中执行,最终渲染出来的
后端路由阶段
后端处理URL和页面之间的映射关系,有利于SEO优化
后端路由的缺点:
1.整个页面的模块是由后端人员来编写和维护的
2.前端人员如果要开发页面,需要使用php和java语言来编写代码
3.HTML代码和数据以及对应的逻辑混在一起,编写和维护都很麻烦
前后端分离阶段
后端只负责提供数据,不负责任何界面的内容;后端只提供API返回数据,前端通过ajax获取数据,并且通过js将数据渲染到页面上
优点:
1.前后端责任清晰,后端专注于数据,前端专注于交互和可视化上
2.开发移动端,后端也不需要进行任何的处理,依然使用之前的一套API即可
单页面富应用阶段
在前后端分离阶段的基础上加了一层前端路由
整个网页只有一个html页面
前端路由
由前端管理url和组件之间的映射关系
路由模式
hash模式(默认),如http://abc.com/#/user/10
JS实现hash路由
核心:hashchange事件
H5 history模式,如http://abc.com/user/10
1.history方法
- history.pushState(data,title,url) 栈结构(入栈操作)
- history.replaceState(data,title,url) 不能返回
- history.go() 控制压栈出栈 history.go(-1) == history.back() , history.go(1) == history.forward()
- history.back()出栈
- history.forward()压榨
注意:后者需要server端支持
2.JS实现h5 history
核心:history.pushState popstate事件
动态路由
:id 结合 $route.params.id 使用
懒加载
按需加载,提高性能(跟异步组件一样)
导航守卫
监听路由的跳转
全局前置钩子beforeEach()
必须手动调一下next()
全局后置钩子afterEach((to,from))
Vue原理
如何理解MVVM
Vue响应式
核心API - Object.defineProperty
监听对象(深度监听)
//更新视图
function updateView(){
console.log("视图更新")
}
//监听数据
function observer(target) {
if (!target || typeof target !== "object") return
for (let key in target){
defineReactive(target,key,target[key])
}
}
//核心函数
function defineReactive(target,key,value) {
observer(value)
Object.defineProperty(target,key,{
get() {
console.log("get:",value)
return value
},
set(v) {
if (v !== value){
observer(v)
value = v
console.log('set:',v)
//更新视图
updateView()
}
}
})
}
监听数组
//更新视图
function updateView(){
console.log("视图更新")
}
//重新定义数组原型
const oldArrayPrototype = Array.prototype
const arrProto = Object.create(oldArrayPrototype);
['push','pop','shift','unshift','splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView()
oldArrayPrototype[methodName].call(this,...arguments)
}
})
//监听数据
function observer(target) {
if (!target || typeof target !== "object") return
//判断是否是数组
if (Array.isArray(target)){
target.__proto__ = arrProto
}
for (let key in target){
defineReactive(target,key,target[key])
}
}
//核心函数
function defineReactive(target,key,value) {
observer(value)
Object.defineProperty(target,key,{
get() {
console.log("get:",value)
return value
},
set(v) {
if (v !== value){
observer(v)
value = v
console.log('set:',v)
//更新视图
updateView()
}
}
})
}
Object.defineProperty的缺点
1.深度监听,需要递归到底,一次性计算量大
2.无法监听新增属性/删除属性(所以需要使用Vue.set/Vue.delete)
3.无法监听原生数组,需要特殊处理
Vue3.0启用Proxy,但Proxy兼容性不好,且无法polyfill
虚拟DOM
用JS模拟DOM结构,计算出最小的变更,再操作DOM
因为DOM操作是非常耗时的,而Javascript的运算速度快,因此,把大量的DOM操作搬运到Javascript中,使用diff算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而显著提高性能。
JS模拟DOM结构
diff算法
diff算法是vdom中最核心,最关键的部分
1.只比较同一层级,不跨级比较
2.tag不相同,则直接删掉重建,不再深度比较
3.tag和key两者都相同,则认为是相同节点,不再深度比较
h函数:根据真实的DOM节点生成vnode
patch函数:
patch(container,vnode)初次渲染的时候,将vnode渲染成真正的DOM然后插入到容器里面。
patch(vnode,newVnode)再次渲染的时候,将新的vnode和旧的vnode进行对比,找出之间差异并更新到真正的DOM树上。
v-for为什么要加key
使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。为了高效的更新虚拟DOM。
模板编译
with语法
- 改变{}内自由变量的查找方式,将{}内的自由变量当做obj的属性来查找
- 如果找不到匹配的obj属性就会报错
- with要慎用,因为它打破了作用域规则,易读性变差
vue模板编译
- 模板编译为render函数,执行render函数返回vnode
- 基于vnode再执行patch和diff
- 使用webpack vue-loader,会在开发环境下编译模板(提高性能)
- vue组件可以用render代替template
vue组件渲染/更新过程
初次渲染过程
1.解析模板为render函数(或在开发环境已完成,webpack,vue-loader)
2.触发响应式,监听data属性getter setter
3.执行render函数,生成vnode
4.将vnode渲染成真正的dom,patch到容器里面
(只监听模板上需要用的data属性)
更新过程
1.修改data,触发setter
2.重新执行render函数,生成newVnode
3.将新的vnode和旧的vnode进行对比,找出之间差异并更新到真正的DOM树上
异步渲染
- 汇总data的修改,一次性更新视图
- 减少DOM操作次数,提高性能
- $nextTick
面试题
vue组件如何通讯
- 父子组件 props 和 this.$emit
- 自定义事件 event.$on event.$emit event.$off
- vuex
组件渲染和更新的过程
双向数据绑定v-model的实现原理
对MVVM的理解
computed有什么特点
缓存,data不变不会重新计算,提高性能
为何组件data必须是一个函数
Object是引用数据类型,如果不用function 返回,每个组件实例的data 都指向内存的同一个地址,当一个组件实例的数据改变了,其他组件实例也会跟着改变;
而函数存在作用域,当data是一个函数时,每个组件实例都拥有自己的作用域,每个组件实例相互独立,不会造成相互影响
ajax请求应该放在哪个生命周期(?)
mounted
何时使用异步组件
- 加载大组件
- 路由异步加载
何时需要使用keep-alive
- 缓存组件,不需要重复渲染
- 如多个静态tab页的切换
- 提高性能
何时需要使用beforeDestroy
- 解绑自定义事件event.$off
- 清除定时器
- 解绑自定义的DOM事件,如window scroll等
描述响应式原理
1.监听data变化
2.组件渲染和更新的流程
简述diff算法过程
vue常见性能优化方式
1.合理使用v-show和v-if
2.合理使用computed
3.v-for时加key,以及避免和v-if使用(v-for优先级更高,每次v-for都要重新计算一遍v-if)
4.自定义事件,DOM事件及时销毁
5.合理使用异步组件
6.合理使用keep-alive
7.data层级不要太深
8.使用vue-loader在开发环境做模板编译(预编译)