一、简介
Vue.js(简称 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。与其他大型框架不同,Vue 设计的初衷是可以逐步引入。它的核心库只关注视图层,易于上手,同时也非常适合单页应用(SPA)的开发。
优点
- 渐进式框架:Vue 可以作为一个简单的库引入到项目中,也可以扩展为一个功能齐全的框架。你可以根据需要逐步引入 Vue 的功能。
- 双向数据绑定:Vue 提供了简洁的双向数据绑定机制,使得视图和数据能够保持同步。
- 组件化:Vue 的组件系统允许开发者构建可复用的自定义元素,使得应用的开发和维护更加方便。
- 虚拟 DOM:Vue 使用虚拟 DOM 来优化性能,通过最小化实际 DOM 操作,提高渲染效率。
- 易于集成:Vue 可以轻松集成到现有项目中,也可以与其他库或框架结合使用。
缺点
- 生态系统不够完善
- 可扩展性稍差
SPA(单页面应用)介绍
多页面应用有多个html文件,通过a标签的连接联通各个页面
- 开发起来太冗余,编译、压缩很耗时间
- 页面之间的跳转速度太慢,这个时候就会出现一个严重的问题,白屏
单页面应用(SPA)
- 不需要刷新页面,因为它就是一个页面
- 这个页面内容在切换
- 单页面内容之间的切换要想实现我们就是用路由了
- 如今我们的app、后台管理系统 ,可视化主要的开发形式就是spa
Vue 实例
每个 Vue 应用都是通过创建一个 Vue 实例开始的。Vue 实例是 Vue 的核心,它包含了应用的数据、模板、方法和生命周期钩子等。
import Vue from 'vue';
// 创建 Vue 应用实例
const app = new Vue({
render: (h) => h(App), // 这是一个渲染函数,用于渲染根组件。
h
是 Vue 的 createElement 函数的别名,相当于render: function(h) { return h(App); }
});
// 将 Vue 实例挂载到 DOM 元素上
app.$mount('#app');
二、vue基本使用
引入vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue引入</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
</body>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Vue的生命周期'
}
})
</script>
插值与指令
- 插值表达式:是vue框架提供的一种在HTML模板中绑定数据的方式,使用 {{变量名}} 方式绑定Vue实例中data中的数据变量,会将绑定的数据实时的在视图中显示出来。
- 指令:本质是标签中的vue自定义属性,指令格式以“v-”开始
指令的作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。(简化操作)
v-text:相当于innerText
v-html:相当于innertHTML,尽量少用甚至不用,因为可能引发XSS攻击。
v-if
、v-else-if、
v-else
:条件渲染v-if中的key作用:当使用
v-if
切换不同的元素时,key
属性可以帮助 Vue 确定哪些元素需要被重新渲染,哪些元素可以被复用。<div v-if="login === '10086'"> <label>10086</label> <input placeholder="Enter 10086" key="10086-key"> </div> <div v-else> <label>10088</label> <input placeholder="Enter10088" key="10088-key"> </div>
v-show:和
v-if一样是用来控制元素的渲染。v-if判断是否加载,可以减轻服务器的压力,在需要时加载,但有更高的切换开销;v-show调整DOM元素的CSS的dispaly属性,可以使客户端操作更加流畅,但有更高的初始渲染开销。如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
v-for:循环渲染,
v-for和v-if不能一块使用<template> <div> <h1>遍历数组</h1> <ul> <li v-for="(item,index) in arr" :key="item.id"> {{index}} --- {{item.name}} </li> </ul> <h1>遍历对象</h1> <ul> <li v-for="(val,key,index) in obj" :key="key"> {{index}}---{{key}}---{{val}} </li> </ul> </div> </template>
v-on
:指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。事件对象event是原生的,对比react不是.事件被挂载到当前元素和 DOM 事件一样
v-model:input textarea checkbox radio select
1
修饰符
.prevent : 阻止事件的默认行为event.preventDefault()
.stop : 停止事件冒泡 event.stopPropagation()
.keycode : 按键修饰符,操作的是某个keycode值的健
.enter : 操作的是enter键
.lazy:在默认情况下,v-model
在每次input
事件触发后将输入框的值与数据进行同步 ,添加lazy
后,从而转为在change
事件后进行同步<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg">
.number:将用户的输入值转为数值类型,绑定给
v-model
<input v-model.number="age" type="number">
.trim:过滤用户输入的首尾空白字符,绑定给
v-model
<input v-model.trim="msg">
1
data属性
data() {
return {
schemaPath: '',
};
},
Vue中的data属性专门用来以对象方式存放数据,在组件中,只接受data(){return {a:1}}
,data作为一个函数名,数据对象作为函数返回值来使用。因为组件可能被用来创建多个实例。如果data仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象,通过提供data函数,每次创建一个新实例后,我们能够调用data函数,从而返回初始数据的一个全新副本数据对象,data响应数据,发生改变,页面自动更新同步。
computed计算属性
在模板中可以把computed当作data属性来使用,computed是一个对象,每个键是计算属性的值,值有两种使用方法:值是一个函数,或者值是一个包含get和set的对象
注意:computed有缓存,data不变则不会重新计算
watch属性:是一个对象,其中的每个键对应一个需要观察的属性。对应的值可以是一个回调函数,也可以是一个对象,包含回调函数和其他选项。
new Vue({
data: {
firstName: 'John',
lastName: 'Doe'
},
watch: {
firstName(newVal, oldVal) {
console.log(`firstName changed from ${oldVal} to ${newVal}`);
},
lastName: {
handler(newVal, oldVal) {
console.log(`lastName changed from ${oldVal} to ${newVal}`);
},
immediate: true,
deep: true
}
}
});
immediate:
立即执行回调(默认情况下,观察器只有在被观察的属性发生变化时才会触发,如果你希望在观察器创建时立即执行回调,可以使用immediate
选项)
deep:
深度观察(默认情况下,watch
只会观察对象的顶层属性。如果你需要观察对象内部嵌套的属性变化,可以使用deep
选项。)
组件间通讯1-props(类型和默认值)和$emit(父子)
组件间通讯2-自定义事件总线(非父子组件通信)
vue组件生命周期(先子后父)
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会。
单个组件生命周期
创建:beforeCreate、created
挂载:beforeMount、mounted
更新:beforeUpdate、updated
销毁:beforeDestroy、destroyed
beforeCreate
实例初始化之后,数据观测 (data observation) 和事件配置之前,在这个阶段,实例的 data 和 methods 等都还未初始化,不能访问
this
上的任何属性或方法。
created
实例创建完成后,数据观测 (data observation) 和事件配置完成,但尚未挂载 DOM,可以访问
this
上的属性和方法,适合在此进行初始数据的获取。数据的初始化或从服务器获取初始数据。在模板渲染成html前调用
beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用,在这个阶段,虚拟 DOM 已经创建完成,但还没有挂载到实际的 DOM 树上。
mounted
实例挂载到 DOM 上之后调用,可以进行 DOM 操作,适合在此进行与 DOM 相关的操作,在模板渲染成html后调用。
beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。可以在数据更新前访问现有的 DOM,适合在此进行一些需要在更新前完成的操作。
updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用,可以在数据更新后访问更新后的 DOM,适合在此进行一些需要在更新后完成的操作。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
该钩子在服务器端渲染期间不被调用。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。适合在此进行一些清理工作,如清除定时器、取消订阅等。
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
三、高级特性(进阶)
filter过滤
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式
场景:日期格式化 大小转换 正则过滤自定义指令
除了核心功能默认内置的指令,Vue也允许注册自定义指令。有的情况下,对普通 DOM 元素进行
底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作。
自定义指令分为: 全局 指令和 局部 指令,当全局指令和局部指令同名时以局部指令为准。
自定义指令常用钩子函数有:
bind:在指令第一次绑定到元素时调用
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
update:数据更新时调用
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
请注意:不管在定义全局还是局部自定义指令时,所提及的指令名均是不带 v- 前缀的名称。
语法如下:
// 全局
Vue.directive('指令名', function (el,binding) {
// 业务逻辑
})
// 局部
directives: {
指令名: function (el,binding) {
// 业务逻辑
}
}
自定义v-model【注意双向绑定实现原理】
场景:实现一个自定义v-mode的形式 用在组件
$nextTick(重要)
mounted挂载后
场景:vue是异步渲染的框架,react也是,data改变之后,dom不会立刻渲染,$nextTick会在dom渲染之后被触发,作用:以获取最新dom节点
refs【组件通信3】
场景:vue处理不了的,使用dom元素来出处理,获取子元素或子组件的值和方法
ref 链:父组件要给子组件传值,在子组件上定义一个 ref 属性,这样通过父组件的 refs属性就可以获取子组件的值了,也可以进行父子,兄弟之间的传值(refs属性就可以获取子组件的值了,也可以进行父子,兄弟之间的传值(parent / $children与 ref类似)
provide和inject【组件通信4】
场景:更改全局背景色,全局字体,全局样式,全局风格,中英文切换等等
注意:boolean,number,string这种,他就不会动态变化,而如果是object,则能够视图响应数据变化
只要组件有继承关系就能使用
把当前父组件打包成一个组件对象
provide() {
return {
elTest: this //组件独享 方法 属性
}
}
所有子组件调用包括多个层级,爷爷-儿子-孙子
inject: ['elTest']
slot(重要)插槽
匿名插槽基本使用
在子组件内使用特殊的元素就可以为这个子组件添加一个 slot (插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的标签及它的内容
场景:父组件和子组件插入一些东西,包括组件,dom节点,任何东西
作用域插槽
说白了就是我在组件上的属性,可以在组件元素内使用!
场景:组件拿到插槽里面的对象,数据
具名插槽
具名插槽,就是给这个插槽起个名字,在组件中,我给插槽起个名字,一个名字叫"girl",一个名字叫"boy",还有一个不起名字。
然后再内,slot属性对应的内容都会和组件中name一一对应。
场景:多个插槽的场景
动态组件
vue内置的组件:
多个组件通过同一个挂载点进行组件的切换,is的值是哪个组件的名称,那么页面就会显示哪个组件。
用法:
渲染一个“元组件”为动态组件。依 is
的值,来决定哪个组件被渲染
场景:需要根据数据,动态渲染的场景,组件类型不确定
transition动画
用法:
<transition>
元素作为单个元素/组件的过渡效果。<transition>
只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
<button @click="startTrans">开始动画</button>
<transition name="slide"
@after-enter="afterEnter"
@leave="afterLeave">
<p v-if="show" class="anima">动画</p>
<!-- <p class="anima">动画1</p> -->
</transition>
。。。。。。。。。。。。
methods:{
startTrans(){
this.show = !this.show
},
afterEnter(e){
e.style.left = '400px'
},
afterLeave(e){
e.style.left = '0px'
}
}
进场和离场动画
.anima{
height: 50px;
width: 50px;
background-color: red;
position:absolute;
}
.slide-enter,.slide-leave-to{
left:0px
}
.slide-enter-active,.slide-leave-active{
transition:all 2s linear;
}
.slide-enter-to,.slide-leave{
left:400px;
}
动画常用第二种方案:
1.安装 animate.css 包
2.import animated from 'animate.css' Vue.use(animated)
3.使用
我们可以通过以下 attribute 来自定义过渡类名:
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
<!-- 控制数据的值切换显示隐藏 -->
<button @click="show=!show">transition</button>
<!-- <transition enter-active-class="animated zoomInLeft" leave-active-class="animated zoomOutRight">
<p v-show="show"></p>
</transition> -->
<!-- 多元素运动 -->
<transition-group enter-active-class="animated zoomInLeft" leave-active-class="animated zoomOutRight">
<p v-show="show" :key="1"></p>
<p v-show="show" :key="2"></p>
</transition-group>
注意:如果完成前三部还是没有效果,可能就是animate.css版本的问题了
直接安装的都是最新版本,而vue官网引入的是3.5.1版本
异步组件
场景:import函数,按需加载,异步加载大组件,vue常用性能优化方案之一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuPdHe9s-1685269742834)(img\1625104284235.png)]
keep-alive 等价于v-show
场景:缓存组件,频繁切换,不需要重复渲染的情况,vue常用性能优化方案之一
用法:
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
当组件在 <keep-alive>
内被切换,它的 activated
和 deactivated
这两个生命周期钩子函数将会被对应执行。
属性:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max
- 数字。最多可以缓存多少组件实例。
复杂的组件使用keepalive 简单的可以使用v-show来提高性能
mixin[不推荐使用 了解]
作用:多个组件有相同的逻辑,抽离出来,使用mixin,但是mixin并不是完美的解决方案,暂时能用,vue3提出的composition api旨在解决这些问题(
问题如下:
1.变量来源不明确,不利于阅读
2.多个mixin可能造成冲突,
3.mixin可能出现多对多关系,关系会复杂
场景:多组件调用相同的逻辑时使用
全局注册组件或插件
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue。
简单来说:头部组件,nav组件,swiper组件,image组件,footer组件,loyout组件,asider组件等
插件 (Plugin) 是用来增强你的技术栈的功能模块,一般是方法,它的目标是 Vue 本身。
简单来说,插件就是指对Vue的功能的增强或补充。一般都是js文件。
四、vuex【组件通信5】
vuex介绍
- **VueX 是一个专门为 Vue.js 应用设计的状态管理架构,统一管理和维护各个vue组件的可变化状态(你能够理解成 vue 组件里的某些 data )。**大白话:集中管理数据的模块
- 它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex有五个核心概念,state, getters, mutations, actions, modules。
- 状态管理: 简单理解就是统一管理和维护各个vue组件的可变化状态(你能够理解成vue组件里的某些data)
vuex在vue中如何使用
state
mapState
computed: {
/**
* 数组形式
* 当映射的计算属性的名称与 与模块中vuexTest中state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组,
* */
…mapState(“vuexTest”, [“moduleVal”, “moduleName”]),
// “vuexTest” 指向模块vuexTest,"moduleVal"表示store.vuexTest.moduleVal
调用函数
1.state 默认局部空间 在模块中,state 是被限制到模块的命名空间下,需要命名空间才能访问。
getters,actions,mutations 默认是全局空间。actions 和mutations, 其实还有 getters 却没有被限制,在默认情况下,它们是注册到全局命名空间下的,所谓的注册到全局命名空间下,其实就是我们访问它们的方式和原来没有module 的时候是一样的
如何限定命名空间?
namespaced:true,//actions, mutations, getters, 也可以限定在当前模块的命名空间中,就不是全局的了
五、vue-router使用
路由模式(hash, h5 history)
hash模式(默认) 例如:http://abc.com/#/user/10
h5 history模式 例如:http://abc.com/user/20 需要server端支持
注意:history有如下问题,vue2.0缺陷
大白话:hash路由自带404错误页面,但是history必须手动加一个404页面。
路由配置
常用配置
import Home from '../../Home.vue'
const routes = [
{
path: '/',
redirect: '/home' //重定向
},
{
path: '/home',
name:'home',
component: Home
}]
如果你使用的是 hash , 那么a标签就可以了、
如果你使用 history , 那么我们最好将a标签改成 router-link 这个组件
router-link 这个组件 身上必须要有一个 to 属性
router-link 这个组件身上加一个 keep-alive属性可以进行浏览器缓存
二级路由
//作业:定义一个home页面,有头部组件,左侧菜单组件,内容部分嵌套
二级路由,有商品内容页,用户管理页
路由传参
传参
<router-link :to = "{name: 'list',params: {id: xxx}, query: {xxx:xxx}}"></router-link>
params传参 是在实体中传参 其实就post传参
query 传参 是在url传参 其实就get传参
接收参数
this.$route.params.id
this.$route.query.xxx
编程式导航
push
this.$router.push('/home')
this.$router.push({name,params,query})
push可以将我们的操作存放到浏览器的历史记录,可以回退,可以前进
replace
this.$router.replace(’/home’)
this.$router.replace({name,params,query})
replace没有将我们的操作存放到浏览器的历史记录, 效果为返回了二级
push/replace的参数就是to属性的参数
路由进阶 – 导航守卫
思考: 有一个业务,当我们点击 /mine的时候,要自动跳转到 /mine/login,这个时候我们发现手段不够用了,生命周期钩子函数也实现不了,这个我们想,如果我们能监听到路由的变化,那该有多好?
解决; vue为了能够监听路由的变化情况,给了一个解决方法: 这个就是导航守卫,导航钩子
导航守卫分为:全局的、单个路由独享的、组件内的三种。
【全局的】:是指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数,如下的写法。钩子函数按执行顺序包括beforeEach、beforeResolve(2.5+)、afterEach三个。
router.beforeEach((to, from, next) => {
//在路由跳转前触发,参数包括to,from,next(参数会单独介绍)三个,//这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免//跳转了再通知就为时已晚。
})
router.beforeResolve((to, from, next) => {
//这个钩子和beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。
})
router.afterEach((to, from) => {
//和beforeEach相反,他是在路由跳转完成后触发,参数包括to,from没有了next,他发生在beforeEach和beforeResolve之后,beforeRouteEnter(组件内守卫,后讲)之前。
})
【路由独享的】是指在单个路由配置的时候也可以设置的钩子函数,其位置就是下面示例中的位置,也就是像Foo这样的组件都存在这样的钩子函数。目前他只有一个钩子函数beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
【组件内的】:是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。钩子函数按执行顺序包括beforeRouteEnter、beforeRouteUpdate (2.2+)、beforeRouteLeave三个。
data(){
//...
},
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
动态路由
懒加载路由
六、vue原理
组件 (Component)是 Vue.js 最强大的功能之一,组件是一个自定义元素或称为一个模块,包括
所需的模板(HTML)、逻辑(JavaScript)和样式(CSS)。
组件化开发的特点:
标准
分治
重用
组合
组件也是有 全局(component) 与 局部(components) 之分。
组件名称命名方式
短横线方式(推荐)
my-component
大驼峰方式(只能在其他组件模板字符串中使用,不能在HTML模板中直接使用)
MyComponent
大驼峰式组件名不能在HTML模板中直接使用,如果需要在HTML模板中使用,需要将其进行
特定规则转化:
首字母从大写转为小写
后续每遇到大写字母都要转化成小写并且在转化后的小写字母前加 -
例如, WoDeZuJian 这个大驼峰组件名在HTML中使用的时候需要写成 wo-de-zu-jian
对比vue和react组件化
和传统组件相比,传统组件只是静态渲染,更新还要依赖于操作dom,而当今流行的框架,vue通过mvvm实现数据驱动视图,react通过setState实现数据驱动视图,而什么叫数据驱动视图,就是我们不在操作dom,我们直接改数据就可以了,使我们更加关注数据,更加关注业务逻辑。
mvvm
model view viewModel,dom通过监听事件操作vue里的data,反之vue中的data通过指令操作dom,这就是所说数据驱动视图,这就是mvvm的理解。
优点是低耦合,视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
双向数据绑定的原理
当把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,使用Object.defineProperty把这些属性全部转为getter/setter(数据劫持/数据映射)。在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
由于vue底层是用Object.definepropty 实现的数据劫持,注意:有一个bug是,vue不能监听到对象数据的添加和删除
响应式
组件data数据一旦变化,立刻触发视图的更新是怎么实现的?(重点)
vue2.0核心api是Object.defineProperty,vue3.0是启用provy实现响应式监听
数组监听都是重写array里面的push.pop.unshift…
Object.defineProperty存在一些问题(深度监听需要一次性递归,无法监听新增属性和删除属性,无法原生监听数组,需要特殊处理),provy兼容性不好,低版本和ie11不能用
vdom和diff
为什么要虚拟DOM?
当用传统的api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完成DOM树,再执行后续操作。但是浏览器没有那么智能,在收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程,显然前面几次都是白白浪费性能。而且操作DOM的代价是很昂贵的,频繁操作可能会出现页面卡顿,影响用户的体验。
虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的,如果一次操作中有10次更新DOM的操作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存在本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。
优点:
-虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能.
缺点:
-首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
vue 首屏时间长,白屏问题。
dom和虚拟dom对比
如何将虚拟dom转为真实dom基本操作?
//dom
<ul id='test' class="header">
<p class='hehe'>这里是p标签</p>
</ul>
//对应的虚拟dom对象
let vdom={
tag:'ul',
attr:{
id:'test',
class:'header'
},
content:[
{
tag:'p',
attr:{
class:'hehe'
},
content:'这里是p标签'
}
]
}
作业:手写一个虚拟dom?[笔试题]
diff算法如何进行比较的?
diff->只比较同一层级,不跨级比较,tag不相同直接删掉重建不在深度比较,tag和值两者都相同,则认为是相同节点,不在深度比较
模板编译
vue模板是什么东西?
vue模板,他不是html,有指令,插值,js表达式,能实现判断,循环,html是标签语言,只有js才能实现判断,循环,vue他通过vue template complier将模板编译为render函数,执行render函数生成vnode,在渲染和更新。(react一直用render函数,没有模板)
异步渲染
上面这一段代码中,在mounted里给val属性进行了两次赋值,如果页面渲染与数据的变化完全同步的话,页面应该是在mounted里有两次渲染。
而由于Vue内部的渲染机制,实际上页面只会渲染一次,把第一次的赋值所带来的的响应与第二次的赋值所带来的的响应进行一次合并,将最终的val只做一次页面渲染。
异步渲染,回顾$nextTick,如果data的修改,一次更新视图,减少dom操作次数来提高性能优点:
虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能.
缺点:
首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
vue 首屏时间长,白屏问题。
dom和虚拟dom对比
如何将虚拟dom转为真实dom基本操作?
//dom
<ul id='test' class="header">
<p class='hehe'>这里是p标签</p>
</ul>
//对应的虚拟dom对象
let vdom={
tag:'ul',
attr:{
id:'test',
class:'header'
},
content:[
{
tag:'p',
attr:{
class:'hehe'
},
content:'这里是p标签'
}
]
}
diff算法如何进行比较的?
diff->只比较同一层级,不跨级比较,tag不相同直接删掉重建不在深度比较,tag和值两者都相同,则认为是相同节点,不在深度比较
模板编译
vue模板是什么东西?
vue模板,他不是html,有指令,插值,js表达式,能实现判断,循环,html是标签语言,只有js才能实现判断,循环,vue他通过vue template complier将模板编译为render函数,执行render函数生成vnode,在渲染和更新。(react一直用render函数,没有模板)
异步渲染
上面这一段代码中,在mounted里给val属性进行了两次赋值,如果页面渲染与数据的变化完全同步的话,页面应该是在mounted里有两次渲染。
而由于Vue内部的渲染机制,实际上页面只会渲染一次,把第一次的赋值所带来的的响应与第二次的赋值所带来的的响应进行一次合并,将最终的val只做一次页面渲染。
异步渲染,回顾$nextTick,如果data的修改,一次更新视图,减少dom操作次数来提高性能